PraisonAI 0.0.59__cp312-cp312-manylinux_2_35_x86_64.whl
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/__init__.py +6 -0
- praisonai/__main__.py +10 -0
- praisonai/agents_generator.py +381 -0
- praisonai/auto.py +190 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/cli.py +416 -0
- praisonai/deploy.py +138 -0
- praisonai/inbuilt_tools/__init__.py +2 -0
- praisonai/inbuilt_tools/autogen_tools.py +209 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +128 -0
- praisonai/public/android-chrome-192x192.png +0 -0
- praisonai/public/android-chrome-512x512.png +0 -0
- praisonai/public/apple-touch-icon.png +0 -0
- praisonai/public/fantasy.svg +3 -0
- praisonai/public/favicon-16x16.png +0 -0
- praisonai/public/favicon-32x32.png +0 -0
- praisonai/public/favicon.ico +0 -0
- praisonai/public/game.svg +3 -0
- praisonai/public/logo_dark.png +0 -0
- praisonai/public/logo_light.png +0 -0
- praisonai/public/movie.svg +3 -0
- praisonai/public/thriller.svg +3 -0
- praisonai/setup/__init__.py +0 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/config.yaml +60 -0
- praisonai/setup/post_install.py +20 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup/setup_conda_env.sh +72 -0
- praisonai/test.py +105 -0
- praisonai/train.py +276 -0
- praisonai/ui/chat.py +304 -0
- praisonai/ui/code.py +318 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/public/fantasy.svg +3 -0
- praisonai/ui/public/game.svg +3 -0
- praisonai/ui/public/logo_dark.png +0 -0
- praisonai/ui/public/logo_light.png +0 -0
- praisonai/ui/public/movie.svg +3 -0
- praisonai/ui/public/thriller.svg +3 -0
- praisonai/ui/sql_alchemy.py +638 -0
- praisonai/version.py +1 -0
- praisonai-0.0.59.dist-info/LICENSE +20 -0
- praisonai-0.0.59.dist-info/METADATA +344 -0
- praisonai-0.0.59.dist-info/RECORD +48 -0
- praisonai-0.0.59.dist-info/WHEEL +4 -0
- praisonai-0.0.59.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import ssl
|
|
3
|
+
import uuid
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
import aiofiles
|
|
9
|
+
import aiohttp
|
|
10
|
+
from chainlit.context import context
|
|
11
|
+
from chainlit.data import BaseDataLayer, BaseStorageClient, queue_until_user_message
|
|
12
|
+
from chainlit.element import ElementDict
|
|
13
|
+
from chainlit.logger import logger
|
|
14
|
+
from chainlit.step import StepDict
|
|
15
|
+
from chainlit.types import (
|
|
16
|
+
Feedback,
|
|
17
|
+
FeedbackDict,
|
|
18
|
+
PageInfo,
|
|
19
|
+
PaginatedResponse,
|
|
20
|
+
Pagination,
|
|
21
|
+
ThreadDict,
|
|
22
|
+
ThreadFilter,
|
|
23
|
+
)
|
|
24
|
+
from chainlit.user import PersistedUser, User
|
|
25
|
+
from sqlalchemy import text
|
|
26
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
27
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
|
|
28
|
+
from sqlalchemy.orm import sessionmaker
|
|
29
|
+
import chainlit as cl
|
|
30
|
+
from literalai.helper import utc_now
|
|
31
|
+
now = utc_now()
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from chainlit.element import Element, ElementDict
|
|
35
|
+
from chainlit.step import StepDict
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SQLAlchemyDataLayer(BaseDataLayer):
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
conninfo: str,
|
|
42
|
+
ssl_require: bool = False,
|
|
43
|
+
storage_provider: Optional[BaseStorageClient] = None,
|
|
44
|
+
user_thread_limit: Optional[int] = 1000,
|
|
45
|
+
show_logger: Optional[bool] = False,
|
|
46
|
+
):
|
|
47
|
+
self._conninfo = conninfo
|
|
48
|
+
self.user_thread_limit = user_thread_limit
|
|
49
|
+
self.show_logger = show_logger
|
|
50
|
+
ssl_args = {}
|
|
51
|
+
if ssl_require:
|
|
52
|
+
# Create an SSL context to require an SSL connection
|
|
53
|
+
ssl_context = ssl.create_default_context()
|
|
54
|
+
ssl_context.check_hostname = False
|
|
55
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
56
|
+
ssl_args["ssl"] = ssl_context
|
|
57
|
+
self.engine: AsyncEngine = create_async_engine(
|
|
58
|
+
self._conninfo, connect_args=ssl_args
|
|
59
|
+
)
|
|
60
|
+
self.async_session = sessionmaker(bind=self.engine, expire_on_commit=False, class_=AsyncSession) # type: ignore
|
|
61
|
+
if storage_provider:
|
|
62
|
+
self.storage_provider: Optional[BaseStorageClient] = storage_provider
|
|
63
|
+
if self.show_logger:
|
|
64
|
+
logger.info("SQLAlchemyDataLayer storage client initialized")
|
|
65
|
+
else:
|
|
66
|
+
self.storage_provider = None
|
|
67
|
+
logger.warn(
|
|
68
|
+
"SQLAlchemyDataLayer storage client is not initialized and elements will not be persisted!"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async def build_debug_url(self) -> str:
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
###### SQL Helpers ######
|
|
75
|
+
async def execute_sql(
|
|
76
|
+
self, query: str, parameters: dict
|
|
77
|
+
) -> Union[List[Dict[str, Any]], int, None]:
|
|
78
|
+
parameterized_query = text(query)
|
|
79
|
+
async with self.async_session() as session:
|
|
80
|
+
try:
|
|
81
|
+
await session.begin()
|
|
82
|
+
result = await session.execute(parameterized_query, parameters)
|
|
83
|
+
await session.commit()
|
|
84
|
+
if result.returns_rows:
|
|
85
|
+
json_result = [dict(row._mapping) for row in result.fetchall()]
|
|
86
|
+
clean_json_result = self.clean_result(json_result)
|
|
87
|
+
return clean_json_result
|
|
88
|
+
else:
|
|
89
|
+
return result.rowcount
|
|
90
|
+
except SQLAlchemyError as e:
|
|
91
|
+
await session.rollback()
|
|
92
|
+
logger.warn(f"An error occurred: {e}")
|
|
93
|
+
return None
|
|
94
|
+
except Exception as e:
|
|
95
|
+
await session.rollback()
|
|
96
|
+
logger.warn(f"An unexpected error occurred: {e}")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
async def get_current_timestamp(self) -> str:
|
|
100
|
+
return datetime.now().isoformat() + "Z"
|
|
101
|
+
|
|
102
|
+
def clean_result(self, obj):
|
|
103
|
+
"""Recursively change UUID -> str and serialize dictionaries"""
|
|
104
|
+
if isinstance(obj, dict):
|
|
105
|
+
return {k: self.clean_result(v) for k, v in obj.items()}
|
|
106
|
+
elif isinstance(obj, list):
|
|
107
|
+
return [self.clean_result(item) for item in obj]
|
|
108
|
+
elif isinstance(obj, uuid.UUID):
|
|
109
|
+
return str(obj)
|
|
110
|
+
return obj
|
|
111
|
+
|
|
112
|
+
###### User ######
|
|
113
|
+
async def get_user(self, identifier: str) -> Optional[PersistedUser]:
|
|
114
|
+
logger.debug(f"Getting user: {identifier}")
|
|
115
|
+
return cl.PersistedUser(id="test", createdAt=now, identifier=identifier)
|
|
116
|
+
if self.show_logger:
|
|
117
|
+
logger.info(f"SQLAlchemy: get_user, identifier={identifier}")
|
|
118
|
+
query = "SELECT * FROM users WHERE identifier = :identifier"
|
|
119
|
+
parameters = {"identifier": identifier}
|
|
120
|
+
result = await self.execute_sql(query=query, parameters=parameters)
|
|
121
|
+
if result and isinstance(result, list):
|
|
122
|
+
user_data = result[0]
|
|
123
|
+
return PersistedUser(**user_data)
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
async def create_user(self, user: User) -> Optional[PersistedUser]:
|
|
127
|
+
logger.debug(f"Creating user: {user.identifier}")
|
|
128
|
+
return cl.PersistedUser(id="test", createdAt=now, identifier=user.identifier)
|
|
129
|
+
if self.show_logger:
|
|
130
|
+
logger.info(f"SQLAlchemy: create_user, user_identifier={user.identifier}")
|
|
131
|
+
existing_user: Optional["PersistedUser"] = await self.get_user(user.identifier)
|
|
132
|
+
user_dict: Dict[str, Any] = {
|
|
133
|
+
"identifier": str(user.identifier),
|
|
134
|
+
"metadata": json.dumps(user.metadata) or {},
|
|
135
|
+
}
|
|
136
|
+
if not existing_user: # create the user
|
|
137
|
+
if self.show_logger:
|
|
138
|
+
logger.info("SQLAlchemy: create_user, creating the user")
|
|
139
|
+
user_dict["id"] = str(uuid.uuid4())
|
|
140
|
+
user_dict["createdAt"] = await self.get_current_timestamp()
|
|
141
|
+
query = """INSERT INTO users ("id", "identifier", "createdAt", "metadata") VALUES (:id, :identifier, :createdAt, :metadata)"""
|
|
142
|
+
await self.execute_sql(query=query, parameters=user_dict)
|
|
143
|
+
else: # update the user
|
|
144
|
+
if self.show_logger:
|
|
145
|
+
logger.info("SQLAlchemy: update user metadata")
|
|
146
|
+
query = """UPDATE users SET "metadata" = :metadata WHERE "identifier" = :identifier"""
|
|
147
|
+
await self.execute_sql(
|
|
148
|
+
query=query, parameters=user_dict
|
|
149
|
+
) # We want to update the metadata
|
|
150
|
+
return await self.get_user(user.identifier)
|
|
151
|
+
|
|
152
|
+
###### Threads ######
|
|
153
|
+
async def get_thread_author(self, thread_id: str) -> str:
|
|
154
|
+
logger.debug(f"Getting thread author: {thread_id}")
|
|
155
|
+
return "admin"
|
|
156
|
+
if self.show_logger:
|
|
157
|
+
logger.info(f"SQLAlchemy: get_thread_author, thread_id={thread_id}")
|
|
158
|
+
query = """SELECT "userIdentifier" FROM threads WHERE "id" = :id"""
|
|
159
|
+
parameters = {"id": thread_id}
|
|
160
|
+
result = await self.execute_sql(query=query, parameters=parameters)
|
|
161
|
+
if isinstance(result, list) and result:
|
|
162
|
+
author_identifier = result[0].get("userIdentifier")
|
|
163
|
+
if author_identifier is not None:
|
|
164
|
+
return author_identifier
|
|
165
|
+
raise ValueError(f"Author not found for thread_id {thread_id}")
|
|
166
|
+
|
|
167
|
+
async def get_thread(self, thread_id: str) -> Optional[ThreadDict]:
|
|
168
|
+
if self.show_logger:
|
|
169
|
+
logger.info(f"SQLAlchemy: get_thread, thread_id={thread_id}")
|
|
170
|
+
user_threads: Optional[List[ThreadDict]] = await self.get_all_user_threads(
|
|
171
|
+
thread_id=thread_id
|
|
172
|
+
)
|
|
173
|
+
if user_threads:
|
|
174
|
+
thread = user_threads[0]
|
|
175
|
+
# Parse the metadata here
|
|
176
|
+
if isinstance(thread['metadata'], str):
|
|
177
|
+
try:
|
|
178
|
+
thread['metadata'] = json.loads(thread['metadata'])
|
|
179
|
+
except json.JSONDecodeError:
|
|
180
|
+
thread['metadata'] = {}
|
|
181
|
+
elif thread['metadata'] is None:
|
|
182
|
+
thread['metadata'] = {}
|
|
183
|
+
return thread
|
|
184
|
+
else:
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
async def update_thread(
|
|
188
|
+
self,
|
|
189
|
+
thread_id: str,
|
|
190
|
+
name: Optional[str] = None,
|
|
191
|
+
user_id: Optional[str] = None,
|
|
192
|
+
metadata: Optional[Dict] = None,
|
|
193
|
+
tags: Optional[List[str]] = None,
|
|
194
|
+
):
|
|
195
|
+
if self.show_logger:
|
|
196
|
+
logger.info(f"SQLAlchemy: update_thread, thread_id={thread_id}")
|
|
197
|
+
if context.session.user is not None:
|
|
198
|
+
user_identifier = context.session.user.identifier
|
|
199
|
+
else:
|
|
200
|
+
raise ValueError("User not found in session context")
|
|
201
|
+
data = {
|
|
202
|
+
"id": thread_id,
|
|
203
|
+
"createdAt": (
|
|
204
|
+
await self.get_current_timestamp() if metadata is None else None
|
|
205
|
+
),
|
|
206
|
+
"name": (
|
|
207
|
+
name
|
|
208
|
+
if name is not None
|
|
209
|
+
else (metadata.get("name") if metadata and "name" in metadata else None)
|
|
210
|
+
),
|
|
211
|
+
"userId": user_id,
|
|
212
|
+
"userIdentifier": user_identifier,
|
|
213
|
+
"tags": tags,
|
|
214
|
+
"metadata": json.dumps(metadata) if metadata else None,
|
|
215
|
+
}
|
|
216
|
+
parameters = {
|
|
217
|
+
key: value for key, value in data.items() if value is not None
|
|
218
|
+
} # Remove keys with None values
|
|
219
|
+
columns = ", ".join(f'"{key}"' for key in parameters.keys())
|
|
220
|
+
values = ", ".join(f":{key}" for key in parameters.keys())
|
|
221
|
+
updates = ", ".join(
|
|
222
|
+
f'"{key}" = EXCLUDED."{key}"' for key in parameters.keys() if key != "id"
|
|
223
|
+
)
|
|
224
|
+
query = f"""
|
|
225
|
+
INSERT INTO threads ({columns})
|
|
226
|
+
VALUES ({values})
|
|
227
|
+
ON CONFLICT ("id") DO UPDATE
|
|
228
|
+
SET {updates};
|
|
229
|
+
"""
|
|
230
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
231
|
+
|
|
232
|
+
async def delete_thread(self, thread_id: str):
|
|
233
|
+
if self.show_logger:
|
|
234
|
+
logger.info(f"SQLAlchemy: delete_thread, thread_id={thread_id}")
|
|
235
|
+
# Delete feedbacks/elements/steps/thread
|
|
236
|
+
feedbacks_query = """DELETE FROM feedbacks WHERE "forId" IN (SELECT "id" FROM steps WHERE "threadId" = :id)"""
|
|
237
|
+
elements_query = """DELETE FROM elements WHERE "threadId" = :id"""
|
|
238
|
+
steps_query = """DELETE FROM steps WHERE "threadId" = :id"""
|
|
239
|
+
thread_query = """DELETE FROM threads WHERE "id" = :id"""
|
|
240
|
+
parameters = {"id": thread_id}
|
|
241
|
+
await self.execute_sql(query=feedbacks_query, parameters=parameters)
|
|
242
|
+
await self.execute_sql(query=elements_query, parameters=parameters)
|
|
243
|
+
await self.execute_sql(query=steps_query, parameters=parameters)
|
|
244
|
+
await self.execute_sql(query=thread_query, parameters=parameters)
|
|
245
|
+
|
|
246
|
+
async def list_threads(
|
|
247
|
+
self, pagination: Pagination, filters: ThreadFilter
|
|
248
|
+
) -> PaginatedResponse:
|
|
249
|
+
if self.show_logger:
|
|
250
|
+
logger.info(
|
|
251
|
+
f"SQLAlchemy: list_threads, pagination={pagination}, filters={filters}"
|
|
252
|
+
)
|
|
253
|
+
if not filters.userId:
|
|
254
|
+
raise ValueError("userId is required")
|
|
255
|
+
all_user_threads: List[ThreadDict] = (
|
|
256
|
+
await self.get_all_user_threads(user_id=filters.userId) or []
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
search_keyword = filters.search.lower() if filters.search else None
|
|
260
|
+
feedback_value = int(filters.feedback) if filters.feedback else None
|
|
261
|
+
|
|
262
|
+
filtered_threads = []
|
|
263
|
+
for thread in all_user_threads:
|
|
264
|
+
keyword_match = True
|
|
265
|
+
feedback_match = True
|
|
266
|
+
if search_keyword or feedback_value is not None:
|
|
267
|
+
if search_keyword:
|
|
268
|
+
keyword_match = any(
|
|
269
|
+
search_keyword in step["output"].lower()
|
|
270
|
+
for step in thread["steps"]
|
|
271
|
+
if "output" in step
|
|
272
|
+
)
|
|
273
|
+
if feedback_value is not None:
|
|
274
|
+
feedback_match = False # Assume no match until found
|
|
275
|
+
for step in thread["steps"]:
|
|
276
|
+
feedback = step.get("feedback")
|
|
277
|
+
if feedback and feedback.get("value") == feedback_value:
|
|
278
|
+
feedback_match = True
|
|
279
|
+
break
|
|
280
|
+
if keyword_match and feedback_match:
|
|
281
|
+
filtered_threads.append(thread)
|
|
282
|
+
|
|
283
|
+
start = 0
|
|
284
|
+
if pagination.cursor:
|
|
285
|
+
for i, thread in enumerate(filtered_threads):
|
|
286
|
+
if (
|
|
287
|
+
thread["id"] == pagination.cursor
|
|
288
|
+
): # Find the start index using pagination.cursor
|
|
289
|
+
start = i + 1
|
|
290
|
+
break
|
|
291
|
+
end = start + pagination.first
|
|
292
|
+
paginated_threads = filtered_threads[start:end] or []
|
|
293
|
+
|
|
294
|
+
has_next_page = len(filtered_threads) > end
|
|
295
|
+
start_cursor = paginated_threads[0]["id"] if paginated_threads else None
|
|
296
|
+
end_cursor = paginated_threads[-1]["id"] if paginated_threads else None
|
|
297
|
+
|
|
298
|
+
return PaginatedResponse(
|
|
299
|
+
pageInfo=PageInfo(
|
|
300
|
+
hasNextPage=has_next_page,
|
|
301
|
+
startCursor=start_cursor,
|
|
302
|
+
endCursor=end_cursor,
|
|
303
|
+
),
|
|
304
|
+
data=paginated_threads,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
###### Steps ######
|
|
308
|
+
@queue_until_user_message()
|
|
309
|
+
async def create_step(self, step_dict: "StepDict"):
|
|
310
|
+
if self.show_logger:
|
|
311
|
+
logger.info(f"SQLAlchemy: create_step, step_id={step_dict.get('id')}")
|
|
312
|
+
if not getattr(context.session.user, "id", None):
|
|
313
|
+
raise ValueError("No authenticated user in context")
|
|
314
|
+
step_dict["showInput"] = (
|
|
315
|
+
str(step_dict.get("showInput", "")).lower()
|
|
316
|
+
if "showInput" in step_dict
|
|
317
|
+
else None
|
|
318
|
+
)
|
|
319
|
+
parameters = {
|
|
320
|
+
key: value
|
|
321
|
+
for key, value in step_dict.items()
|
|
322
|
+
if value is not None and not (isinstance(value, dict) and not value)
|
|
323
|
+
}
|
|
324
|
+
parameters["metadata"] = json.dumps(step_dict.get("metadata", {}))
|
|
325
|
+
parameters["generation"] = json.dumps(step_dict.get("generation", {}))
|
|
326
|
+
columns = ", ".join(f'"{key}"' for key in parameters.keys())
|
|
327
|
+
values = ", ".join(f":{key}" for key in parameters.keys())
|
|
328
|
+
updates = ", ".join(
|
|
329
|
+
f'"{key}" = :{key}' for key in parameters.keys() if key != "id"
|
|
330
|
+
)
|
|
331
|
+
query = f"""
|
|
332
|
+
INSERT INTO steps ({columns})
|
|
333
|
+
VALUES ({values})
|
|
334
|
+
ON CONFLICT (id) DO UPDATE
|
|
335
|
+
SET {updates};
|
|
336
|
+
"""
|
|
337
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
338
|
+
|
|
339
|
+
@queue_until_user_message()
|
|
340
|
+
async def update_step(self, step_dict: "StepDict"):
|
|
341
|
+
if self.show_logger:
|
|
342
|
+
logger.info(f"SQLAlchemy: update_step, step_id={step_dict.get('id')}")
|
|
343
|
+
await self.create_step(step_dict)
|
|
344
|
+
|
|
345
|
+
@queue_until_user_message()
|
|
346
|
+
async def delete_step(self, step_id: str):
|
|
347
|
+
if self.show_logger:
|
|
348
|
+
logger.info(f"SQLAlchemy: delete_step, step_id={step_id}")
|
|
349
|
+
# Delete feedbacks/elements/steps
|
|
350
|
+
feedbacks_query = """DELETE FROM feedbacks WHERE "forId" = :id"""
|
|
351
|
+
elements_query = """DELETE FROM elements WHERE "forId" = :id"""
|
|
352
|
+
steps_query = """DELETE FROM steps WHERE "id" = :id"""
|
|
353
|
+
parameters = {"id": step_id}
|
|
354
|
+
await self.execute_sql(query=feedbacks_query, parameters=parameters)
|
|
355
|
+
await self.execute_sql(query=elements_query, parameters=parameters)
|
|
356
|
+
await self.execute_sql(query=steps_query, parameters=parameters)
|
|
357
|
+
|
|
358
|
+
###### Feedback ######
|
|
359
|
+
async def upsert_feedback(self, feedback: Feedback) -> str:
|
|
360
|
+
if self.show_logger:
|
|
361
|
+
logger.info(f"SQLAlchemy: upsert_feedback, feedback_id={feedback.id}")
|
|
362
|
+
feedback.id = feedback.id or str(uuid.uuid4())
|
|
363
|
+
feedback_dict = asdict(feedback)
|
|
364
|
+
parameters = {
|
|
365
|
+
key: value for key, value in feedback_dict.items() if value is not None
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
columns = ", ".join(f'"{key}"' for key in parameters.keys())
|
|
369
|
+
values = ", ".join(f":{key}" for key in parameters.keys())
|
|
370
|
+
updates = ", ".join(
|
|
371
|
+
f'"{key}" = :{key}' for key in parameters.keys() if key != "id"
|
|
372
|
+
)
|
|
373
|
+
query = f"""
|
|
374
|
+
INSERT INTO feedbacks ({columns})
|
|
375
|
+
VALUES ({values})
|
|
376
|
+
ON CONFLICT (id) DO UPDATE
|
|
377
|
+
SET {updates};
|
|
378
|
+
"""
|
|
379
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
380
|
+
return feedback.id
|
|
381
|
+
|
|
382
|
+
async def delete_feedback(self, feedback_id: str) -> bool:
|
|
383
|
+
if self.show_logger:
|
|
384
|
+
logger.info(f"SQLAlchemy: delete_feedback, feedback_id={feedback_id}")
|
|
385
|
+
query = """DELETE FROM feedbacks WHERE "id" = :feedback_id"""
|
|
386
|
+
parameters = {"feedback_id": feedback_id}
|
|
387
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
388
|
+
return True
|
|
389
|
+
|
|
390
|
+
###### Elements ######
|
|
391
|
+
@queue_until_user_message()
|
|
392
|
+
async def create_element(self, element: "Element"):
|
|
393
|
+
if self.show_logger:
|
|
394
|
+
logger.info(f"SQLAlchemy: create_element, element_id = {element.id}")
|
|
395
|
+
if not getattr(context.session.user, "id", None):
|
|
396
|
+
raise ValueError("No authenticated user in context")
|
|
397
|
+
if not self.storage_provider:
|
|
398
|
+
logger.warn(
|
|
399
|
+
f"SQLAlchemy: create_element error. No blob_storage_client is configured!"
|
|
400
|
+
)
|
|
401
|
+
return
|
|
402
|
+
if not element.for_id:
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
content: Optional[Union[bytes, str]] = None
|
|
406
|
+
|
|
407
|
+
if element.path:
|
|
408
|
+
async with aiofiles.open(element.path, "rb") as f:
|
|
409
|
+
content = await f.read()
|
|
410
|
+
elif element.url:
|
|
411
|
+
async with aiohttp.ClientSession() as session:
|
|
412
|
+
async with session.get(element.url) as response:
|
|
413
|
+
if response.status == 200:
|
|
414
|
+
content = await response.read()
|
|
415
|
+
else:
|
|
416
|
+
content = None
|
|
417
|
+
elif element.content:
|
|
418
|
+
content = element.content
|
|
419
|
+
else:
|
|
420
|
+
raise ValueError("Element url, path or content must be provided")
|
|
421
|
+
if content is None:
|
|
422
|
+
raise ValueError("Content is None, cannot upload file")
|
|
423
|
+
|
|
424
|
+
context_user = context.session.user
|
|
425
|
+
|
|
426
|
+
user_folder = getattr(context_user, "id", "unknown")
|
|
427
|
+
file_object_key = f"{user_folder}/{element.id}" + (
|
|
428
|
+
f"/{element.name}" if element.name else ""
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
if not element.mime:
|
|
432
|
+
element.mime = "application/octet-stream"
|
|
433
|
+
|
|
434
|
+
uploaded_file = await self.storage_provider.upload_file(
|
|
435
|
+
object_key=file_object_key, data=content, mime=element.mime, overwrite=True
|
|
436
|
+
)
|
|
437
|
+
if not uploaded_file:
|
|
438
|
+
raise ValueError(
|
|
439
|
+
"SQLAlchemy Error: create_element, Failed to persist data in storage_provider"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
element_dict: ElementDict = element.to_dict()
|
|
443
|
+
|
|
444
|
+
element_dict["url"] = uploaded_file.get("url")
|
|
445
|
+
element_dict["objectKey"] = uploaded_file.get("object_key")
|
|
446
|
+
element_dict_cleaned = {k: v for k, v in element_dict.items() if v is not None}
|
|
447
|
+
|
|
448
|
+
columns = ", ".join(f'"{column}"' for column in element_dict_cleaned.keys())
|
|
449
|
+
placeholders = ", ".join(f":{column}" for column in element_dict_cleaned.keys())
|
|
450
|
+
query = f"INSERT INTO elements ({columns}) VALUES ({placeholders})"
|
|
451
|
+
await self.execute_sql(query=query, parameters=element_dict_cleaned)
|
|
452
|
+
|
|
453
|
+
@queue_until_user_message()
|
|
454
|
+
async def delete_element(self, element_id: str, thread_id: Optional[str] = None):
|
|
455
|
+
if self.show_logger:
|
|
456
|
+
logger.info(f"SQLAlchemy: delete_element, element_id={element_id}")
|
|
457
|
+
query = """DELETE FROM elements WHERE "id" = :id"""
|
|
458
|
+
parameters = {"id": element_id}
|
|
459
|
+
await self.execute_sql(query=query, parameters=parameters)
|
|
460
|
+
|
|
461
|
+
async def delete_user_session(self, id: str) -> bool:
|
|
462
|
+
return False # Not sure why documentation wants this
|
|
463
|
+
|
|
464
|
+
async def get_all_user_threads(
|
|
465
|
+
self, user_id: Optional[str] = None, thread_id: Optional[str] = None
|
|
466
|
+
) -> Optional[List[ThreadDict]]:
|
|
467
|
+
"""Fetch all user threads up to self.user_thread_limit, or one thread by id if thread_id is provided."""
|
|
468
|
+
if self.show_logger:
|
|
469
|
+
logger.info(f"SQLAlchemy: get_all_user_threads")
|
|
470
|
+
user_threads_query = """
|
|
471
|
+
SELECT
|
|
472
|
+
"id" AS thread_id,
|
|
473
|
+
"createdAt" AS thread_createdat,
|
|
474
|
+
"name" AS thread_name,
|
|
475
|
+
"userId" AS user_id,
|
|
476
|
+
"userIdentifier" AS user_identifier,
|
|
477
|
+
"tags" AS thread_tags,
|
|
478
|
+
"metadata" AS thread_metadata
|
|
479
|
+
FROM threads
|
|
480
|
+
WHERE "userId" = :user_id OR "id" = :thread_id
|
|
481
|
+
ORDER BY "createdAt" DESC
|
|
482
|
+
LIMIT :limit
|
|
483
|
+
"""
|
|
484
|
+
user_threads = await self.execute_sql(
|
|
485
|
+
query=user_threads_query,
|
|
486
|
+
parameters={
|
|
487
|
+
"user_id": user_id,
|
|
488
|
+
"limit": self.user_thread_limit,
|
|
489
|
+
"thread_id": thread_id,
|
|
490
|
+
},
|
|
491
|
+
)
|
|
492
|
+
if not isinstance(user_threads, list):
|
|
493
|
+
return None
|
|
494
|
+
if not user_threads:
|
|
495
|
+
return []
|
|
496
|
+
else:
|
|
497
|
+
thread_ids = (
|
|
498
|
+
"('"
|
|
499
|
+
+ "','".join(map(str, [thread["thread_id"] for thread in user_threads]))
|
|
500
|
+
+ "')"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
steps_feedbacks_query = f"""
|
|
504
|
+
SELECT
|
|
505
|
+
s."id" AS step_id,
|
|
506
|
+
s."name" AS step_name,
|
|
507
|
+
s."type" AS step_type,
|
|
508
|
+
s."threadId" AS step_threadid,
|
|
509
|
+
s."parentId" AS step_parentid,
|
|
510
|
+
s."streaming" AS step_streaming,
|
|
511
|
+
s."waitForAnswer" AS step_waitforanswer,
|
|
512
|
+
s."isError" AS step_iserror,
|
|
513
|
+
s."metadata" AS step_metadata,
|
|
514
|
+
s."tags" AS step_tags,
|
|
515
|
+
s."input" AS step_input,
|
|
516
|
+
s."output" AS step_output,
|
|
517
|
+
s."createdAt" AS step_createdat,
|
|
518
|
+
s."start" AS step_start,
|
|
519
|
+
s."end" AS step_end,
|
|
520
|
+
s."generation" AS step_generation,
|
|
521
|
+
s."showInput" AS step_showinput,
|
|
522
|
+
s."language" AS step_language,
|
|
523
|
+
s."indent" AS step_indent,
|
|
524
|
+
f."value" AS feedback_value,
|
|
525
|
+
f."comment" AS feedback_comment
|
|
526
|
+
FROM steps s LEFT JOIN feedbacks f ON s."id" = f."forId"
|
|
527
|
+
WHERE s."threadId" IN {thread_ids}
|
|
528
|
+
ORDER BY s."createdAt" ASC
|
|
529
|
+
"""
|
|
530
|
+
steps_feedbacks = await self.execute_sql(
|
|
531
|
+
query=steps_feedbacks_query, parameters={}
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
elements_query = f"""
|
|
535
|
+
SELECT
|
|
536
|
+
e."id" AS element_id,
|
|
537
|
+
e."threadId" as element_threadid,
|
|
538
|
+
e."type" AS element_type,
|
|
539
|
+
e."chainlitKey" AS element_chainlitkey,
|
|
540
|
+
e."url" AS element_url,
|
|
541
|
+
e."objectKey" as element_objectkey,
|
|
542
|
+
e."name" AS element_name,
|
|
543
|
+
e."display" AS element_display,
|
|
544
|
+
e."size" AS element_size,
|
|
545
|
+
e."language" AS element_language,
|
|
546
|
+
e."page" AS element_page,
|
|
547
|
+
e."forId" AS element_forid,
|
|
548
|
+
e."mime" AS element_mime
|
|
549
|
+
FROM elements e
|
|
550
|
+
WHERE e."threadId" IN {thread_ids}
|
|
551
|
+
"""
|
|
552
|
+
elements = await self.execute_sql(query=elements_query, parameters={})
|
|
553
|
+
|
|
554
|
+
thread_dicts = {}
|
|
555
|
+
for thread in user_threads:
|
|
556
|
+
thread_id = thread["thread_id"]
|
|
557
|
+
if thread_id is not None:
|
|
558
|
+
thread_dicts[thread_id] = ThreadDict(
|
|
559
|
+
id=thread_id,
|
|
560
|
+
createdAt=thread["thread_createdat"],
|
|
561
|
+
name=thread["thread_name"],
|
|
562
|
+
userId=thread["user_id"],
|
|
563
|
+
userIdentifier=thread["user_identifier"],
|
|
564
|
+
tags=thread["thread_tags"],
|
|
565
|
+
metadata=thread["thread_metadata"],
|
|
566
|
+
steps=[],
|
|
567
|
+
elements=[],
|
|
568
|
+
)
|
|
569
|
+
# Process steps_feedbacks to populate the steps in the corresponding ThreadDict
|
|
570
|
+
if isinstance(steps_feedbacks, list):
|
|
571
|
+
for step_feedback in steps_feedbacks:
|
|
572
|
+
thread_id = step_feedback["step_threadid"]
|
|
573
|
+
if thread_id is not None:
|
|
574
|
+
feedback = None
|
|
575
|
+
if step_feedback["feedback_value"] is not None:
|
|
576
|
+
feedback = FeedbackDict(
|
|
577
|
+
forId=step_feedback["step_id"],
|
|
578
|
+
id=step_feedback.get("feedback_id"),
|
|
579
|
+
value=step_feedback["feedback_value"],
|
|
580
|
+
comment=step_feedback.get("feedback_comment"),
|
|
581
|
+
)
|
|
582
|
+
step_dict = StepDict(
|
|
583
|
+
id=step_feedback["step_id"],
|
|
584
|
+
name=step_feedback["step_name"],
|
|
585
|
+
type=step_feedback["step_type"],
|
|
586
|
+
threadId=thread_id,
|
|
587
|
+
parentId=step_feedback.get("step_parentid"),
|
|
588
|
+
streaming=step_feedback.get("step_streaming", False),
|
|
589
|
+
waitForAnswer=step_feedback.get("step_waitforanswer"),
|
|
590
|
+
isError=step_feedback.get("step_iserror"),
|
|
591
|
+
metadata=(
|
|
592
|
+
step_feedback["step_metadata"]
|
|
593
|
+
if step_feedback.get("step_metadata") is not None
|
|
594
|
+
else {}
|
|
595
|
+
),
|
|
596
|
+
tags=step_feedback.get("step_tags"),
|
|
597
|
+
input=(
|
|
598
|
+
step_feedback.get("step_input", "")
|
|
599
|
+
if step_feedback["step_showinput"] == "true"
|
|
600
|
+
else None
|
|
601
|
+
),
|
|
602
|
+
output=step_feedback.get("step_output", ""),
|
|
603
|
+
createdAt=step_feedback.get("step_createdat"),
|
|
604
|
+
start=step_feedback.get("step_start"),
|
|
605
|
+
end=step_feedback.get("step_end"),
|
|
606
|
+
generation=step_feedback.get("step_generation"),
|
|
607
|
+
showInput=step_feedback.get("step_showinput"),
|
|
608
|
+
language=step_feedback.get("step_language"),
|
|
609
|
+
indent=step_feedback.get("step_indent"),
|
|
610
|
+
feedback=feedback,
|
|
611
|
+
)
|
|
612
|
+
# Append the step to the steps list of the corresponding ThreadDict
|
|
613
|
+
thread_dicts[thread_id]["steps"].append(step_dict)
|
|
614
|
+
|
|
615
|
+
if isinstance(elements, list):
|
|
616
|
+
for element in elements:
|
|
617
|
+
thread_id = element["element_threadid"]
|
|
618
|
+
if thread_id is not None:
|
|
619
|
+
element_dict = ElementDict(
|
|
620
|
+
id=element["element_id"],
|
|
621
|
+
threadId=thread_id,
|
|
622
|
+
type=element["element_type"],
|
|
623
|
+
chainlitKey=element.get("element_chainlitkey"),
|
|
624
|
+
url=element.get("element_url"),
|
|
625
|
+
objectKey=element.get("element_objectkey"),
|
|
626
|
+
name=element["element_name"],
|
|
627
|
+
display=element["element_display"],
|
|
628
|
+
size=element.get("element_size"),
|
|
629
|
+
language=element.get("element_language"),
|
|
630
|
+
autoPlay=element.get("element_autoPlay"),
|
|
631
|
+
playerConfig=element.get("element_playerconfig"),
|
|
632
|
+
page=element.get("element_page"),
|
|
633
|
+
forId=element.get("element_forid"),
|
|
634
|
+
mime=element.get("element_mime"),
|
|
635
|
+
)
|
|
636
|
+
thread_dicts[thread_id]["elements"].append(element_dict) # type: ignore
|
|
637
|
+
|
|
638
|
+
return list(thread_dicts.values())
|
praisonai/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|