Compare commits

...
80 Commits
Author SHA1 Message Date
Mervin Praison 7501c57c8e Merge pull request #167 from MervinPraison/develop
Custom Tools in PraisonAI Realtime
2024-10-08 06:47:42 +01:00
MervinPraison 115a95a8e0 Custom Tools in PraisonAI Realtime 2024-10-08 06:47:24 +01:00
Mervin Praison 42752a8ed7 Merge pull request #166 from MervinPraison/develop
Develop
2024-10-08 04:00:28 +01:00
MervinPraison f0a2983a9f Adding Depenencies 2024-10-08 04:00:08 +01:00
MervinPraison adccce2f3d updating readme 2024-10-08 01:58:15 +01:00
Mervin Praison 29080b6e78 Merge pull request #165 from MervinPraison/develop
Release version 0.1.0 With OpenAI Realtime Integration
2024-10-08 01:55:31 +01:00
MervinPraison aeff0f63a4 Release version 0.1.0 With OpenAI Realtime Integration 2024-10-08 01:54:55 +01:00
Mervin Praison edcfea1da0 Merge pull request #164 from MervinPraison/develop
Release version 0.0.74
2024-10-08 01:21:02 +01:00
MervinPraison d2925c3415 Release version 0.0.74 2024-10-08 01:20:39 +01:00
MervinPraison 6962709186 Adding OpenAI Realtime Docs 2024-10-08 01:18:48 +01:00
MervinPraison de20247418 Adding OpenAI Realtime API and multiple tools 2024-10-08 01:10:08 +01:00
MervinPraison 4e328c0504 Adding Realtime Voice API OpenAI 2024-10-05 03:55:14 +01:00
Mervin Praison 232ba25c86 Merge pull request #163 from MervinPraison/develop
README update
2024-10-02 09:29:44 +01:00
MervinPraison 4bf2c36ffe Modifying docs to add search and vlm feature 2024-10-02 09:29:34 +01:00
MervinPraison 442bae1fed README update 2024-10-02 09:24:25 +01:00
Mervin Praison 151facab62 Merge pull request #162 from MervinPraison/develop
Adding Support to Multimodal Model
2024-10-02 09:19:47 +01:00
MervinPraison d4c2a9a2dd Adding Support to Multimodal Model 2024-10-02 09:19:27 +01:00
Mervin Praison 5c0a86fab0 Merge pull request #161 from MervinPraison/develop
Adding crawl4ai
2024-09-30 10:11:43 +01:00
MervinPraison 8c1c67453f Adding crawl4ai 2024-09-30 10:11:06 +01:00
Mervin Praison 5375a65e60 Merge pull request #159 from MervinPraison/develop
Develop
2024-09-25 06:51:39 +01:00
MervinPraison 5eb6090c01 Adding search and website scrape 2024-09-25 06:51:16 +01:00
MervinPraison 704bcaa599 Adding Crawl4AI for crawling each url during search 2024-09-23 23:17:51 +01:00
Mervin Praison c4cbe88bbb Merge pull request #158 from MervinPraison/develop
Adding Search with Tavily Feature to Chat and Code
2024-09-23 20:17:02 +01:00
MervinPraison 720921f1ef Adding Search with Tavily Feature to Chat and Code 2024-09-23 20:16:47 +01:00
Mervin Praison 285e6c5cc4 Merge pull request #157 from MervinPraison/develop
Fixing the bug to update the model name
2024-09-22 07:27:21 +01:00
MervinPraison f58646510c Fixing the bug to update the model name 2024-09-22 07:26:46 +01:00
Mervin Praison 07fb9a3e8c Merge pull request #156 from MervinPraison/develop
Upgrading Chainlit to version 1.2.0"
2024-09-22 07:07:58 +01:00
MervinPraison bef3359e9b Upgrading Chainlit to version 1.2.0"
"
2024-09-22 07:07:20 +01:00
Mervin Praison 760877ccce Merge pull request #155 from MervinPraison/develop
Fixing the ThreadDict issue
2024-09-22 06:28:33 +01:00
MervinPraison 99c27533bc Fixing the ThreadDict issue 2024-09-22 06:27:58 +01:00
Mervin Praison 1d0bdd22c7 Merge pull request #153 from manjaroblack/main
Rename __init__.py to __init__.py
2024-09-22 05:50:34 +01:00
Mervin Praison 0aaf7cb866 Merge pull request #154 from MervinPraison/develop
Update sql alchemy based on chainlit
2024-09-22 05:47:26 +01:00
MervinPraison da5483b47b Update sql alchemy based on chainlit 2024-09-22 05:46:58 +01:00
manjaroblack f420ab5d5b Update .gitignore
add .venv to .gitignore
2024-09-17 11:33:27 -05:00
manjaroblack a170749327 Rename __init__.py to __init__.py
The file name contained a hidden character at the end of the .py extension. This is causing issues when cloning on Windows as it is incompatible with the file system.
2024-09-17 11:19:10 -05:00
Mervin Praison f608f90c51 Merge pull request #151 from MervinPraison/develop
Adding CHAINLIT_APP_ROOT env variable to set the config
2024-09-04 15:29:42 +01:00
MervinPraison be238c841e Adding CHAINLIT_APP_ROOT env variable to set the config 2024-09-04 14:46:39 +01:00
Mervin Praison 4472bfa9d4 Merge pull request #150 from MervinPraison/develop
Upgrading 0.0.65
2024-09-04 12:12:39 +01:00
MervinPraison abf12b1aab Upgrading 0.0.65 2024-09-04 12:12:18 +01:00
Mervin Praison 717476adab Merge pull request #145 from MervinPraison/develop
Upgrading 0.0.64
2024-08-18 07:37:03 +01:00
MervinPraison fc802aa254 Upgrading 0.0.64 2024-08-18 07:36:12 +01:00
Mervin Praison ab9342933b Merge pull request #144 from MervinPraison/develop
Fixing DB Storing issue for PraisonAI Chat
2024-08-18 07:27:02 +01:00
MervinPraison 060c322bfe Fixing DB Storing issue for PraisonAI Chat 2024-08-18 07:26:40 +01:00
Mervin Praison 392d3a8694 Merge pull request #143 from MervinPraison/develop
Fixing DB Storing issue
2024-08-18 07:16:24 +01:00
MervinPraison 635483ec3d Fixing DB Storing issue 2024-08-18 07:15:41 +01:00
Mervin Praison 660248b95a Merge pull request #139 from MervinPraison/develop
Updating docs for praisonai train
2024-08-06 10:12:58 +01:00
MervinPraison f29adfe74c Updating docs for praisonai train 2024-08-06 10:12:44 +01:00
Mervin Praison 29f56e21a2 Merge pull request #138 from MervinPraison/develop
minor bug fix praisonai train
2024-08-05 23:29:40 +01:00
MervinPraison 5378a37dd1 minor bug fix praisonai train 2024-08-05 23:28:35 +01:00
Mervin Praison 273aa48861 Merge pull request #136 from MervinPraison/develop
praisonai train minor fix
2024-08-05 17:31:56 +01:00
MervinPraison 04be538472 praisonai train minor fix 2024-08-05 17:31:03 +01:00
Mervin Praison e50c614835 Merge pull request #135 from MervinPraison/develop
praisonai train final release
2024-08-05 16:50:20 +01:00
MervinPraison 9871768c3a praisonai train final release 2024-08-05 16:49:50 +01:00
Mervin Praison 1f9e2ae839 Merge pull request #134 from MervinPraison/develop
Develop
2024-08-05 11:07:28 +01:00
MervinPraison 62af1fe7ab Adding dynamic config 2024-08-05 11:07:11 +01:00
MervinPraison 320aa968db Select training section 2024-08-05 07:16:11 +01:00
Mervin Praison bf8eb9b82e Merge pull request #133 from MervinPraison/develop
Streaming working
2024-08-05 06:52:27 +01:00
MervinPraison 03d3cb544f Streaming working 2024-08-05 06:51:53 +01:00
Mervin Praison 92b769a73f Merge pull request #132 from MervinPraison/develop
streaming subprocess working
2024-08-05 06:07:09 +01:00
MervinPraison fe23957f41 streaming subprocess working 2024-08-05 06:06:16 +01:00
Mervin Praison a9f7393809 Merge pull request #131 from MervinPraison/develop
streaming subprocess
2024-08-05 05:47:06 +01:00
MervinPraison df1f572d1c streaming subprocess 2024-08-05 05:46:52 +01:00
Mervin Praison 619f0173cc Merge pull request #129 from MervinPraison/develop
Install conda for praisonai train
2024-08-04 09:57:52 +01:00
MervinPraison adb707b593 Install conda for praisonai train 2024-08-04 09:57:37 +01:00
Mervin Praison e2b95eb9b8 Merge pull request #128 from MervinPraison/develop
Repointing the pyproject.toml setup
2024-08-04 08:53:34 +01:00
MervinPraison 8889cb94c5 Repointing the pyproject.toml setup 2024-08-04 08:51:41 +01:00
Mervin Praison 83e6eb1b2b Merge pull request #127 from MervinPraison/develop
moving setup folder
2024-08-04 08:48:14 +01:00
MervinPraison 3e14f4abb5 moving setup folder 2024-08-04 08:47:40 +01:00
Mervin Praison 510d1eef56 Merge pull request #126 from MervinPraison/develop
adding praisonai train init
2024-08-04 08:41:16 +01:00
MervinPraison ba672bfad1 adding praisonai train init 2024-08-04 08:41:03 +01:00
Mervin Praison 04841330f7 Merge pull request #125 from MervinPraison/develop
praisonai train release candidate post install fix
2024-08-04 07:32:22 +01:00
MervinPraison ff28049a76 praisonai train release candidate post install fix 2024-08-04 07:31:35 +01:00
Mervin Praison af0e6ff659 Merge pull request #124 from MervinPraison/develop
praisonai train release candidate
2024-08-04 07:23:10 +01:00
MervinPraison e1677d1f7c praisonai train release candidate 2024-08-04 07:22:55 +01:00
Mervin Praison bfb98efee0 Merge pull request #123 from MervinPraison/develop
adding .praisoninclude and .praisoncontext
2024-08-01 01:39:33 +01:00
MervinPraison a5e69cb77e adding .praisoninclude and .praisoncontext 2024-08-01 01:39:13 +01:00
Mervin Praison e207bd904e Merge pull request #114 from MervinPraison/develop
Changing the default DB location
2024-07-21 02:10:40 +01:00
MervinPraison 1985f73d60 Changing the default DB location 2024-07-21 02:10:20 +01:00
Mervin Praison 317b07661e Merge pull request #113 from MervinPraison/develop
Setting gpt-4o-mini as the default model
2024-07-21 00:59:37 +01:00
MervinPraison 6b9d85b842 Setting gpt-4o-mini as the default model 2024-07-21 00:59:09 +01:00
50 changed files with 8651 additions and 2894 deletions

No files matched your search

+8 -1
View File
@@ -45,6 +45,13 @@ praisonai/doc
# agentops
trained_agents_data.pkl
agentops.log
# crewAI
crewAI
crewAI
# virtualenv
.venv
# Credentials
credentials.json
+2
View File
@@ -0,0 +1,2 @@
# praisonai/ui/context.py
# /Users/praison/miniconda3/envs/praisonai-package/lib/python3.11/site-packages/llama_index/packs/code_hierarchy/code_hierarchy.py
+3 -1
View File
@@ -1,3 +1,5 @@
cookbooks
tests
docs
docs
praisonai/ui/*
praisonai/inbuilt_tools/*
+3 -1
View File
@@ -1 +1,3 @@
#praisonai
# praisonai/ui/context.py
# /Users/praison/miniconda3/envs/praisonai-package/lib/python3.11/site-packages/llama_index/packs/code_hierarchy/code_hierarchy.py
praisonai/setup
+1 -1
View File
@@ -1,6 +1,6 @@
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask praisonai==0.0.55 gunicorn markdown
RUN pip install flask praisonai==0.1.2 gunicorn markdown
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
+35 -10
View File
@@ -35,6 +35,12 @@ Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, rep
| **UI** | Multi Agents such as CrewAI or AutoGen | [https://docs.praison.ai/ui/ui](https://docs.praison.ai/ui/ui) |
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praison.ai/ui/chat](https://docs.praison.ai/ui/chat) |
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praison.ai/ui/code](https://docs.praison.ai/ui/code) |
| **Realtime** | Real-time voice interaction with AI | [https://docs.praison.ai/ui/realtime](https://docs.praison.ai/ui/realtime) |
| Other Features | Description | Docs |
|---|---|---|
| **Train** | Fine-tune LLMs using your custom data | [https://docs.praison.ai/train](https://docs.praison.ai/train) |
## Google Colab Multi Agents
@@ -45,19 +51,26 @@ Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, rep
## Install
| PraisonAI | PraisonAI Code | PraisonAI Chat |
| --- | --- | --- |
| `pip install praisonai` | `pip install "praisonai[code]"` | `pip install "praisonai[chat]"` |
| | Installation |
|---|---|
| **PraisonAI** | `pip install praisonai` |
| **PraisonAI Code** | `pip install "praisonai[code]"` |
| **PraisonAI Chat** | `pip install "praisonai[chat]"` |
| **PraisonAI Train** | `pip install "praisonai[train]"` |
| **PraisonAI Realtime** | `pip install "praisonai[realtime]"` |
## Key Features
- Automated AI Agents Creation
- Use CrewAI or AutoGen Framework
- 100+ LLM Support
- Chat with ENTIRE Codebase
- Interactive UIs
- YAML-based Configuration
- Custom Tool Integration
- 🤖 Automated AI Agents Creation
- 🔄 Use CrewAI or AutoGen Framework
- 💯 100+ LLM Support
- 💻 Chat with ENTIRE Codebase
- 🖥️ Interactive UIs
- 📄 YAML-based Configuration
- 🛠️ Custom Tool Integration
- 🔍 Internet Search Capability (using Crawl4AI and Tavily)
- 👁️ Vision Language Model (VLM) Support
- 🎙️ Real-time Voice Interaction
## TL;DR Multi Agents
@@ -170,6 +183,14 @@ export OPENAI_API_KEY="Enter your API key"
praisonai chat
```
### Internet Search
Praison AI Chat and Praison AI Code now includes internet search capabilities using Crawl4AI and Tavily, allowing you to retrieve up-to-date information during your conversations.
### Vision Language Model Support
You can now upload images and ask questions based on them using Vision Language Models. This feature enables visual understanding and analysis within your chat sessions.
## Praison AI Code
```bash
@@ -178,6 +199,10 @@ export OPENAI_API_KEY="Enter your API key"
praisonai code
```
### Internet Search
Praison AI Code also includes internet search functionality, enabling you to find relevant code snippets and programming information online.
## Create Custom Tools
- https://docs.praison.ai/tools/custom/
+60
View File
@@ -0,0 +1,60 @@
ollama_save: "true"
huggingface_save: "true"
train: "true"
model_name: "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
hf_model_name: "mervinpraison/llama-3.1-instruct"
ollama_model: "mervinpraison/llama3.1-instruct"
model_parameters: "8b"
dataset:
- name: "yahma/alpaca-cleaned"
split_type: "train"
processing_func: "format_prompts"
rename:
input: "input"
output: "output"
instruction: "instruction"
filter_data: false
filter_column_value: "id"
filter_value: "alpaca"
num_samples: 20000
dataset_text_field: "text"
dataset_num_proc: 2
packing: false
max_seq_length: 2048
load_in_4bit: true
lora_r: 16
lora_target_modules:
- "q_proj"
- "k_proj"
- "v_proj"
- "o_proj"
- "gate_proj"
- "up_proj"
- "down_proj"
lora_alpha: 16
lora_dropout: 0
lora_bias: "none"
use_gradient_checkpointing: "unsloth"
random_state: 3407
use_rslora: false
loftq_config: null
per_device_train_batch_size: 2
gradient_accumulation_steps: 2
warmup_steps: 5
num_train_epochs: 1
max_steps: 10
learning_rate: 2.0e-4
logging_steps: 1
optim: "adamw_8bit"
weight_decay: 0.01
lr_scheduler_type: "linear"
seed: 3407
output_dir: "outputs"
quantization_method:
- "q4_k_m"
+77 -1
View File
@@ -31,6 +31,22 @@ hljs.highlightAll();
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="praisonai.cli.stream_subprocess"><code class="name flex">
<span>def <span class="ident">stream_subprocess</span></span>(<span>command, env=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Execute a subprocess command and stream the output to the terminal in real-time.</p>
<h2 id="args">Args</h2>
<dl>
<dt><strong><code>command</code></strong> :&ensp;<code>list</code></dt>
<dd>A list containing the command and its arguments.</dd>
<dt><strong><code>env</code></strong> :&ensp;<code>dict</code>, optional</dt>
<dd>Environment variables for the subprocess.</dd>
</dl></div>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
@@ -136,6 +152,57 @@ init (bool): A flag indicating whether to enable initialization mode. Defaults t
self.create_code_interface()
return
if args.agent_file == &#39;train&#39;:
package_root = os.path.dirname(os.path.abspath(__file__))
config_yaml_destination = os.path.join(os.getcwd(), &#39;config.yaml&#39;)
# Create config.yaml only if it doesn&#39;t exist or --model or --dataset is provided
if not os.path.exists(config_yaml_destination) or args.model or args.dataset:
config = generate_config(
model_name=args.model,
hf_model_name=args.hf,
ollama_model_name=args.ollama,
dataset=[{
&#34;name&#34;: args.dataset
}]
)
with open(&#39;config.yaml&#39;, &#39;w&#39;) as f:
yaml.dump(config, f, default_flow_style=False, indent=2)
# Overwrite huggingface_save and ollama_save if --hf or --ollama are provided
if args.hf:
config[&#34;huggingface_save&#34;] = &#34;true&#34;
if args.ollama:
config[&#34;ollama_save&#34;] = &#34;true&#34;
if &#39;init&#39; in sys.argv:
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print(&#34;All packages installed&#34;)
return
try:
result = subprocess.check_output([&#39;conda&#39;, &#39;env&#39;, &#39;list&#39;])
if &#39;praison_env&#39; in result.decode(&#39;utf-8&#39;):
print(&#34;Conda environment &#39;praison_env&#39; found.&#34;)
else:
raise subprocess.CalledProcessError(1, &#39;grep&#39;)
except subprocess.CalledProcessError:
print(&#34;Conda environment &#39;praison_env&#39; not found. Setting it up...&#34;)
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print(&#34;All packages installed.&#34;)
train_args = sys.argv[2:] # Get all arguments after &#39;train&#39;
train_script_path = os.path.join(package_root, &#39;train.py&#39;)
# Set environment variables
env = os.environ.copy()
env[&#39;PYTHONUNBUFFERED&#39;] = &#39;1&#39;
stream_subprocess([&#39;conda&#39;, &#39;run&#39;, &#39;--no-capture-output&#39;, &#39;--name&#39;, &#39;praison_env&#39;, &#39;python&#39;, &#39;-u&#39;, train_script_path, &#39;train&#39;], env=env)
return
invocation_cmd = &#34;praisonai&#34;
version_string = f&#34;PraisonAI version {__version__}&#34;
@@ -206,7 +273,11 @@ init (bool): A flag indicating whether to enable initialization mode. Defaults t
parser.add_argument(&#34;--auto&#34;, nargs=argparse.REMAINDER, help=&#34;Enable auto mode and pass arguments for it&#34;)
parser.add_argument(&#34;--init&#34;, nargs=argparse.REMAINDER, help=&#34;Enable auto mode and pass arguments for it&#34;)
parser.add_argument(&#34;agent_file&#34;, nargs=&#34;?&#34;, help=&#34;Specify the agent file&#34;)
parser.add_argument(&#34;--deploy&#34;, action=&#34;store_true&#34;, help=&#34;Deploy the application&#34;) # New argument
parser.add_argument(&#34;--deploy&#34;, action=&#34;store_true&#34;, help=&#34;Deploy the application&#34;)
parser.add_argument(&#34;--model&#34;, type=str, help=&#34;Model name&#34;)
parser.add_argument(&#34;--hf&#34;, type=str, help=&#34;Hugging Face model name&#34;)
parser.add_argument(&#34;--ollama&#34;, type=str, help=&#34;Ollama model name&#34;)
parser.add_argument(&#34;--dataset&#34;, type=str, help=&#34;Dataset name for training&#34;, default=&#34;yahma/alpaca-cleaned&#34;)
args, unknown_args = parser.parse_known_args()
if unknown_args and unknown_args[0] == &#39;-b&#39; and unknown_args[1] == &#39;api:app&#39;:
@@ -497,6 +568,11 @@ the successful creation of a file.</p></div>
<li><code><a title="praisonai" href="index.html">praisonai</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="praisonai.cli.stream_subprocess" href="#praisonai.cli.stream_subprocess">stream_subprocess</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
+1 -1
View File
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
file.write(&#34;FROM python:3.11-slim\n&#34;)
file.write(&#34;WORKDIR /app\n&#34;)
file.write(&#34;COPY . .\n&#34;)
file.write(&#34;RUN pip install flask praisonai==0.0.55 gunicorn markdown\n&#34;)
file.write(&#34;RUN pip install flask praisonai==0.1.2 gunicorn markdown\n&#34;)
file.write(&#34;EXPOSE 8080\n&#34;)
file.write(&#39;CMD [&#34;gunicorn&#34;, &#34;-b&#34;, &#34;0.0.0.0:8080&#34;, &#34;api:app&#34;]\n&#39;)
+69
View File
@@ -0,0 +1,69 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.1">
<title>praisonai.inc.config API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>praisonai.inc.config</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="praisonai.inc.config.generate_config"><code class="name flex">
<span>def <span class="ident">generate_config</span></span>(<span>ollama_save=None, huggingface_save=None, train=None, model_name=None, hf_model_name=None, ollama_model_name=None, model_parameters=None, max_seq_length=None, load_in_4bit=None, lora_r=None, lora_target_modules=None, lora_alpha=None, lora_dropout=None, lora_bias=None, use_gradient_checkpointing=None, random_state=None, use_rslora=None, loftq_config=None, dataset=None, dataset_text_field=None, dataset_num_proc=None, packing=None, per_device_train_batch_size=None, gradient_accumulation_steps=None, warmup_steps=None, num_train_epochs=None, max_steps=None, learning_rate=None, logging_steps=None, optim=None, weight_decay=None, lr_scheduler_type=None, seed=None, output_dir=None, quantization_method=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Generates the configuration for PraisonAI with dynamic overrides.</p></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="praisonai.inc" href="index.html">praisonai.inc</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="praisonai.inc.config.generate_config" href="#praisonai.inc.config.generate_config">generate_config</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.1</a>.</p>
</footer>
</body>
</html>
+5
View File
@@ -29,6 +29,10 @@ hljs.highlightAll();
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="praisonai.inc.config" href="config.html">praisonai.inc.config</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="praisonai.inc.models" href="models.html">praisonai.inc.models</a></code></dt>
<dd>
<div class="desc"></div>
@@ -54,6 +58,7 @@ hljs.highlightAll();
</li>
<li><h3><a href="#header-submodules">Sub-modules</a></h3>
<ul>
<li><code><a title="praisonai.inc.config" href="config.html">praisonai.inc.config</a></code></li>
<li><code><a title="praisonai.inc.models" href="models.html">praisonai.inc.models</a></code></li>
</ul>
</li>
+5
View File
@@ -53,6 +53,10 @@ hljs.highlightAll();
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="praisonai.setup" href="setup/index.html">praisonai.setup</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="praisonai.test" href="test.html">praisonai.test</a></code></dt>
<dd>
<div class="desc"></div>
@@ -87,6 +91,7 @@ hljs.highlightAll();
<li><code><a title="praisonai.deploy" href="deploy.html">praisonai.deploy</a></code></li>
<li><code><a title="praisonai.inbuilt_tools" href="inbuilt_tools/index.html">praisonai.inbuilt_tools</a></code></li>
<li><code><a title="praisonai.inc" href="inc/index.html">praisonai.inc</a></code></li>
<li><code><a title="praisonai.setup" href="setup/index.html">praisonai.setup</a></code></li>
<li><code><a title="praisonai.test" href="test.html">praisonai.test</a></code></li>
<li><code><a title="praisonai.ui" href="ui/index.html">praisonai.ui</a></code></li>
<li><code><a title="praisonai.version" href="version.html">praisonai.version</a></code></li>
+69
View File
@@ -0,0 +1,69 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.1">
<title>praisonai.setup.build API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>praisonai.setup.build</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="praisonai.setup.build.build"><code class="name flex">
<span>def <span class="ident">build</span></span>(<span>setup_kwargs)</span>
</code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="praisonai.setup" href="index.html">praisonai.setup</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="praisonai.setup.build.build" href="#praisonai.setup.build.build">build</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.1</a>.</p>
</footer>
</body>
</html>
+77
View File
@@ -0,0 +1,77 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.1">
<title>praisonai.setup API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Namespace <code>praisonai.setup</code></h1>
</header>
<section id="section-intro">
</section>
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="praisonai.setup.build" href="build.html">praisonai.setup.build</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="praisonai.setup.post_install" href="post_install.html">praisonai.setup.post_install</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt><code class="name"><a title="praisonai.setup.setup_conda_env" href="setup_conda_env.html">praisonai.setup.setup_conda_env</a></code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
<section>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="praisonai" href="../index.html">praisonai</a></code></li>
</ul>
</li>
<li><h3><a href="#header-submodules">Sub-modules</a></h3>
<ul>
<li><code><a title="praisonai.setup.build" href="build.html">praisonai.setup.build</a></code></li>
<li><code><a title="praisonai.setup.post_install" href="post_install.html">praisonai.setup.post_install</a></code></li>
<li><code><a title="praisonai.setup.setup_conda_env" href="setup_conda_env.html">praisonai.setup.setup_conda_env</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.1</a>.</p>
</footer>
</body>
</html>
@@ -0,0 +1,69 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.1">
<title>praisonai.setup.post_install API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>praisonai.setup.post_install</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="praisonai.setup.post_install.main"><code class="name flex">
<span>def <span class="ident">main</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="praisonai.setup" href="index.html">praisonai.setup</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="praisonai.setup.post_install.main" href="#praisonai.setup.post_install.main">main</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.1</a>.</p>
</footer>
</body>
</html>
@@ -0,0 +1,69 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.1">
<title>praisonai.setup.setup_conda_env API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin-bottom:1em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>praisonai.setup.setup_conda_env</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="praisonai.setup.setup_conda_env.main"><code class="name flex">
<span>def <span class="ident">main</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="praisonai.setup" href="index.html">praisonai.setup</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="praisonai.setup.setup_conda_env.main" href="#praisonai.setup.setup_conda_env.main">main</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.1</a>.</p>
</footer>
</body>
</html>
+116 -23
View File
@@ -68,10 +68,12 @@ hljs.highlightAll();
self.max_file_size = max_file_size
self.max_tokens = int(os.getenv(&#34;PRAISONAI_MAX_TOKENS&#34;, max_tokens))
self.ignore_patterns = self.get_ignore_patterns()
self.include_paths = self.get_include_paths()
self.included_files = []
def get_ignore_patterns(self):
&#34;&#34;&#34;
Loads ignore patterns from various sources, prioritizing them in
Loads ignore patterns from various sources, prioritizing them in
the following order:
1. .praisonignore
2. settings.yaml (under code.ignore_files)
@@ -129,6 +131,38 @@ hljs.highlightAll();
logger.debug(f&#34;Final ignore patterns: {modified_ignore_patterns}&#34;)
return modified_ignore_patterns
def get_include_paths(self):
&#34;&#34;&#34;
Loads include paths from:
1. .praisoninclude (includes ONLY files/directories listed)
2. .praisoncontext (if .praisoninclude doesn&#39;t exist, this is used
to include all other relevant files, excluding ignore patterns)
&#34;&#34;&#34;
include_paths = []
include_all = False # Flag to indicate if we need to include all files
include_file = os.path.join(self.directory, &#39;.praisoncontext&#39;)
if os.path.exists(include_file):
with open(include_file, &#39;r&#39;) as f:
include_paths.extend(
line.strip() for line in f
if line.strip() and not line.startswith(&#39;#&#39;)
)
# If .praisoncontext doesn&#39;t exist, fall back to .praisoninclude
# for including all relevant files
if not include_paths:
include_file = os.path.join(self.directory, &#39;.praisoninclude&#39;)
if os.path.exists(include_file):
with open(include_file, &#39;r&#39;) as f:
include_paths.extend(
line.strip() for line in f
if line.strip() and not line.startswith(&#39;#&#39;)
)
include_all = True # Include all files along with specified paths
return include_paths, include_all
def should_ignore(self, file_path):
&#34;&#34;&#34;
Check if a file or directory should be ignored based on patterns.
@@ -150,31 +184,82 @@ hljs.highlightAll();
any(file_path.endswith(ext) for ext in self.relevant_extensions)
def gather_context(self):
&#34;&#34;&#34;Gather context from relevant files, respecting ignore patterns.&#34;&#34;&#34;
&#34;&#34;&#34;
Gather context from relevant files, respecting ignore patterns
and include options from .praisoninclude and .praisoncontext.
&#34;&#34;&#34;
context = []
total_files = 0
processed_files = 0
self.include_paths, include_all = self.get_include_paths()
for root, dirs, files in os.walk(self.directory):
total_files += len(files)
dirs[:] = [d for d in dirs if not self.should_ignore(os.path.join(root, d))]
for file in files:
file_path = os.path.join(root, file)
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
try:
with open(file_path, &#39;r&#39;, encoding=&#39;utf-8&#39;) as f:
content = f.read()
context.append(f&#34;File: {file_path}\n\n{content}\n\n{&#39;=&#39;*50}\n&#34;)
except Exception as e:
logger.error(f&#34;Error reading {file_path}: {e}&#34;)
def add_file_content(file_path):
&#34;&#34;&#34;Helper function to add file content to context.&#34;&#34;&#34;
try:
with open(file_path, &#39;r&#39;, encoding=&#39;utf-8&#39;) as f:
content = f.read()
context.append(
f&#34;File: {file_path}\n\n{content}\n\n{&#39;=&#39; * 50}\n&#34;
)
self.included_files.append(
Path(file_path).relative_to(self.directory)
)
except Exception as e:
logger.error(f&#34;Error reading {file_path}: {e}&#34;)
def process_path(path):
&#34;&#34;&#34;Helper function to process a single path (file or directory).&#34;&#34;&#34;
nonlocal total_files, processed_files
if os.path.isdir(path):
for root, dirs, files in os.walk(path):
total_files += len(files)
dirs[:] = [
d
for d in dirs
if not self.should_ignore(os.path.join(root, d))
]
for file in files:
file_path = os.path.join(root, file)
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
add_file_content(file_path)
processed_files += 1
print(
f&#34;\rProcessed {processed_files}/{total_files} files&#34;,
end=&#34;&#34;,
flush=True,
)
elif os.path.isfile(path) and self.is_relevant_file(path):
add_file_content(path)
processed_files += 1
print(f&#34;\rProcessed {processed_files}/{total_files} files&#34;, end=&#34;&#34;, flush=True)
print(
f&#34;\rProcessed {processed_files}/1 files&#34;,
end=&#34;&#34;,
flush=True,
)
if include_all:
# Include ALL relevant files from the entire directory
process_path(self.directory)
# Include files from .praisoninclude specifically
for include_path in self.include_paths:
full_path = os.path.join(self.directory, include_path)
process_path(full_path)
elif self.include_paths:
# Include only files specified in .praisoncontext
for include_path in self.include_paths:
full_path = os.path.join(self.directory, include_path)
process_path(full_path)
else:
# No include options, process the entire directory
process_path(self.directory)
print() # New line after progress indicator
return &#39;\n&#39;.join(context)
return &#34;\n&#34;.join(context)
def count_tokens(self, text):
&#34;&#34;&#34;Count tokens using a simple whitespace-based tokenizer.&#34;&#34;&#34;
return len(text.split())
return len(text.split())
def truncate_context(self, context):
&#34;&#34;&#34;Truncate context to stay within the token limit.&#34;&#34;&#34;
@@ -199,12 +284,9 @@ hljs.highlightAll();
contents = sorted(path.iterdir())
pointers = [(&#39;└── &#39; if i == len(contents) - 1 else &#39;├── &#39;) for i in range(len(contents))]
for pointer, item in zip(pointers, contents):
# Use should_ignore for consistency
if self.should_ignore(item):
continue
rel_path = item.relative_to(start_dir)
tree.append(f&#34;{prefix}{pointer}{rel_path}&#34;)
if rel_path in self.included_files:
tree.append(f&#34;{prefix}{pointer}{rel_path}&#34;)
if item.is_dir():
add_to_tree(item, prefix + (&#39; &#39; if pointer == &#39;└── &#39; else &#39;│ &#39;))
@@ -236,7 +318,8 @@ hljs.highlightAll();
<span>def <span class="ident">gather_context</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Gather context from relevant files, respecting ignore patterns.</p></div>
<div class="desc"><p>Gather context from relevant files, respecting ignore patterns
and include options from .praisoninclude and .praisoncontext.</p></div>
</dd>
<dt id="praisonai.ui.context.ContextGatherer.get_context_tree"><code class="name flex">
<span>def <span class="ident">get_context_tree</span></span>(<span>self)</span>
@@ -256,6 +339,15 @@ the following order:
4. .gitignore
5. Default patterns</p></div>
</dd>
<dt id="praisonai.ui.context.ContextGatherer.get_include_paths"><code class="name flex">
<span>def <span class="ident">get_include_paths</span></span>(<span>self)</span>
</code></dt>
<dd>
<div class="desc"><p>Loads include paths from:
1. .praisoninclude (includes ONLY files/directories listed)
2. .praisoncontext (if .praisoninclude doesn't exist, this is used
to include all other relevant files, excluding ignore patterns)</p></div>
</dd>
<dt id="praisonai.ui.context.ContextGatherer.is_relevant_file"><code class="name flex">
<span>def <span class="ident">is_relevant_file</span></span>(<span>self, file_path)</span>
</code></dt>
@@ -316,6 +408,7 @@ Handles both file names and directory names for more comprehensive filtering.</p
<li><code><a title="praisonai.ui.context.ContextGatherer.gather_context" href="#praisonai.ui.context.ContextGatherer.gather_context">gather_context</a></code></li>
<li><code><a title="praisonai.ui.context.ContextGatherer.get_context_tree" href="#praisonai.ui.context.ContextGatherer.get_context_tree">get_context_tree</a></code></li>
<li><code><a title="praisonai.ui.context.ContextGatherer.get_ignore_patterns" href="#praisonai.ui.context.ContextGatherer.get_ignore_patterns">get_ignore_patterns</a></code></li>
<li><code><a title="praisonai.ui.context.ContextGatherer.get_include_paths" href="#praisonai.ui.context.ContextGatherer.get_include_paths">get_include_paths</a></code></li>
<li><code><a title="praisonai.ui.context.ContextGatherer.is_relevant_file" href="#praisonai.ui.context.ContextGatherer.is_relevant_file">is_relevant_file</a></code></li>
<li><code><a title="praisonai.ui.context.ContextGatherer.run" href="#praisonai.ui.context.ContextGatherer.run">run</a></code></li>
<li><code><a title="praisonai.ui.context.ContextGatherer.save_context" href="#praisonai.ui.context.ContextGatherer.save_context">save_context</a></code></li>
+367 -220
View File
@@ -4,30 +4,30 @@
"source": "https://excalidraw.com",
"elements": [
{
"id": "gBkFdzkAVdEWutG3qTrB0",
"type": "rectangle",
"x": 536.4349670410156,
"y": -677.1792297363279,
"width": 178.43490600585935,
"height": 44.315124511718736,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 443,
"versionNonce": 523921598,
"index": "b0g",
"isDeleted": false,
"id": "gBkFdzkAVdEWutG3qTrB0",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 536.4349670410156,
"y": -677.1792297363279,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 178.43490600585935,
"height": 44.315124511718736,
"seed": 1579902398,
"groupIds": [],
"frameId": null,
"index": "b0g",
"roundness": {
"type": 3
},
"seed": 1579902398,
"version": 443,
"versionNonce": 523921598,
"isDeleted": false,
"boundElements": [
{
"type": "text",
@@ -51,35 +51,35 @@
"locked": false
},
{
"id": "rXvHMTDzQw3KXG5Bw6Miq",
"type": "text",
"x": 564.0524749755859,
"y": -667.5216674804685,
"width": 123.19989013671875,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 342,
"versionNonce": 538330338,
"index": "b0gV",
"isDeleted": false,
"id": "rXvHMTDzQw3KXG5Bw6Miq",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 564.0524749755859,
"y": -667.5216674804685,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 123.19989013671875,
"height": 25,
"seed": 1207531362,
"groupIds": [],
"frameId": null,
"index": "b0gV",
"roundness": null,
"seed": 1207531362,
"version": 342,
"versionNonce": 538330338,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721295807540,
"link": null,
"locked": false,
"text": "Multi Agents",
"fontSize": 20,
"fontFamily": 1,
"text": "Multi Agents",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "gBkFdzkAVdEWutG3qTrB0",
@@ -135,35 +135,35 @@
"locked": false
},
{
"id": "Fnl1rz2BlXU6J8Yq8YZwu",
"type": "text",
"x": 858.4403305053711,
"y": -672.9149169921873,
"width": 116.81990051269531,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 142,
"versionNonce": 1682602622,
"index": "b0iV",
"isDeleted": false,
"id": "Fnl1rz2BlXU6J8Yq8YZwu",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 858.4403305053711,
"y": -672.9149169921873,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 116.81990051269531,
"height": 25,
"seed": 594220962,
"groupIds": [],
"frameId": null,
"index": "b0iV",
"roundness": null,
"seed": 594220962,
"version": 142,
"versionNonce": 1682602622,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721295812159,
"link": null,
"locked": false,
"text": "Single Agent",
"fontSize": 20,
"fontFamily": 1,
"text": "Single Agent",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "hOUwrI2Zw6CbQVBzSj3j_",
@@ -172,34 +172,44 @@
"lineHeight": 1.25
},
{
"id": "3zLj8LrUzhGGXhZaDSJgs",
"type": "arrow",
"x": 775.7563686946191,
"y": -733.9083557128904,
"width": 64.00441130704803,
"height": 52.950469970703125,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 800,
"versionNonce": 1469757538,
"index": "b0k",
"isDeleted": false,
"id": "3zLj8LrUzhGGXhZaDSJgs",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 775.7563686946191,
"y": -733.9083557128904,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 64.00441130704803,
"height": 52.950469970703125,
"seed": 1611346302,
"groupIds": [],
"frameId": null,
"index": "b0k",
"roundness": {
"type": 2
},
"seed": 1611346302,
"version": 800,
"versionNonce": 1469757538,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721296052140,
"link": null,
"locked": false,
"startBinding": null,
"endBinding": {
"elementId": "gBkFdzkAVdEWutG3qTrB0",
"gap": 3.778656005859375,
"focus": 0.47197049085398457,
"fixedPoint": null
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
@@ -209,46 +219,47 @@
-64.00441130704803,
52.950469970703125
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": {
"elementId": "gBkFdzkAVdEWutG3qTrB0",
"gap": 3.778656005859375,
"focus": 0.47197049085398457
},
"startArrowhead": null,
"endArrowhead": "arrow"
]
},
{
"id": "YYmrmCN3zGtGeOPK2Ccbw",
"type": "arrow",
"x": 810.3017634410855,
"y": -732.6739807128904,
"width": 85.77694061921636,
"height": 44.679656982421875,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 776,
"versionNonce": 774462498,
"index": "b0l",
"isDeleted": false,
"id": "YYmrmCN3zGtGeOPK2Ccbw",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 810.3017634410855,
"y": -732.6739807128904,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 85.77694061921636,
"height": 44.679656982421875,
"seed": 412089534,
"groupIds": [],
"frameId": null,
"index": "b0l",
"roundness": {
"type": 2
},
"seed": 412089534,
"version": 776,
"versionNonce": 774462498,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721296052140,
"link": null,
"locked": false,
"startBinding": null,
"endBinding": {
"elementId": "hOUwrI2Zw6CbQVBzSj3j_",
"gap": 7.261688232421875,
"focus": 0.24350278296403458,
"fixedPoint": null
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
@@ -258,16 +269,7 @@
85.77694061921636,
44.679656982421875
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": {
"elementId": "hOUwrI2Zw6CbQVBzSj3j_",
"gap": 7.261688232421875,
"focus": 0.24350278296403458
},
"startArrowhead": null,
"endArrowhead": "arrow"
]
},
{
"type": "rectangle",
@@ -574,34 +576,49 @@
"lineHeight": 1.25
},
{
"id": "cKGXib4-dKdUl9yJcSfq-",
"type": "arrow",
"x": 627.2005615234375,
"y": -630.5438232421873,
"width": 60.1224365234375,
"height": 55.1927490234375,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 98,
"versionNonce": 1160484030,
"index": "b0w",
"isDeleted": false,
"id": "cKGXib4-dKdUl9yJcSfq-",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 627.2005615234375,
"y": -630.5438232421873,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 60.1224365234375,
"height": 55.1927490234375,
"seed": 233127038,
"groupIds": [],
"frameId": null,
"index": "b0w",
"roundness": {
"type": 2
},
"seed": 233127038,
"version": 98,
"versionNonce": 1160484030,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721295849323,
"link": null,
"locked": false,
"startBinding": {
"elementId": "gBkFdzkAVdEWutG3qTrB0",
"focus": -0.24888641301408482,
"gap": 2.320281982421875,
"fixedPoint": null
},
"endBinding": {
"elementId": "nsG_S4C6dOJXv5Hn71Vpv",
"focus": -0.18497317396435772,
"gap": 5.627456665039119,
"fixedPoint": null
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
@@ -611,50 +628,52 @@
-60.1224365234375,
55.1927490234375
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "gBkFdzkAVdEWutG3qTrB0",
"focus": -0.24888641301408482,
"gap": 2.320281982421875
},
"endBinding": {
"elementId": "nsG_S4C6dOJXv5Hn71Vpv",
"focus": -0.18497317396435772,
"gap": 5.627456665039119
},
"startArrowhead": null,
"endArrowhead": "arrow"
]
},
{
"id": "BS019CDTkphQWLmagOrGS",
"type": "arrow",
"x": 630.2474365234375,
"y": -629.0698547363279,
"width": 80.703125,
"height": 53.463531494140625,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 80,
"versionNonce": 799711486,
"index": "b0x",
"isDeleted": false,
"id": "BS019CDTkphQWLmagOrGS",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 630.2474365234375,
"y": -629.0698547363279,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 80.703125,
"height": 53.463531494140625,
"seed": 310377790,
"groupIds": [],
"frameId": null,
"index": "b0x",
"roundness": {
"type": 2
},
"seed": 310377790,
"version": 80,
"versionNonce": 799711486,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721295852022,
"link": null,
"locked": false,
"startBinding": {
"elementId": "gBkFdzkAVdEWutG3qTrB0",
"focus": 0.28190124455103954,
"gap": 3.79425048828125,
"fixedPoint": null
},
"endBinding": {
"elementId": "QCX8dp2PO9HkjqWidvhuK",
"focus": 0.366344355951018,
"gap": 5.657794922835194,
"fixedPoint": null
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
@@ -664,50 +683,52 @@
80.703125,
53.463531494140625
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "gBkFdzkAVdEWutG3qTrB0",
"focus": 0.28190124455103954,
"gap": 3.79425048828125
},
"endBinding": {
"elementId": "QCX8dp2PO9HkjqWidvhuK",
"focus": 0.366344355951018,
"gap": 5.657794922835194
},
"startArrowhead": null,
"endArrowhead": "arrow"
]
},
{
"id": "e8q5v4IQp7HYVNQjKLNNS",
"type": "arrow",
"x": 923.2083740234375,
"y": -638.9448547363279,
"width": 36.4427490234375,
"height": 58.135406494140625,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 78,
"versionNonce": 780221630,
"index": "b0y",
"isDeleted": false,
"id": "e8q5v4IQp7HYVNQjKLNNS",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 923.2083740234375,
"y": -638.9448547363279,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 36.4427490234375,
"height": 58.135406494140625,
"seed": 895023486,
"groupIds": [],
"frameId": null,
"index": "b0y",
"roundness": {
"type": 2
},
"seed": 895023486,
"version": 78,
"versionNonce": 780221630,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721295854370,
"link": null,
"locked": false,
"startBinding": {
"elementId": "hOUwrI2Zw6CbQVBzSj3j_",
"focus": -0.1871843733465698,
"gap": 1.15234375,
"fixedPoint": null
},
"endBinding": {
"elementId": "7Ii8GSxtLkw5d3u_f-_kt",
"focus": -0.16423249726949576,
"gap": 5.475143432617244,
"fixedPoint": null
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
@@ -717,50 +738,52 @@
-36.4427490234375,
58.135406494140625
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "hOUwrI2Zw6CbQVBzSj3j_",
"focus": -0.1871843733465698,
"gap": 1.15234375
},
"endBinding": {
"elementId": "7Ii8GSxtLkw5d3u_f-_kt",
"focus": -0.16423249726949576,
"gap": 5.475143432617244
},
"startArrowhead": null,
"endArrowhead": "arrow"
]
},
{
"id": "jBFbBBsN9eixvuKN38SL2",
"type": "arrow",
"x": 922.2291870117188,
"y": -637.4943237304685,
"width": 98.93487548828125,
"height": 53.84375,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"version": 96,
"versionNonce": 1061850366,
"index": "b0z",
"isDeleted": false,
"id": "jBFbBBsN9eixvuKN38SL2",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 922.2291870117188,
"y": -637.4943237304685,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 98.93487548828125,
"height": 53.84375,
"seed": 277587262,
"groupIds": [],
"frameId": null,
"index": "b0z",
"roundness": {
"type": 2
},
"seed": 277587262,
"version": 96,
"versionNonce": 1061850366,
"isDeleted": false,
"boundElements": null,
"boundElements": [],
"updated": 1721295856562,
"link": null,
"locked": false,
"startBinding": {
"elementId": "hOUwrI2Zw6CbQVBzSj3j_",
"focus": 0.2815822612188661,
"gap": 2.602874755859375,
"fixedPoint": null
},
"endBinding": {
"elementId": "PqS5K_ghWhUe3nTiPZEWE",
"focus": 0.3663457157233071,
"gap": 8.804920166975819,
"fixedPoint": null
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
@@ -770,28 +793,15 @@
98.93487548828125,
53.84375
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "hOUwrI2Zw6CbQVBzSj3j_",
"focus": 0.2815822612188661,
"gap": 2.602874755859375
},
"endBinding": {
"elementId": "PqS5K_ghWhUe3nTiPZEWE",
"focus": 0.3663457157233071,
"gap": 8.804920166975819
},
"startArrowhead": null,
"endArrowhead": "arrow"
]
},
{
"id": "TPQS4IlMmufv2ygx4l4Yf",
"id": "w6A71Se_QwWE1ccul18sf",
"type": "image",
"x": 653.6796875,
"y": -811.0686543782549,
"width": 266.3379620962191,
"height": 88.4737120216356,
"x": 606.0624237060547,
"y": -835.5551305016445,
"width": 367.8131151972591,
"height": 131.33013522121672,
"angle": 0,
"strokeColor": "transparent",
"backgroundColor": "transparent",
@@ -802,22 +812,159 @@
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b10",
"index": "b14",
"roundness": null,
"seed": 1698053026,
"version": 457,
"versionNonce": 1848779518,
"seed": 1980634807,
"version": 359,
"versionNonce": 1138184023,
"isDeleted": false,
"boundElements": null,
"updated": 1721296094680,
"boundElements": [
{
"id": "jg5gTXheFUbYRooICAtzN",
"type": "arrow"
}
],
"updated": 1722935394882,
"link": null,
"locked": false,
"status": "saved",
"fileId": "80dea94d36f7e9692b17699d94280a7f4cdefbfe",
"fileId": "74aba821e88621a6bb8521793d440bf841a59ea0",
"scale": [
1,
1
]
},
{
"type": "rectangle",
"version": 965,
"versionNonce": 909026711,
"index": "b15",
"isDeleted": false,
"id": "IFIv6elA7hgqd6ItvFFH-",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 739.3193019654001,
"y": -477.73723093668605,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 130.18418049302826,
"height": 37.446437865250886,
"seed": 507775321,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "wG91Uz2UvbG9nM_BLS2yb"
},
{
"id": "jg5gTXheFUbYRooICAtzN",
"type": "arrow"
}
],
"updated": 1722935394882,
"link": null,
"locked": false
},
{
"type": "text",
"version": 361,
"versionNonce": 1337452983,
"index": "b16",
"isDeleted": false,
"id": "wG91Uz2UvbG9nM_BLS2yb",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 778.2014107704164,
"y": -471.5140120040606,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 52.419962882995605,
"height": 25,
"seed": 1938519609,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1722935387219,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 1,
"text": "Train",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "IFIv6elA7hgqd6ItvFFH-",
"originalText": "Train",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "jg5gTXheFUbYRooICAtzN",
"type": "arrow",
"x": 794.5650482177734,
"y": -699.2901045333574,
"width": 4.5546875,
"height": 210.64584350585938,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "b17",
"roundness": {
"type": 2
},
"seed": 733226999,
"version": 197,
"versionNonce": 1023495287,
"isDeleted": false,
"boundElements": null,
"updated": 1722935394882,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
4.5546875,
210.64584350585938
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "w6A71Se_QwWE1ccul18sf",
"focus": -0.01656277827105869,
"gap": 4.9348907470703125,
"fixedPoint": null
},
"endBinding": {
"elementId": "IFIv6elA7hgqd6ItvFFH-",
"focus": -0.07101060384161016,
"gap": 10.907030090811986,
"fixedPoint": null
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
}
],
"appState": {
@@ -825,12 +972,12 @@
"viewBackgroundColor": "#ffffff"
},
"files": {
"80dea94d36f7e9692b17699d94280a7f4cdefbfe": {
"74aba821e88621a6bb8521793d440bf841a59ea0": {
"mimeType": "image/png",
"id": "80dea94d36f7e9692b17699d94280a7f4cdefbfe",
"dataURL": "data:image/png;base64,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 truncated
"created": 1721296075040,
"lastRetrieved": 1721296075040
"id": "74aba821e88621a6bb8521793d440bf841a59ea0",
"dataURL": "data:image/png;base64,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 truncated
"created": 1722935366846,
"lastRetrieved": 1722935366846
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 171 KiB

+101
View File
@@ -0,0 +1,101 @@
# PraisonAI Train
```bash
pip install "praisonai[train]"
```
## To upload to Huggingface
```bash
export HF_TOKEN=xxxxxxxxxxxxxx
```
## To upload to ollama.com (Linux)
```bash
sudo cat /usr/share/ollama/.ollama/id_ed25519.pub
```
Save the output from above to ollama.com --> Ollama keys
## RUN PraisonAI Train
```bash
praisonai train \
--model unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit \
--dataset yahma/alpaca-cleaned \
--hf mervinpraison/llama3.1-instruct \
--ollama mervinpraison/llama3.1-instruct
```
Note: PraisonAI Train currently tested on Linux with 1 GPU. With pytorch-cuda=12.1
## Config.yaml example
```yaml
ollama_save: "true"
huggingface_save: "true"
train: "true"
model_name: "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
hf_model_name: "mervinpraison/llama-3.1-instruct"
ollama_model: "mervinpraison/llama3.1-instruct"
model_parameters: "8b"
dataset:
- name: "yahma/alpaca-cleaned"
split_type: "train"
processing_func: "format_prompts"
rename:
input: "input"
output: "output"
instruction: "instruction"
filter_data: false
filter_column_value: "id"
filter_value: "alpaca"
num_samples: 20000
dataset_text_field: "text"
dataset_num_proc: 2
packing: false
max_seq_length: 2048
load_in_4bit: true
lora_r: 16
lora_target_modules:
- "q_proj"
- "k_proj"
- "v_proj"
- "o_proj"
- "gate_proj"
- "up_proj"
- "down_proj"
lora_alpha: 16
lora_dropout: 0
lora_bias: "none"
use_gradient_checkpointing: "unsloth"
random_state: 3407
use_rslora: false
loftq_config: null
per_device_train_batch_size: 2
gradient_accumulation_steps: 2
warmup_steps: 5
num_train_epochs: 1
max_steps: 10
learning_rate: 2.0e-4
logging_steps: 1
optim: "adamw_8bit"
weight_decay: 0.01
lr_scheduler_type: "linear"
seed: 3407
output_dir: "outputs"
quantization_method:
- "q4_k_m"
```
```bash
praisonai train
```
+22 -4
View File
@@ -10,19 +10,37 @@ Use 100+ LLMs
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praison.ai/ui/chat](https://docs.praison.ai/ui/chat) |
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praison.ai/ui/code](https://docs.praison.ai/ui/code) |
1.
## Quick Start
1. Install PraisonAI Chat:
```bash
pip install "praisonai[chat]"
```
2.
2. Set up your OpenAI API key:
```bash
export OPENAI_API_KEY=xxxxxxxx
```
3.
3. Launch PraisonAI Chat:
```bash
praisonai chat
```
4. Set Model name to be gpt-4o-mini in the settings
4. Set Model name to be gpt-3.5-turbo in the settings
## Key Features
### Internet Search
PraisonAI Chat now includes internet search capabilities using Crawl4AI and Tavily. This feature allows you to retrieve up-to-date information during your conversations, enhancing the AI's ability to provide current and relevant information.
### Vision Language Model (VLM) Support
You can now upload images and ask questions based on them using Vision Language Models. This multimodal support enables visual understanding and analysis within your chat sessions, allowing for a more comprehensive interaction with the AI.
To use this feature:
1. Upload an image to the chat interface
2. Ask questions or request analysis based on the uploaded image
3. The VLM will process the image and provide insights or answers based on its visual content
These new features significantly expand the capabilities of PraisonAI Chat, allowing for more diverse and informative interactions.
+98 -11
View File
@@ -10,6 +10,21 @@ PraisonAI Code helps you to interact with your whole codebase using the power of
| **Chat** | Chat with 100+ LLMs, single AI Agent | [https://docs.praison.ai/ui/chat](https://docs.praison.ai/ui/chat) |
| **Code** | Chat with entire Codebase, single AI Agent | [https://docs.praison.ai/ui/code](https://docs.praison.ai/ui/code) |
## Table of Contents
- [Install PraisonAI Code](#install-praisonai-code)
- [Other Models](#other-models)
- [To Use Gemini 1.5](#to-use-gemini-15)
- [Ignore Files](#ignore-files)
- [Using .praisonignore](#using-praisonignore)
- [Using settings.yaml](#using-settingsyaml)
- [Using .env File](#using-env-file)
- [Using Environment Variables in the Terminal](#using-environment-variables-in-the-terminal)
- [Include Files](#include-files-praisoninclude)
- [Set Max Tokens](#set-max-tokens)
## Install PraisonAI Code
1.
```bash
pip install "praisonai[code]"
@@ -27,12 +42,12 @@ praisonai code
4. Username and Password will be asked for the first time. `admin` is the default username and password.
5. Set Model name to be gpt-3.5-turbo in the settings
5. Set Model name to be gpt-4o-mini in the settings
## Other Models
* Use 100+ LLMs
* Use 100+ LLMs - [Litellm](https://litellm.vercel.app/docs/providers)
* Includes Gemini 1.5 for 2 Million Context Length
## To Use Gemini 1.5
@@ -43,9 +58,29 @@ praisonai code
## Ignore Files
### Using settings.yaml
### Using .praisonignore
* Create a settings.yaml file in the root folder of the project
* Create a `.praisonignore` file in the root folder of the project
* Add files to ignore
```bash
.*
*.pyc
pycache
.git
.gitignore
.vscode
.idea
.DS_Store
.lock
.pyc
.env
```
### Using settings.yaml
(.praisonignore is preferred)
* Create a `settings.yaml` file in the root folder of the project
* Add below Variables and required Ignore Files
```yaml
@@ -66,25 +101,51 @@ code:
### Using .env File
* Create a .env file in the root folder of the project
* Create a `.env` file in the root folder of the project
* Add below Variables and required Ignore Files
```bash
PRAISONAI_IGNORE_FILES=".*,*.pyc,__pycache__,.git,.gitignore,.vscode,.idea,.DS_Store,*.lock,.env,docs,tests,test,tmp,temp,*.txt,*.md,*.json,*.csv,*.tsv,public,*.sql,*.sqlite,*.db,*.db3,*.sqlite3,*.log,*.zip,*.gz,*.tar,*.rar,*.7z,*.pdf,*.jpg,*.jpeg,*.png,*.gif,*.svg,cookbooks,assets,dist,build,node_modules,venv,crewAI,.cache,*.__pycache__,*chroma.sqlite3,test/,dist/,praisonAI.egg-info,test.yaml,db,praisonai_prompt.txt,watch.sh,docs.sh,other,output,*.chainlit,.files,site,flagged,*public,threads.db,trained_agents_data.pkl,.pytest_cache"
PRAISONAI_IGNORE_FILES=".*,*.pyc,__pycache__,.git,.gitignore,.vscode"
```
### Using Environment Variables in the Terminal
```bash
export PRAISONAI_IGNORE_FILES=".*,*.pyc,__pycache__,.git,.gitignore,.vscode,.idea,.DS_Store,*.lock,.env,docs,tests,test,tmp,temp,*.txt,*.md,*.json,*.csv,*.tsv,public,*.sql,*.sqlite,*.db,*.db3,*.sqlite3,*.log,*.zip,*.gz,*.tar,*.rar,*.7z,*.pdf,*.jpg,*.jpeg,*.png,*.gif,*.svg,cookbooks,assets,dist,build,node_modules,venv,crewAI,.cache,*.__pycache__,*chroma.sqlite3,test/,dist/,praisonAI.egg-info,test.yaml,db,praisonai_prompt.txt,watch.sh,docs.sh,other,output,*.chainlit,.files,site,flagged,*public,threads.db,trained_agents_data.pkl,.pytest_cache"
export PRAISONAI_IGNORE_FILES=".*,*.pyc,__pycache__,.git,.gitignore,.vscode"
```
## Include Files .praisoninclude
- Add files you wish to Include files in the context
- This will include the files/folders mentioned in `.praisoninclude` to the original context (files in the folder - .gitignore - .praisonignore)
* Create a `.praisoninclude` file in the root folder of the project
* Add files to Include
```bash
projectfiles
docs
```
## Include ONLY these Files .praisoncontext (Context)
- Add files you wish to Include files in the context
- This will include ONLY the files/folders mentioned in `.praisoncontext` to the context
* Create a `.praisoncontext` file in the root folder of the project
* Add files to Include
```bash
projectfiles
docs
```
## Set Max Tokens
Note: By Default Max Tokens set is 128,000
Note: By Default Max Tokens set is 900,000
```bash
export PRAISONAI_MAX_TOKENS=200000
export PRAISONAI_MAX_TOKENS=1000000
```
or
@@ -92,5 +153,31 @@ or
* Create a .env file in the root folder of the project
* Add below Variables and required Max Tokens
* ```
PRAISONAI_MAX_TOKENS=200000
```
PRAISONAI_MAX_TOKENS=1000000
```
## Default DB Location
`~/.praison/database.sqlite`
## Key Features
### Internet Search
PraisonAI Code now includes internet search capabilities using Crawl4AI and Tavily. This feature allows you to retrieve up-to-date information and code snippets during your coding sessions, enhancing your ability to find relevant programming information and examples.
To use this feature:
1. Ask a question or request information about a specific coding topic
2. The AI will use internet search to find the most relevant and current information
3. You'll receive code snippets, documentation references, or explanations based on the latest available resources
### Vision Language Model (VLM) Support
While primarily designed for code interactions, PraisonAI Code also supports Vision Language Model capabilities. This feature can be particularly useful when dealing with visual aspects of programming, such as UI design, data visualization, or understanding code structure through diagrams.
To use this feature:
1. Upload an image related to your coding query (e.g., a screenshot of a UI, a flowchart, or a code snippet image)
2. Ask questions or request analysis based on the uploaded image
3. The VLM will process the image and provide insights or answers based on its visual content, helping you understand or implement the visual concepts in your code
These new features significantly expand the capabilities of PraisonAI Code, allowing for more comprehensive and up-to-date coding assistance.
+59
View File
@@ -0,0 +1,59 @@
# Realtime Voice Interface
The Realtime Voice Interface in PraisonAI provides an interactive, voice-based communication channel with AI models. This interface allows for real-time audio input and output, enabling a more natural and dynamic conversation experience.
## Features
- Real-time voice input processing
- Text-to-speech output for AI responses
- Seamless integration with OpenAI's realtime API
- Support for various AI models
- Persistent conversation history
- Financial data integration with yfinance
## Getting Started
To use the Realtime Voice Interface, follow these steps:
1. Install PraisonAI with the realtime dependencies:
```bash
pip install "praisonai[realtime]"
```
2. Set up your OpenAI API key:
```bash
export OPENAI_API_KEY="your-api-key-here"
```
3. Launch the Realtime Voice Interface:
```bash
praisonai realtime
```
## Usage
Once the interface is launched:
1. Click the microphone button or press 'P' to start voice input.
2. Speak your message or query.
3. The AI will process your input and respond with both text and voice.
4. You can ask for financial data, which will be fetched using yfinance.
5. The conversation history is maintained for context in ongoing discussions.
## Configuration
You can configure various aspects of the Realtime Voice Interface:
- Model selection: Choose different AI models for processing.
- Voice settings: Adjust voice characteristics for the AI's speech output.
- Audio settings: Configure input/output audio formats and quality.
## Troubleshooting
If you encounter issues:
- Ensure your microphone is properly connected and permitted in your browser.
- Check your internet connection for stable real-time communication.
- Verify that your OpenAI API key is correctly set and has the necessary permissions.
For more detailed information and advanced usage, please refer to the [PraisonAI documentation](https://docs.praison.ai).
+2
View File
@@ -18,6 +18,8 @@ nav:
- User Interface: ui/ui.md
- Chat: ui/chat.md
- Code: ui/code.md
- Realtime: ui/realtime.md
- Train: train.md
- CrewAI: framework/crewai.md
- AutoGen: framework/autogen.md
Generated
+2861 -2443
View File
File diff suppressed because it is too large. Load diff
+20
View File
@@ -0,0 +1,20 @@
class Praisonai < Formula
include Language::Python::Virtualenv
desc "AI tools for various AI applications"
homepage "https://github.com/MervinPraison/PraisonAI"
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/0.1.2.tar.gz"
sha256 "1828fb9227d10f991522c3f24f061943a254b667196b40b1a3e4a54a8d30ce32" # Replace with actual SHA256 checksum
license "MIT"
depends_on "[email protected]"
def install
virtualenv_install_with_resources
end
test do
system "#{bin}/praisonai", "--version"
end
end
+126 -6
View File
@@ -12,11 +12,14 @@ import argparse
from .auto import AutoGenerator
from .agents_generator import AgentsGenerator
from .inbuilt_tools import *
from .inc.config import generate_config
import shutil
import subprocess
import logging
logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO'), format='%(asctime)s - %(levelname)s - %(message)s')
try:
os.environ["CHAINLIT_APP_ROOT"] = os.path.join(os.path.expanduser("~"), ".praison")
from chainlit.cli import chainlit_run
CHAINLIT_AVAILABLE = True
except ImportError:
@@ -27,6 +30,34 @@ try:
GRADIO_AVAILABLE = True
except ImportError:
GRADIO_AVAILABLE = False
def stream_subprocess(command, env=None):
"""
Execute a subprocess command and stream the output to the terminal in real-time.
Args:
command (list): A list containing the command and its arguments.
env (dict, optional): Environment variables for the subprocess.
"""
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
universal_newlines=True,
env=env
)
for line in iter(process.stdout.readline, ''):
print(line, end='')
sys.stdout.flush() # Ensure output is flushed immediately
process.stdout.close()
return_code = process.wait()
if return_code != 0:
raise subprocess.CalledProcessError(return_code, command)
class PraisonAI:
def __init__(self, agent_file="agents.yaml", framework="", auto=False, init=False, agent_yaml=None):
@@ -99,6 +130,61 @@ class PraisonAI:
self.create_code_interface()
return
if getattr(args, 'realtime', False):
self.create_realtime_interface()
return
if args.agent_file == 'train':
package_root = os.path.dirname(os.path.abspath(__file__))
config_yaml_destination = os.path.join(os.getcwd(), 'config.yaml')
# Create config.yaml only if it doesn't exist or --model or --dataset is provided
if not os.path.exists(config_yaml_destination) or args.model or args.dataset:
config = generate_config(
model_name=args.model,
hf_model_name=args.hf,
ollama_model_name=args.ollama,
dataset=[{
"name": args.dataset
}]
)
with open('config.yaml', 'w') as f:
yaml.dump(config, f, default_flow_style=False, indent=2)
# Overwrite huggingface_save and ollama_save if --hf or --ollama are provided
if args.hf:
config["huggingface_save"] = "true"
if args.ollama:
config["ollama_save"] = "true"
if 'init' in sys.argv:
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print("All packages installed")
return
try:
result = subprocess.check_output(['conda', 'env', 'list'])
if 'praison_env' in result.decode('utf-8'):
print("Conda environment 'praison_env' found.")
else:
raise subprocess.CalledProcessError(1, 'grep')
except subprocess.CalledProcessError:
print("Conda environment 'praison_env' not found. Setting it up...")
from praisonai.setup.setup_conda_env import main as setup_conda_main
setup_conda_main()
print("All packages installed.")
train_args = sys.argv[2:] # Get all arguments after 'train'
train_script_path = os.path.join(package_root, 'train.py')
# Set environment variables
env = os.environ.copy()
env['PYTHONUNBUFFERED'] = '1'
stream_subprocess(['conda', 'run', '--no-capture-output', '--name', 'praison_env', 'python', '-u', train_script_path, 'train'], env=env)
return
invocation_cmd = "praisonai"
version_string = f"PraisonAI version {__version__}"
@@ -169,7 +255,12 @@ class PraisonAI:
parser.add_argument("--auto", nargs=argparse.REMAINDER, help="Enable auto mode and pass arguments for it")
parser.add_argument("--init", nargs=argparse.REMAINDER, help="Enable auto mode and pass arguments for it")
parser.add_argument("agent_file", nargs="?", help="Specify the agent file")
parser.add_argument("--deploy", action="store_true", help="Deploy the application") # New argument
parser.add_argument("--deploy", action="store_true", help="Deploy the application")
parser.add_argument("--model", type=str, help="Model name")
parser.add_argument("--hf", type=str, help="Hugging Face model name")
parser.add_argument("--ollama", type=str, help="Ollama model name")
parser.add_argument("--dataset", type=str, help="Dataset name for training", default="yahma/alpaca-cleaned")
parser.add_argument("--realtime", action="store_true", help="Start the realtime voice interaction interface")
args, unknown_args = parser.parse_known_args()
if unknown_args and unknown_args[0] == '-b' and unknown_args[1] == 'api:app':
@@ -184,6 +275,8 @@ class PraisonAI:
if args.agent_file == 'code':
args.ui = 'chainlit'
args.code = True
if args.agent_file == 'realtime':
args.realtime = True
return args
@@ -201,10 +294,12 @@ class PraisonAI:
if CHAINLIT_AVAILABLE:
import praisonai
os.environ["CHAINLIT_PORT"] = "8084"
root_path = os.path.join(os.path.expanduser("~"), ".praison")
os.environ["CHAINLIT_APP_ROOT"] = root_path
public_folder = os.path.join(os.path.dirname(praisonai.__file__), 'public')
if not os.path.exists("public"): # Check if the folder exists in the current directory
if not os.path.exists(os.path.join(root_path, "public")): # Check if the folder exists in the current directory
if os.path.exists(public_folder):
shutil.copytree(public_folder, 'public', dirs_exist_ok=True)
shutil.copytree(public_folder, os.path.join(root_path, "public"), dirs_exist_ok=True)
logging.info("Public folder copied successfully!")
else:
logging.info("Public folder not found in the package.")
@@ -229,10 +324,12 @@ class PraisonAI:
if CHAINLIT_AVAILABLE:
import praisonai
os.environ["CHAINLIT_PORT"] = "8086"
public_folder = os.path.join(os.path.dirname(praisonai.__file__), 'public')
if not os.path.exists("public"): # Check if the folder exists in the current directory
root_path = os.path.join(os.path.expanduser("~"), ".praison")
os.environ["CHAINLIT_APP_ROOT"] = root_path
public_folder = os.path.join(os.path.dirname(__file__), 'public')
if not os.path.exists(os.path.join(root_path, "public")): # Check if the folder exists in the current directory
if os.path.exists(public_folder):
shutil.copytree(public_folder, 'public', dirs_exist_ok=True)
shutil.copytree(public_folder, os.path.join(root_path, "public"), dirs_exist_ok=True)
logging.info("Public folder copied successfully!")
else:
logging.info("Public folder not found in the package.")
@@ -326,6 +423,29 @@ class PraisonAI:
else:
print("ERROR: Chainlit is not installed. Please install it with 'pip install \"praisonai\[ui]\"' to use the UI.")
def create_realtime_interface(self):
"""
Create a Chainlit interface for the realtime voice interaction application.
"""
if CHAINLIT_AVAILABLE:
import praisonai
os.environ["CHAINLIT_PORT"] = "8088" # Ensure this port is not in use by another service
root_path = os.path.join(os.path.expanduser("~"), ".praison")
os.environ["CHAINLIT_APP_ROOT"] = root_path
public_folder = os.path.join(os.path.dirname(praisonai.__file__), 'public')
if not os.path.exists(os.path.join(root_path, "public")):
if os.path.exists(public_folder):
shutil.copytree(public_folder, os.path.join(root_path, "public"), dirs_exist_ok=True)
logging.info("Public folder copied successfully!")
else:
logging.info("Public folder not found in the package.")
else:
logging.info("Public folder already exists.")
realtime_ui_path = os.path.join(os.path.dirname(praisonai.__file__), 'ui', 'realtime.py')
chainlit_run([realtime_ui_path])
else:
print("ERROR: Realtime UI is not installed. Please install it with 'pip install \"praisonai[realtime]\"' to use the realtime UI.")
if __name__ == "__main__":
praison_ai = PraisonAI()
praison_ai.main()
+1 -1
View File
@@ -56,7 +56,7 @@ class CloudDeployer:
file.write("FROM python:3.11-slim\n")
file.write("WORKDIR /app\n")
file.write("COPY . .\n")
file.write("RUN pip install flask praisonai==0.0.55 gunicorn markdown\n")
file.write("RUN pip install flask praisonai==0.1.2 gunicorn markdown\n")
file.write("EXPOSE 8080\n")
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
+96
View File
@@ -0,0 +1,96 @@
def generate_config(
ollama_save=None,
huggingface_save=None,
train=None,
model_name=None,
hf_model_name=None,
ollama_model_name=None,
model_parameters=None,
max_seq_length=None,
load_in_4bit=None,
lora_r=None,
lora_target_modules=None,
lora_alpha=None,
lora_dropout=None,
lora_bias=None,
use_gradient_checkpointing=None,
random_state=None,
use_rslora=None,
loftq_config=None,
dataset=None,
dataset_text_field=None,
dataset_num_proc=None,
packing=None,
per_device_train_batch_size=None,
gradient_accumulation_steps=None,
warmup_steps=None,
num_train_epochs=None,
max_steps=None,
learning_rate=None,
logging_steps=None,
optim=None,
weight_decay=None,
lr_scheduler_type=None,
seed=None,
output_dir=None,
quantization_method=None
):
"""Generates the configuration for PraisonAI with dynamic overrides."""
config = {
"ollama_save": ollama_save or "true",
"huggingface_save": huggingface_save or "true",
"train": train or "true",
"model_name": model_name or "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit",
"hf_model_name": hf_model_name or "mervinpraison/llama-3.1-tamilan-8B-test",
"ollama_model": ollama_model_name or "mervinpraison/llama3.1-tamilan-test",
"model_parameters": model_parameters or "8b",
"dataset": dataset or [
{
"name": "yahma/alpaca-cleaned",
"split_type": "train",
"processing_func": "format_prompts",
"rename": {"input": "input", "output": "output", "instruction": "instruction"},
"filter_data": False,
"filter_column_value": "id",
"filter_value": "alpaca",
"num_samples": 20000
}
],
"dataset_text_field": dataset_text_field or "text",
"dataset_num_proc": dataset_num_proc or 2,
"packing": packing or False,
"max_seq_length": max_seq_length or 2048,
"load_in_4bit": load_in_4bit or True,
"lora_r": lora_r or 16,
"lora_target_modules": lora_target_modules or [
"q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"
],
"lora_alpha": lora_alpha or 16,
"lora_dropout": lora_dropout or 0,
"lora_bias": lora_bias or "none",
"use_gradient_checkpointing": use_gradient_checkpointing or "unsloth",
"random_state": random_state or 3407,
"use_rslora": use_rslora or False,
"loftq_config": loftq_config or None,
"per_device_train_batch_size": per_device_train_batch_size or 2,
"gradient_accumulation_steps": gradient_accumulation_steps or 2,
"warmup_steps": warmup_steps or 5,
"num_train_epochs": num_train_epochs or 1,
"max_steps": max_steps or 10,
"learning_rate": learning_rate or 2.0e-4,
"logging_steps": logging_steps or 1,
"optim": optim or "adamw_8bit",
"weight_decay": weight_decay or 0.01,
"lr_scheduler_type": lr_scheduler_type or "linear",
"seed": seed or 3407,
"output_dir": output_dir or "outputs",
"quantization_method": quantization_method or ["q4_k_m"]
}
return config
+1
View File
@@ -0,0 +1 @@
+21
View File
@@ -0,0 +1,21 @@
import subprocess
import sys
import os
def build(setup_kwargs):
try:
# Get the directory of the current script
script_dir = os.path.dirname(os.path.abspath(__file__))
# Construct the path to post_install.py
post_install_script = os.path.join(script_dir, 'post_install.py')
# Run the post_install.py script
subprocess.check_call([sys.executable, post_install_script])
except subprocess.CalledProcessError as e:
print(f"Error occurred while running the post-install script: {e}")
sys.exit(1)
return setup_kwargs
if __name__ == "__main__":
build({})
+60
View File
@@ -0,0 +1,60 @@
ollama_save: "true"
huggingface_save: "true"
train: "true"
model_name: "unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit"
hf_model_name: "mervinpraison/llama-3.1-tamilan-8B-test"
ollama_model: "mervinpraison/llama3.1-tamilan-test"
model_parameters: "8b"
max_seq_length: 2048
load_in_4bit: true
lora_r: 16
lora_target_modules:
- "q_proj"
- "k_proj"
- "v_proj"
- "o_proj"
- "gate_proj"
- "up_proj"
- "down_proj"
lora_alpha: 16
lora_dropout: 0
lora_bias: "none"
use_gradient_checkpointing: "unsloth"
random_state: 3407
use_rslora: false
loftq_config: null
dataset:
- name: "yahma/alpaca-cleaned"
split_type: "train"
processing_func: "format_prompts"
rename:
input: "input"
output: "output"
instruction: "instruction"
filter_data: false
filter_column_value: "id"
filter_value: "alpaca"
num_samples: 20000
dataset_text_field: "text"
dataset_num_proc: 2
packing: false
per_device_train_batch_size: 2
gradient_accumulation_steps: 2
warmup_steps: 5
num_train_epochs: 1
max_steps: 10
learning_rate: 2.0e-4
logging_steps: 1
optim: "adamw_8bit"
weight_decay: 0.01
lr_scheduler_type: "linear"
seed: 3407
output_dir: "outputs"
quantization_method:
- "q4_k_m"
+20
View File
@@ -0,0 +1,20 @@
import subprocess
import sys
import os
def main():
try:
# Get the absolute path of the current file
current_file = os.path.abspath(__file__)
# Get the directory of the current file
script_dir = os.path.dirname(current_file)
# Construct the path to setup_conda_env.py
setup_script = os.path.join(script_dir, 'setup_conda_env.py')
except subprocess.CalledProcessError as e:
print(f"Error occurred while running the setup script: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
+25
View File
@@ -0,0 +1,25 @@
import subprocess
import os
import sys
import platform
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
script_path = os.path.join(script_dir, 'setup_conda_env.sh')
if platform.system() == 'Windows':
print("Windows detected. Please run the setup_conda_env.sh script manually in Git Bash or WSL.")
print(f"Script location: {script_path}")
sys.exit(1)
try:
subprocess.check_call(['bash', script_path])
except subprocess.CalledProcessError as e:
print(f"Error occurred while running the setup script: {e}")
print("Setup failed. Please check the error message above and try to resolve the issue.")
sys.exit(1)
print("Conda environment setup completed successfully!")
if __name__ == "__main__":
main()
+72
View File
@@ -0,0 +1,72 @@
#!/bin/bash
# Detect OS and architecture
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
if [[ $(uname -m) == 'arm64' ]]; then
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh"
else
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh"
fi
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh"
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
# Windows
MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe"
echo "Windows detected. Please run this script in Git Bash or WSL."
exit 1
else
echo "Unsupported operating system: $OSTYPE"
exit 1
fi
# Check if conda is already installed
if ! command -v conda &> /dev/null; then
echo "Conda is not installed. Installing Miniconda..."
wget $MINICONDA_URL -O ~/miniconda.sh
bash ~/miniconda.sh -b -p $HOME/miniconda
source $HOME/miniconda/bin/activate
conda init
else
echo "Conda is already installed."
fi
# Create and activate the Conda environment
ENV_NAME="praison_env"
if conda info --envs | grep -q $ENV_NAME; then
echo "Environment $ENV_NAME already exists. Recreating..."
conda env remove -y -n $ENV_NAME # Remove existing environment
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS (both Intel and M1/M2)
conda create --name $ENV_NAME python=3.10 pytorch=2.3.0 -c pytorch -y
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
conda create --name $ENV_NAME python=3.10 pytorch=2.3.0 cudatoolkit=11.8 -c pytorch -c nvidia -y
fi
# conda activate $ENV_NAME
else
echo "Creating new environment $ENV_NAME..."
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS (both Intel and M1/M2)
conda create --name $ENV_NAME python=3.10 pytorch=2.3.0 -c pytorch -y
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
conda create --name $ENV_NAME python=3.10 pytorch=2.3.0 cudatoolkit=11.8 -c pytorch -c nvidia -y
fi
# conda activate $ENV_NAME
fi
# source $HOME/miniconda/bin/activate $ENV_NAME
# Get full path of pip
PIP_FULL_PATH=$(conda run -n $ENV_NAME which pip)
# Install other packages within the activated environment
# Use PIP_FULL_PATH to run pip commands
$PIP_FULL_PATH install --upgrade pip
$PIP_FULL_PATH install "xformers==0.0.26.post1"
$PIP_FULL_PATH install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git@4e570be9ae4ced8cdc64e498125708e34942befc"
$PIP_FULL_PATH install --no-deps "trl<0.9.0" peft accelerate bitsandbytes
echo "Setup completed successfully!"
+1 -1
View File
@@ -7,7 +7,7 @@ load_dotenv()
import autogen
config_list = [
{
'model': os.environ.get("OPENAI_MODEL_NAME", "gpt-3.5-turbo"),
'model': os.environ.get("OPENAI_MODEL_NAME", "gpt-4o-mini"),
'base_url': os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1"),
'api_key': os.environ.get("OPENAI_API_KEY")
}
+276
View File
@@ -0,0 +1,276 @@
import subprocess
import os
import sys
import yaml
import torch
import shutil
from transformers import TextStreamer
from unsloth import FastLanguageModel, is_bfloat16_supported
from trl import SFTTrainer
from transformers import TrainingArguments
from datasets import load_dataset, concatenate_datasets, Dataset
from psutil import virtual_memory
class train:
def __init__(self, config_path="config.yaml"):
self.load_config(config_path)
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model, self.tokenizer = None, None
def load_config(self, path):
with open(path, "r") as file:
self.config = yaml.safe_load(file)
def print_system_info(self):
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA version: {torch.version.cuda}")
if torch.cuda.is_available():
device_capability = torch.cuda.get_device_capability()
print(f"CUDA Device Capability: {device_capability}")
else:
print("CUDA is not available")
python_version = sys.version
pip_version = subprocess.check_output(['pip', '--version']).decode().strip()
python_path = sys.executable
pip_path = subprocess.check_output(['which', 'pip']).decode().strip()
print(f"Python Version: {python_version}")
print(f"Pip Version: {pip_version}")
print(f"Python Path: {python_path}")
print(f"Pip Path: {pip_path}")
def check_gpu(self):
gpu_stats = torch.cuda.get_device_properties(0)
print(f"GPU = {gpu_stats.name}. Max memory = {round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)} GB.")
def check_ram(self):
ram_gb = virtual_memory().total / 1e9
print('Your runtime has {:.1f} gigabytes of available RAM\n'.format(ram_gb))
if ram_gb < 20:
print('Not using a high-RAM runtime')
else:
print('You are using a high-RAM runtime!')
# def install_packages(self):
# subprocess.run(["pip", "install", "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git@4e570be9ae4ced8cdc64e498125708e34942befc"])
# subprocess.run(["pip", "install", "--no-deps", "trl<0.9.0", "peft==0.12.0", "accelerate==0.33.0", "bitsandbytes==0.43.3"])
def prepare_model(self):
self.model, self.tokenizer = FastLanguageModel.from_pretrained(
model_name=self.config["model_name"],
max_seq_length=self.config["max_seq_length"],
dtype=None,
load_in_4bit=self.config["load_in_4bit"]
)
self.model = FastLanguageModel.get_peft_model(
self.model,
r=self.config["lora_r"],
target_modules=self.config["lora_target_modules"],
lora_alpha=self.config["lora_alpha"],
lora_dropout=self.config["lora_dropout"],
bias=self.config["lora_bias"],
use_gradient_checkpointing=self.config["use_gradient_checkpointing"],
random_state=self.config["random_state"],
use_rslora=self.config["use_rslora"],
loftq_config=self.config["loftq_config"],
)
def process_dataset(self, dataset_info):
dataset_name = dataset_info["name"]
split_type = dataset_info.get("split_type", "train")
processing_func = getattr(self, dataset_info.get("processing_func", "format_prompts"))
rename = dataset_info.get("rename", {})
filter_data = dataset_info.get("filter_data", False)
filter_column_value = dataset_info.get("filter_column_value", "id")
filter_value = dataset_info.get("filter_value", "alpaca")
num_samples = dataset_info.get("num_samples", 20000)
dataset = load_dataset(dataset_name, split=split_type)
if rename:
dataset = dataset.rename_columns(rename)
if filter_data:
dataset = dataset.filter(lambda example: filter_value in example[filter_column_value]).shuffle(seed=42).select(range(num_samples))
dataset = dataset.map(processing_func, batched=True)
return dataset
def format_prompts(self, examples):
alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
### Instruction:
{}
### Input:
{}
### Response:
{}"""
texts = [alpaca_prompt.format(ins, inp, out) + self.tokenizer.eos_token for ins, inp, out in zip(examples["instruction"], examples["input"], examples["output"])]
return {"text": texts}
def load_datasets(self):
datasets = []
for dataset_info in self.config["dataset"]:
datasets.append(self.process_dataset(dataset_info))
return concatenate_datasets(datasets)
def train_model(self):
dataset = self.load_datasets()
trainer = SFTTrainer(
model=self.model,
tokenizer=self.tokenizer,
train_dataset=dataset,
dataset_text_field=self.config["dataset_text_field"],
max_seq_length=self.config["max_seq_length"],
dataset_num_proc=self.config["dataset_num_proc"],
packing=self.config["packing"],
args=TrainingArguments(
per_device_train_batch_size=self.config["per_device_train_batch_size"],
gradient_accumulation_steps=self.config["gradient_accumulation_steps"],
warmup_steps=self.config["warmup_steps"],
num_train_epochs=self.config["num_train_epochs"],
max_steps=self.config["max_steps"],
learning_rate=self.config["learning_rate"],
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(),
logging_steps=self.config["logging_steps"],
optim=self.config["optim"],
weight_decay=self.config["weight_decay"],
lr_scheduler_type=self.config["lr_scheduler_type"],
seed=self.config["seed"],
output_dir=self.config["output_dir"],
),
)
trainer.train()
self.model.save_pretrained("lora_model") # Local saving
self.tokenizer.save_pretrained("lora_model")
def inference(self, instruction, input_text):
FastLanguageModel.for_inference(self.model)
alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
### Instruction:
{}
### Input:
{}
### Response:
{}"""
inputs = self.tokenizer([alpaca_prompt.format(instruction, input_text, "")], return_tensors="pt").to("cuda")
outputs = self.model.generate(**inputs, max_new_tokens=64, use_cache=True)
print(self.tokenizer.batch_decode(outputs))
def load_model(self):
"""Loads the model and tokenizer using the FastLanguageModel library."""
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name=self.config["output_dir"],
max_seq_length=2048,
dtype=None,
load_in_4bit=self.config["load_in_4bit"],
)
return model, tokenizer
def save_model_merged(self):
if os.path.exists(self.config["hf_model_name"]):
shutil.rmtree(self.config["hf_model_name"])
self.model.push_to_hub_merged(
self.config["hf_model_name"],
self.tokenizer,
save_method="merged_16bit",
token=os.getenv('HF_TOKEN')
)
def push_model_gguf(self):
self.model.push_to_hub_gguf(
self.config["hf_model_name"],
self.tokenizer,
quantization_method=self.config["quantization_method"],
token=os.getenv('HF_TOKEN')
)
def save_model_gguf(self):
self.model.save_pretrained_gguf(
self.config["hf_model_name"],
self.tokenizer,
quantization_method="q4_k_m"
)
def prepare_modelfile_content(self):
output_model = self.config["hf_model_name"]
gguf_path = f"{output_model}/unsloth.Q4_K_M.gguf"
# Check if the GGUF file exists. If not, generate it ## TODO Multiple Quantisation other than Q4_K_M.gguf
if not os.path.exists(gguf_path):
self.model, self.tokenizer = self.load_model()
self.save_model_gguf()
return f"""FROM {output_model}/unsloth.Q4_K_M.gguf
TEMPLATE \"\"\"Below are some instructions that describe some tasks. Write responses that appropriately complete each request.{{{{ if .Prompt }}}}
### Instruction:
{{{{ .Prompt }}}}
{{{{ end }}}}### Response:
{{{{ .Response }}}}\"\"\"
PARAMETER stop ""
PARAMETER stop ""
PARAMETER stop ""
PARAMETER stop ""
PARAMETER stop "<|reserved_special_token_"
"""
def create_and_push_ollama_model(self):
modelfile_content = self.prepare_modelfile_content()
with open('Modelfile', 'w') as file:
file.write(modelfile_content)
subprocess.run(["ollama", "serve"])
subprocess.run(["ollama", "create", f"{self.config['ollama_model']}:{self.config['model_parameters']}", "-f", "Modelfile"])
subprocess.run(["ollama", "push", f"{self.config['ollama_model']}:{self.config['model_parameters']}"])
def run(self):
self.print_system_info()
self.check_gpu()
self.check_ram()
# self.install_packages()
if self.config.get("train", "true").lower() == "true":
self.prepare_model()
self.train_model()
if self.config.get("huggingface_save", "true").lower() == "true":
# self.model, self.tokenizer = self.load_model()
self.save_model_merged()
if self.config.get("huggingface_save_gguf", "true").lower() == "true":
# self.model, self.tokenizer = self.load_model()
self.push_model_gguf()
# if self.config.get("save_gguf", "true").lower() == "true": ## TODO
# self.model, self.tokenizer = self.load_model()
# self.save_model_gguf()
# if self.config.get("save_merged", "true").lower() == "true": ## TODO
# self.model, self.tokenizer = self.load_model()
# self.save_model_merged()
if self.config.get("ollama_save", "true").lower() == "true":
self.create_and_push_ollama_model()
def main():
import argparse
parser = argparse.ArgumentParser(description='PraisonAI Training Script')
parser.add_argument('command', choices=['train'], help='Command to execute')
parser.add_argument('--config', default='config.yaml', help='Path to configuration file')
args = parser.parse_args()
if args.command == 'train':
ai = train(config_path=args.config)
ai.run()
if __name__ == '__main__':
main()
+252 -27
View File
@@ -1,6 +1,6 @@
import chainlit as cl
from chainlit.input_widget import TextInput
from chainlit.types import ThreadDict
from chainlit.types import ThreadDict # Change this import
from litellm import acompletion
import os
import sqlite3
@@ -14,6 +14,12 @@ from literalai.helper import utc_now
import logging
import json
from sql_alchemy import SQLAlchemyDataLayer
from tavily import TavilyClient
from crawl4ai import WebCrawler
import asyncio
from PIL import Image
import io
import base64
# Set up logging
logger = logging.getLogger(__name__)
@@ -40,9 +46,10 @@ now = utc_now()
create_step_counter = 0
DB_PATH = "threads.db"
DB_PATH = os.path.expanduser("~/.praison/database.sqlite")
def initialize_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''
@@ -72,11 +79,11 @@ def initialize_db():
type TEXT NOT NULL,
threadId UUID NOT NULL,
parentId UUID,
disableFeedback BOOLEAN NOT NULL,
streaming BOOLEAN NOT NULL,
waitForAnswer BOOLEAN,
isError BOOLEAN,
metadata JSONB,
disableFeedback BOOLEAN NOT NULL DEFAULT 0,
streaming BOOLEAN NOT NULL DEFAULT 0,
waitForAnswer BOOLEAN DEFAULT 0,
isError BOOLEAN NOT NULL DEFAULT 0,
metadata JSONB DEFAULT '{}',
tags TEXT[],
input TEXT,
output TEXT,
@@ -170,6 +177,69 @@ deleted_thread_ids = [] # type: List[str]
cl_data._data_layer = SQLAlchemyDataLayer(conninfo=f"sqlite+aiosqlite:///{DB_PATH}")
# Set Tavily API key
tavily_api_key = os.getenv("TAVILY_API_KEY")
tavily_client = TavilyClient(api_key=tavily_api_key) if tavily_api_key else None
# Modify the tavily_web_search function to be synchronous
def tavily_web_search(query):
if not tavily_client:
return json.dumps({
"query": query,
"error": "Tavily API key is not set. Web search is unavailable."
})
response = tavily_client.search(query)
logger.debug(f"Tavily search response: {response}")
# Create an instance of WebCrawler
crawler = WebCrawler()
# Warm up the crawler (load necessary models)
crawler.warmup()
# Prepare the results
results = []
for result in response.get('results', []):
url = result.get('url')
if url:
try:
# Run the crawler on each URL
crawl_result = crawler.run(url=url)
results.append({
"content": result.get('content'),
"url": url,
"full_content": crawl_result.markdown
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('content'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
return json.dumps({
"query": query,
"results": results
})
# Update the tools definition
tools = [{
"type": "function",
"function": {
"name": "tavily_web_search",
"description": "Search the web using Tavily API and crawl the resulting URLs",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"]
}
}
}] if tavily_api_key else []
@cl.on_chat_start
async def start():
initialize_db()
@@ -179,7 +249,7 @@ async def start():
cl.user_session.set("model_name", model_name)
else:
# If no setting found, use default or environment variable
model_name = os.getenv("MODEL_NAME", "gpt-3.5-turbo")
model_name = os.getenv("MODEL_NAME", "gpt-4o-mini")
cl.user_session.set("model_name", model_name)
logger.debug(f"Model name: {model_name}")
settings = cl.ChatSettings(
@@ -187,7 +257,7 @@ async def start():
TextInput(
id="model_name",
label="Enter the Model Name",
placeholder="e.g., gpt-3.5-turbo",
placeholder="e.g., gpt-4o-mini",
initial=model_name
)
]
@@ -221,33 +291,168 @@ async def setup_agent(settings):
@cl.on_message
async def main(message: cl.Message):
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-3.5-turbo"
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
message_history = cl.user_session.get("message_history", [])
message_history.append({"role": "user", "content": message.content})
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Check if an image was uploaded with this message
image = None
if message.elements and isinstance(message.elements[0], cl.Image):
image_element = message.elements[0]
try:
# Open the image and keep it in memory
image = Image.open(image_element.path)
image.load() # This ensures the file is fully loaded into memory
cl.user_session.set("image", image)
except Exception as e:
logger.error(f"Error processing image: {str(e)}")
await cl.Message(content="There was an error processing the uploaded image. Please try again.").send()
return
# Prepare user message
user_message = f"""
Answer the question and use tools if needed:\n
Current Date and Time: {now}
User Question: {message.content}
"""
if image:
user_message = f"Image uploaded. {user_message}"
message_history.append({"role": "user", "content": user_message})
msg = cl.Message(content="")
await msg.send()
response = await acompletion(
model=model_name,
messages=message_history,
stream=True,
# temperature=0.7,
# max_tokens=500,
# top_p=1
)
# Prepare the completion parameters
completion_params = {
"model": model_name,
"messages": message_history,
"stream": True,
}
# If an image is uploaded, include it in the message
if image:
buffered = io.BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
completion_params["messages"][-1] = {
"role": "user",
"content": [
{"type": "text", "text": user_message},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_str}"}}
]
}
# Only add tools and tool_choice if Tavily API key is available
if tavily_api_key:
completion_params["tools"] = tools
completion_params["tool_choice"] = "auto"
response = await acompletion(**completion_params)
full_response = ""
tool_calls = []
current_tool_call = None
async for part in response:
if token := part['choices'][0]['delta']['content']:
await msg.stream_token(token)
full_response += token
if 'choices' in part and len(part['choices']) > 0:
delta = part['choices'][0].get('delta', {})
if 'content' in delta and delta['content'] is not None:
token = delta['content']
await msg.stream_token(token)
full_response += token
if tavily_api_key and 'tool_calls' in delta and delta['tool_calls'] is not None:
for tool_call in delta['tool_calls']:
if current_tool_call is None or tool_call.index != current_tool_call['index']:
if current_tool_call:
tool_calls.append(current_tool_call)
current_tool_call = {
'id': tool_call.id,
'type': tool_call.type,
'index': tool_call.index,
'function': {
'name': tool_call.function.name if tool_call.function else None,
'arguments': ''
}
}
if tool_call.function:
if tool_call.function.name:
current_tool_call['function']['name'] = tool_call.function.name
if tool_call.function.arguments:
current_tool_call['function']['arguments'] += tool_call.function.arguments
if current_tool_call:
tool_calls.append(current_tool_call)
logger.debug(f"Full response: {full_response}")
logger.debug(f"Tool calls: {tool_calls}")
message_history.append({"role": "assistant", "content": full_response})
logger.debug(f"Message history: {message_history}")
cl.user_session.set("message_history", message_history)
await msg.update()
# Handle tool calls if any
if tavily_api_key and tool_calls:
available_functions = {
"tavily_web_search": tavily_web_search,
}
messages = message_history + [{"role": "assistant", "content": None, "function_call": {
"name": tool_calls[0]['function']['name'],
"arguments": tool_calls[0]['function']['arguments']
}}]
for tool_call in tool_calls:
function_name = tool_call['function']['name']
if function_name in available_functions:
function_to_call = available_functions[function_name]
function_args = tool_call['function']['arguments']
if function_args:
try:
function_args = json.loads(function_args)
# Call the function synchronously
function_response = function_to_call(
query=function_args.get("query"),
)
messages.append(
{
"role": "function",
"name": function_name,
"content": function_response,
}
)
except json.JSONDecodeError:
logger.error(f"Failed to parse function arguments: {function_args}")
second_response = await acompletion(
model=model_name,
stream=True,
messages=messages,
)
logger.debug(f"Second LLM response: {second_response}")
# Handle the streaming response
full_response = ""
async for part in second_response:
if 'choices' in part and len(part['choices']) > 0:
delta = part['choices'][0].get('delta', {})
if 'content' in delta and delta['content'] is not None:
token = delta['content']
await msg.stream_token(token)
full_response += token
# Update the message content
msg.content = full_response
await msg.update()
else:
# If no tool calls, the full_response is already set
msg.content = full_response
await msg.update()
username = os.getenv("CHAINLIT_USERNAME", "admin") # Default to "admin" if not found
password = os.getenv("CHAINLIT_PASSWORD", "admin") # Default to "admin" if not found
@@ -266,16 +471,16 @@ async def send_count():
).send()
@cl.on_chat_resume
async def on_chat_resume(thread: cl_data.ThreadDict):
async def on_chat_resume(thread: ThreadDict):
logger.info(f"Resuming chat: {thread['id']}")
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-3.5-turbo"
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
logger.debug(f"Model name: {model_name}")
settings = cl.ChatSettings(
[
TextInput(
id="model_name",
label="Enter the Model Name",
placeholder="e.g., gpt-3.5-turbo",
placeholder="e.g., gpt-4o-mini",
initial=model_name
)
]
@@ -284,8 +489,14 @@ async def on_chat_resume(thread: cl_data.ThreadDict):
thread_id = thread["id"]
cl.user_session.set("thread_id", thread["id"])
# The metadata should now already be a dictionary
# Ensure metadata is a dictionary
metadata = thread.get("metadata", {})
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
metadata = {}
cl.user_session.set("metadata", metadata)
message_history = cl.user_session.get("message_history", [])
@@ -297,7 +508,21 @@ async def on_chat_resume(thread: cl_data.ThreadDict):
message_history.append({"role": "user", "content": message.get("output", "")})
elif msg_type == "assistant_message":
message_history.append({"role": "assistant", "content": message.get("output", "")})
elif msg_type == "run":
# Handle 'run' type messages
if message.get("isError"):
message_history.append({"role": "system", "content": f"Error: {message.get('output', '')}"})
else:
# You might want to handle non-error 'run' messages differently
pass
else:
logger.warning(f"Message without type: {message}")
logger.warning(f"Message without recognized type: {message}")
cl.user_session.set("message_history", message_history)
# Check if there's an image in the thread metadata
image_data = metadata.get("image")
if image_data:
image = Image.open(io.BytesIO(base64.b64decode(image_data)))
cl.user_session.set("image", image)
await cl.Message(content="Previous image loaded. You can continue asking questions about it or upload a new image.").send()
+265 -36
View File
@@ -1,7 +1,7 @@
import chainlit as cl
from chainlit.input_widget import TextInput
from chainlit.types import ThreadDict
from litellm import acompletion
from litellm import acompletion, completion
import os
import sqlite3
from datetime import datetime
@@ -15,6 +15,12 @@ import logging
import json
from sql_alchemy import SQLAlchemyDataLayer
from context import ContextGatherer
from tavily import TavilyClient
from datetime import datetime
from crawl4ai import WebCrawler
from PIL import Image
import io
import base64
# Set up logging
logger = logging.getLogger(__name__)
@@ -41,9 +47,10 @@ now = utc_now()
create_step_counter = 0
DB_PATH = "threads.db"
DB_PATH = os.path.expanduser("~/.praison/database.sqlite")
def initialize_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''
@@ -73,11 +80,11 @@ def initialize_db():
type TEXT NOT NULL,
threadId UUID NOT NULL,
parentId UUID,
disableFeedback BOOLEAN NOT NULL,
streaming BOOLEAN NOT NULL,
waitForAnswer BOOLEAN,
isError BOOLEAN,
metadata JSONB,
disableFeedback BOOLEAN NOT NULL DEFAULT 0,
streaming BOOLEAN NOT NULL DEFAULT 0,
waitForAnswer BOOLEAN DEFAULT 0,
isError BOOLEAN NOT NULL DEFAULT 0,
metadata JSONB DEFAULT '{}',
tags TEXT[],
input TEXT,
output TEXT,
@@ -176,11 +183,11 @@ async def start():
initialize_db()
model_name = load_setting("model_name")
if model_name:
if (model_name):
cl.user_session.set("model_name", model_name)
else:
# If no setting found, use default or environment variable
model_name = os.getenv("MODEL_NAME", "gpt-3.5-turbo")
model_name = os.getenv("MODEL_NAME", "gpt-4o-mini")
cl.user_session.set("model_name", model_name)
logger.debug(f"Model name: {model_name}")
settings = cl.ChatSettings(
@@ -188,7 +195,7 @@ async def start():
TextInput(
id="model_name",
label="Enter the Model Name",
placeholder="e.g., gpt-3.5-turbo",
placeholder="e.g., gpt-4o-mini",
initial=model_name
)
]
@@ -215,53 +222,255 @@ async def setup_agent(settings):
# Save in thread metadata
thread_id = cl.user_session.get("thread_id")
if thread_id:
thread = await cl_data.get_thread(thread_id)
thread = await cl_data._data_layer.get_thread(thread_id)
if thread:
metadata = thread.get("metadata", {})
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
metadata = {}
metadata["model_name"] = model_name
# Always store metadata as a JSON string
await cl_data.update_thread(thread_id, metadata=json.dumps(metadata))
# Always store metadata as a dictionary
await cl_data._data_layer.update_thread(thread_id, metadata=metadata)
# Update the user session with the new metadata
cl.user_session.set("metadata", metadata)
# Set Tavily API key
tavily_api_key = os.getenv("TAVILY_API_KEY")
tavily_client = TavilyClient(api_key=tavily_api_key) if tavily_api_key else None
# Function to call Tavily Search API and crawl the results
def tavily_web_search(query):
if not tavily_client:
return json.dumps({
"query": query,
"error": "Tavily API key is not set. Web search is unavailable."
})
response = tavily_client.search(query)
logger.debug(f"Tavily search response: {response}")
# Create an instance of WebCrawler
crawler = WebCrawler()
# Warm up the crawler (load necessary models)
crawler.warmup()
# Prepare the results
results = []
for result in response.get('results', []):
url = result.get('url')
if url:
try:
# Run the crawler on each URL
crawl_result = crawler.run(url=url)
results.append({
"content": result.get('content'),
"url": url,
"full_content": crawl_result.markdown
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('content'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
return json.dumps({
"query": query,
"results": results
})
# Define the tool for function calling
tools = [{
"type": "function",
"function": {
"name": "tavily_web_search",
"description": "Search the web using Tavily API and crawl the resulting URLs",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"]
}
}
}] if tavily_api_key else []
@cl.on_message
async def main(message: cl.Message):
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-3.5-turbo"
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
message_history = cl.user_session.get("message_history", [])
message_history.append({"role": "user", "content": message.content})
gatherer = ContextGatherer()
context, token_count, context_tree = gatherer.run()
prompt_history = message_history
prompt_history.append({"role": "user", "content": """
Answer the question:\n{question}.\n\n
Below is the Context:\n{context}\n\n"""
.format(context=context, question=message.content)})
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Check if an image was uploaded with this message
image = None
if message.elements and isinstance(message.elements[0], cl.Image):
image_element = message.elements[0]
try:
# Open the image and keep it in memory
image = Image.open(image_element.path)
image.load() # This ensures the file is fully loaded into memory
cl.user_session.set("image", image)
except Exception as e:
logger.error(f"Error processing image: {str(e)}")
await cl.Message(content="There was an error processing the uploaded image. Please try again.").send()
return
# Prepare user message
user_message = f"""
Answer the question and use tools if needed:\n{message.content}.\n\n
Current Date and Time: {now}
Context:
{context}
"""
if image:
user_message = f"Image uploaded. {user_message}"
message_history.append({"role": "user", "content": user_message})
msg = cl.Message(content="")
await msg.send()
response = await acompletion(
model=model_name,
messages=prompt_history,
stream=True,
# temperature=0.7,
# max_tokens=500,
# top_p=1
)
# Prepare the completion parameters
completion_params = {
"model": model_name,
"messages": message_history,
"stream": True,
}
# If an image is uploaded, include it in the message
if image:
buffered = io.BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
completion_params["messages"][-1] = {
"role": "user",
"content": [
{"type": "text", "text": user_message},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_str}"}}
]
}
# Use a vision-capable model when an image is present
completion_params["model"] = "gpt-4-vision-preview" # Adjust this to your actual vision-capable model
# Only add tools and tool_choice if Tavily API key is available and no image is uploaded
if tavily_api_key:
completion_params["tools"] = tools
completion_params["tool_choice"] = "auto"
response = await acompletion(**completion_params)
logger.debug(f"LLM response: {response}")
full_response = ""
tool_calls = []
current_tool_call = None
async for part in response:
if token := part['choices'][0]['delta']['content']:
await msg.stream_token(token)
full_response += token
logger.debug(f"LLM part: {part}")
if 'choices' in part and len(part['choices']) > 0:
delta = part['choices'][0].get('delta', {})
if 'content' in delta and delta['content'] is not None:
token = delta['content']
await msg.stream_token(token)
full_response += token
if tavily_api_key and 'tool_calls' in delta and delta['tool_calls'] is not None:
for tool_call in delta['tool_calls']:
if current_tool_call is None or tool_call.index != current_tool_call['index']:
if current_tool_call:
tool_calls.append(current_tool_call)
current_tool_call = {
'id': tool_call.id,
'type': tool_call.type,
'index': tool_call.index,
'function': {
'name': tool_call.function.name if tool_call.function else None,
'arguments': ''
}
}
if tool_call.function:
if tool_call.function.name:
current_tool_call['function']['name'] = tool_call.function.name
if tool_call.function.arguments:
current_tool_call['function']['arguments'] += tool_call.function.arguments
if current_tool_call:
tool_calls.append(current_tool_call)
logger.debug(f"Full response: {full_response}")
logger.debug(f"Tool calls: {tool_calls}")
message_history.append({"role": "assistant", "content": full_response})
logger.debug(f"Message history: {message_history}")
cl.user_session.set("message_history", message_history)
await msg.update()
if tavily_api_key and tool_calls:
available_functions = {
"tavily_web_search": tavily_web_search,
}
messages = message_history + [{"role": "assistant", "content": None, "function_call": {
"name": tool_calls[0]['function']['name'],
"arguments": tool_calls[0]['function']['arguments']
}}]
for tool_call in tool_calls:
function_name = tool_call['function']['name']
if function_name in available_functions:
function_to_call = available_functions[function_name]
function_args = tool_call['function']['arguments']
if function_args:
try:
function_args = json.loads(function_args)
function_response = function_to_call(
query=function_args.get("query"),
)
messages.append(
{
"role": "function",
"name": function_name,
"content": function_response,
}
)
except json.JSONDecodeError:
logger.error(f"Failed to parse function arguments: {function_args}")
second_response = await acompletion(
model=model_name,
stream=True,
messages=messages,
)
logger.debug(f"Second LLM response: {second_response}")
# Handle the streaming response
full_response = ""
async for part in second_response:
if 'choices' in part and len(part['choices']) > 0:
delta = part['choices'][0].get('delta', {})
if 'content' in delta and delta['content'] is not None:
token = delta['content']
await msg.stream_token(token)
full_response += token
# Update the message content
msg.content = full_response
await msg.update()
else:
# If no tool calls or Tavily API key is not set, the full_response is already set
msg.content = full_response
await msg.update()
username = os.getenv("CHAINLIT_USERNAME", "admin") # Default to "admin" if not found
password = os.getenv("CHAINLIT_PASSWORD", "admin") # Default to "admin" if not found
@@ -280,16 +489,16 @@ async def send_count():
).send()
@cl.on_chat_resume
async def on_chat_resume(thread: cl_data.ThreadDict):
async def on_chat_resume(thread: ThreadDict):
logger.info(f"Resuming chat: {thread['id']}")
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-3.5-turbo"
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
logger.debug(f"Model name: {model_name}")
settings = cl.ChatSettings(
[
TextInput(
id="model_name",
label="Enter the Model Name",
placeholder="e.g., gpt-3.5-turbo",
placeholder="e.g., gpt-4o-mini",
initial=model_name
)
]
@@ -298,8 +507,14 @@ async def on_chat_resume(thread: cl_data.ThreadDict):
thread_id = thread["id"]
cl.user_session.set("thread_id", thread["id"])
# The metadata should now already be a dictionary
# Ensure metadata is a dictionary
metadata = thread.get("metadata", {})
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
metadata = {}
cl.user_session.set("metadata", metadata)
message_history = cl.user_session.get("message_history", [])
@@ -311,7 +526,21 @@ async def on_chat_resume(thread: cl_data.ThreadDict):
message_history.append({"role": "user", "content": message.get("output", "")})
elif msg_type == "assistant_message":
message_history.append({"role": "assistant", "content": message.get("output", "")})
elif msg_type == "run":
# Handle 'run' type messages
if message.get("isError"):
message_history.append({"role": "system", "content": f"Error: {message.get('output', '')}"})
else:
# You might want to handle non-error 'run' messages differently
pass
else:
logger.warning(f"Message without type: {message}")
logger.warning(f"Message without recognized type: {message}")
cl.user_session.set("message_history", message_history)
# Check if there's an image in the thread metadata
image_data = metadata.get("image")
if image_data:
image = Image.open(io.BytesIO(base64.b64decode(image_data)))
cl.user_session.set("image", image)
await cl.Message(content="Previous image loaded. You can continue asking questions about it, upload a new image, or just chat.").send()
+87 -51
View File
@@ -97,17 +97,36 @@ class ContextGatherer:
return modified_ignore_patterns
def get_include_paths(self):
"""
Loads include paths from:
1. .praisoninclude (includes ONLY files/directories listed)
2. .praisoncontext (if .praisoninclude doesn't exist, this is used
to include all other relevant files, excluding ignore patterns)
"""
include_paths = []
# 1. Load from .praisoninclude
include_file = os.path.join(self.directory, '.praisoninclude')
include_all = False # Flag to indicate if we need to include all files
include_file = os.path.join(self.directory, '.praisoncontext')
if os.path.exists(include_file):
with open(include_file, 'r') as f:
include_paths.extend(
line.strip() for line in f
if line.strip() and not line.startswith('#')
)
return include_paths
# If .praisoncontext doesn't exist, fall back to .praisoninclude
# for including all relevant files
if not include_paths:
include_file = os.path.join(self.directory, '.praisoninclude')
if os.path.exists(include_file):
with open(include_file, 'r') as f:
include_paths.extend(
line.strip() for line in f
if line.strip() and not line.startswith('#')
)
include_all = True # Include all files along with specified paths
return include_paths, include_all
def should_ignore(self, file_path):
"""
@@ -130,61 +149,78 @@ class ContextGatherer:
any(file_path.endswith(ext) for ext in self.relevant_extensions)
def gather_context(self):
"""Gather context from relevant files, respecting ignore patterns and include paths."""
"""
Gather context from relevant files, respecting ignore patterns
and include options from .praisoninclude and .praisoncontext.
"""
context = []
total_files = 0
processed_files = 0
self.include_paths, include_all = self.get_include_paths()
if not self.include_paths:
# No include paths specified, process the entire directory
for root, dirs, files in os.walk(self.directory):
total_files += len(files)
dirs[:] = [d for d in dirs if not self.should_ignore(os.path.join(root, d))]
for file in files:
file_path = os.path.join(root, file)
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
context.append(f"File: {file_path}\n\n{content}\n\n{'='*50}\n")
self.included_files.append(Path(file_path).relative_to(self.directory))
except Exception as e:
logger.error(f"Error reading {file_path}: {e}")
processed_files += 1
print(f"\rProcessed {processed_files}/{total_files} files", end="", flush=True)
else:
# Process specified include paths
def add_file_content(file_path):
"""Helper function to add file content to context."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
context.append(
f"File: {file_path}\n\n{content}\n\n{'=' * 50}\n"
)
self.included_files.append(
Path(file_path).relative_to(self.directory)
)
except Exception as e:
logger.error(f"Error reading {file_path}: {e}")
def process_path(path):
"""Helper function to process a single path (file or directory)."""
nonlocal total_files, processed_files
if os.path.isdir(path):
for root, dirs, files in os.walk(path):
total_files += len(files)
dirs[:] = [
d
for d in dirs
if not self.should_ignore(os.path.join(root, d))
]
for file in files:
file_path = os.path.join(root, file)
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
add_file_content(file_path)
processed_files += 1
print(
f"\rProcessed {processed_files}/{total_files} files",
end="",
flush=True,
)
elif os.path.isfile(path) and self.is_relevant_file(path):
add_file_content(path)
processed_files += 1
print(
f"\rProcessed {processed_files}/1 files",
end="",
flush=True,
)
if include_all:
# Include ALL relevant files from the entire directory
process_path(self.directory)
# Include files from .praisoninclude specifically
for include_path in self.include_paths:
full_path = os.path.join(self.directory, include_path)
if os.path.isdir(full_path):
for root, dirs, files in os.walk(full_path):
total_files += len(files)
dirs[:] = [d for d in dirs if not self.should_ignore(os.path.join(root, d))]
for file in files:
file_path = os.path.join(root, file)
if not self.should_ignore(file_path) and self.is_relevant_file(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
context.append(f"File: {file_path}\n\n{content}\n\n{'='*50}\n")
self.included_files.append(Path(file_path).relative_to(self.directory))
except Exception as e:
logger.error(f"Error reading {file_path}: {e}")
processed_files += 1
print(f"\rProcessed {processed_files}/{total_files} files", end="", flush=True)
elif os.path.isfile(full_path) and self.is_relevant_file(full_path):
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
context.append(f"File: {full_path}\n\n{content}\n\n{'='*50}\n")
self.included_files.append(Path(full_path).relative_to(self.directory))
except Exception as e:
logger.error(f"Error reading {full_path}: {e}")
processed_files += 1
print(f"\rProcessed {processed_files}/{total_files} files", end="", flush=True)
process_path(full_path)
elif self.include_paths:
# Include only files specified in .praisoncontext
for include_path in self.include_paths:
full_path = os.path.join(self.directory, include_path)
process_path(full_path)
else:
# No include options, process the entire directory
process_path(self.directory)
print() # New line after progress indicator
return '\n'.join(context)
return "\n".join(context)
def count_tokens(self, text):
"""Count tokens using a simple whitespace-based tokenizer."""
+414
View File
@@ -0,0 +1,414 @@
import os
import asyncio
import sqlite3
from datetime import datetime
from uuid import uuid4
from openai import AsyncOpenAI
import chainlit as cl
from chainlit.logger import logger
from chainlit.input_widget import TextInput
from chainlit.types import ThreadDict
from realtimeclient import RealtimeClient
from realtimeclient.tools import tools
from sql_alchemy import SQLAlchemyDataLayer
import chainlit.data as cl_data
from literalai.helper import utc_now
import json
# Set up CHAINLIT_AUTH_SECRET
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
if not CHAINLIT_AUTH_SECRET:
os.environ["CHAINLIT_AUTH_SECRET"] = "p8BPhQChpg@J>jBz$wGxqLX2V>yTVgP*7Ky9H$aV:axW~ANNX-7_T:o@lnyCBu^U"
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
# Database path
DB_PATH = os.path.expanduser("~/.praison/database.sqlite")
def initialize_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY,
identifier TEXT NOT NULL UNIQUE,
metadata JSONB NOT NULL,
createdAt TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS threads (
id UUID PRIMARY KEY,
createdAt TEXT,
name TEXT,
userId UUID,
userIdentifier TEXT,
tags TEXT[],
metadata JSONB NOT NULL DEFAULT '{}',
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS steps (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
threadId UUID NOT NULL,
parentId UUID,
disableFeedback BOOLEAN NOT NULL DEFAULT 0,
streaming BOOLEAN NOT NULL DEFAULT 0,
waitForAnswer BOOLEAN DEFAULT 0,
isError BOOLEAN NOT NULL DEFAULT 0,
metadata JSONB DEFAULT '{}',
tags TEXT[],
input TEXT,
output TEXT,
createdAt TEXT,
start TEXT,
end TEXT,
generation JSONB,
showInput TEXT,
language TEXT,
indent INT,
FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS elements (
id UUID PRIMARY KEY,
threadId UUID,
type TEXT,
url TEXT,
chainlitKey TEXT,
name TEXT NOT NULL,
display TEXT,
objectKey TEXT,
size TEXT,
page INT,
language TEXT,
forId UUID,
mime TEXT,
FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS feedbacks (
id UUID PRIMARY KEY,
forId UUID NOT NULL,
value INT NOT NULL,
threadId UUID,
comment TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE,
value TEXT
)
''')
conn.commit()
conn.close()
def save_setting(key: str, value: str):
"""Saves a setting to the database."""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"""
INSERT OR REPLACE INTO settings (id, key, value)
VALUES ((SELECT id FROM settings WHERE key = ?), ?, ?)
""",
(key, key, value),
)
conn.commit()
conn.close()
def load_setting(key: str) -> str:
"""Loads a setting from the database."""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('SELECT value FROM settings WHERE key = ?', (key,))
result = cursor.fetchone()
conn.close()
return result[0] if result else None
# Initialize the database
initialize_db()
# Set up SQLAlchemy data layer
cl_data._data_layer = SQLAlchemyDataLayer(conninfo=f"sqlite+aiosqlite:///{DB_PATH}")
client = AsyncOpenAI()
# Add these new imports and code
import importlib.util
import logging
from importlib import import_module
from pathlib import Path
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Try to import tools from the root directory
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
tools_path = os.path.join(root_dir, 'tools.py')
logger.info(f"Tools path: {tools_path}")
def import_tools_from_file(file_path):
spec = importlib.util.spec_from_file_location("custom_tools", file_path)
custom_tools_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(custom_tools_module)
logger.debug(f"Imported tools from {file_path}")
logger.debug(f"Tools: {custom_tools_module}")
return custom_tools_module
try:
if os.path.exists(tools_path):
# tools.py exists in the root directory, import from file
custom_tools_module = import_tools_from_file(tools_path)
logger.info("Successfully imported custom tools from root tools.py")
else:
logger.info("No custom tools.py file found in the root directory")
custom_tools_module = None
if custom_tools_module:
# Update the tools list with custom tools
if hasattr(custom_tools_module, 'tools') and isinstance(custom_tools_module.tools, list):
tools.extend(custom_tools_module.tools)
else:
for name, obj in custom_tools_module.__dict__.items():
if callable(obj) and not name.startswith("__"):
tools.append(({"type": "function", "function": obj}, obj))
except Exception as e:
logger.warning(f"Error importing custom tools: {str(e)}. Continuing without custom tools.")
@cl.on_chat_start
async def start():
initialize_db()
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-4o-mini")
cl.user_session.set("model_name", model_name)
cl.user_session.set("message_history", []) # Initialize message history
logger.debug(f"Model name: {model_name}")
# settings = cl.ChatSettings(
# [
# TextInput(
# id="model_name",
# label="Enter the Model Name",
# placeholder="e.g., gpt-4o-mini",
# initial=model_name
# )
# ]
# )
# cl.user_session.set("settings", settings)
# await settings.send()
await cl.Message(
content="Welcome to the PraisonAI realtime. Press `P` to talk!"
).send()
await setup_openai_realtime()
@cl.on_message
async def on_message(message: cl.Message):
openai_realtime: RealtimeClient = cl.user_session.get("openai_realtime")
message_history = cl.user_session.get("message_history", [])
if openai_realtime and openai_realtime.is_connected():
current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
prompt = f"Current time Just for reference: {current_date}\n\n{message.content}"
# Add user message to history
message_history.append({"role": "user", "content": prompt})
cl.user_session.set("message_history", message_history)
await openai_realtime.send_user_message_content([{ "type": 'input_text', "text": message.content }])
else:
await cl.Message(content="Please activate voice mode before sending messages!").send()
async def setup_openai_realtime():
"""Instantiate and configure the OpenAI Realtime Client"""
openai_realtime = RealtimeClient(api_key=os.getenv("OPENAI_API_KEY"))
cl.user_session.set("track_id", str(uuid4()))
async def handle_conversation_updated(event):
item = event.get("item")
delta = event.get("delta")
"""Currently used to stream audio back to the client."""
if delta:
if 'audio' in delta:
audio = delta['audio'] # Int16Array, audio added
await cl.context.emitter.send_audio_chunk(cl.OutputAudioChunk(mimeType="pcm16", data=audio, track=cl.user_session.get("track_id")))
if 'transcript' in delta:
transcript = delta['transcript'] # string, transcript added
logger.debug(f"Transcript delta: {transcript}")
if 'text' in delta:
text = delta['text'] # string, text added
logger.debug(f"Text delta: {text}")
if 'arguments' in delta:
arguments = delta['arguments'] # string, function arguments added
logger.debug(f"Function arguments delta: {arguments}")
async def handle_item_completed(event):
"""Used to populate the chat context with transcription once an item is completed."""
try:
item = event.get("item")
logger.debug(f"Item completed: {json.dumps(item, indent=2, default=str)}")
await openai_realtime._send_chainlit_message(item)
# Add assistant message to history
message_history = cl.user_session.get("message_history", [])
content = item.get("formatted", {}).get("text", "") or item.get("formatted", {}).get("transcript", "")
if content:
message_history.append({"role": "assistant", "content": content})
cl.user_session.set("message_history", message_history)
except Exception as e:
error_message = f"Error in handle_item_completed: {str(e)}"
logger.error(error_message)
debug_item = json.dumps(item, indent=2, default=str)
logger.error(f"Item causing error: {debug_item}")
async def handle_conversation_interrupt(event):
"""Used to cancel the client previous audio playback."""
cl.user_session.set("track_id", str(uuid4()))
await cl.context.emitter.send_audio_interrupt()
async def handle_error(event):
logger.error(event)
await cl.Message(content=f"Error: {event}", author="System").send()
openai_realtime.on('conversation.updated', handle_conversation_updated)
openai_realtime.on('conversation.item.completed', handle_item_completed)
openai_realtime.on('conversation.interrupted', handle_conversation_interrupt)
openai_realtime.on('error', handle_error)
cl.user_session.set("openai_realtime", openai_realtime)
coros = [openai_realtime.add_tool(tool_def, tool_handler) for tool_def, tool_handler in tools]
await asyncio.gather(*coros)
@cl.on_settings_update
async def setup_agent(settings):
logger.debug(settings)
cl.user_session.set("settings", settings)
model_name = settings["model_name"]
cl.user_session.set("model_name", model_name)
# Save in settings table
save_setting("model_name", model_name)
# Save in thread metadata
thread_id = cl.user_session.get("thread_id")
if thread_id:
thread = await cl_data._data_layer.get_thread(thread_id)
if thread:
metadata = thread.get("metadata", {})
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
metadata = {}
metadata["model_name"] = model_name
# Always store metadata as a dictionary
await cl_data._data_layer.update_thread(thread_id, metadata=metadata)
# Update the user session with the new metadata
cl.user_session.set("metadata", metadata)
@cl.on_audio_start
async def on_audio_start():
try:
openai_realtime: RealtimeClient = cl.user_session.get("openai_realtime")
await openai_realtime.connect()
logger.info("Connected to OpenAI realtime")
return True
except Exception as e:
await cl.ErrorMessage(content=f"Failed to connect to OpenAI realtime: {e}").send()
return False
@cl.on_audio_chunk
async def on_audio_chunk(chunk: cl.InputAudioChunk):
openai_realtime: RealtimeClient = cl.user_session.get("openai_realtime")
if openai_realtime.is_connected():
await openai_realtime.append_input_audio(chunk.data)
else:
logger.info("RealtimeClient is not connected")
@cl.on_audio_end
@cl.on_chat_end
@cl.on_stop
async def on_end():
openai_realtime: RealtimeClient = cl.user_session.get("openai_realtime")
if openai_realtime and openai_realtime.is_connected():
await openai_realtime.disconnect()
@cl.password_auth_callback
def auth_callback(username: str, password: str):
# You can customize this function to use your own authentication logic
expected_username = os.getenv("CHAINLIT_USERNAME", "admin")
expected_password = os.getenv("CHAINLIT_PASSWORD", "admin")
if (username, password) == (expected_username, expected_password):
return cl.User(
identifier=username, metadata={"role": "ADMIN", "provider": "credentials"}
)
else:
return None
@cl.on_chat_resume
async def on_chat_resume(thread: ThreadDict):
logger.info(f"Resuming chat: {thread['id']}")
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
logger.debug(f"Model name: {model_name}")
settings = cl.ChatSettings(
[
TextInput(
id="model_name",
label="Enter the Model Name",
placeholder="e.g., gpt-4o-mini",
initial=model_name
)
]
)
await settings.send()
thread_id = thread["id"]
cl.user_session.set("thread_id", thread["id"])
# Ensure metadata is a dictionary
metadata = thread.get("metadata", {})
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
metadata = {}
cl.user_session.set("metadata", metadata)
message_history = []
steps = thread["steps"]
for message in steps:
msg_type = message.get("type")
if msg_type == "user_message":
message_history.append({"role": "user", "content": message.get("output", "")})
elif msg_type == "assistant_message":
message_history.append({"role": "assistant", "content": message.get("output", "")})
elif msg_type == "run":
# Handle 'run' type messages
if message.get("isError"):
message_history.append({"role": "system", "content": f"Error: {message.get('output', '')}"})
else:
# You might want to handle non-error 'run' messages differently
pass
else:
logger.warning(f"Message without recognized type: {message}")
cl.user_session.set("message_history", message_history)
# Reconnect to OpenAI realtime
await setup_openai_realtime()
+653
View File
@@ -0,0 +1,653 @@
# Derived from https://github.com/openai/openai-realtime-console. Will integrate with Chainlit when more mature.
import os
import asyncio
import inspect
import numpy as np
import json
import websockets
from datetime import datetime
from collections import defaultdict
import base64
from chainlit.logger import logger
from chainlit.config import config
def float_to_16bit_pcm(float32_array):
"""
Converts a numpy array of float32 amplitude data to a numpy array in int16 format.
:param float32_array: numpy array of float32
:return: numpy array of int16
"""
int16_array = np.clip(float32_array, -1, 1) * 32767
return int16_array.astype(np.int16)
def base64_to_array_buffer(base64_string):
"""
Converts a base64 string to a numpy array buffer.
:param base64_string: base64 encoded string
:return: numpy array of uint8
"""
binary_data = base64.b64decode(base64_string)
return np.frombuffer(binary_data, dtype=np.uint8)
def array_buffer_to_base64(array_buffer):
"""
Converts a numpy array buffer to a base64 string.
:param array_buffer: numpy array
:return: base64 encoded string
"""
if array_buffer.dtype == np.float32:
array_buffer = float_to_16bit_pcm(array_buffer)
elif array_buffer.dtype == np.int16:
array_buffer = array_buffer.tobytes()
else:
array_buffer = array_buffer.tobytes()
return base64.b64encode(array_buffer).decode('utf-8')
def merge_int16_arrays(left, right):
"""
Merge two numpy arrays of int16.
:param left: numpy array of int16
:param right: numpy array of int16
:return: merged numpy array of int16
"""
if isinstance(left, np.ndarray) and left.dtype == np.int16 and isinstance(right, np.ndarray) and right.dtype == np.int16:
return np.concatenate((left, right))
else:
raise ValueError("Both items must be numpy arrays of int16")
class RealtimeEventHandler:
def __init__(self):
self.event_handlers = defaultdict(list)
def on(self, event_name, handler):
self.event_handlers[event_name].append(handler)
def clear_event_handlers(self):
self.event_handlers = defaultdict(list)
def dispatch(self, event_name, event):
for handler in self.event_handlers[event_name]:
if inspect.iscoroutinefunction(handler):
asyncio.create_task(handler(event))
else:
handler(event)
async def wait_for_next(self, event_name):
future = asyncio.Future()
def handler(event):
if not future.done():
future.set_result(event)
self.on(event_name, handler)
return await future
class RealtimeAPI(RealtimeEventHandler):
def __init__(self, url=None, api_key=None):
super().__init__()
self.default_url = 'wss://api.openai.com/v1/realtime'
self.url = url or self.default_url
self.api_key = api_key or os.getenv("OPENAI_API_KEY")
self.ws = None
def is_connected(self):
return self.ws is not None
def log(self, *args):
logger.debug(f"[Websocket/{datetime.utcnow().isoformat()}]", *args)
async def connect(self, model='gpt-4o-realtime-preview-2024-10-01'):
if self.is_connected():
raise Exception("Already connected")
self.ws = await websockets.connect(f"{self.url}?model={model}", extra_headers={
'Authorization': f'Bearer {self.api_key}',
'OpenAI-Beta': 'realtime=v1'
})
self.log(f"Connected to {self.url}")
asyncio.create_task(self._receive_messages())
async def _receive_messages(self):
async for message in self.ws:
event = json.loads(message)
if event['type'] == "error":
logger.error("ERROR", event)
self.log("received:", event)
self.dispatch(f"server.{event['type']}", event)
self.dispatch("server.*", event)
async def send(self, event_name, data=None):
if not self.is_connected():
raise Exception("RealtimeAPI is not connected")
data = data or {}
if not isinstance(data, dict):
raise Exception("data must be a dictionary")
event = {
"event_id": self._generate_id("evt_"),
"type": event_name,
**data
}
self.dispatch(f"client.{event_name}", event)
self.dispatch("client.*", event)
self.log("sent:", event)
await self.ws.send(json.dumps(event))
def _generate_id(self, prefix):
return f"{prefix}{int(datetime.utcnow().timestamp() * 1000)}"
async def disconnect(self):
if self.ws:
await self.ws.close()
self.ws = None
self.log(f"Disconnected from {self.url}")
class RealtimeConversation:
default_frequency = config.features.audio.sample_rate
EventProcessors = {
'conversation.item.created': lambda self, event: self._process_item_created(event),
'conversation.item.truncated': lambda self, event: self._process_item_truncated(event),
'conversation.item.deleted': lambda self, event: self._process_item_deleted(event),
'conversation.item.input_audio_transcription.completed': lambda self, event: self._process_input_audio_transcription_completed(event),
'input_audio_buffer.speech_started': lambda self, event: self._process_speech_started(event),
'input_audio_buffer.speech_stopped': lambda self, event, input_audio_buffer: self._process_speech_stopped(event, input_audio_buffer),
'response.created': lambda self, event: self._process_response_created(event),
'response.output_item.added': lambda self, event: self._process_output_item_added(event),
'response.output_item.done': lambda self, event: self._process_output_item_done(event),
'response.content_part.added': lambda self, event: self._process_content_part_added(event),
'response.audio_transcript.delta': lambda self, event: self._process_audio_transcript_delta(event),
'response.audio.delta': lambda self, event: self._process_audio_delta(event),
'response.text.delta': lambda self, event: self._process_text_delta(event),
'response.function_call_arguments.delta': lambda self, event: self._process_function_call_arguments_delta(event),
}
def __init__(self):
self.clear()
def clear(self):
self.item_lookup = {}
self.items = []
self.response_lookup = {}
self.responses = []
self.queued_speech_items = {}
self.queued_transcript_items = {}
self.queued_input_audio = None
def queue_input_audio(self, input_audio):
self.queued_input_audio = input_audio
def process_event(self, event, *args):
event_processor = self.EventProcessors.get(event['type'])
if not event_processor:
raise Exception(f"Missing conversation event processor for {event['type']}")
return event_processor(self, event, *args)
def get_item(self, id):
return self.item_lookup.get(id)
def get_items(self):
return self.items[:]
def _process_item_created(self, event):
item = event['item']
new_item = item.copy()
if new_item['id'] not in self.item_lookup:
self.item_lookup[new_item['id']] = new_item
self.items.append(new_item)
new_item['formatted'] = {
'audio': [],
'text': '',
'transcript': ''
}
if new_item['id'] in self.queued_speech_items:
new_item['formatted']['audio'] = self.queued_speech_items[new_item['id']]['audio']
del self.queued_speech_items[new_item['id']]
if 'content' in new_item:
text_content = [c for c in new_item['content'] if c['type'] in ['text', 'input_text']]
for content in text_content:
new_item['formatted']['text'] += content['text']
if new_item['id'] in self.queued_transcript_items:
new_item['formatted']['transcript'] = self.queued_transcript_items[new_item['id']]['transcript']
del self.queued_transcript_items[new_item['id']]
if new_item['type'] == 'message':
if new_item['role'] == 'user':
new_item['status'] = 'completed'
if self.queued_input_audio:
new_item['formatted']['audio'] = self.queued_input_audio
self.queued_input_audio = None
else:
new_item['status'] = 'in_progress'
elif new_item['type'] == 'function_call':
new_item['formatted']['tool'] = {
'type': 'function',
'name': new_item['name'],
'call_id': new_item['call_id'],
'arguments': ''
}
new_item['status'] = 'in_progress'
elif new_item['type'] == 'function_call_output':
new_item['status'] = 'completed'
new_item['formatted']['output'] = new_item['output']
return new_item, None
def _process_item_truncated(self, event):
item_id = event['item_id']
audio_end_ms = event['audio_end_ms']
item = self.item_lookup.get(item_id)
if not item:
raise Exception(f'item.truncated: Item "{item_id}" not found')
end_index = (audio_end_ms * self.default_frequency) // 1000
item['formatted']['transcript'] = ''
item['formatted']['audio'] = item['formatted']['audio'][:end_index]
return item, None
def _process_item_deleted(self, event):
item_id = event['item_id']
item = self.item_lookup.get(item_id)
if not item:
raise Exception(f'item.deleted: Item "{item_id}" not found')
del self.item_lookup[item['id']]
self.items.remove(item)
return item, None
def _process_input_audio_transcription_completed(self, event):
item_id = event['item_id']
content_index = event['content_index']
transcript = event['transcript']
formatted_transcript = transcript or ' '
item = self.item_lookup.get(item_id)
if not item:
self.queued_transcript_items[item_id] = {'transcript': formatted_transcript}
return None, None
item['content'][content_index]['transcript'] = transcript
item['formatted']['transcript'] = formatted_transcript
return item, {'transcript': transcript}
def _process_speech_started(self, event):
item_id = event['item_id']
audio_start_ms = event['audio_start_ms']
self.queued_speech_items[item_id] = {'audio_start_ms': audio_start_ms}
return None, None
def _process_speech_stopped(self, event, input_audio_buffer):
item_id = event['item_id']
audio_end_ms = event['audio_end_ms']
speech = self.queued_speech_items[item_id]
speech['audio_end_ms'] = audio_end_ms
if input_audio_buffer:
start_index = (speech['audio_start_ms'] * self.default_frequency) // 1000
end_index = (speech['audio_end_ms'] * self.default_frequency) // 1000
speech['audio'] = input_audio_buffer[start_index:end_index]
return None, None
def _process_response_created(self, event):
response = event['response']
if response['id'] not in self.response_lookup:
self.response_lookup[response['id']] = response
self.responses.append(response)
return None, None
def _process_output_item_added(self, event):
response_id = event['response_id']
item = event['item']
response = self.response_lookup.get(response_id)
if not response:
raise Exception(f'response.output_item.added: Response "{response_id}" not found')
response['output'].append(item['id'])
return None, None
def _process_output_item_done(self, event):
item = event['item']
if not item:
raise Exception('response.output_item.done: Missing "item"')
found_item = self.item_lookup.get(item['id'])
if not found_item:
raise Exception(f'response.output_item.done: Item "{item["id"]}" not found')
found_item['status'] = item['status']
return found_item, None
def _process_content_part_added(self, event):
item_id = event['item_id']
part = event['part']
item = self.item_lookup.get(item_id)
if not item:
raise Exception(f'response.content_part.added: Item "{item_id}" not found')
item['content'].append(part)
return item, None
def _process_audio_transcript_delta(self, event):
item_id = event['item_id']
content_index = event['content_index']
delta = event['delta']
item = self.item_lookup.get(item_id)
if not item:
raise Exception(f'response.audio_transcript.delta: Item "{item_id}" not found')
item['content'][content_index]['transcript'] += delta
item['formatted']['transcript'] += delta
return item, {'transcript': delta}
def _process_audio_delta(self, event):
item_id = event['item_id']
content_index = event['content_index']
delta = event['delta']
item = self.item_lookup.get(item_id)
if not item:
logger.debug(f'response.audio.delta: Item "{item_id}" not found')
return None, None
array_buffer = base64_to_array_buffer(delta)
append_values = array_buffer.tobytes()
# TODO: make it work
# item['formatted']['audio'] = merge_int16_arrays(item['formatted']['audio'], append_values)
return item, {'audio': append_values}
def _process_text_delta(self, event):
item_id = event['item_id']
content_index = event['content_index']
delta = event['delta']
item = self.item_lookup.get(item_id)
if not item:
raise Exception(f'response.text.delta: Item "{item_id}" not found')
item['content'][content_index]['text'] += delta
item['formatted']['text'] += delta
return item, {'text': delta}
def _process_function_call_arguments_delta(self, event):
item_id = event['item_id']
delta = event['delta']
item = self.item_lookup.get(item_id)
if not item:
raise Exception(f'response.function_call_arguments.delta: Item "{item_id}" not found')
item['arguments'] += delta
item['formatted']['tool']['arguments'] += delta
return item, {'arguments': delta}
class RealtimeClient(RealtimeEventHandler):
def __init__(self, url=None, api_key=None):
super().__init__()
self.default_session_config = {
"modalities": ["text", "audio"],
"instructions": "System settings:\nTool use: enabled.\n\nInstructions:\n- You are an artificial intelligence agent responsible for helping test realtime voice capabilities\n- Please make sure to respond with a helpful voice via audio\n- Be kind, helpful, and curteous\n- It is okay to ask the user questions\n- Use tools and functions you have available liberally, it is part of the training apparatus\n- Be open to exploration and conversation\n- Remember: this is just for fun and testing!\n\nPersonality:\n- Be upbeat and genuine\n- Try speaking quickly as if excited\n",
"voice": "shimmer",
"input_audio_format": "pcm16",
"output_audio_format": "pcm16",
"input_audio_transcription": { "model": 'whisper-1' },
"turn_detection": { "type": 'server_vad' },
"tools": [],
"tool_choice": "auto",
"temperature": 0.8,
"max_response_output_tokens": 4096,
}
self.session_config = {}
self.transcription_models = [{"model": "whisper-1"}]
self.default_server_vad_config = {
"type": "server_vad",
"threshold": 0.5,
"prefix_padding_ms": 300,
"silence_duration_ms": 200,
}
self.realtime = RealtimeAPI(url, api_key)
self.conversation = RealtimeConversation()
self._reset_config()
self._add_api_event_handlers()
def _reset_config(self):
self.session_created = False
self.tools = {}
self.session_config = self.default_session_config.copy()
self.input_audio_buffer = bytearray()
return True
def _add_api_event_handlers(self):
self.realtime.on("client.*", self._log_event)
self.realtime.on("server.*", self._log_event)
self.realtime.on("server.session.created", self._on_session_created)
self.realtime.on("server.response.created", self._process_event)
self.realtime.on("server.response.output_item.added", self._process_event)
self.realtime.on("server.response.content_part.added", self._process_event)
self.realtime.on("server.input_audio_buffer.speech_started", self._on_speech_started)
self.realtime.on("server.input_audio_buffer.speech_stopped", self._on_speech_stopped)
self.realtime.on("server.conversation.item.created", self._on_item_created)
self.realtime.on("server.conversation.item.truncated", self._process_event)
self.realtime.on("server.conversation.item.deleted", self._process_event)
self.realtime.on("server.conversation.item.input_audio_transcription.completed", self._process_event)
self.realtime.on("server.response.audio_transcript.delta", self._process_event)
self.realtime.on("server.response.audio.delta", self._process_event)
self.realtime.on("server.response.text.delta", self._process_event)
self.realtime.on("server.response.function_call_arguments.delta", self._process_event)
self.realtime.on("server.response.output_item.done", self._on_output_item_done)
def _log_event(self, event):
realtime_event = {
"time": datetime.utcnow().isoformat(),
"source": "client" if event["type"].startswith("client.") else "server",
"event": event,
}
self.dispatch("realtime.event", realtime_event)
def _on_session_created(self, event):
print(f"Session created: {event}")
logger.debug(f"Session created: {event}")
self.session_created = True
def _process_event(self, event, *args):
item, delta = self.conversation.process_event(event, *args)
if item:
self.dispatch("conversation.updated", {"item": item, "delta": delta})
return item, delta
def _on_speech_started(self, event):
self._process_event(event)
self.dispatch("conversation.interrupted", event)
def _on_speech_stopped(self, event):
self._process_event(event, self.input_audio_buffer)
def _on_item_created(self, event):
item, delta = self._process_event(event)
self.dispatch("conversation.item.appended", {"item": item})
if item and item["status"] == "completed":
self.dispatch("conversation.item.completed", {"item": item})
async def _on_output_item_done(self, event):
item, delta = self._process_event(event)
if item and item["status"] == "completed":
self.dispatch("conversation.item.completed", {"item": item})
if item and item.get("formatted", {}).get("tool"):
await self._call_tool(item["formatted"]["tool"])
async def _call_tool(self, tool):
try:
json_arguments = json.loads(tool["arguments"])
tool_config = self.tools.get(tool["name"])
if not tool_config:
raise Exception(f'Tool "{tool["name"]}" has not been added')
result = await tool_config["handler"](**json_arguments)
await self.realtime.send("conversation.item.create", {
"item": {
"type": "function_call_output",
"call_id": tool["call_id"],
"output": json.dumps(result),
}
})
except Exception as e:
error_message = json.dumps({"error": str(e)})
logger.error(f"Tool call error: {error_message}")
await self.realtime.send("conversation.item.create", {
"item": {
"type": "function_call_output",
"call_id": tool["call_id"],
"output": error_message,
}
})
await self.create_response()
def is_connected(self):
return self.realtime.is_connected()
def reset(self):
self.disconnect()
self.realtime.clear_event_handlers()
self._reset_config()
self._add_api_event_handlers()
return True
async def connect(self):
if self.is_connected():
raise Exception("Already connected, use .disconnect() first")
await self.realtime.connect()
await self.update_session()
return True
async def wait_for_session_created(self):
if not self.is_connected():
raise Exception("Not connected, use .connect() first")
while not self.session_created:
await asyncio.sleep(0.001)
return True
async def disconnect(self):
self.session_created = False
self.conversation.clear()
if self.realtime.is_connected():
await self.realtime.disconnect()
def get_turn_detection_type(self):
return self.session_config.get("turn_detection", {}).get("type")
async def add_tool(self, definition, handler):
if not definition.get("name"):
raise Exception("Missing tool name in definition")
name = definition["name"]
if name in self.tools:
raise Exception(f'Tool "{name}" already added. Please use .removeTool("{name}") before trying to add again.')
if not callable(handler):
raise Exception(f'Tool "{name}" handler must be a function')
self.tools[name] = {"definition": definition, "handler": handler}
await self.update_session()
return self.tools[name]
def remove_tool(self, name):
if name not in self.tools:
raise Exception(f'Tool "{name}" does not exist, can not be removed.')
del self.tools[name]
return True
async def delete_item(self, id):
await self.realtime.send("conversation.item.delete", {"item_id": id})
return True
async def update_session(self, **kwargs):
self.session_config.update(kwargs)
use_tools = [
{**tool_definition, "type": "function"}
for tool_definition in self.session_config.get("tools", [])
] + [
{**self.tools[key]["definition"], "type": "function"}
for key in self.tools
]
session = {**self.session_config, "tools": use_tools}
logger.debug(f"Updating session: {session}")
if self.realtime.is_connected():
await self.realtime.send("session.update", {"session": session})
return True
async def create_conversation_item(self, item):
await self.realtime.send("conversation.item.create", {
"item": item
})
async def send_user_message_content(self, content=[]):
if content:
for c in content:
if c["type"] == "input_audio":
if isinstance(c["audio"], (bytes, bytearray)):
c["audio"] = array_buffer_to_base64(c["audio"])
await self.realtime.send("conversation.item.create", {
"item": {
"type": "message",
"role": "user",
"content": content,
}
})
await self.create_response()
return True
async def append_input_audio(self, array_buffer):
if len(array_buffer) > 0:
await self.realtime.send("input_audio_buffer.append", {
"audio": array_buffer_to_base64(np.array(array_buffer)),
})
self.input_audio_buffer.extend(array_buffer)
return True
async def create_response(self):
if self.get_turn_detection_type() is None and len(self.input_audio_buffer) > 0:
await self.realtime.send("input_audio_buffer.commit")
self.conversation.queue_input_audio(self.input_audio_buffer)
self.input_audio_buffer = bytearray()
await self.realtime.send("response.create")
return True
async def cancel_response(self, id=None, sample_count=0):
if not id:
await self.realtime.send("response.cancel")
return {"item": None}
else:
item = self.conversation.get_item(id)
if not item:
raise Exception(f'Could not find item "{id}"')
if item["type"] != "message":
raise Exception('Can only cancelResponse messages with type "message"')
if item["role"] != "assistant":
raise Exception('Can only cancelResponse messages with role "assistant"')
await self.realtime.send("response.cancel")
audio_index = next((i for i, c in enumerate(item["content"]) if c["type"] == "audio"), -1)
if audio_index == -1:
raise Exception("Could not find audio on item to cancel")
await self.realtime.send("conversation.item.truncate", {
"item_id": id,
"content_index": audio_index,
"audio_end_ms": int((sample_count / self.conversation.default_frequency) * 1000),
})
return {"item": item}
async def wait_for_next_item(self):
event = await self.wait_for_next("conversation.item.appended")
return {"item": event["item"]}
async def wait_for_next_completed_item(self):
event = await self.wait_for_next("conversation.item.completed")
return {"item": event["item"]}
async def _send_chainlit_message(self, item):
import chainlit as cl
# Debug logging
logger.debug(f"Received item structure: {json.dumps({k: type(v).__name__ for k, v in item.items()}, indent=2)}")
if "type" in item and item["type"] == "function_call_output":
# Don't send function call outputs directly to Chainlit
logger.debug(f"Function call output received: {item.get('output', '')}")
elif "role" in item:
if item["role"] == "user":
content = item.get("formatted", {}).get("text", "") or item.get("formatted", {}).get("transcript", "")
if content:
await cl.Message(content=content, author="User").send()
elif item["role"] == "assistant":
content = item.get("formatted", {}).get("text", "") or item.get("formatted", {}).get("transcript", "")
if content:
await cl.Message(content=content, author="AI").send()
else:
logger.warning(f"Unhandled role: {item['role']}")
else:
# Handle items without a 'role' or 'type'
logger.debug(f"Unhandled item type:\n{json.dumps(item, indent=2)}")
# Additional debug logging
logger.debug(f"Processed Chainlit message for item: {item.get('id', 'unknown')}")
File diff suppressed because it is too large. Load diff
+236
View File
@@ -0,0 +1,236 @@
import yfinance as yf
import chainlit as cl
import plotly
import json
from tavily import TavilyClient
from crawl4ai import WebCrawler
import os
import logging
import asyncio
from openai import OpenAI
import base64
from io import BytesIO
from datetime import datetime
from duckduckgo_search import DDGS
# Set up logging
logger = logging.getLogger(__name__)
log_level = os.getenv("LOGLEVEL", "INFO").upper()
logger.setLevel(log_level)
# Set Tavily API key
tavily_api_key = os.getenv("TAVILY_API_KEY")
tavily_client = TavilyClient(api_key=tavily_api_key) if tavily_api_key else None
# Set up OpenAI client
openai_client = OpenAI()
query_stock_price_def = {
"name": "query_stock_price",
"description": "Queries the latest stock price information for a given stock symbol.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "The stock symbol to query (e.g., 'AAPL' for Apple Inc.)"
},
"period": {
"type": "string",
"description": "The time period for which to retrieve stock data (e.g., '1d' for one day, '1mo' for one month)"
}
},
"required": ["symbol", "period"]
}
}
async def query_stock_price_handler(symbol, period):
"""
Queries the latest stock price information for a given stock symbol.
"""
try:
stock = yf.Ticker(symbol)
hist = stock.history(period=period)
if hist.empty:
return {"error": "No data found for the given symbol."}
return hist.to_json()
except Exception as e:
return {"error": str(e)}
query_stock_price = (query_stock_price_def, query_stock_price_handler)
draw_plotly_chart_def = {
"name": "draw_plotly_chart",
"description": "Draws a Plotly chart based on the provided JSON figure and displays it with an accompanying message.",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The message to display alongside the chart"
},
"plotly_json_fig": {
"type": "string",
"description": "A JSON string representing the Plotly figure to be drawn"
}
},
"required": ["message", "plotly_json_fig"]
}
}
async def draw_plotly_chart_handler(message: str, plotly_json_fig):
fig = plotly.io.from_json(plotly_json_fig)
elements = [cl.Plotly(name="chart", figure=fig, display="inline")]
await cl.Message(content=message, elements=elements).send()
return {"status": "success"} # Add a return value
draw_plotly_chart = (draw_plotly_chart_def, draw_plotly_chart_handler)
tavily_web_search_def = {
"name": "tavily_web_search",
"description": "Search the web using Tavily API and crawl the resulting URLs",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"]
}
}
async def tavily_web_search_handler(query):
current_date = datetime.now().strftime("%d %B %Y")
query_with_date = query + f" {current_date}"
if tavily_client:
try:
response = tavily_client.search(query_with_date)
logger.debug(f"Tavily search response: {response}")
results = process_tavily_results(response)
except Exception as e:
logger.error(f"Error in Tavily search: {str(e)}")
results = await fallback_to_duckduckgo(query_with_date)
else:
logger.info("Tavily API key is not set. Using DuckDuckGo as fallback.")
results = await fallback_to_duckduckgo(query_with_date)
return json.dumps({
"query": query,
"results": results
})
def process_tavily_results(response):
crawler = WebCrawler()
crawler.warmup()
results = []
for result in response.get('results', []):
url = result.get('url')
if url:
try:
crawl_result = crawler.run(url=url)
results.append({
"content": result.get('content'),
"url": url,
"full_content": crawl_result.markdown
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('content'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
return results
async def fallback_to_duckduckgo(query):
try:
with DDGS() as ddgs:
ddg_results = list(ddgs.text(query, max_results=5))
logger.debug(f"DuckDuckGo search results: {ddg_results}")
crawler = WebCrawler()
crawler.warmup()
results = []
for result in ddg_results:
url = result.get('href')
if url:
try:
crawl_result = crawler.run(url=url)
results.append({
"content": result.get('body'),
"url": url,
"full_content": crawl_result.markdown
})
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
results.append({
"content": result.get('body'),
"url": url,
"full_content": "Error: Unable to crawl this URL"
})
else:
results.append({
"content": result.get('body'),
"url": "N/A",
"full_content": "No URL provided for crawling"
})
return results
except Exception as e:
logger.error(f"Error in DuckDuckGo search: {str(e)}")
return []
tavily_web_search = (tavily_web_search_def, tavily_web_search_handler)
# New image generation tool
generate_image_def = {
"name": "generate_image",
"description": "Generate an image based on a text prompt using DALL-E 3",
"parameters": {
"type": "object",
"properties": {
"prompt": {"type": "string", "description": "The text prompt to generate the image"},
"size": {"type": "string", "description": "Image size (1024x1024, 1024x1792, or 1792x1024)", "default": "1024x1024"},
"quality": {"type": "string", "description": "Image quality (standard or hd)", "default": "standard"},
},
"required": ["prompt"]
}
}
async def generate_image_handler(prompt, size="1024x1024", quality="standard"):
try:
response = openai_client.images.generate(
model="dall-e-3",
prompt=prompt,
size=size,
quality=quality,
n=1,
)
image_url = response.data[0].url
# Download the image
import requests
image_content = requests.get(image_url).content
# Convert image to base64
image_base64 = base64.b64encode(image_content).decode('utf-8')
# Create a Chainlit Image element
image_element = cl.Image(content=image_content, name="generated_image", display="inline")
# Send the image in a Chainlit message
await cl.Message(content=f"Generated image for prompt: '{prompt}'", elements=[image_element]).send()
return {"status": "success", "message": "Image generated and displayed"}
except Exception as e:
logger.error(f"Error generating image: {str(e)}")
return {"status": "error", "message": str(e)}
generate_image = (generate_image_def, generate_image_handler)
tools = [query_stock_price, draw_plotly_chart, tavily_web_search, generate_image]
+112 -44
View File
@@ -7,8 +7,9 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
import aiofiles
import aiohttp
from chainlit.context import context
from chainlit.data import BaseDataLayer, BaseStorageClient, queue_until_user_message
from chainlit.data.base import BaseDataLayer, BaseStorageClient
from chainlit.data.utils import queue_until_user_message
from chainlit.element import ElementDict
from chainlit.logger import logger
from chainlit.step import StepDict
@@ -26,9 +27,6 @@ from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
import chainlit as cl
from literalai.helper import utc_now
now = utc_now()
if TYPE_CHECKING:
from chainlit.element import Element, ElementDict
@@ -57,7 +55,9 @@ class SQLAlchemyDataLayer(BaseDataLayer):
self.engine: AsyncEngine = create_async_engine(
self._conninfo, connect_args=ssl_args
)
self.async_session = sessionmaker(bind=self.engine, expire_on_commit=False, class_=AsyncSession) # type: ignore
self.async_session = sessionmaker(
bind=self.engine, expire_on_commit=False, class_=AsyncSession
) # type: ignore
if storage_provider:
self.storage_provider: Optional[BaseStorageClient] = storage_provider
if self.show_logger:
@@ -84,6 +84,9 @@ class SQLAlchemyDataLayer(BaseDataLayer):
if result.returns_rows:
json_result = [dict(row._mapping) for row in result.fetchall()]
clean_json_result = self.clean_result(json_result)
assert isinstance(clean_json_result, list) or isinstance(
clean_json_result, int
)
return clean_json_result
else:
return result.rowcount
@@ -111,8 +114,6 @@ class SQLAlchemyDataLayer(BaseDataLayer):
###### User ######
async def get_user(self, identifier: str) -> Optional[PersistedUser]:
logger.debug(f"Getting user: {identifier}")
return cl.PersistedUser(id="test", createdAt=now, identifier=identifier)
if self.show_logger:
logger.info(f"SQLAlchemy: get_user, identifier={identifier}")
query = "SELECT * FROM users WHERE identifier = :identifier"
@@ -120,12 +121,50 @@ class SQLAlchemyDataLayer(BaseDataLayer):
result = await self.execute_sql(query=query, parameters=parameters)
if result and isinstance(result, list):
user_data = result[0]
return PersistedUser(**user_data)
# SQLite returns JSON as string, we most convert it. (#1137)
metadata = user_data.get("metadata", {})
if isinstance(metadata, str):
metadata = json.loads(metadata)
assert isinstance(metadata, dict)
assert isinstance(user_data["id"], str)
assert isinstance(user_data["identifier"], str)
assert isinstance(user_data["createdAt"], str)
return PersistedUser(
id=user_data["id"],
identifier=user_data["identifier"],
createdAt=user_data["createdAt"],
metadata=metadata,
)
return None
async def _get_user_identifer_by_id(self, user_id: str) -> str:
if self.show_logger:
logger.info(f"SQLAlchemy: _get_user_identifer_by_id, user_id={user_id}")
query = "SELECT identifier FROM users WHERE id = :user_id"
parameters = {"user_id": user_id}
result = await self.execute_sql(query=query, parameters=parameters)
assert result
assert isinstance(result, list)
return result[0]["identifier"]
async def _get_user_id_by_thread(self, thread_id: str) -> Optional[str]:
if self.show_logger:
logger.info(f"SQLAlchemy: _get_user_id_by_thread, thread_id={thread_id}")
query = "SELECT userId FROM threads WHERE id = :thread_id"
parameters = {"thread_id": thread_id}
result = await self.execute_sql(query=query, parameters=parameters)
if result:
assert isinstance(result, list)
return result[0]["userId"]
return None
async def create_user(self, user: User) -> Optional[PersistedUser]:
logger.debug(f"Creating user: {user.identifier}")
return cl.PersistedUser(id="test", createdAt=now, identifier=user.identifier)
if self.show_logger:
logger.info(f"SQLAlchemy: create_user, user_identifier={user.identifier}")
existing_user: Optional["PersistedUser"] = await self.get_user(user.identifier)
@@ -151,8 +190,6 @@ class SQLAlchemyDataLayer(BaseDataLayer):
###### Threads ######
async def get_thread_author(self, thread_id: str) -> str:
logger.debug(f"Getting thread author: {thread_id}")
return "admin"
if self.show_logger:
logger.info(f"SQLAlchemy: get_thread_author, thread_id={thread_id}")
query = """SELECT "userIdentifier" FROM threads WHERE "id" = :id"""
@@ -171,16 +208,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
thread_id=thread_id
)
if user_threads:
thread = user_threads[0]
# Parse the metadata here
if isinstance(thread['metadata'], str):
try:
thread['metadata'] = json.loads(thread['metadata'])
except json.JSONDecodeError:
thread['metadata'] = {}
elif thread['metadata'] is None:
thread['metadata'] = {}
return thread
return user_threads[0]
else:
return None
@@ -194,10 +222,11 @@ class SQLAlchemyDataLayer(BaseDataLayer):
):
if self.show_logger:
logger.info(f"SQLAlchemy: update_thread, thread_id={thread_id}")
if context.session.user is not None:
user_identifier = context.session.user.identifier
else:
raise ValueError("User not found in session context")
user_identifier = None
if user_id:
user_identifier = await self._get_user_identifer_by_id(user_id)
data = {
"id": thread_id,
"createdAt": (
@@ -309,8 +338,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
async def create_step(self, step_dict: "StepDict"):
if self.show_logger:
logger.info(f"SQLAlchemy: create_step, step_id={step_dict.get('id')}")
if not getattr(context.session.user, "id", None):
raise ValueError("No authenticated user in context")
step_dict["showInput"] = (
str(step_dict.get("showInput", "")).lower()
if "showInput" in step_dict
@@ -388,15 +416,48 @@ class SQLAlchemyDataLayer(BaseDataLayer):
return True
###### Elements ######
async def get_element(
self, thread_id: str, element_id: str
) -> Optional["ElementDict"]:
if self.show_logger:
logger.info(
f"SQLAlchemy: get_element, thread_id={thread_id}, element_id={element_id}"
)
query = """SELECT * FROM elements WHERE "threadId" = :thread_id AND "id" = :element_id"""
parameters = {"thread_id": thread_id, "element_id": element_id}
element: Union[List[Dict[str, Any]], int, None] = await self.execute_sql(
query=query, parameters=parameters
)
if isinstance(element, list) and element:
element_dict: Dict[str, Any] = element[0]
return ElementDict(
id=element_dict["id"],
threadId=element_dict.get("threadId"),
type=element_dict["type"],
chainlitKey=element_dict.get("chainlitKey"),
url=element_dict.get("url"),
objectKey=element_dict.get("objectKey"),
name=element_dict["name"],
display=element_dict["display"],
size=element_dict.get("size"),
language=element_dict.get("language"),
page=element_dict.get("page"),
autoPlay=element_dict.get("autoPlay"),
playerConfig=element_dict.get("playerConfig"),
forId=element_dict.get("forId"),
mime=element_dict.get("mime"),
)
else:
return None
@queue_until_user_message()
async def create_element(self, element: "Element"):
if self.show_logger:
logger.info(f"SQLAlchemy: create_element, element_id = {element.id}")
if not getattr(context.session.user, "id", None):
raise ValueError("No authenticated user in context")
if not self.storage_provider:
logger.warn(
f"SQLAlchemy: create_element error. No blob_storage_client is configured!"
"SQLAlchemy: create_element error. No blob_storage_client is configured!"
)
return
if not element.for_id:
@@ -421,10 +482,8 @@ class SQLAlchemyDataLayer(BaseDataLayer):
if content is None:
raise ValueError("Content is None, cannot upload file")
context_user = context.session.user
user_folder = getattr(context_user, "id", "unknown")
file_object_key = f"{user_folder}/{element.id}" + (
user_id: str = await self._get_user_id_by_thread(element.thread_id) or "unknown"
file_object_key = f"{user_id}/{element.id}" + (
f"/{element.name}" if element.name else ""
)
@@ -458,15 +517,12 @@ class SQLAlchemyDataLayer(BaseDataLayer):
parameters = {"id": element_id}
await self.execute_sql(query=query, parameters=parameters)
async def delete_user_session(self, id: str) -> bool:
return False # Not sure why documentation wants this
async def get_all_user_threads(
self, user_id: Optional[str] = None, thread_id: Optional[str] = None
) -> Optional[List[ThreadDict]]:
"""Fetch all user threads up to self.user_thread_limit, or one thread by id if thread_id is provided."""
if self.show_logger:
logger.info(f"SQLAlchemy: get_all_user_threads")
logger.info("SQLAlchemy: get_all_user_threads")
user_threads_query = """
SELECT
"id" AS thread_id,
@@ -522,7 +578,8 @@ class SQLAlchemyDataLayer(BaseDataLayer):
s."language" AS step_language,
s."indent" AS step_indent,
f."value" AS feedback_value,
f."comment" AS feedback_comment
f."comment" AS feedback_comment,
f."id" AS feedback_id
FROM steps s LEFT JOIN feedbacks f ON s."id" = f."forId"
WHERE s."threadId" IN {thread_ids}
ORDER BY s."createdAt" ASC
@@ -555,6 +612,16 @@ class SQLAlchemyDataLayer(BaseDataLayer):
for thread in user_threads:
thread_id = thread["thread_id"]
if thread_id is not None:
# Ensure metadata is a dictionary
metadata = thread["thread_metadata"]
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
metadata = {}
elif metadata is None:
metadata = {}
thread_dicts[thread_id] = ThreadDict(
id=thread_id,
createdAt=thread["thread_createdat"],
@@ -562,7 +629,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
userId=thread["user_id"],
userIdentifier=thread["user_identifier"],
tags=thread["thread_tags"],
metadata=thread["thread_metadata"],
metadata=metadata,
steps=[],
elements=[],
)
@@ -596,8 +663,9 @@ class SQLAlchemyDataLayer(BaseDataLayer):
tags=step_feedback.get("step_tags"),
input=(
step_feedback.get("step_input", "")
if step_feedback["step_showinput"] == "true"
else None
if step_feedback.get("step_showinput")
not in [None, "false"]
else ""
),
output=step_feedback.get("step_output", ""),
createdAt=step_feedback.get("step_createdat"),
+27 -8
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "PraisonAI"
version = "0.0.55"
version = "0.1.2"
description = "PraisonAI application combines AutoGen and CrewAI or similar frameworks into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration."
authors = ["Mervin Praison"]
license = ""
@@ -20,11 +20,11 @@ pyautogen = ">=0.2.19"
crewai = ">=0.32.0"
markdown = ">=3.5"
praisonai-tools = ">=0.0.7"
pyparsing = ">=3.0.0" # Added to fix a colab issue. Temp Fix.
chainlit = {version = "^1.1.301", optional = true}
pyparsing = ">=3.0.0"
chainlit = {version = "==1.3.0rc1", optional = true}
gradio = {version = ">=4.26.0", optional = true}
flask = {version = ">=3.0.0", optional = true}
agentops = {version = ">=0.2.6", optional = true}
agentops = {version = ">=0.3.12", optional = true}
langchain-google-genai = {version = ">=1.0.4", optional = true}
langchain-anthropic = {version = ">=0.1.13", optional = true}
langchain-openai = {version = ">=0.1.7", optional = true}
@@ -32,6 +32,12 @@ langchain-cohere = {version = ">=0.1.4", optional = true}
litellm = {version = ">=1.41.8", optional = true}
aiosqlite= {version = ">=0.20.0", optional = true}
greenlet = {version = ">=3.0.3", optional = true}
tavily-python = {version = "==0.5.0", optional=true}
crawl4ai = {version = "==0.3.4", optional = true}
websockets = {version = ">=12.0", optional = true}
plotly = {version = ">=5.24.0", optional = true}
yfinance = {version = ">=0.2.44", optional = true}
duckduckgo_search = {version = ">=6.3.0", optional = true}
[tool.poetry.group.docs.dependencies]
mkdocs = "*"
@@ -70,10 +76,10 @@ mkdocstrings-python = "*"
pdoc3 = "*"
xmlrunner = "*"
unittest2 = "*"
chainlit = "^1.1.301"
chainlit = "==1.3.0rc1"
gradio = ">=4.26.0"
flask = ">=3.0.0"
agentops = ">=0.2.6"
agentops = ">=0.3.12"
langchain-google-genai = ">=1.0.4"
langchain-anthropic = ">=0.1.13"
langchain-openai = ">=0.1.7"
@@ -89,6 +95,8 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
praisonai = "praisonai.__main__:main"
setup-conda-env = "setup.setup_conda_env:main"
post-install = "setup.post_install:main"
[tool.poetry.extras]
ui = ["chainlit"]
@@ -99,5 +107,16 @@ google = ["langchain-google-genai"]
openai = ["langchain-openai"]
anthropic = ["langchain-anthropic"]
cohere = ["langchain-cohere"]
chat = ["chainlit", "litellm", "aiosqlite", "greenlet"]
code = ["chainlit", "litellm", "aiosqlite", "greenlet"]
chat = ["chainlit", "litellm", "aiosqlite", "greenlet", "tavily-python", "crawl4ai"]
code = ["chainlit", "litellm", "aiosqlite", "greenlet", "tavily-python", "crawl4ai"]
train = ["setup-conda-env"]
realtime = ["chainlit", "litellm", "aiosqlite", "greenlet", "tavily-python", "crawl4ai", "websockets", "plotly", "yfinance", "duckduckgo_search"]
[tool.poetry-dynamic-versioning]
enable = true
vcs = "git"
style = "semver"
[tool.poetry.build]
generate-setup-file = false
script = "praisonai/setup/post_install.py"
+1 -1
View File
@@ -21,7 +21,7 @@ assistant = ConversableAgent(
system_message="You are a helpful AI assistant. "
"You can help with simple calculations. "
"Return 'TERMINATE' when the task is done.",
llm_config={"config_list": [{"model": "gpt-3.5-turbo", "api_key": os.environ["OPENAI_API_KEY"]}]},
llm_config={"config_list": [{"model": "gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}]},
)
user_proxy = ConversableAgent(
+1 -1
View File
@@ -63,7 +63,7 @@ llm_config = {
generate_llm_config(custom_tool),
generate_llm_config(read_file_tool),
],
"config_list": [{"model": "gpt-3.5-turbo", "api_key": os.environ["OPENAI_API_KEY"]}],
"config_list": [{"model": "gpt-4o-mini", "api_key": os.environ["OPENAI_API_KEY"]}],
"timeout": 120,
}
+221 -1
View File
@@ -1,7 +1,227 @@
# # tools.py
# from google.oauth2.credentials import Credentials
# from googleapiclient.discovery import build
# from google.auth.transport.requests import Request
# from google_auth_oauthlib.flow import Flow
# from google_auth_oauthlib.flow import InstalledAppFlow
# import os
# import json
# import webbrowser
# from http.server import HTTPServer, BaseHTTPRequestHandler
# from urllib.parse import urlparse, parse_qs
# import threading
# from datetime import datetime, timedelta
# import logging
# # Set up logging
# log_level = os.getenv('LOGLEVEL', 'INFO').upper()
# logging.basicConfig(level=log_level)
# logger = logging.getLogger(__name__)
# logger.setLevel(log_level)
# # Set up Google Calendar API
# SCOPES = ['https://www.googleapis.com/auth/calendar']
# def get_calendar_service():
# logger.debug("Getting calendar service")
# creds = None
# token_dir = os.path.join(os.path.expanduser('~'), '.praison')
# token_path = os.path.join(token_dir, 'token.json')
# credentials_path = os.path.join(os.getcwd(), 'credentials.json')
# if os.path.exists(token_path):
# creds = Credentials.from_authorized_user_file(token_path, SCOPES)
# logger.debug(f"Credentials loaded from {token_path}")
# if not creds or not creds.valid:
# if creds and creds.expired and creds.refresh_token:
# logger.debug(f"Refreshing credentials")
# creds.refresh(Request())
# else:
# logger.debug(f"Starting new OAuth 2.0 flow")
# flow = InstalledAppFlow.from_client_secrets_file(credentials_path, SCOPES)
# logger.debug(f"Credentials path: {credentials_path}")
# creds = flow.run_local_server(port=8090)
# logger.debug(f"Setting up flow from {credentials_path}")
# # creds = flow.run_local_server(port=8090) # Use run_local_server from InstalledAppFlow
# # Ensure the ~/.praison directory exists
# os.makedirs(os.path.dirname(token_path), exist_ok=True)
# logger.debug(f"Saving credentials to {token_path}")
# with open(token_path, 'w') as token:
# token.write(creds.to_json())
# logger.debug("Building calendar service")
# return build('calendar', 'v3', credentials=creds)
# check_calendar_def = {
# "name": "check_calendar",
# "description": "Check Google Calendar for events within a specified time range",
# "parameters": {
# "type": "object",
# "properties": {
# "start_time": {"type": "string", "description": "Start time in ISO format (e.g., '2023-04-20T09:00:00-07:00')"},
# "end_time": {"type": "string", "description": "End time in ISO format (e.g., '2023-04-20T17:00:00-07:00')"}
# },
# "required": ["start_time", "end_time"]
# }
# }
# async def check_calendar_handler(start_time, end_time):
# try:
# service = get_calendar_service()
# events_result = service.events().list(calendarId='primary', timeMin=start_time,
# timeMax=end_time, singleEvents=True,
# orderBy='startTime').execute()
# events = events_result.get('items', [])
# logger.debug(f"Found {len(events)} events in the calendar")
# logger.debug(f"Events: {events}")
# return json.dumps(events)
# except Exception as e:
# return {"error": str(e)}
# check_calendar = (check_calendar_def, check_calendar_handler)
# add_calendar_event_def = {
# "name": "add_calendar_event",
# "description": "Add a new event to Google Calendar",
# "parameters": {
# "type": "object",
# "properties": {
# "summary": {"type": "string", "description": "Event title"},
# "start_time": {"type": "string", "description": "Start time in ISO format"},
# "end_time": {"type": "string", "description": "End time in ISO format"},
# "description": {"type": "string", "description": "Event description"}
# },
# "required": ["summary", "start_time", "end_time"]
# }
# }
# async def add_calendar_event_handler(summary, start_time, end_time, description=""):
# try:
# service = get_calendar_service()
# event = {
# 'summary': summary,
# 'description': description,
# 'start': {'dateTime': start_time, 'timeZone': 'UTC'},
# 'end': {'dateTime': end_time, 'timeZone': 'UTC'},
# }
# event = service.events().insert(calendarId='primary', body=event).execute()
# logger.debug(f"Event added: {event}")
# return {"status": "success", "event_id": event['id']}
# except Exception as e:
# return {"error": str(e)}
# add_calendar_event = (add_calendar_event_def, add_calendar_event_handler)
# list_calendar_events_def = {
# "name": "list_calendar_events",
# "description": "List Google Calendar events for a specific date",
# "parameters": {
# "type": "object",
# "properties": {
# "date": {"type": "string", "description": "Date in YYYY-MM-DD format"}
# },
# "required": ["date"]
# }
# }
# async def list_calendar_events_handler(date):
# try:
# service = get_calendar_service()
# start_of_day = f"{date}T00:00:00Z"
# end_of_day = f"{date}T23:59:59Z"
# events_result = service.events().list(calendarId='primary', timeMin=start_of_day,
# timeMax=end_of_day, singleEvents=True,
# orderBy='startTime').execute()
# events = events_result.get('items', [])
# logger.debug(f"Found {len(events)} events in the calendar for {date}")
# logger.debug(f"Events: {events}")
# return json.dumps(events)
# except Exception as e:
# return {"error": str(e)}
# list_calendar_events = (list_calendar_events_def, list_calendar_events_handler)
# update_calendar_event_def = {
# "name": "update_calendar_event",
# "description": "Update an existing Google Calendar event",
# "parameters": {
# "type": "object",
# "properties": {
# "event_id": {"type": "string", "description": "ID of the event to update"},
# "summary": {"type": "string", "description": "New event title"},
# "start_time": {"type": "string", "description": "New start time in ISO format"},
# "end_time": {"type": "string", "description": "New end time in ISO format"},
# "description": {"type": "string", "description": "New event description"}
# },
# "required": ["event_id"]
# }
# }
# async def update_calendar_event_handler(event_id, summary=None, start_time=None, end_time=None, description=None):
# try:
# service = get_calendar_service()
# event = service.events().get(calendarId='primary', eventId=event_id).execute()
# if summary:
# event['summary'] = summary
# if description:
# event['description'] = description
# if start_time:
# event['start'] = {'dateTime': start_time, 'timeZone': 'UTC'}
# if end_time:
# event['end'] = {'dateTime': end_time, 'timeZone': 'UTC'}
# updated_event = service.events().update(calendarId='primary', eventId=event_id, body=event).execute()
# logger.debug(f"Event updated: {updated_event}")
# return {"status": "success", "updated_event": updated_event}
# except Exception as e:
# return {"error": str(e)}
# update_calendar_event = (update_calendar_event_def, update_calendar_event_handler)
# delete_calendar_event_def = {
# "name": "delete_calendar_event",
# "description": "Delete a Google Calendar event",
# "parameters": {
# "type": "object",
# "properties": {
# "event_id": {"type": "string", "description": "ID of the event to delete"}
# },
# "required": ["event_id"]
# }
# }
# async def delete_calendar_event_handler(event_id):
# try:
# service = get_calendar_service()
# service.events().delete(calendarId='primary', eventId=event_id).execute()
# logger.debug(f"Event deleted: {event_id}")
# return {"status": "success", "message": f"Event with ID {event_id} has been deleted"}
# except Exception as e:
# return {"error": str(e)}
# delete_calendar_event = (delete_calendar_event_def, delete_calendar_event_handler)
# tools = [
# check_calendar,
# add_calendar_event,
# list_calendar_events,
# update_calendar_event,
# delete_calendar_event,
# ]
# from duckduckgo_search import DDGS
# from praisonai_tools import BaseTool
# class InternetSearchTool(BaseTool):
# name: str = "InternetSearchTool"
# description: str = "Search Internet for relevant information based on a query or latest news"