PraisonAI 1.0.4__tar.gz → 1.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of PraisonAI might be problematic. Click here for more details.
- {praisonai-1.0.4 → praisonai-1.0.6}/PKG-INFO +1 -1
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/deploy.py +1 -1
- praisonai-1.0.6/praisonai/ui/README.md +21 -0
- praisonai-1.0.6/praisonai/ui/chat.py +598 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/sql_alchemy.py +165 -169
- {praisonai-1.0.4 → praisonai-1.0.6}/pyproject.toml +1 -1
- praisonai-1.0.4/praisonai/ui/chat.py +0 -654
- {praisonai-1.0.4 → praisonai-1.0.6}/LICENSE +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/README.md +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/__init__.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/__main__.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/agents_generator.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/api/call.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/auto.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/chainlit_ui.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/cli.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/inbuilt_tools/__init__.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/inc/__init__.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/inc/config.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/inc/models.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/android-chrome-192x192.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/android-chrome-512x512.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/apple-touch-icon.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/fantasy.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/favicon-16x16.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/favicon-32x32.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/favicon.ico +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/game.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/logo_dark.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/logo_light.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/movie.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/public/thriller.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/setup/__init__.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/setup/build.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/setup/config.yaml +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/setup/post_install.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/setup/setup_conda_env.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/setup/setup_conda_env.sh +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/setup.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/test.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/train.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/code.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/context.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/public/fantasy.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/public/game.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/public/logo_dark.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/public/logo_light.png +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/public/movie.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/public/thriller.svg +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/realtime.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/realtimeclient/__init__.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/realtimeclient/realtimedocs.txt +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/ui/realtimeclient/tools.py +0 -0
- {praisonai-1.0.4 → praisonai-1.0.6}/praisonai/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PraisonAI
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: 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.
|
|
5
5
|
Author: Mervin Praison
|
|
6
6
|
Requires-Python: >=3.10,<3.13
|
|
@@ -56,7 +56,7 @@ class CloudDeployer:
|
|
|
56
56
|
file.write("FROM python:3.11-slim\n")
|
|
57
57
|
file.write("WORKDIR /app\n")
|
|
58
58
|
file.write("COPY . .\n")
|
|
59
|
-
file.write("RUN pip install flask praisonai==1.0.
|
|
59
|
+
file.write("RUN pip install flask praisonai==1.0.6 gunicorn markdown\n")
|
|
60
60
|
file.write("EXPOSE 8080\n")
|
|
61
61
|
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
|
62
62
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changes to DB
|
|
2
|
+
|
|
3
|
+
The following columns are renamed or modified between the first and second versions of the code:
|
|
4
|
+
|
|
5
|
+
| Table Name | Original Column Name | New Column Name |
|
|
6
|
+
|-------------|----------------------------|--------------------|
|
|
7
|
+
| `users` | `metadata` | `meta` |
|
|
8
|
+
| `users` | `created_at` | `createdAt` |
|
|
9
|
+
| `threads` | `metadata` | `meta` |
|
|
10
|
+
| `threads` | `created_at` | `createdAt` |
|
|
11
|
+
| `steps` | `metadata` | `meta` |
|
|
12
|
+
| `steps` | `start_time` | `startTime` |
|
|
13
|
+
| `steps` | `end_time` | `endTime` |
|
|
14
|
+
| `elements` | `metadata` | (Removed) |
|
|
15
|
+
|
|
16
|
+
Key changes:
|
|
17
|
+
1. The `metadata` column in several tables is renamed to `meta`.
|
|
18
|
+
2. Timestamps (`created_at`, `start_time`, and `end_time`) are renamed to PascalCase (`createdAt`, `startTime`, and `endTime`).
|
|
19
|
+
3. Some columns are removed (e.g., `metadata` in `elements`).
|
|
20
|
+
|
|
21
|
+
These changes make the column names consistent and follow a specific naming convention.
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import chainlit as cl
|
|
2
|
+
from chainlit.input_widget import TextInput
|
|
3
|
+
from chainlit.types import ThreadDict
|
|
4
|
+
from litellm import acompletion
|
|
5
|
+
import os
|
|
6
|
+
import sqlite3
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Dict, List, Optional
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
load_dotenv()
|
|
11
|
+
import chainlit.data as cl_data
|
|
12
|
+
from chainlit.step import StepDict
|
|
13
|
+
from literalai.helper import utc_now
|
|
14
|
+
import logging
|
|
15
|
+
import json
|
|
16
|
+
from sql_alchemy import SQLAlchemyDataLayer
|
|
17
|
+
from tavily import TavilyClient
|
|
18
|
+
from crawl4ai import AsyncWebCrawler
|
|
19
|
+
import asyncio
|
|
20
|
+
from PIL import Image
|
|
21
|
+
import io
|
|
22
|
+
import base64
|
|
23
|
+
|
|
24
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
25
|
+
from sqlalchemy import text
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
log_level = os.getenv("LOGLEVEL", "INFO").upper()
|
|
29
|
+
logger.handlers = []
|
|
30
|
+
console_handler = logging.StreamHandler()
|
|
31
|
+
console_handler.setLevel(log_level)
|
|
32
|
+
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
33
|
+
console_handler.setFormatter(console_formatter)
|
|
34
|
+
logger.addHandler(console_handler)
|
|
35
|
+
logger.setLevel(log_level)
|
|
36
|
+
|
|
37
|
+
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
|
|
38
|
+
if not CHAINLIT_AUTH_SECRET:
|
|
39
|
+
os.environ["CHAINLIT_AUTH_SECRET"] = "p8BPhQChpg@J>jBz$wGxqLX2V>yTVgP*7Ky9H$aV:axW~ANNX-7_T:o@lnyCBu^U"
|
|
40
|
+
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
|
|
41
|
+
|
|
42
|
+
now = utc_now()
|
|
43
|
+
create_step_counter = 0
|
|
44
|
+
|
|
45
|
+
DATABASE_URL = os.getenv("DATABASE_URL")
|
|
46
|
+
|
|
47
|
+
async def create_schema_async():
|
|
48
|
+
if not DATABASE_URL:
|
|
49
|
+
return
|
|
50
|
+
engine = create_async_engine(DATABASE_URL, echo=False)
|
|
51
|
+
async with engine.begin() as conn:
|
|
52
|
+
# Use double quotes for column names and renamed start/end to startTime/endTime
|
|
53
|
+
await conn.execute(text('''
|
|
54
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
55
|
+
"id" TEXT PRIMARY KEY,
|
|
56
|
+
"identifier" TEXT NOT NULL UNIQUE,
|
|
57
|
+
"meta" TEXT NOT NULL DEFAULT '{}',
|
|
58
|
+
"createdAt" TEXT
|
|
59
|
+
);
|
|
60
|
+
'''))
|
|
61
|
+
await conn.execute(text('''
|
|
62
|
+
CREATE TABLE IF NOT EXISTS threads (
|
|
63
|
+
"id" TEXT PRIMARY KEY,
|
|
64
|
+
"createdAt" TEXT,
|
|
65
|
+
"name" TEXT,
|
|
66
|
+
"userId" TEXT,
|
|
67
|
+
"userIdentifier" TEXT,
|
|
68
|
+
"tags" TEXT DEFAULT '[]',
|
|
69
|
+
"meta" TEXT NOT NULL DEFAULT '{}',
|
|
70
|
+
FOREIGN KEY ("userId") REFERENCES users("id") ON DELETE CASCADE
|
|
71
|
+
);
|
|
72
|
+
'''))
|
|
73
|
+
await conn.execute(text('''
|
|
74
|
+
CREATE TABLE IF NOT EXISTS steps (
|
|
75
|
+
"id" TEXT PRIMARY KEY,
|
|
76
|
+
"name" TEXT NOT NULL,
|
|
77
|
+
"type" TEXT NOT NULL,
|
|
78
|
+
"threadId" TEXT NOT NULL,
|
|
79
|
+
"parentId" TEXT,
|
|
80
|
+
"disableFeedback" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
81
|
+
"streaming" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
82
|
+
"waitForAnswer" BOOLEAN DEFAULT FALSE,
|
|
83
|
+
"isError" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
84
|
+
"meta" TEXT DEFAULT '{}',
|
|
85
|
+
"tags" TEXT DEFAULT '[]',
|
|
86
|
+
"input" TEXT,
|
|
87
|
+
"output" TEXT,
|
|
88
|
+
"createdAt" TEXT,
|
|
89
|
+
"startTime" TEXT,
|
|
90
|
+
"endTime" TEXT,
|
|
91
|
+
"generation" TEXT,
|
|
92
|
+
"showInput" TEXT,
|
|
93
|
+
"language" TEXT,
|
|
94
|
+
"indent" INT,
|
|
95
|
+
FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE
|
|
96
|
+
);
|
|
97
|
+
'''))
|
|
98
|
+
await conn.execute(text('''
|
|
99
|
+
CREATE TABLE IF NOT EXISTS elements (
|
|
100
|
+
"id" TEXT PRIMARY KEY,
|
|
101
|
+
"threadId" TEXT,
|
|
102
|
+
"type" TEXT,
|
|
103
|
+
"url" TEXT,
|
|
104
|
+
"chainlitKey" TEXT,
|
|
105
|
+
"name" TEXT NOT NULL,
|
|
106
|
+
"display" TEXT,
|
|
107
|
+
"objectKey" TEXT,
|
|
108
|
+
"size" TEXT,
|
|
109
|
+
"page" INT,
|
|
110
|
+
"language" TEXT,
|
|
111
|
+
"forId" TEXT,
|
|
112
|
+
"mime" TEXT,
|
|
113
|
+
FOREIGN KEY ("threadId") REFERENCES threads("id") ON DELETE CASCADE
|
|
114
|
+
);
|
|
115
|
+
'''))
|
|
116
|
+
await conn.execute(text('''
|
|
117
|
+
CREATE TABLE IF NOT EXISTS feedbacks (
|
|
118
|
+
"id" TEXT PRIMARY KEY,
|
|
119
|
+
"forId" TEXT NOT NULL,
|
|
120
|
+
"value" INT NOT NULL,
|
|
121
|
+
"threadId" TEXT,
|
|
122
|
+
"comment" TEXT
|
|
123
|
+
);
|
|
124
|
+
'''))
|
|
125
|
+
await conn.execute(text('''
|
|
126
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
127
|
+
"id" SERIAL PRIMARY KEY,
|
|
128
|
+
"key" TEXT UNIQUE,
|
|
129
|
+
"value" TEXT
|
|
130
|
+
);
|
|
131
|
+
'''))
|
|
132
|
+
await engine.dispose()
|
|
133
|
+
|
|
134
|
+
def create_schema_sqlite(db_path: str):
|
|
135
|
+
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
136
|
+
conn = sqlite3.connect(db_path)
|
|
137
|
+
cursor = conn.cursor()
|
|
138
|
+
# Renamed start to startTime, end to endTime
|
|
139
|
+
cursor.execute('''
|
|
140
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
141
|
+
"id" TEXT PRIMARY KEY,
|
|
142
|
+
"identifier" TEXT NOT NULL UNIQUE,
|
|
143
|
+
"meta" TEXT NOT NULL DEFAULT '{}',
|
|
144
|
+
"createdAt" TEXT
|
|
145
|
+
)
|
|
146
|
+
''')
|
|
147
|
+
cursor.execute('''
|
|
148
|
+
CREATE TABLE IF NOT EXISTS threads (
|
|
149
|
+
"id" TEXT PRIMARY KEY,
|
|
150
|
+
"createdAt" TEXT,
|
|
151
|
+
"name" TEXT,
|
|
152
|
+
"userId" TEXT,
|
|
153
|
+
"userIdentifier" TEXT,
|
|
154
|
+
"tags" TEXT DEFAULT '[]',
|
|
155
|
+
"meta" TEXT NOT NULL DEFAULT '{}',
|
|
156
|
+
FOREIGN KEY ("userId") REFERENCES users("id") ON DELETE CASCADE
|
|
157
|
+
)
|
|
158
|
+
''')
|
|
159
|
+
cursor.execute('''
|
|
160
|
+
CREATE TABLE IF NOT EXISTS steps (
|
|
161
|
+
"id" TEXT PRIMARY KEY,
|
|
162
|
+
"name" TEXT NOT NULL,
|
|
163
|
+
"type" TEXT NOT NULL,
|
|
164
|
+
"threadId" TEXT NOT NULL,
|
|
165
|
+
"parentId" TEXT,
|
|
166
|
+
"disableFeedback" BOOLEAN NOT NULL DEFAULT 0,
|
|
167
|
+
"streaming" BOOLEAN NOT NULL DEFAULT 0,
|
|
168
|
+
"waitForAnswer" BOOLEAN DEFAULT 0,
|
|
169
|
+
"isError" BOOLEAN NOT NULL DEFAULT 0,
|
|
170
|
+
"meta" TEXT DEFAULT '{}',
|
|
171
|
+
"tags" TEXT DEFAULT '[]',
|
|
172
|
+
"input" TEXT,
|
|
173
|
+
"output" TEXT,
|
|
174
|
+
"createdAt" TEXT,
|
|
175
|
+
"startTime" TEXT,
|
|
176
|
+
"endTime" TEXT,
|
|
177
|
+
"generation" TEXT,
|
|
178
|
+
"showInput" TEXT,
|
|
179
|
+
"language" TEXT,
|
|
180
|
+
"indent" INT,
|
|
181
|
+
FOREIGN KEY ("threadId") REFERENCES threads ("id") ON DELETE CASCADE
|
|
182
|
+
)
|
|
183
|
+
''')
|
|
184
|
+
cursor.execute('''
|
|
185
|
+
CREATE TABLE IF NOT EXISTS elements (
|
|
186
|
+
"id" TEXT PRIMARY KEY,
|
|
187
|
+
"threadId" TEXT,
|
|
188
|
+
"type" TEXT,
|
|
189
|
+
"url" TEXT,
|
|
190
|
+
"chainlitKey" TEXT,
|
|
191
|
+
"name" TEXT NOT NULL,
|
|
192
|
+
"display" TEXT,
|
|
193
|
+
"objectKey" TEXT,
|
|
194
|
+
"size" TEXT,
|
|
195
|
+
"page" INT,
|
|
196
|
+
"language" TEXT,
|
|
197
|
+
"forId" TEXT,
|
|
198
|
+
"mime" TEXT,
|
|
199
|
+
FOREIGN KEY ("threadId") REFERENCES threads ("id") ON DELETE CASCADE
|
|
200
|
+
)
|
|
201
|
+
''')
|
|
202
|
+
cursor.execute('''
|
|
203
|
+
CREATE TABLE IF NOT EXISTS feedbacks (
|
|
204
|
+
"id" TEXT PRIMARY KEY,
|
|
205
|
+
"forId" TEXT NOT NULL,
|
|
206
|
+
"value" INT NOT NULL,
|
|
207
|
+
"threadId" TEXT,
|
|
208
|
+
"comment" TEXT
|
|
209
|
+
)
|
|
210
|
+
''')
|
|
211
|
+
cursor.execute('''
|
|
212
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
213
|
+
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
214
|
+
"key" TEXT UNIQUE,
|
|
215
|
+
"value" TEXT
|
|
216
|
+
)
|
|
217
|
+
''')
|
|
218
|
+
conn.commit()
|
|
219
|
+
conn.close()
|
|
220
|
+
|
|
221
|
+
if DATABASE_URL:
|
|
222
|
+
asyncio.run(create_schema_async())
|
|
223
|
+
conninfo = DATABASE_URL
|
|
224
|
+
else:
|
|
225
|
+
DB_PATH = os.path.expanduser("~/.praison/database.sqlite")
|
|
226
|
+
create_schema_sqlite(DB_PATH)
|
|
227
|
+
conninfo = f"sqlite+aiosqlite:///{DB_PATH}"
|
|
228
|
+
|
|
229
|
+
def save_setting(key: str, value: str):
|
|
230
|
+
if DATABASE_URL:
|
|
231
|
+
async def save_setting_async():
|
|
232
|
+
engine = create_async_engine(DATABASE_URL, echo=False)
|
|
233
|
+
async with engine.begin() as conn:
|
|
234
|
+
await conn.execute(text("""
|
|
235
|
+
INSERT INTO settings ("key", "value") VALUES (:key, :value)
|
|
236
|
+
ON CONFLICT ("key") DO UPDATE SET "value" = EXCLUDED."value"
|
|
237
|
+
"""), {"key": key, "value": value})
|
|
238
|
+
await engine.dispose()
|
|
239
|
+
asyncio.run(save_setting_async())
|
|
240
|
+
else:
|
|
241
|
+
conn = sqlite3.connect(DB_PATH)
|
|
242
|
+
cursor = conn.cursor()
|
|
243
|
+
cursor.execute(
|
|
244
|
+
"""
|
|
245
|
+
INSERT OR REPLACE INTO settings (id, "key", "value")
|
|
246
|
+
VALUES ((SELECT id FROM settings WHERE "key" = ?), ?, ?)
|
|
247
|
+
""",
|
|
248
|
+
(key, key, value),
|
|
249
|
+
)
|
|
250
|
+
conn.commit()
|
|
251
|
+
conn.close()
|
|
252
|
+
|
|
253
|
+
def load_setting(key: str) -> str:
|
|
254
|
+
if DATABASE_URL:
|
|
255
|
+
async def load_setting_async():
|
|
256
|
+
engine = create_async_engine(DATABASE_URL, echo=False)
|
|
257
|
+
async with engine.connect() as conn:
|
|
258
|
+
result = await conn.execute(text('SELECT "value" FROM settings WHERE "key" = :key'), {"key": key})
|
|
259
|
+
row = result.fetchone()
|
|
260
|
+
await engine.dispose()
|
|
261
|
+
return row[0] if row else None
|
|
262
|
+
return asyncio.run(load_setting_async())
|
|
263
|
+
else:
|
|
264
|
+
conn = sqlite3.connect(DB_PATH)
|
|
265
|
+
cursor = conn.cursor()
|
|
266
|
+
cursor.execute('SELECT "value" FROM settings WHERE "key" = ?', (key,))
|
|
267
|
+
result = cursor.fetchone()
|
|
268
|
+
conn.close()
|
|
269
|
+
return result[0] if result else None
|
|
270
|
+
|
|
271
|
+
cl_data._data_layer = SQLAlchemyDataLayer(conninfo=conninfo)
|
|
272
|
+
|
|
273
|
+
tavily_api_key = os.getenv("TAVILY_API_KEY")
|
|
274
|
+
tavily_client = TavilyClient(api_key=tavily_api_key) if tavily_api_key else None
|
|
275
|
+
|
|
276
|
+
async def tavily_web_search(query):
|
|
277
|
+
if not tavily_client:
|
|
278
|
+
return json.dumps({
|
|
279
|
+
"query": query,
|
|
280
|
+
"error": "Tavily API key is not set. Web search is unavailable."
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
response = tavily_client.search(query)
|
|
284
|
+
logger.debug(f"Tavily search response: {response}")
|
|
285
|
+
|
|
286
|
+
async with AsyncWebCrawler() as crawler:
|
|
287
|
+
results = []
|
|
288
|
+
for result in response.get('results', []):
|
|
289
|
+
url = result.get('url')
|
|
290
|
+
if url:
|
|
291
|
+
try:
|
|
292
|
+
crawl_result = await crawler.arun(url=url)
|
|
293
|
+
results.append({
|
|
294
|
+
"content": result.get('content'),
|
|
295
|
+
"url": url,
|
|
296
|
+
"full_content": crawl_result.markdown
|
|
297
|
+
})
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Error crawling {url}: {str(e)}")
|
|
300
|
+
results.append({
|
|
301
|
+
"content": result.get('content'),
|
|
302
|
+
"url": url,
|
|
303
|
+
"full_content": "Error: Unable to crawl this URL"
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
return json.dumps({
|
|
307
|
+
"query": query,
|
|
308
|
+
"results": results
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
tools = [{
|
|
312
|
+
"type": "function",
|
|
313
|
+
"function": {
|
|
314
|
+
"name": "tavily_web_search",
|
|
315
|
+
"description": "Search the web using Tavily API and crawl the resulting URLs",
|
|
316
|
+
"parameters": {
|
|
317
|
+
"type": "object",
|
|
318
|
+
"properties": {
|
|
319
|
+
"query": {"type": "string", "description": "Search query"}
|
|
320
|
+
},
|
|
321
|
+
"required": ["query"]
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}] if tavily_api_key else []
|
|
325
|
+
|
|
326
|
+
# Authentication configuration
|
|
327
|
+
AUTH_PASSWORD_ENABLED = os.getenv("AUTH_PASSWORD_ENABLED", "true").lower() == "true" # Password authentication enabled by default
|
|
328
|
+
AUTH_OAUTH_ENABLED = os.getenv("AUTH_OAUTH_ENABLED", "false").lower() == "true" # OAuth authentication disabled by default
|
|
329
|
+
|
|
330
|
+
username = os.getenv("CHAINLIT_USERNAME", "admin")
|
|
331
|
+
password = os.getenv("CHAINLIT_PASSWORD", "admin")
|
|
332
|
+
|
|
333
|
+
def auth_callback(u: str, p: str):
|
|
334
|
+
if (u, p) == (username, password):
|
|
335
|
+
return cl.User(identifier=username, metadata={"role": "ADMIN", "provider": "credentials"})
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
def oauth_callback(
|
|
339
|
+
provider_id: str,
|
|
340
|
+
token: str,
|
|
341
|
+
raw_user_data: Dict[str, str],
|
|
342
|
+
default_user: cl.User,
|
|
343
|
+
) -> Optional[cl.User]:
|
|
344
|
+
return default_user
|
|
345
|
+
|
|
346
|
+
if AUTH_PASSWORD_ENABLED:
|
|
347
|
+
auth_callback = cl.password_auth_callback(auth_callback)
|
|
348
|
+
|
|
349
|
+
if AUTH_OAUTH_ENABLED:
|
|
350
|
+
oauth_callback = cl.oauth_callback(oauth_callback)
|
|
351
|
+
|
|
352
|
+
async def send_count():
|
|
353
|
+
await cl.Message(
|
|
354
|
+
f"Create step counter: {create_step_counter}", disable_feedback=True
|
|
355
|
+
).send()
|
|
356
|
+
|
|
357
|
+
@cl.on_chat_start
|
|
358
|
+
async def start():
|
|
359
|
+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-4o-mini")
|
|
360
|
+
cl.user_session.set("model_name", model_name)
|
|
361
|
+
logger.debug(f"Model name: {model_name}")
|
|
362
|
+
settings = cl.ChatSettings(
|
|
363
|
+
[
|
|
364
|
+
TextInput(
|
|
365
|
+
id="model_name",
|
|
366
|
+
label="Enter the Model Name",
|
|
367
|
+
placeholder="e.g., gpt-4o-mini",
|
|
368
|
+
initial=model_name
|
|
369
|
+
)
|
|
370
|
+
]
|
|
371
|
+
)
|
|
372
|
+
cl.user_session.set("settings", settings)
|
|
373
|
+
await settings.send()
|
|
374
|
+
|
|
375
|
+
@cl.on_settings_update
|
|
376
|
+
async def setup_agent(settings):
|
|
377
|
+
logger.debug(settings)
|
|
378
|
+
cl.user_session.set("settings", settings)
|
|
379
|
+
model_name = settings["model_name"]
|
|
380
|
+
cl.user_session.set("model_name", model_name)
|
|
381
|
+
|
|
382
|
+
save_setting("model_name", model_name)
|
|
383
|
+
|
|
384
|
+
thread_id = cl.user_session.get("thread_id")
|
|
385
|
+
if thread_id:
|
|
386
|
+
thread = await cl_data.get_thread(thread_id)
|
|
387
|
+
if thread:
|
|
388
|
+
metadata = thread.get("metadata", {})
|
|
389
|
+
if isinstance(metadata, str):
|
|
390
|
+
try:
|
|
391
|
+
metadata = json.loads(metadata)
|
|
392
|
+
except:
|
|
393
|
+
metadata = {}
|
|
394
|
+
metadata["model_name"] = model_name
|
|
395
|
+
await cl_data.update_thread(thread_id, metadata=metadata)
|
|
396
|
+
cl.user_session.set("metadata", metadata)
|
|
397
|
+
|
|
398
|
+
@cl.on_message
|
|
399
|
+
async def main(message: cl.Message):
|
|
400
|
+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-4o-mini")
|
|
401
|
+
message_history = cl.user_session.get("message_history", [])
|
|
402
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
403
|
+
|
|
404
|
+
image = None
|
|
405
|
+
if message.elements and isinstance(message.elements[0], cl.Image):
|
|
406
|
+
image_element = message.elements[0]
|
|
407
|
+
try:
|
|
408
|
+
image = Image.open(image_element.path)
|
|
409
|
+
image.load()
|
|
410
|
+
cl.user_session.set("image", image)
|
|
411
|
+
except Exception as e:
|
|
412
|
+
logger.error(f"Error processing image: {str(e)}")
|
|
413
|
+
await cl.Message(content="Error processing the image. Please try again.").send()
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
user_message = f"""
|
|
417
|
+
Answer the question and use tools if needed:
|
|
418
|
+
|
|
419
|
+
Current Date and Time: {now}
|
|
420
|
+
|
|
421
|
+
User Question: {message.content}
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
if image:
|
|
425
|
+
user_message = f"Image uploaded. {user_message}"
|
|
426
|
+
|
|
427
|
+
message_history.append({"role": "user", "content": user_message})
|
|
428
|
+
msg = cl.Message(content="")
|
|
429
|
+
await msg.send()
|
|
430
|
+
|
|
431
|
+
completion_params = {
|
|
432
|
+
"model": model_name,
|
|
433
|
+
"messages": message_history,
|
|
434
|
+
"stream": True,
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if image:
|
|
438
|
+
buffered = io.BytesIO()
|
|
439
|
+
image.save(buffered, format="PNG")
|
|
440
|
+
img_str = base64.b64encode(buffered.getvalue()).decode()
|
|
441
|
+
completion_params["messages"][-1] = {
|
|
442
|
+
"role": "user",
|
|
443
|
+
"content": [
|
|
444
|
+
{"type": "text", "text": user_message},
|
|
445
|
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_str}"}}
|
|
446
|
+
]
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if tavily_api_key:
|
|
450
|
+
completion_params["tools"] = tools
|
|
451
|
+
completion_params["tool_choice"] = "auto"
|
|
452
|
+
|
|
453
|
+
response = await acompletion(**completion_params)
|
|
454
|
+
|
|
455
|
+
full_response = ""
|
|
456
|
+
tool_calls = []
|
|
457
|
+
current_tool_call = None
|
|
458
|
+
|
|
459
|
+
async for part in response:
|
|
460
|
+
if 'choices' in part and len(part['choices']) > 0:
|
|
461
|
+
delta = part['choices'][0].get('delta', {})
|
|
462
|
+
|
|
463
|
+
if 'content' in delta and delta['content'] is not None:
|
|
464
|
+
token = delta['content']
|
|
465
|
+
await msg.stream_token(token)
|
|
466
|
+
full_response += token
|
|
467
|
+
|
|
468
|
+
if tavily_api_key and 'tool_calls' in delta and delta['tool_calls'] is not None:
|
|
469
|
+
for tool_call in delta['tool_calls']:
|
|
470
|
+
if current_tool_call is None or tool_call.index != current_tool_call['index']:
|
|
471
|
+
if current_tool_call:
|
|
472
|
+
tool_calls.append(current_tool_call)
|
|
473
|
+
current_tool_call = {
|
|
474
|
+
'id': tool_call.id,
|
|
475
|
+
'type': tool_call.type,
|
|
476
|
+
'index': tool_call.index,
|
|
477
|
+
'function': {
|
|
478
|
+
'name': tool_call.function.name if tool_call.function else None,
|
|
479
|
+
'arguments': ''
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if tool_call.function:
|
|
483
|
+
if tool_call.function.name:
|
|
484
|
+
current_tool_call['function']['name'] = tool_call.function.name
|
|
485
|
+
if tool_call.function.arguments:
|
|
486
|
+
current_tool_call['function']['arguments'] += tool_call.function.arguments
|
|
487
|
+
|
|
488
|
+
if current_tool_call:
|
|
489
|
+
tool_calls.append(current_tool_call)
|
|
490
|
+
|
|
491
|
+
logger.debug(f"Full response: {full_response}")
|
|
492
|
+
logger.debug(f"Tool calls: {tool_calls}")
|
|
493
|
+
message_history.append({"role": "assistant", "content": full_response})
|
|
494
|
+
logger.debug(f"Message history: {message_history}")
|
|
495
|
+
cl.user_session.set("message_history", message_history)
|
|
496
|
+
await msg.update()
|
|
497
|
+
|
|
498
|
+
if tavily_api_key and tool_calls:
|
|
499
|
+
available_functions = {
|
|
500
|
+
"tavily_web_search": tavily_web_search,
|
|
501
|
+
}
|
|
502
|
+
messages = message_history + [{"role": "assistant", "content": None, "function_call": {
|
|
503
|
+
"name": tool_calls[0]['function']['name'],
|
|
504
|
+
"arguments": tool_calls[0]['function']['arguments']
|
|
505
|
+
}}]
|
|
506
|
+
|
|
507
|
+
for tool_call in tool_calls:
|
|
508
|
+
function_name = tool_call['function']['name']
|
|
509
|
+
if function_name in available_functions:
|
|
510
|
+
function_to_call = available_functions[function_name]
|
|
511
|
+
function_args = tool_call['function']['arguments']
|
|
512
|
+
if function_args:
|
|
513
|
+
try:
|
|
514
|
+
function_args = json.loads(function_args)
|
|
515
|
+
function_response = await function_to_call(
|
|
516
|
+
query=function_args.get("query"),
|
|
517
|
+
)
|
|
518
|
+
messages.append(
|
|
519
|
+
{
|
|
520
|
+
"role": "function",
|
|
521
|
+
"name": function_name,
|
|
522
|
+
"content": function_response,
|
|
523
|
+
}
|
|
524
|
+
)
|
|
525
|
+
except json.JSONDecodeError:
|
|
526
|
+
logger.error(f"Failed to parse function arguments: {function_args}")
|
|
527
|
+
|
|
528
|
+
second_response = await acompletion(
|
|
529
|
+
model=model_name,
|
|
530
|
+
stream=True,
|
|
531
|
+
messages=messages,
|
|
532
|
+
)
|
|
533
|
+
logger.debug(f"Second LLM response: {second_response}")
|
|
534
|
+
|
|
535
|
+
full_response = ""
|
|
536
|
+
async for part in second_response:
|
|
537
|
+
if 'choices' in part and len(part['choices']) > 0:
|
|
538
|
+
delta = part['choices'][0].get('delta', {})
|
|
539
|
+
if 'content' in delta and delta['content'] is not None:
|
|
540
|
+
token = delta['content']
|
|
541
|
+
await msg.stream_token(token)
|
|
542
|
+
full_response += token
|
|
543
|
+
|
|
544
|
+
msg.content = full_response
|
|
545
|
+
await msg.update()
|
|
546
|
+
else:
|
|
547
|
+
msg.content = full_response
|
|
548
|
+
await msg.update()
|
|
549
|
+
|
|
550
|
+
@cl.on_chat_resume
|
|
551
|
+
async def on_chat_resume(thread: ThreadDict):
|
|
552
|
+
logger.info(f"Resuming chat: {thread['id']}")
|
|
553
|
+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME", "gpt-4o-mini")
|
|
554
|
+
logger.debug(f"Model name: {model_name}")
|
|
555
|
+
settings = cl.ChatSettings(
|
|
556
|
+
[
|
|
557
|
+
TextInput(
|
|
558
|
+
id="model_name",
|
|
559
|
+
label="Enter the Model Name",
|
|
560
|
+
placeholder="e.g., gpt-4o-mini",
|
|
561
|
+
initial=model_name
|
|
562
|
+
)
|
|
563
|
+
]
|
|
564
|
+
)
|
|
565
|
+
await settings.send()
|
|
566
|
+
thread_id = thread["id"]
|
|
567
|
+
cl.user_session.set("thread_id", thread_id)
|
|
568
|
+
|
|
569
|
+
metadata = thread.get("metadata", {})
|
|
570
|
+
if isinstance(metadata, str):
|
|
571
|
+
try:
|
|
572
|
+
metadata = json.loads(metadata)
|
|
573
|
+
except json.JSONDecodeError:
|
|
574
|
+
metadata = {}
|
|
575
|
+
cl.user_session.set("metadata", metadata)
|
|
576
|
+
|
|
577
|
+
message_history = cl.user_session.get("message_history", [])
|
|
578
|
+
steps = thread["steps"]
|
|
579
|
+
|
|
580
|
+
for m in steps:
|
|
581
|
+
msg_type = m.get("type")
|
|
582
|
+
if msg_type == "user_message":
|
|
583
|
+
message_history.append({"role": "user", "content": m.get("output", "")})
|
|
584
|
+
elif msg_type == "assistant_message":
|
|
585
|
+
message_history.append({"role": "assistant", "content": m.get("output", "")})
|
|
586
|
+
elif msg_type == "run":
|
|
587
|
+
if m.get("isError"):
|
|
588
|
+
message_history.append({"role": "system", "content": f"Error: {m.get('output', '')}"})
|
|
589
|
+
else:
|
|
590
|
+
logger.warning(f"Message without recognized type: {m}")
|
|
591
|
+
|
|
592
|
+
cl.user_session.set("message_history", message_history)
|
|
593
|
+
|
|
594
|
+
image_data = metadata.get("image")
|
|
595
|
+
if image_data:
|
|
596
|
+
image = Image.open(io.BytesIO(base64.b64decode(image_data)))
|
|
597
|
+
cl.user_session.set("image", image)
|
|
598
|
+
await cl.Message(content="Previous image loaded.").send()
|