PraisonAI 1.0.1__tar.gz → 1.0.3__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.

Files changed (53) hide show
  1. {praisonai-1.0.1 → praisonai-1.0.3}/PKG-INFO +6 -3
  2. {praisonai-1.0.1 → praisonai-1.0.3}/README.md +2 -0
  3. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/cli.py +9 -4
  4. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/deploy.py +1 -1
  5. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/chat.py +266 -131
  6. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/code.py +2 -5
  7. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/sql_alchemy.py +28 -26
  8. {praisonai-1.0.1 → praisonai-1.0.3}/pyproject.toml +8 -3
  9. {praisonai-1.0.1 → praisonai-1.0.3}/LICENSE +0 -0
  10. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/__init__.py +0 -0
  11. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/__main__.py +0 -0
  12. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/agents_generator.py +0 -0
  13. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/api/call.py +0 -0
  14. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/auto.py +0 -0
  15. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/chainlit_ui.py +0 -0
  16. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/inbuilt_tools/__init__.py +0 -0
  17. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
  18. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/inc/__init__.py +0 -0
  19. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/inc/config.py +0 -0
  20. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/inc/models.py +0 -0
  21. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/android-chrome-192x192.png +0 -0
  22. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/android-chrome-512x512.png +0 -0
  23. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/apple-touch-icon.png +0 -0
  24. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/fantasy.svg +0 -0
  25. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/favicon-16x16.png +0 -0
  26. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/favicon-32x32.png +0 -0
  27. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/favicon.ico +0 -0
  28. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/game.svg +0 -0
  29. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/logo_dark.png +0 -0
  30. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/logo_light.png +0 -0
  31. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/movie.svg +0 -0
  32. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/public/thriller.svg +0 -0
  33. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/setup/__init__.py +0 -0
  34. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/setup/build.py +0 -0
  35. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/setup/config.yaml +0 -0
  36. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/setup/post_install.py +0 -0
  37. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/setup/setup_conda_env.py +0 -0
  38. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/setup/setup_conda_env.sh +0 -0
  39. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/setup.py +0 -0
  40. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/test.py +0 -0
  41. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/train.py +0 -0
  42. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/context.py +0 -0
  43. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/public/fantasy.svg +0 -0
  44. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/public/game.svg +0 -0
  45. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/public/logo_dark.png +0 -0
  46. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/public/logo_light.png +0 -0
  47. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/public/movie.svg +0 -0
  48. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/public/thriller.svg +0 -0
  49. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/realtime.py +0 -0
  50. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/realtimeclient/__init__.py +0 -0
  51. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/realtimeclient/realtimedocs.txt +0 -0
  52. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/ui/realtimeclient/tools.py +0 -0
  53. {praisonai-1.0.1 → praisonai-1.0.3}/praisonai/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PraisonAI
3
- Version: 1.0.1
3
+ Version: 1.0.3
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
@@ -34,7 +34,7 @@ Requires-Dist: flaml[automl] (>=2.3.1) ; extra == "call"
34
34
  Requires-Dist: flask (>=3.0.0) ; extra == "api"
35
35
  Requires-Dist: gradio (>=4.26.0) ; extra == "gradio"
36
36
  Requires-Dist: greenlet (>=3.0.3) ; extra == "chat" or extra == "code" or extra == "realtime"
37
- Requires-Dist: instructor (>=0.4.8) ; extra == "call" or extra == "crewai" or extra == "autogen"
37
+ Requires-Dist: instructor (>=0.4.8) ; extra == "chat" or extra == "call" or extra == "crewai" or extra == "autogen"
38
38
  Requires-Dist: langchain-anthropic (>=0.1.13) ; extra == "anthropic"
39
39
  Requires-Dist: langchain-cohere (>=0.1.4) ; extra == "cohere"
40
40
  Requires-Dist: langchain-google-genai (>=1.0.4) ; extra == "google"
@@ -46,10 +46,11 @@ Requires-Dist: playwright (>=1.47.0) ; extra == "chat" or extra == "code"
46
46
  Requires-Dist: plotly (>=5.24.0) ; extra == "realtime"
47
47
  Requires-Dist: praisonai-tools (>=0.0.7) ; extra == "crewai" or extra == "autogen"
48
48
  Requires-Dist: pyautogen (>=0.2.19) ; extra == "autogen"
49
+ Requires-Dist: pydantic (<=2.10.1) ; extra == "chat" or extra == "code"
49
50
  Requires-Dist: pyngrok (>=1.4.0) ; extra == "call"
50
51
  Requires-Dist: pyparsing (>=3.0.0)
51
52
  Requires-Dist: python-dotenv (>=0.19.0) ; extra == "call"
52
- Requires-Dist: rich (>=13.7) ; extra == "call"
53
+ Requires-Dist: rich (>=13.7) ; extra == "chat" or extra == "call"
53
54
  Requires-Dist: sqlalchemy (>=2.0.36) ; extra == "chat" or extra == "code" or extra == "realtime"
54
55
  Requires-Dist: tavily-python (==0.5.0) ; extra == "chat" or extra == "code" or extra == "realtime"
55
56
  Requires-Dist: twilio (>=7.0.0) ; extra == "call"
@@ -78,6 +79,8 @@ Description-Content-Type: text/markdown
78
79
 
79
80
  # Praison AI
80
81
 
82
+ <a href="https://trendshift.io/repositories/9130" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9130" alt="MervinPraison%2FPraisonAI | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
83
+
81
84
  </div>
82
85
 
83
86
  Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, represents a low-code, centralised framework designed to simplify the creation and orchestration of multi-agent systems for various LLM applications, emphasizing ease of use, customization, and human-agent interaction.
@@ -16,6 +16,8 @@
16
16
 
17
17
  # Praison AI
18
18
 
19
+ <a href="https://trendshift.io/repositories/9130" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9130" alt="MervinPraison%2FPraisonAI | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
20
+
19
21
  </div>
20
22
 
21
23
  Praison AI, leveraging both AutoGen and CrewAI or any other agent framework, represents a low-code, centralised framework designed to simplify the creation and orchestration of multi-agent systems for various LLM applications, emphasizing ease of use, customization, and human-agent interaction.
@@ -26,7 +26,9 @@ CREWAI_AVAILABLE = False
26
26
  AUTOGEN_AVAILABLE = False
27
27
 
28
28
  try:
29
- os.environ["CHAINLIT_APP_ROOT"] = os.path.join(os.path.expanduser("~"), ".praison")
29
+ # Set CHAINLIT_APP_ROOT only if it doesn't exist
30
+ if "CHAINLIT_APP_ROOT" not in os.environ:
31
+ os.environ["CHAINLIT_APP_ROOT"] = os.path.join(os.path.expanduser("~"), ".praison")
30
32
  from chainlit.cli import chainlit_run
31
33
  CHAINLIT_AVAILABLE = True
32
34
  except ImportError:
@@ -389,7 +391,8 @@ class PraisonAI:
389
391
  import praisonai
390
392
  os.environ["CHAINLIT_PORT"] = "8084"
391
393
  root_path = os.path.join(os.path.expanduser("~"), ".praison")
392
- os.environ["CHAINLIT_APP_ROOT"] = root_path
394
+ if "CHAINLIT_APP_ROOT" not in os.environ:
395
+ os.environ["CHAINLIT_APP_ROOT"] = root_path
393
396
  public_folder = os.path.join(os.path.dirname(praisonai.__file__), 'public')
394
397
  if not os.path.exists(os.path.join(root_path, "public")):
395
398
  if os.path.exists(public_folder):
@@ -412,7 +415,8 @@ class PraisonAI:
412
415
  import praisonai
413
416
  os.environ["CHAINLIT_PORT"] = "8086"
414
417
  root_path = os.path.join(os.path.expanduser("~"), ".praison")
415
- os.environ["CHAINLIT_APP_ROOT"] = root_path
418
+ if "CHAINLIT_APP_ROOT" not in os.environ:
419
+ os.environ["CHAINLIT_APP_ROOT"] = root_path
416
420
  public_folder = os.path.join(os.path.dirname(__file__), 'public')
417
421
  if not os.path.exists(os.path.join(root_path, "public")):
418
422
  if os.path.exists(public_folder):
@@ -481,7 +485,8 @@ class PraisonAI:
481
485
  import praisonai
482
486
  os.environ["CHAINLIT_PORT"] = "8088"
483
487
  root_path = os.path.join(os.path.expanduser("~"), ".praison")
484
- os.environ["CHAINLIT_APP_ROOT"] = root_path
488
+ if "CHAINLIT_APP_ROOT" not in os.environ:
489
+ os.environ["CHAINLIT_APP_ROOT"] = root_path
485
490
  public_folder = os.path.join(os.path.dirname(praisonai.__file__), 'public')
486
491
  if not os.path.exists(os.path.join(root_path, "public")):
487
492
  if os.path.exists(public_folder):
@@ -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.1 gunicorn markdown\n")
59
+ file.write("RUN pip install flask praisonai==1.0.3 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
 
@@ -1,6 +1,6 @@
1
1
  import chainlit as cl
2
2
  from chainlit.input_widget import TextInput
3
- from chainlit.types import ThreadDict # Change this import
3
+ from chainlit.types import ThreadDict
4
4
  from litellm import acompletion
5
5
  import os
6
6
  import sqlite3
@@ -9,7 +9,6 @@ from typing import Dict, List, Optional
9
9
  from dotenv import load_dotenv
10
10
  load_dotenv()
11
11
  import chainlit.data as cl_data
12
- from chainlit.step import StepDict
13
12
  from literalai.helper import utc_now
14
13
  import logging
15
14
  import json
@@ -20,6 +19,8 @@ import asyncio
20
19
  from PIL import Image
21
20
  import io
22
21
  import base64
22
+ from sqlalchemy import text
23
+ from sqlalchemy.exc import DatabaseError
23
24
 
24
25
  # Set up logging
25
26
  logger = logging.getLogger(__name__)
@@ -48,134 +49,225 @@ create_step_counter = 0
48
49
 
49
50
  DB_PATH = os.path.expanduser("~/.praison/database.sqlite")
50
51
 
51
- def initialize_db():
52
- os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
53
- conn = sqlite3.connect(DB_PATH)
54
- cursor = conn.cursor()
55
- cursor.execute('''
56
- CREATE TABLE IF NOT EXISTS users (
57
- id UUID PRIMARY KEY,
58
- identifier TEXT NOT NULL UNIQUE,
59
- metadata JSONB NOT NULL,
60
- createdAt TEXT
61
- )
62
- ''')
63
- cursor.execute('''
64
- CREATE TABLE IF NOT EXISTS threads (
65
- id UUID PRIMARY KEY,
66
- createdAt TEXT,
67
- name TEXT,
68
- userId UUID,
69
- userIdentifier TEXT,
70
- tags TEXT[],
71
- metadata JSONB NOT NULL DEFAULT '{}',
72
- FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
73
- )
74
- ''')
75
- cursor.execute('''
76
- CREATE TABLE IF NOT EXISTS steps (
77
- id UUID PRIMARY KEY,
78
- name TEXT NOT NULL,
79
- type TEXT NOT NULL,
80
- threadId UUID NOT NULL,
81
- parentId UUID,
82
- disableFeedback BOOLEAN NOT NULL DEFAULT 0,
83
- streaming BOOLEAN NOT NULL DEFAULT 0,
84
- waitForAnswer BOOLEAN DEFAULT 0,
85
- isError BOOLEAN NOT NULL DEFAULT 0,
86
- metadata JSONB DEFAULT '{}',
87
- tags TEXT[],
88
- input TEXT,
89
- output TEXT,
90
- createdAt TEXT,
91
- start TEXT,
92
- end TEXT,
93
- generation JSONB,
94
- showInput TEXT,
95
- language TEXT,
96
- indent INT,
97
- FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
98
- )
99
- ''')
100
- cursor.execute('''
101
- CREATE TABLE IF NOT EXISTS elements (
102
- id UUID PRIMARY KEY,
103
- threadId UUID,
104
- type TEXT,
105
- url TEXT,
106
- chainlitKey TEXT,
107
- name TEXT NOT NULL,
108
- display TEXT,
109
- objectKey TEXT,
110
- size TEXT,
111
- page INT,
112
- language TEXT,
113
- forId UUID,
114
- mime TEXT,
115
- FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
116
- )
117
- ''')
118
- cursor.execute('''
119
- CREATE TABLE IF NOT EXISTS feedbacks (
120
- id UUID PRIMARY KEY,
121
- forId UUID NOT NULL,
122
- value INT NOT NULL,
123
- threadId UUID,
124
- comment TEXT
125
- )
126
- ''')
127
- cursor.execute('''
128
- CREATE TABLE IF NOT EXISTS settings (
129
- id INTEGER PRIMARY KEY AUTOINCREMENT,
130
- key TEXT UNIQUE,
131
- value TEXT
132
- )
133
- ''')
134
- conn.commit()
135
- conn.close()
136
-
137
- def save_setting(key: str, value: str):
138
- """Saves a setting to the database.
139
-
140
- Args:
141
- key: The setting key.
142
- value: The setting value.
143
- """
144
- conn = sqlite3.connect(DB_PATH)
145
- cursor = conn.cursor()
146
- cursor.execute(
147
- """
148
- INSERT OR REPLACE INTO settings (id, key, value)
149
- VALUES ((SELECT id FROM settings WHERE key = ?), ?, ?)
150
- """,
151
- (key, key, value),
152
- )
153
- conn.commit()
154
- conn.close()
155
-
156
- def load_setting(key: str) -> str:
157
- """Loads a setting from the database.
158
-
159
- Args:
160
- key: The setting key.
161
-
162
- Returns:
163
- The setting value, or None if the key is not found.
164
- """
165
- conn = sqlite3.connect(DB_PATH)
166
- cursor = conn.cursor()
167
- cursor.execute('SELECT value FROM settings WHERE key = ?', (key,))
168
- result = cursor.fetchone()
169
- conn.close()
170
- return result[0] if result else None
171
-
52
+ async def create_tables(engine):
53
+ """Create all necessary tables if they don't exist."""
54
+ try:
55
+ async with engine.begin() as conn:
56
+ # Check if we're using PostgreSQL
57
+ dialect = engine.dialect.name
58
+ if dialect == 'postgresql':
59
+ # Create tables with PostgreSQL-specific types
60
+ await conn.execute(text("""
61
+ CREATE TABLE IF NOT EXISTS users (
62
+ id UUID PRIMARY KEY,
63
+ identifier TEXT NOT NULL UNIQUE,
64
+ metadata JSONB NOT NULL,
65
+ createdAt TEXT
66
+ )
67
+ """))
68
+
69
+ await conn.execute(text("""
70
+ CREATE TABLE IF NOT EXISTS threads (
71
+ id UUID PRIMARY KEY,
72
+ createdAt TEXT,
73
+ name TEXT,
74
+ userId UUID,
75
+ userIdentifier TEXT,
76
+ tags TEXT[],
77
+ metadata JSONB NOT NULL DEFAULT '{}',
78
+ FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
79
+ )
80
+ """))
81
+
82
+ await conn.execute(text("""
83
+ CREATE TABLE IF NOT EXISTS steps (
84
+ id UUID PRIMARY KEY,
85
+ name TEXT NOT NULL,
86
+ type TEXT NOT NULL,
87
+ threadId UUID NOT NULL,
88
+ parentId UUID,
89
+ disableFeedback BOOLEAN NOT NULL DEFAULT 0,
90
+ streaming BOOLEAN NOT NULL DEFAULT 0,
91
+ waitForAnswer BOOLEAN DEFAULT 0,
92
+ isError BOOLEAN NOT NULL DEFAULT 0,
93
+ metadata JSONB DEFAULT '{}',
94
+ tags TEXT[],
95
+ input TEXT,
96
+ output TEXT,
97
+ createdAt TEXT,
98
+ start TEXT,
99
+ end TEXT,
100
+ generation JSONB,
101
+ showInput TEXT,
102
+ language TEXT,
103
+ indent INT,
104
+ FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
105
+ )
106
+ """))
107
+
108
+ await conn.execute(text("""
109
+ CREATE TABLE IF NOT EXISTS elements (
110
+ id UUID PRIMARY KEY,
111
+ threadId UUID,
112
+ type TEXT,
113
+ url TEXT,
114
+ chainlitKey TEXT,
115
+ name TEXT NOT NULL,
116
+ display TEXT,
117
+ objectKey TEXT,
118
+ size TEXT,
119
+ page INT,
120
+ language TEXT,
121
+ forId UUID,
122
+ mime TEXT,
123
+ FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
124
+ )
125
+ """))
126
+
127
+ await conn.execute(text("""
128
+ CREATE TABLE IF NOT EXISTS feedbacks (
129
+ id UUID PRIMARY KEY,
130
+ forId UUID NOT NULL,
131
+ value INT NOT NULL,
132
+ threadId UUID,
133
+ comment TEXT
134
+ )
135
+ """))
136
+
137
+ await conn.execute(text("""
138
+ CREATE TABLE IF NOT EXISTS settings (
139
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
140
+ key TEXT UNIQUE,
141
+ value TEXT
142
+ )
143
+ """))
144
+
145
+ # Create indexes
146
+ await conn.execute(text(
147
+ "CREATE INDEX IF NOT EXISTS idx_steps_threadId ON steps(threadId)"
148
+ ))
149
+ await conn.execute(text(
150
+ "CREATE INDEX IF NOT EXISTS idx_threads_createdAt ON threads(createdAt DESC)"
151
+ ))
152
+ else:
153
+ # SQLite tables (existing schema)
154
+ await conn.execute(text("""
155
+ CREATE TABLE IF NOT EXISTS users (
156
+ id TEXT PRIMARY KEY,
157
+ identifier TEXT NOT NULL UNIQUE,
158
+ metadata TEXT NOT NULL,
159
+ createdAt TEXT
160
+ )
161
+ """))
162
+
163
+ await conn.execute(text("""
164
+ CREATE TABLE IF NOT EXISTS threads (
165
+ id TEXT PRIMARY KEY,
166
+ createdAt TEXT,
167
+ name TEXT,
168
+ userId TEXT,
169
+ userIdentifier TEXT,
170
+ tags TEXT,
171
+ metadata TEXT NOT NULL DEFAULT '{}',
172
+ FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
173
+ )
174
+ """))
175
+
176
+ await conn.execute(text("""
177
+ CREATE TABLE IF NOT EXISTS steps (
178
+ id TEXT PRIMARY KEY,
179
+ name TEXT NOT NULL,
180
+ type TEXT NOT NULL,
181
+ threadId TEXT NOT NULL,
182
+ parentId TEXT,
183
+ disableFeedback BOOLEAN NOT NULL DEFAULT 0,
184
+ streaming BOOLEAN NOT NULL DEFAULT 0,
185
+ waitForAnswer BOOLEAN DEFAULT 0,
186
+ isError BOOLEAN NOT NULL DEFAULT 0,
187
+ metadata TEXT DEFAULT '{}',
188
+ tags TEXT,
189
+ input TEXT,
190
+ output TEXT,
191
+ createdAt TEXT,
192
+ start TEXT,
193
+ end TEXT,
194
+ generation TEXT,
195
+ showInput TEXT,
196
+ language TEXT,
197
+ indent INT,
198
+ FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
199
+ )
200
+ """))
201
+
202
+ await conn.execute(text("""
203
+ CREATE TABLE IF NOT EXISTS elements (
204
+ id TEXT PRIMARY KEY,
205
+ threadId TEXT,
206
+ type TEXT,
207
+ url TEXT,
208
+ chainlitKey TEXT,
209
+ name TEXT NOT NULL,
210
+ display TEXT,
211
+ objectKey TEXT,
212
+ size TEXT,
213
+ page INT,
214
+ language TEXT,
215
+ forId TEXT,
216
+ mime TEXT,
217
+ FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
218
+ )
219
+ """))
220
+
221
+ await conn.execute(text("""
222
+ CREATE TABLE IF NOT EXISTS feedbacks (
223
+ id TEXT PRIMARY KEY,
224
+ forId TEXT NOT NULL,
225
+ value INT NOT NULL,
226
+ threadId TEXT,
227
+ comment TEXT
228
+ )
229
+ """))
230
+
231
+ await conn.execute(text("""
232
+ CREATE TABLE IF NOT EXISTS settings (
233
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
234
+ key TEXT UNIQUE,
235
+ value TEXT
236
+ )
237
+ """))
238
+
239
+ # Create indexes
240
+ await conn.execute(text(
241
+ "CREATE INDEX IF NOT EXISTS idx_steps_threadId ON steps(threadId)"
242
+ ))
243
+ await conn.execute(text(
244
+ "CREATE INDEX IF NOT EXISTS idx_threads_createdAt ON threads(createdAt DESC)"
245
+ ))
246
+
247
+ logger.info(f"Successfully created tables for {dialect} database")
248
+ except Exception as e:
249
+ logger.error(f"Error creating tables: {str(e)}")
250
+ raise DatabaseError(f"Failed to create database tables: {str(e)}")
172
251
 
173
252
  # Initialize the database
174
- initialize_db()
253
+ async def initialize_db():
254
+ await create_tables(cl_data._data_layer.engine)
175
255
 
176
- deleted_thread_ids = [] # type: List[str]
256
+ # Get database connection string from environment variable or use SQLite as default
257
+ DB_URL = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DB_PATH}")
258
+ if DB_URL.startswith('postgresql'):
259
+ # Convert postgresql:// to postgresql+psycopg:// if needed
260
+ DB_URL = DB_URL.replace('postgresql://', 'postgresql+psycopg://')
177
261
 
178
- cl_data._data_layer = SQLAlchemyDataLayer(conninfo=f"sqlite+aiosqlite:///{DB_PATH}")
262
+ # Initialize SQLAlchemy data layer
263
+ cl_data._data_layer = SQLAlchemyDataLayer(
264
+ conninfo=DB_URL,
265
+ ssl_require=bool(os.getenv("DATABASE_SSL", False))
266
+ )
267
+
268
+ # Create tables if using PostgreSQL
269
+ if DB_URL.startswith('postgresql'):
270
+ asyncio.run(initialize_db())
179
271
 
180
272
  # Set Tavily API key
181
273
  tavily_api_key = os.getenv("TAVILY_API_KEY")
@@ -236,10 +328,53 @@ tools = [{
236
328
  }
237
329
  }] if tavily_api_key else []
238
330
 
331
+ async def save_setting(key: str, value: str):
332
+ """Saves a setting to the database.
333
+
334
+ Args:
335
+ key: The setting key.
336
+ value: The setting value.
337
+ """
338
+ try:
339
+ async with cl_data._data_layer.engine.begin() as conn:
340
+ await conn.execute(
341
+ text("""
342
+ INSERT INTO settings (key, value)
343
+ VALUES (:key, :value)
344
+ ON CONFLICT (key) DO UPDATE SET value = :value
345
+ """),
346
+ {"key": key, "value": value}
347
+ )
348
+ logger.debug(f"Saved setting {key}={value}")
349
+ except Exception as e:
350
+ logger.error(f"Error saving setting {key}: {str(e)}")
351
+ raise DatabaseError(f"Failed to save setting: {str(e)}")
352
+
353
+ async def load_setting(key: str) -> str:
354
+ """Loads a setting from the database.
355
+
356
+ Args:
357
+ key: The setting key.
358
+
359
+ Returns:
360
+ The setting value, or None if the key is not found.
361
+ """
362
+ try:
363
+ async with cl_data._data_layer.engine.connect() as conn:
364
+ result = await conn.execute(
365
+ text("SELECT value FROM settings WHERE key = :key"),
366
+ {"key": key}
367
+ )
368
+ row = result.fetchone()
369
+ return row[0] if row else None
370
+ except Exception as e:
371
+ logger.error(f"Error loading setting {key}: {str(e)}")
372
+ return None
373
+
239
374
  @cl.on_chat_start
240
375
  async def start():
241
- initialize_db()
242
- model_name = load_setting("model_name")
376
+ await initialize_db()
377
+ model_name = await load_setting("model_name")
243
378
 
244
379
  if model_name:
245
380
  cl.user_session.set("model_name", model_name)
@@ -269,7 +404,7 @@ async def setup_agent(settings):
269
404
  cl.user_session.set("model_name", model_name)
270
405
 
271
406
  # Save in settings table
272
- save_setting("model_name", model_name)
407
+ await save_setting("model_name", model_name)
273
408
 
274
409
  # Save in thread metadata
275
410
  thread_id = cl.user_session.get("thread_id")
@@ -287,7 +422,7 @@ async def setup_agent(settings):
287
422
 
288
423
  @cl.on_message
289
424
  async def main(message: cl.Message):
290
- model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
425
+ model_name = await load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
291
426
  message_history = cl.user_session.get("message_history", [])
292
427
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
293
428
 
@@ -469,7 +604,7 @@ async def send_count():
469
604
  @cl.on_chat_resume
470
605
  async def on_chat_resume(thread: ThreadDict):
471
606
  logger.info(f"Resuming chat: {thread['id']}")
472
- model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
607
+ model_name = await load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-4o-mini"
473
608
  logger.debug(f"Model name: {model_name}")
474
609
  settings = cl.ChatSettings(
475
610
  [
@@ -1,7 +1,6 @@
1
1
  import chainlit as cl
2
2
  from chainlit.input_widget import TextInput
3
- from chainlit.types import ThreadDict
4
- from litellm import acompletion, completion
3
+ from chainlit.types import ThreadDict, StepDict
5
4
  import os
6
5
  import sqlite3
7
6
  from datetime import datetime
@@ -9,8 +8,6 @@ from typing import Dict, List, Optional
9
8
  from dotenv import load_dotenv
10
9
  load_dotenv()
11
10
  import chainlit.data as cl_data
12
- from chainlit.step import StepDict
13
- from literalai.helper import utc_now
14
11
  import logging
15
12
  import json
16
13
  from sql_alchemy import SQLAlchemyDataLayer
@@ -43,7 +40,7 @@ if not CHAINLIT_AUTH_SECRET:
43
40
  os.environ["CHAINLIT_AUTH_SECRET"] = "p8BPhQChpg@J>jBz$wGxqLX2V>yTVgP*7Ky9H$aV:axW~ANNX-7_T:o@lnyCBu^U"
44
41
  CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
45
42
 
46
- now = utc_now()
43
+ now = datetime.now()
47
44
 
48
45
  create_step_counter = 0
49
46
 
@@ -12,6 +12,7 @@ from chainlit.data.base import BaseDataLayer, BaseStorageClient
12
12
  from chainlit.data.utils import queue_until_user_message
13
13
  from chainlit.element import ElementDict
14
14
  from chainlit.logger import logger
15
+ from chainlit.message import Message
15
16
  from chainlit.step import StepDict
16
17
  from chainlit.types import (
17
18
  Feedback,
@@ -204,9 +205,10 @@ class SQLAlchemyDataLayer(BaseDataLayer):
204
205
  async def get_thread(self, thread_id: str) -> Optional[ThreadDict]:
205
206
  if self.show_logger:
206
207
  logger.info(f"SQLAlchemy: get_thread, thread_id={thread_id}")
207
- user_threads: Optional[List[ThreadDict]] = await self.get_all_user_threads(
208
- thread_id=thread_id
208
+ user_threads: Optional[List[ThreadDict]] = (
209
+ await self.get_all_user_threads(thread_id=thread_id) or []
209
210
  )
211
+
210
212
  if user_threads:
211
213
  return user_threads[0]
212
214
  else:
@@ -335,7 +337,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
335
337
 
336
338
  ###### Steps ######
337
339
  @queue_until_user_message()
338
- async def create_step(self, step_dict: "StepDict"):
340
+ async def create_step(self, step_dict: dict):
339
341
  if self.show_logger:
340
342
  logger.info(f"SQLAlchemy: create_step, step_id={step_dict.get('id')}")
341
343
 
@@ -365,7 +367,7 @@ class SQLAlchemyDataLayer(BaseDataLayer):
365
367
  await self.execute_sql(query=query, parameters=parameters)
366
368
 
367
369
  @queue_until_user_message()
368
- async def update_step(self, step_dict: "StepDict"):
370
+ async def update_step(self, step_dict: dict):
369
371
  if self.show_logger:
370
372
  logger.info(f"SQLAlchemy: update_step, step_id={step_dict.get('id')}")
371
373
  await self.create_step(step_dict)
@@ -646,37 +648,37 @@ class SQLAlchemyDataLayer(BaseDataLayer):
646
648
  value=step_feedback["feedback_value"],
647
649
  comment=step_feedback.get("feedback_comment"),
648
650
  )
649
- step_dict = StepDict(
650
- id=step_feedback["step_id"],
651
- name=step_feedback["step_name"],
652
- type=step_feedback["step_type"],
653
- threadId=thread_id,
654
- parentId=step_feedback.get("step_parentid"),
655
- streaming=step_feedback.get("step_streaming", False),
656
- waitForAnswer=step_feedback.get("step_waitforanswer"),
657
- isError=step_feedback.get("step_iserror"),
658
- metadata=(
651
+ step_dict = {
652
+ "id": step_feedback["step_id"],
653
+ "name": step_feedback["step_name"],
654
+ "type": step_feedback["step_type"],
655
+ "threadId": thread_id,
656
+ "parentId": step_feedback.get("step_parentid"),
657
+ "streaming": step_feedback.get("step_streaming", False),
658
+ "waitForAnswer": step_feedback.get("step_waitforanswer"),
659
+ "isError": step_feedback.get("step_iserror"),
660
+ "metadata": (
659
661
  step_feedback["step_metadata"]
660
662
  if step_feedback.get("step_metadata") is not None
661
663
  else {}
662
664
  ),
663
- tags=step_feedback.get("step_tags"),
664
- input=(
665
+ "tags": step_feedback.get("step_tags"),
666
+ "input": (
665
667
  step_feedback.get("step_input", "")
666
668
  if step_feedback.get("step_showinput")
667
669
  not in [None, "false"]
668
670
  else ""
669
671
  ),
670
- output=step_feedback.get("step_output", ""),
671
- createdAt=step_feedback.get("step_createdat"),
672
- start=step_feedback.get("step_start"),
673
- end=step_feedback.get("step_end"),
674
- generation=step_feedback.get("step_generation"),
675
- showInput=step_feedback.get("step_showinput"),
676
- language=step_feedback.get("step_language"),
677
- indent=step_feedback.get("step_indent"),
678
- feedback=feedback,
679
- )
672
+ "output": step_feedback.get("step_output", ""),
673
+ "createdAt": step_feedback.get("step_createdat"),
674
+ "start": step_feedback.get("step_start"),
675
+ "end": step_feedback.get("step_end"),
676
+ "generation": step_feedback.get("step_generation"),
677
+ "showInput": step_feedback.get("step_showinput"),
678
+ "language": step_feedback.get("step_language"),
679
+ "indent": step_feedback.get("step_indent"),
680
+ "feedback": feedback,
681
+ }
680
682
  # Append the step to the steps list of the corresponding ThreadDict
681
683
  thread_dicts[thread_id]["steps"].append(step_dict)
682
684
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "PraisonAI"
3
- version = "1.0.1"
3
+ version = "1.0.3"
4
4
  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."
5
5
  authors = ["Mervin Praison"]
6
6
  license = ""
@@ -48,6 +48,7 @@ pyngrok = {version = ">=1.4.0", optional = true}
48
48
  sqlalchemy = {version = ">=2.0.36", optional = true}
49
49
  playwright = {version = ">=1.47.0", optional = true}
50
50
  openai = {version = ">=1.54.0", optional = true}
51
+ pydantic = {version = "<=2.10.1", optional = true}
51
52
 
52
53
  [tool.poetry.group.docs.dependencies]
53
54
  mkdocs = "*"
@@ -125,7 +126,10 @@ chat = [
125
126
  "tavily-python",
126
127
  "crawl4ai",
127
128
  "sqlalchemy",
128
- "playwright"
129
+ "playwright",
130
+ "rich",
131
+ "instructor",
132
+ "pydantic"
129
133
  ]
130
134
  code = [
131
135
  "chainlit",
@@ -135,7 +139,8 @@ code = [
135
139
  "tavily-python",
136
140
  "crawl4ai",
137
141
  "sqlalchemy",
138
- "playwright"
142
+ "playwright",
143
+ "pydantic"
139
144
  ]
140
145
  train = ["setup-conda-env"]
141
146
  realtime = [
File without changes
File without changes
File without changes
File without changes
File without changes