mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-12 01:41:26 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7501c57c8e | ||
|
|
115a95a8e0 | ||
|
|
42752a8ed7 | ||
|
|
f0a2983a9f | ||
|
|
adccce2f3d | ||
|
|
29080b6e78 | ||
|
|
aeff0f63a4 | ||
|
|
edcfea1da0 | ||
|
|
d2925c3415 | ||
|
|
6962709186 | ||
|
|
de20247418 | ||
|
|
4e328c0504 | ||
|
|
232ba25c86 | ||
|
|
4bf2c36ffe | ||
|
|
442bae1fed | ||
|
|
151facab62 | ||
|
|
d4c2a9a2dd | ||
|
|
5c0a86fab0 | ||
|
|
8c1c67453f | ||
|
|
5375a65e60 | ||
|
|
5eb6090c01 | ||
|
|
704bcaa599 | ||
|
|
c4cbe88bbb | ||
|
|
720921f1ef | ||
|
|
285e6c5cc4 | ||
|
|
f58646510c | ||
|
|
07fb9a3e8c | ||
|
|
bef3359e9b | ||
|
|
760877ccce | ||
|
|
99c27533bc | ||
|
|
1d0bdd22c7 | ||
|
|
0aaf7cb866 | ||
|
|
da5483b47b | ||
|
|
f420ab5d5b | ||
|
|
a170749327 | ||
|
|
f608f90c51 | ||
|
|
be238c841e | ||
|
|
4472bfa9d4 | ||
|
|
abf12b1aab | ||
|
|
717476adab | ||
|
|
fc802aa254 | ||
|
|
ab9342933b | ||
|
|
060c322bfe | ||
|
|
392d3a8694 | ||
|
|
635483ec3d | ||
|
|
660248b95a | ||
|
|
f29adfe74c | ||
|
|
29f56e21a2 | ||
|
|
5378a37dd1 | ||
|
|
273aa48861 | ||
|
|
04be538472 | ||
|
|
e50c614835 | ||
|
|
9871768c3a | ||
|
|
1f9e2ae839 | ||
|
|
62af1fe7ab | ||
|
|
320aa968db | ||
|
|
bf8eb9b82e | ||
|
|
03d3cb544f | ||
|
|
92b769a73f | ||
|
|
fe23957f41 | ||
|
|
a9f7393809 | ||
|
|
df1f572d1c | ||
|
|
619f0173cc | ||
|
|
adb707b593 | ||
|
|
e2b95eb9b8 | ||
|
|
8889cb94c5 | ||
|
|
83e6eb1b2b | ||
|
|
3e14f4abb5 | ||
|
|
510d1eef56 | ||
|
|
ba672bfad1 | ||
|
|
04841330f7 | ||
|
|
ff28049a76 | ||
|
|
af0e6ff659 | ||
|
|
e1677d1f7c | ||
|
|
bfb98efee0 | ||
|
|
a5e69cb77e | ||
|
|
e207bd904e | ||
|
|
1985f73d60 | ||
|
|
317b07661e | ||
|
|
6b9d85b842 |
No files matched your search
+8
-1
@@ -45,6 +45,13 @@ praisonai/doc
|
||||
|
||||
# agentops
|
||||
trained_agents_data.pkl
|
||||
agentops.log
|
||||
|
||||
# crewAI
|
||||
crewAI
|
||||
crewAI
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
|
||||
# Credentials
|
||||
credentials.json
|
||||
@@ -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
@@ -1,3 +1,5 @@
|
||||
cookbooks
|
||||
tests
|
||||
docs
|
||||
docs
|
||||
praisonai/ui/*
|
||||
praisonai/inbuilt_tools/*
|
||||
+3
-1
@@ -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
@@ -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,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
@@ -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"
|
||||
@@ -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> : <code>list</code></dt>
|
||||
<dd>A list containing the command and its arguments.</dd>
|
||||
<dt><strong><code>env</code></strong> : <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 == '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__}"
|
||||
|
||||
@@ -206,7 +273,11 @@ init (bool): A flag indicating whether to enable initialization mode. Defaults t
|
||||
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")
|
||||
args, unknown_args = parser.parse_known_args()
|
||||
|
||||
if unknown_args and unknown_args[0] == '-b' and unknown_args[1] == 'api:app':
|
||||
@@ -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>
|
||||
|
||||
@@ -110,7 +110,7 @@ Loads environment variables from .env file or system and sets them.</p>
|
||||
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')
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -68,10 +68,12 @@ hljs.highlightAll();
|
||||
self.max_file_size = max_file_size
|
||||
self.max_tokens = int(os.getenv("PRAISONAI_MAX_TOKENS", max_tokens))
|
||||
self.ignore_patterns = self.get_ignore_patterns()
|
||||
self.include_paths = self.get_include_paths()
|
||||
self.included_files = []
|
||||
|
||||
def get_ignore_patterns(self):
|
||||
"""
|
||||
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"Final ignore patterns: {modified_ignore_patterns}")
|
||||
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 = []
|
||||
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('#')
|
||||
)
|
||||
|
||||
# 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):
|
||||
"""
|
||||
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):
|
||||
"""Gather context from relevant files, respecting ignore patterns."""
|
||||
"""
|
||||
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()
|
||||
|
||||
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")
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading {file_path}: {e}")
|
||||
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}/{total_files} files", end="", flush=True)
|
||||
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)
|
||||
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."""
|
||||
return len(text.split())
|
||||
return len(text.split())
|
||||
|
||||
def truncate_context(self, context):
|
||||
"""Truncate context to stay within the token limit."""
|
||||
@@ -199,12 +284,9 @@ hljs.highlightAll();
|
||||
contents = sorted(path.iterdir())
|
||||
pointers = [('└── ' if i == len(contents) - 1 else '├── ') 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"{prefix}{pointer}{rel_path}")
|
||||
if rel_path in self.included_files:
|
||||
tree.append(f"{prefix}{pointer}{rel_path}")
|
||||
|
||||
if item.is_dir():
|
||||
add_to_tree(item, prefix + (' ' if pointer == '└── ' else '│ '))
|
||||
@@ -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>
|
||||
|
||||
@@ -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,iVBORw0KGgoAAAANSUhEUgAAAkUAAADBCAYAAADBwMQmAAAAAXNSR0IArs4c6QAAIABJREFUeF7tnQn8f9Wc/98vIo1thCiJRKIyIWuIKERky9QQGtkyUShZhmRpNGQra8k24l/EZGRJ9n2LUBHJoGTfBoP3/7ya8+Xbx2e5595z7j3383mdx+Pz+MX33HPf53nvPfd93+e9wNREQAREQAREQAREQAQMYiACIiACIiACIiACImBSinQTiIAIiIAIiIAIiAAJyFKk+0AEREAEREAEREAEpBSZuftVzewWZnZTM9sk/q5pZvxdzcw2NLPLx3/535cxs9/F3x/iv7+f+Pd/zOxHZnahmX0//i4ws/MB8Fg1ERABERABERCBygisjKXI3Tcys38ws5uZ2bZmto2ZbW1mW0RFp49L82cz+2lUln5oZl83s6+a2RfN7EwA/LuaCIiACIiACIjAAASWVily9+uY2e5mdjcz28HMbmBmGwzAuOkpaV06JypKZ0VF6SOyLDXFp34iIAIiIAIi0I3A0ihF7n4FM7urme1mZjub2fY9WoC6XYXZR//azD4T5nOGmZ0G4AulTqRxRUAEREAERGDVCYxaKXJ3boHtaWa7mNntzOxKS35BfxAsXx83sw+FbcB3ALh4yeer6YmACIiACIhAbwRGpxS5+1XMbJ/422kJrEFtLzYdtt8X/KL+IypIf2w7kI4TAREQAREQAREYUUi+u3NbbF8z28PMqBip/ZXARVSMgt/UGwF8WmBEQASaE3D3O8VI088CYACEmgiIwIoSqNpS5O43NLNHmNk/RkfpFb1MSdP+ipm9zMxer2i2JG7qvAIE3H27EPH5gOCrd08zu82MKTONxqfM7FQAb1gBLJqiCIhAJFClUuTuzBt0qJndv/KIsZpvpPPM7CVm9hoAzKekJgIrSyAqQ88yswcmQqCCdBSAlyYep+4iIAIjJFCVUuTutw9JEJ8aQ+kvO0KeNYr8vWg5eiWA39QooGQSgZIE3P04M9uv4zm+EfKLPQbARzuOo8NFQAQqJlCFUuTuDKWnZYg5haqQqeJr1lY0+ko8EwBfEGoisPQE3H1TM+P2166ZJvtLM3sCgBMyjadhREAEKiMwqALi7vcws6eZ2R0r47LM4nw4Luz0PVITgaUkEBWi083sJgUmeBAAbk2riYAILBmBQZQid2edsSNjJNmSIR3FdBjOT2fs52hLbRTXS0ImEghb8e9fYCH6lZlxS+yTIZjju6xLGJO+XnddGaB5Z91ZW2mJF0XdRWAEBHpVimLx1SPM7FGxwOoIEC21iN82s8cC4AtETQSWgoC7Hx+jVmfN5/Vm9vR54ffuzqSwx4RAhc1mDMKttJ0AsCSPmgiIwJIQ6E0pcndGfbzYzPglplYPgf8NflyHA3hePSJJEhFoRyBGmbHI8rTGjPAHADilyehxC47PBdOCTGsnB6UoNZqtyanVRwREYCACxZUid+eX1itCFfj7DTRHnbYZgXdz8Qfw02bd1UsE6iMQlKKTYh6iSeGoEO3YJjmjuz/bzBjOP61tL2tRffeBJBKBtgSKKkXuzjxDNEFfu62AOq5XAt9k+RQAn+/1rDqZCGQgsMBKdL+mFqJporg7CzPfesrfPgggV3RbBgoaQgREoAuBIkqRu29kZkdH36Ei5+gyaR07lwBzGe0LgGVD1ERgNATmWHSY3b1TnqK4lXaOmV15AsgvAVx1NJAkqAiIwFwC2RUWd79BCIN9G03VYj9aAoxOezgAXkc1ERgFAXf/QMx1NinvZm22zSYHmaN0dbJCjQKuhBSBFSGQVSlyd5qR32Rm11oRfss8TZYG2R/AG5d5kprb8hDwoLVMmQ2LvM6qcZY0+RiR9s4pB70EwEFJg6mzCIhAlQSyKUXu/lDW2Qr5h65Q5UwlVBsCf4wh+69rc7COEYE+CcxQijpvna2fw4xzSCnq80LrXCJQkEAWpcjd+ZV0lJmpXlnBizXQ0H9i3ShZjAair9M2IjDHyTpr9ml3Z8LHbSaEymaNajRZdRIBEShGoLNSFBWiF6lmWbFrVMPAdL7eDQCz/6qJQHUE5mxtZfX3mRGFJqWoujtCAolAOwKdlCJ33z/4D71yJBYivtgvjr8fxX8vMjOm+6dj8e/jv/xv/uifsGH8cUuQ/83Ik2vG3zVC/iX+mIzy6u3wj+qo/44ZfC8YldQSdmUI9LG1FT4CfxGS0F5lAqqSOK7MXaaJLjuB1kqRu+9uZswMe7nKIP0k1jRizh2G0NLcfSYA1jcq0mKCyptFs/p2ZnYrM2N9tw2KnHC4QT9rZncG8D/DiaAzi8B0AjOUomx5hOZs0cmnSDelCCwJgVZKUSzo+tEKLCS05lD5+ZyZMbnaxwB8uYZrE+u83ZlKRJDtnmZ24xrkyiDDmwHQqV5NBKoiECzX359Sq+z7ADbPIai7P9zMWDdtsmX1W8ohq8YQARFoRyBZKXJ3mo4/FS0h7c7a7ajfhlwkHzczFjF9F4BvdRuun6Pd/eax/MCDzGzrfs5a7CwPBvD2YqNrYBFoQWBOIdgsfkXu/mkzmxber1IfLa6XDhGBGgm0UYpODFtSD+55MvTxYWI2JhM8BQD9g0bb3P0+ZvbooFzefST+WJOsWUfqZgC4VakmAlUQmONsTQvSrbokcJyTuPHskOT0JlUAkBAiIAKdCSQpRe7++LBl9vLOZ20+AH1Y3sLfMr6A4zbkEWa2p5ldpjmWKnqeEPy0ZlUPr0JACbF6BGZsoRHESQBopU1u0ZfoE1McrDmWts6SieoAEaiXQGOlKL7A6bdzpcLT4fYY624dC4DbdEvf3P2OIbrtBYzuGtFk6c91bwD/NSKZJeqSE3D3J8a6i9NmejSAg1MQuPudQkHrVwUr9TRr0A8AXCdlPPUVARGom0CKUsQcNbcrOB2Gx7/WzF4GgKHzK9fc/Ukh39PhIRHmFUcy+XP5sgDw55HIKzFXgMCcivacPT/sHgngrEUoQjTbs8yMStRkCP7aoY8AcMKicfR3ERCB8RBopBS5+4Fm9tJC0zo/bsm9euy+Qjn4uDtD+xnhcosc4/UwxqMBsLyLmghUQSBud/EjbrKi/Zp8v+R2WnjG3m1mTLz4w7U/RMsQgyL2nuFUvdY1a/mQKsBJCBEQAVuoFLk7ExMy30/uBIVMAvhiJn8EwOKjapGAu9NS9FYz22MEUBj9R2sR66SpiUAVBKJy85GGwtARm8lJmxaOzZb7qKF86iYCItATgSZKEUt4JO3DL5CdkUtUho4BwKgytSkE3J2O18fGKLXaGT0GwKtrF1LyrRaBmFfoZXMsRm2AfNDM9u0SydbmpDpGBESgHwJzlaKYk4iZoP8+gzg0UXMLjj5DyojcEKi7H2Nmj2vYfahu5zGbt6xFQ+HXeWcRiBYjWl03y0BJW2YZIGoIEaiZwCKlaL9Q2+u4jhOgWZph/LQMDZJfyN0ZMbd9LMPx25BXhPmORtGixejkGLZfs8z7AODLR00EqiLg7puGckTPM7O2KSRo3T4AAMsaqYmACCwxgUVKEUNRmWSwTaMDNU3X9BkaZJvM3ekjQCvLA83s79ZN4ikA/r3NpIY4xt03MjP6R7CmWq3tVABj8IGqlZ/kKkwgOmA/O0bRNrEcMU/aWwG8pLBoGl4ERKASAouUotPNbJdEWb9qZvQvee1QDtTxy5AL2V4zZL8wRHhdbyj5Enle0t3dtzUzlhkonSeqjXg85veRKVMrqIlA1QTittr9opDXDRZx/hixxkaXgbfJb6jqSyjhRKAIgUVKUVN/FpZ7eJ+ZvQ7AGUUkbTiou7MECS1Umyw4ZA8ApzYctopuCxLT1SDjIQCOqkEQySACIiACIiACqQQWKUUMw6efyK4TA1MJ+pqZnRkLs55Wg5Otuz8jyEnz+GUbgHgJgIMa9Kuqi7szy/dtqxLqr8J8ISiaO1Yqm8QSAREQAREQgbkEFobk8+gQAXVNM9vCzH5uZhcB+HVtXN2d2bAfmSDXBwDsltC/iq5BKWIR2dOqEGa6EFsCoD+ZWk8E4lbQXUJ5i6vGH7da2ViaYn02ZmZzXmvrt4rerq2ini6WTiMCIlA1gUZKUdUz+D+ljf5DT0iU83wAWyYeU0V3d2eulLtWIczfCqEM1z1cmKgIPczMqCTnqL/1DfrRmNkZAD46awpxC5cfSH2045uU41gvSHSmZtRsH+0COWH3gVnnEIH+CIxeKXL3p4TIshe2QPZ7AFdocdzgh7j77iES7T2DCzJdgLcAeEilso1aLHff08z+KUZP5VCEZvFghmf6CLKA6qVqhC2oK5ab7/1Sw+Ajo3fmFmTGeCwR0jQLdk8i6TQiIAJdCIxaKXL3h8Y6YU18iKZx2gLA97oAHOpYd/+OmV1/qPPPOe93ANygQrmSRHJ3OupzHvSr29jM6Ef3bTP7Vt/+c9H6wSzwk759SXNq2fnoUKD4qLXtNSlFl6JYnVIUE+7y3qXLA+9bfvhdzswub2bMkk/XB9Z++5WZ/SL+fjJU2pSW92TRw2Jeu6vFTOisn8f3JHldwg7Az4oKsEKDx3Qz3ObnOssk0SxxxeTOZM3781wAP+oTyWiVouhbw2RqXaw9OwCgs/joWriZ/s3MDqlU8K0BfLNS2aaK5e58aTCf1X3NjAVBbxgXw8n+f4q1ALnFRKvYx0vNM6aWoCX0n+dUai91+vXj8iX6rwBeKqVoeKUovkhYMJo/FpC+Xoj83Tz+ZhXBnXef8J5mLUo+s+ea2dkhncnXmQJkmasPuDu53dHMbm1mW8W0DORIhWhe4/PApMT8oOaH0ufM7ONjW/P6WDgmz+Hu3Hq/T3T/uGn88NxggSz8IOW9Sev1O0u/s0epFLn71sGXglFY/BLq0nYKztZrDqddxun9WHffIbzAv9T7iZudcD8Ar2/W9a+93P1jURlJPXTuAhYybd94VocYRPC0YAHilt81Wpz43gCLine truncated
|
||||
"created": 1721296075040,
|
||||
"lastRetrieved": 1721296075040
|
||||
"id": "74aba821e88621a6bb8521793d440bf841a59ea0",
|
||||
"dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAz0AAAEoCAYAAABy7J90AAAAAXNSR0IArs4c6QAAIABJREFUeF7snQmcHFW1/8+pnukJAQSBZKZudSBAEARcMIDik80NFBBQQUUBRcUVFdGn4MOVRZ+CysNdQQU3cGFRkF1BRVBQ+AsoxBCg69YMiRCEhGQmXeffFzqSZbq7quvW1v27n08+8t7ce86531sz3b+6957DhAYCIAACIAACIAACIAACIAACfUyA+3humBoIgAAIgAAIgAAIgAAIgAAIEEQPHgIQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZAAEQAAEQAAEQAAEQAAEQ6GsCED19vbyYHAiAAAiAAAiAAAiAAAiAAEQPngEQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZAAEQAAEQAAEQAAEQAAEQ6GsCED19vbyYHAiAAAiAAAiAAAiAAAiAAEQPngEQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZAAEQAAEQAAEQAAEQAAEQ6GsCED19vbyYHAiAAAiAAAiAAAiAAAiAAEQPngEQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZAAEQAAEQAAEQAAEQAAEQ6GsCED19vbyYHAiAAAiAAAiAAAiAAAiAAEQPngEQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZAAEQAAEQAAEQAAEQAAEQ6GsCED19vbyYHAiAAAiAAAiAAAiAAAiAAEQPngEQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZAAEQAAEQAAEQAAEQAAEQ6GsCED19vbyYHAiAAAiAAAiAAAiAAAiAAEQPngEQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZAAEQAAEQAAEQAAEQAAEQ6GsCED19vbyYHAiAAAiAAAiAAAiAAAiAAEQPngEQAAEQAAEQAAEQAAEQAIG+JgDR09fLi8mBAAiAAAiAAAiAAAiAAAhA9OAZmJbANttss8nk5OQsEXniHxGt/rcJEc0kog3X/F9mHp7OkIg8LiIPMrMmooeY+UERCYjoMcdxHmPmZStXrnxMRJYtXrz4MSwHCIAACIAACIAACIAACNgmANFjm2iJ7Hme9xwi2l5E5hLRXBHZipm3Nv9NRBvkMRVmXigi9xDRP5l5ARHdHYbhwiAI7sojHvgEARAAARAAARAAARAoPwGInvKvYaQZKKW2ZObdRWRXItqDiOa3dmsijS9Ip0VGBBGREUW3NhqN6yYmJu4tSGwIAwRAAARAAARAAARAoKAEIHoKujBJwxodHZ1dqVReSkQvI6J9iWirpDYLOt4Iod+IyDWVSuWP9Xrd7A6hgQAIgAAIgAAIgAAIgMB/CED09MnDMGvWrI2Gh4eNuDEix4idZ/bJ1OJO4yEi+hMR3SQiNwZB8Ou4BtAfBEAABEAABEAABECgvwhA9JR3PStKqResIXKeT0RD5Z1OapGb5AiXisiFG2644WULFixYmZonGAYBEAABEAABEAABECgkAYieQi5L+6DGxsZ2cxznKCJ6ExFtWrLw8w53OTNfLiKXh2F4yfj4+OK8A4J/EAABEAABEAABEACB9AlA9KTPOLGHWbNmjQ0PDx9NRG8lou0SG4SB1QRuJaKLp6amvrl48eJxYAEBEBg8AqOjoztXKpWtRWRTZt5URDYmomWO4zwiIg+LiB8EwZ8HjwxmDAIgAAL9RQCip7jrOeS67sFEdAwz70dEleKGWvrIVhHRT5n5LN/3byz9bDABEACBjgRc1zXp+d9ARIc3X3zsEgHX34noAmb+ge/7JoMkGgiAAAiAQMkIQPQUbMGUUqZuzrHMfGSrIGjBIuz7cP4iIl8MguC8vp8pJggCA0Zgzpw524Zh+EkROYKInB6nf0mj0fjYxMTE33ocj2EgAAIgAAI5EIDoyQH6dC5d192fmT9ERC8pSEiDHsYDRHTWihUrvvnQQw/9e9BhYP4gUGYCSqktiOhTRPRuW/MQke87jvMx3/frtmzCDgiAAAiAQHoEIHrSYxvF8pDneW8Mw/AEZn5WlAHokzmBpUR0erVaPWvRokUrMvcOhyAAAokIKKVeSEQ/JqI5iQxNP3iJiByJ1PgpkIVJEAABELBMAKLHMtCo5jzPe5eIfIyIvKhj0C9XAj4Rnay1PjfXKOAcBEAgMgGl1IebqfxPIaJq5EHxO4YicnoQBJ9o/o1oxB+OESAAAiAAAlkQgOjJgvIaPjzPO1xEzIcwsrBlzN6SuzvCMHzP+Pj4by3ZgxkQAIEUCCiljAj5ZAqm25n8rtb6LRn6gysQAAEQAIEYBCB6YsBK0nVsbGxvx3G+QES7JrGDsYUhcEkYhu8fHx9fVJiIEAgIgMATBFovl34SEYcpYHw5M98UhuE4M08Q0eYi4jLzzkT0aiJ6ekRb/621/nzEvugGAiAAAiCQIQGInpRhj46OblOpVM4gokNSdgXz2RNYSUSfnTlz5ukLFiww/40GAiCQMwHXdecz82+IaKMuodzWrMlzRvM+5c+01ss79XVd99WtRDN7RJjewVrrSyL0QxcQAAEQAIEMCUD0pAR7iy222LharZ5MRO9P+Tx5SjOA2RgEFjHzW33fvzbGGHQFARBIgUBToNweITHMW7XW58R1r5QytdO+T0RP6zB2YsaMGdsvXLjwkbj20R8EQAAEQCA9AhA9KbB1XfdIZja7O7NSMA+TxSXw7Wq1ehyyvBV3gRBZfxNQSh3TzNL2nQ6zfIyZD/F9/5peSSilTDHTy4horIONz2qtT+zVB8aBAAiAAAjYJwDRY5GpKXzXaDTM28O9LJqFqXIR+EcYhq8ZHx+/o1xhI1oQKDeBuXPnzpicnFzQISOmybJ2gI300q7rPo+ZbyCimW2oGXH1TNTwKfczhehBAAT6iwBEj6X1VEqZLEEmWxAaCBAzH+37vjkGgwYCIJABAaXUG4johx1cfUJr/WlboUTw9ymtdZbZ42xNDXZAAARAoC8JQPQkXNaxsbGdHMcxWYJ2SmgKw/uPwKe11hDC/beumFEBCSilfkBER7QJ7a/N5ALPa76YEpuhK6V+RESvb2PzVq31fJv+YAsEQAAEQKB3AhA9vbMzaVFPFhFrbw4ThIKhBSXAzOf5vn9UQcNDWCDQFwR23HHH6tKlS5cQ0cZtJvRyrfVVtifruu5WzPz3ZhbHGdPZDsNwa6S1t00d9kAABECgNwIQPT1wGx0dne04zqXMvHsPwzFk8Ahc6TjOIfV6/fHBmzpmDALpE3Bdd39mvryNp7u11tunFYVS6sdE9Lrp7DPzCb7vn5mWb9gFARAAARCITgCiJzqrJ3q2Mvf80vxnzKHoPsAEROTmkZGR/RYtWrR0gDFg6iCQCgHXdU9k5tPaGP+c1vqjqTh+shDqUSLyvTai53zf949MyzfsggAIgAAIRCcA0ROdFbmu+xpmNpfT22XsiWENXQeQwO1hGL50fHx88QDOHVMGgdQIKKW+QEQntBEeh/m+/9O0nDd3enZo7vTc1cb+r7XWr0jLN+yCAAiAAAhEJwDRE5FV603iqUQEZhGZodu0BP4ehuFeED54OkDAHgGl1HeJ6OjpLDqO8/x6vX6zPW9rW1JKmZdgy9rYv0VrvWtavmEXBEAABEAgOgF8gY/ASin1LSJ6W4Su6AICUQiYt8J7aa3NxWs0EACBhASUUhcR0cHTmZmamnIXL148ntBFx+FKqXqb+kD3aq23SdM3bIMACIAACEQjANHThZNS6tdEtF80nOgFApEJ3Ki1fmHk3ugIAiDQloDneeeJyJum69BoNLadmJhYmCY+pZQ5srrFND7u0FrvnKZv2AYBEAABEIhGAKKnPadK6+3hgdFQohcIxCPAzD/zff+18UahNwiAwLoEXNc9k5mPn44MM+/h+/4fU6Q2pJSaamP/Gq31S1P0DdMgAAIgAAIRCUD0tAHV6c1hRLZF7/YIEWki8s0/ZvbDMFzmOI5Jq7xCRFas/l/z35VKxfzf5uz6E/9EZMM1/u/NiWg2Ec0SkdnMbP57tOgAihAfM5/s+/4pRYgFMYBAWQkopUx2ttOni19Ejg2CwBxRTqV5nreHiPyhjfEfaq3fmIpjGAUBEAABEIhFAKJnGlxKqU8R0cdjkSxm50XNauF/Z+Z/EJH5Z4roGYHzQBY1Y0zhPsdxtgnDcF4z6505174tMz9TRHDcY43nRUT2D4LgimI+QogKBIpPQCl1DBF9p02kF2itp62jY2NmSqlPEtEn2giuLwZB8EEbfmADBEAABEAgGQGInnX4KaUOIaJfJMOay+g/MvMfwzC8hZnNOfK/5BJFBKee520ehuHezLy3iOzbPOb1rAjD+rnLwyKySxAE9/XzJDE3EEiLQK1We3YYhre1sf+I1trct1mVhn+l1I1E9II2tt+otf5hGn5hEwRAAARAIB4BiJ41eI2Nje3oOI5JbWqObhW5mSNoJsHCNSJyUxAEfy5ysN1iGx0dne04zsHM/JoBThqB1LbdHhT8HAQ6EFBKmd3sZ0zXRUTeEQTBN20DrNVq+zRf4FzXxu6KycnJ2UuWLHnUtl/YAwEQAAEQiE8AoqfFrFVr4a9EtF18jJmMeJSZLw7D8CJmvlxrvTwTrxk72WyzzZ42MjJiBJA5Bz9QWfOY+XTf90/KGDncgUBfEHBd9xRm/libyZjsajvaTBM/b968keXLl5vdpe2n88nMP/d937zIQQMBEAABECgAAYiep0TPj5p3Xl5fgDVZM4TlzfsvP61UKj+p1+uXFSy21MMZGxub6zjOO1s1kkyyhL5Line 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
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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({})
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Executable
+72
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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."""
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user