Skip to content

Commit 361ff59

Browse files
Merge pull request #83 from MervinPraison/develop
Adding PraisonAI Code
2 parents 2eb55f9 + 5491f16 commit 361ff59

File tree

7 files changed

+498
-5
lines changed

7 files changed

+498
-5
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM python:3.11-slim
22
WORKDIR /app
33
COPY . .
4-
RUN pip install flask praisonai==0.0.47 gunicorn markdown
4+
RUN pip install flask praisonai==0.0.48 gunicorn markdown
55
EXPOSE 8080
66
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]

docs/api/praisonai/deploy.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ <h2 id="raises">Raises</h2>
110110
file.write(&#34;FROM python:3.11-slim\n&#34;)
111111
file.write(&#34;WORKDIR /app\n&#34;)
112112
file.write(&#34;COPY . .\n&#34;)
113-
file.write(&#34;RUN pip install flask praisonai==0.0.47 gunicorn markdown\n&#34;)
113+
file.write(&#34;RUN pip install flask praisonai==0.0.48 gunicorn markdown\n&#34;)
114114
file.write(&#34;EXPOSE 8080\n&#34;)
115115
file.write(&#39;CMD [&#34;gunicorn&#34;, &#34;-b&#34;, &#34;0.0.0.0:8080&#34;, &#34;api:app&#34;]\n&#39;)
116116

praisonai/cli.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ def main(self):
9595
self.create_chainlit_chat_interface()
9696
return
9797

98+
if getattr(args, 'code', False):
99+
self.create_code_interface()
100+
return
101+
98102
invocation_cmd = "praisonai"
99103
version_string = f"PraisonAI version {__version__}"
100104

@@ -177,6 +181,9 @@ def parse_args(self):
177181
if args.agent_file == 'chat':
178182
args.ui = 'chainlit'
179183
args.chat = True
184+
if args.agent_file == 'code':
185+
args.ui = 'chainlit'
186+
args.code = True
180187

181188
return args
182189

@@ -207,6 +214,34 @@ def create_chainlit_chat_interface(self):
207214
chainlit_run([chat_ui_path])
208215
else:
209216
print("ERROR: Chat UI is not installed. Please install it with 'pip install \"praisonai\[chat]\"' to use the chat UI.")
217+
218+
def create_code_interface(self):
219+
"""
220+
Create a Chainlit interface for the code application.
221+
222+
This function sets up a Chainlit application that listens for messages.
223+
When a message is received, it runs PraisonAI with the provided message as the topic.
224+
The generated agents are then used to perform tasks.
225+
226+
Returns:
227+
None: This function does not return any value. It starts the Chainlit application.
228+
"""
229+
if CHAINLIT_AVAILABLE:
230+
import praisonai
231+
os.environ["CHAINLIT_PORT"] = "8086"
232+
public_folder = os.path.join(os.path.dirname(praisonai.__file__), 'public')
233+
if not os.path.exists("public"): # Check if the folder exists in the current directory
234+
if os.path.exists(public_folder):
235+
shutil.copytree(public_folder, 'public', dirs_exist_ok=True)
236+
logging.info("Public folder copied successfully!")
237+
else:
238+
logging.info("Public folder not found in the package.")
239+
else:
240+
logging.info("Public folder already exists.")
241+
code_ui_path = os.path.join(os.path.dirname(praisonai.__file__), 'ui', 'code.py')
242+
chainlit_run([code_ui_path])
243+
else:
244+
print("ERROR: Code UI is not installed. Please install it with 'pip install \"praisonai\[code]\"' to use the code UI.")
210245

211246
def create_gradio_interface(self):
212247
"""

praisonai/deploy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def create_dockerfile(self):
5656
file.write("FROM python:3.11-slim\n")
5757
file.write("WORKDIR /app\n")
5858
file.write("COPY . .\n")
59-
file.write("RUN pip install flask praisonai==0.0.47 gunicorn markdown\n")
59+
file.write("RUN pip install flask praisonai==0.0.48 gunicorn markdown\n")
6060
file.write("EXPOSE 8080\n")
6161
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
6262

praisonai/ui/code.py

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
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 context import ContextGatherer
18+
19+
# Set up logging
20+
logger = logging.getLogger(__name__)
21+
log_level = os.getenv("LOGLEVEL", "INFO").upper()
22+
logger.handlers = []
23+
24+
# Set up logging to console
25+
console_handler = logging.StreamHandler()
26+
console_handler.setLevel(log_level)
27+
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
28+
console_handler.setFormatter(console_formatter)
29+
logger.addHandler(console_handler)
30+
31+
# Set the logging level for the logger
32+
logger.setLevel(log_level)
33+
34+
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
35+
36+
if not CHAINLIT_AUTH_SECRET:
37+
os.environ["CHAINLIT_AUTH_SECRET"] = "p8BPhQChpg@J>jBz$wGxqLX2V>yTVgP*7Ky9H$aV:axW~ANNX-7_T:o@lnyCBu^U"
38+
CHAINLIT_AUTH_SECRET = os.getenv("CHAINLIT_AUTH_SECRET")
39+
40+
now = utc_now()
41+
42+
create_step_counter = 0
43+
44+
DB_PATH = "threads.db"
45+
46+
def initialize_db():
47+
conn = sqlite3.connect(DB_PATH)
48+
cursor = conn.cursor()
49+
cursor.execute('''
50+
CREATE TABLE IF NOT EXISTS users (
51+
id UUID PRIMARY KEY,
52+
identifier TEXT NOT NULL UNIQUE,
53+
metadata JSONB NOT NULL,
54+
createdAt TEXT
55+
)
56+
''')
57+
cursor.execute('''
58+
CREATE TABLE IF NOT EXISTS threads (
59+
id UUID PRIMARY KEY,
60+
createdAt TEXT,
61+
name TEXT,
62+
userId UUID,
63+
userIdentifier TEXT,
64+
tags TEXT[],
65+
metadata JSONB NOT NULL DEFAULT '{}',
66+
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
67+
)
68+
''')
69+
cursor.execute('''
70+
CREATE TABLE IF NOT EXISTS steps (
71+
id UUID PRIMARY KEY,
72+
name TEXT NOT NULL,
73+
type TEXT NOT NULL,
74+
threadId UUID NOT NULL,
75+
parentId UUID,
76+
disableFeedback BOOLEAN NOT NULL,
77+
streaming BOOLEAN NOT NULL,
78+
waitForAnswer BOOLEAN,
79+
isError BOOLEAN,
80+
metadata JSONB,
81+
tags TEXT[],
82+
input TEXT,
83+
output TEXT,
84+
createdAt TEXT,
85+
start TEXT,
86+
end TEXT,
87+
generation JSONB,
88+
showInput TEXT,
89+
language TEXT,
90+
indent INT,
91+
FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
92+
)
93+
''')
94+
cursor.execute('''
95+
CREATE TABLE IF NOT EXISTS elements (
96+
id UUID PRIMARY KEY,
97+
threadId UUID,
98+
type TEXT,
99+
url TEXT,
100+
chainlitKey TEXT,
101+
name TEXT NOT NULL,
102+
display TEXT,
103+
objectKey TEXT,
104+
size TEXT,
105+
page INT,
106+
language TEXT,
107+
forId UUID,
108+
mime TEXT,
109+
FOREIGN KEY (threadId) REFERENCES threads (id) ON DELETE CASCADE
110+
)
111+
''')
112+
cursor.execute('''
113+
CREATE TABLE IF NOT EXISTS feedbacks (
114+
id UUID PRIMARY KEY,
115+
forId UUID NOT NULL,
116+
value INT NOT NULL,
117+
threadId UUID,
118+
comment TEXT
119+
)
120+
''')
121+
cursor.execute('''
122+
CREATE TABLE IF NOT EXISTS settings (
123+
id INTEGER PRIMARY KEY AUTOINCREMENT,
124+
key TEXT UNIQUE,
125+
value TEXT
126+
)
127+
''')
128+
conn.commit()
129+
conn.close()
130+
131+
def save_setting(key: str, value: str):
132+
"""Saves a setting to the database.
133+
134+
Args:
135+
key: The setting key.
136+
value: The setting value.
137+
"""
138+
conn = sqlite3.connect(DB_PATH)
139+
cursor = conn.cursor()
140+
cursor.execute(
141+
"""
142+
INSERT OR REPLACE INTO settings (id, key, value)
143+
VALUES ((SELECT id FROM settings WHERE key = ?), ?, ?)
144+
""",
145+
(key, key, value),
146+
)
147+
conn.commit()
148+
conn.close()
149+
150+
def load_setting(key: str) -> str:
151+
"""Loads a setting from the database.
152+
153+
Args:
154+
key: The setting key.
155+
156+
Returns:
157+
The setting value, or None if the key is not found.
158+
"""
159+
conn = sqlite3.connect(DB_PATH)
160+
cursor = conn.cursor()
161+
cursor.execute('SELECT value FROM settings WHERE key = ?', (key,))
162+
result = cursor.fetchone()
163+
conn.close()
164+
return result[0] if result else None
165+
166+
167+
# Initialize the database
168+
initialize_db()
169+
170+
deleted_thread_ids = [] # type: List[str]
171+
172+
cl_data._data_layer = SQLAlchemyDataLayer(conninfo=f"sqlite+aiosqlite:///{DB_PATH}")
173+
174+
@cl.on_chat_start
175+
async def start():
176+
initialize_db()
177+
model_name = load_setting("model_name")
178+
179+
if model_name:
180+
cl.user_session.set("model_name", model_name)
181+
else:
182+
# If no setting found, use default or environment variable
183+
model_name = os.getenv("MODEL_NAME", "gpt-3.5-turbo")
184+
cl.user_session.set("model_name", model_name)
185+
logger.debug(f"Model name: {model_name}")
186+
settings = cl.ChatSettings(
187+
[
188+
TextInput(
189+
id="model_name",
190+
label="Enter the Model Name",
191+
placeholder="e.g., gpt-3.5-turbo",
192+
initial=model_name
193+
)
194+
]
195+
)
196+
cl.user_session.set("settings", settings)
197+
await settings.send()
198+
gatherer = ContextGatherer()
199+
context, token_count, context_tree = gatherer.run()
200+
msg = cl.Message(content="""Token Count: {token_count},
201+
Files include: \n```bash\n{context_tree}\n"""
202+
.format(token_count=token_count, context_tree=context_tree))
203+
await msg.send()
204+
205+
@cl.on_settings_update
206+
async def setup_agent(settings):
207+
logger.debug(settings)
208+
cl.user_session.set("settings", settings)
209+
model_name = settings["model_name"]
210+
cl.user_session.set("model_name", model_name)
211+
212+
# Save in settings table
213+
save_setting("model_name", model_name)
214+
215+
# Save in thread metadata
216+
thread_id = cl.user_session.get("thread_id")
217+
if thread_id:
218+
thread = await cl_data.get_thread(thread_id)
219+
if thread:
220+
metadata = thread.get("metadata", {})
221+
metadata["model_name"] = model_name
222+
223+
# Always store metadata as a JSON string
224+
await cl_data.update_thread(thread_id, metadata=json.dumps(metadata))
225+
226+
# Update the user session with the new metadata
227+
cl.user_session.set("metadata", metadata)
228+
229+
@cl.on_message
230+
async def main(message: cl.Message):
231+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-3.5-turbo"
232+
message_history = cl.user_session.get("message_history", [])
233+
message_history.append({"role": "user", "content": message.content})
234+
gatherer = ContextGatherer()
235+
context, token_count, context_tree = gatherer.run()
236+
prompt_history = message_history
237+
prompt_history.append({"role": "user", "content": """
238+
Answer the question:\n{question}.\n\n
239+
Below is the Context:\n{context}\n\n"""
240+
.format(context=context, question=message.content)})
241+
242+
msg = cl.Message(content="")
243+
await msg.send()
244+
245+
response = await acompletion(
246+
model=model_name,
247+
messages=prompt_history,
248+
stream=True,
249+
# temperature=0.7,
250+
# max_tokens=500,
251+
# top_p=1
252+
)
253+
254+
full_response = ""
255+
async for part in response:
256+
if token := part['choices'][0]['delta']['content']:
257+
await msg.stream_token(token)
258+
full_response += token
259+
logger.debug(f"Full response: {full_response}")
260+
message_history.append({"role": "assistant", "content": full_response})
261+
logger.debug(f"Message history: {message_history}")
262+
cl.user_session.set("message_history", message_history)
263+
await msg.update()
264+
265+
username = os.getenv("CHAINLIT_USERNAME", "admin") # Default to "admin" if not found
266+
password = os.getenv("CHAINLIT_PASSWORD", "admin") # Default to "admin" if not found
267+
268+
@cl.password_auth_callback
269+
def auth_callback(username: str, password: str):
270+
if (username, password) == (username, password):
271+
return cl.User(
272+
identifier=username, metadata={"role": "ADMIN", "provider": "credentials"}
273+
)
274+
else:
275+
return None
276+
277+
async def send_count():
278+
await cl.Message(
279+
f"Create step counter: {create_step_counter}", disable_feedback=True
280+
).send()
281+
282+
@cl.on_chat_resume
283+
async def on_chat_resume(thread: cl_data.ThreadDict):
284+
logger.info(f"Resuming chat: {thread['id']}")
285+
model_name = load_setting("model_name") or os.getenv("MODEL_NAME") or "gpt-3.5-turbo"
286+
logger.debug(f"Model name: {model_name}")
287+
settings = cl.ChatSettings(
288+
[
289+
TextInput(
290+
id="model_name",
291+
label="Enter the Model Name",
292+
placeholder="e.g., gpt-3.5-turbo",
293+
initial=model_name
294+
)
295+
]
296+
)
297+
await settings.send()
298+
thread_id = thread["id"]
299+
cl.user_session.set("thread_id", thread["id"])
300+
301+
# The metadata should now already be a dictionary
302+
metadata = thread.get("metadata", {})
303+
cl.user_session.set("metadata", metadata)
304+
305+
message_history = cl.user_session.get("message_history", [])
306+
steps = thread["steps"]
307+
308+
for message in steps:
309+
msg_type = message.get("type")
310+
if msg_type == "user_message":
311+
message_history.append({"role": "user", "content": message.get("output", "")})
312+
elif msg_type == "assistant_message":
313+
message_history.append({"role": "assistant", "content": message.get("output", "")})
314+
else:
315+
logger.warning(f"Message without type: {message}")
316+
317+
cl.user_session.set("message_history", message_history)

0 commit comments

Comments
 (0)