chainlit 1.1.403rc0__py3-none-any.whl → 1.2.0__py3-none-any.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 chainlit might be problematic. Click here for more details.
- chainlit/__init__.py +53 -305
- chainlit/_utils.py +8 -0
- chainlit/callbacks.py +308 -0
- chainlit/config.py +60 -33
- chainlit/copilot/dist/index.js +510 -629
- chainlit/data/__init__.py +6 -512
- chainlit/data/base.py +121 -0
- chainlit/data/dynamodb.py +2 -5
- chainlit/data/literalai.py +395 -0
- chainlit/data/sql_alchemy.py +10 -9
- chainlit/data/storage_clients.py +69 -15
- chainlit/data/utils.py +29 -0
- chainlit/element.py +3 -7
- chainlit/frontend/dist/assets/{DailyMotion-dd3d0f11.js → DailyMotion-05f4fe48.js} +1 -1
- chainlit/frontend/dist/assets/{Facebook-53a09094.js → Facebook-f25411d1.js} +1 -1
- chainlit/frontend/dist/assets/{FilePlayer-7fd97f72.js → FilePlayer-40ff3414.js} +1 -1
- chainlit/frontend/dist/assets/{Kaltura-127ca0f7.js → Kaltura-6cbf3897.js} +1 -1
- chainlit/frontend/dist/assets/{Mixcloud-14459c9d.js → Mixcloud-34e7c912.js} +1 -1
- chainlit/frontend/dist/assets/{Mux-913f2511.js → Mux-8aaff6ac.js} +1 -1
- chainlit/frontend/dist/assets/{Preview-cb48c96c.js → Preview-2d3bf558.js} +1 -1
- chainlit/frontend/dist/assets/{SoundCloud-f416790b.js → SoundCloud-b835f90f.js} +1 -1
- chainlit/frontend/dist/assets/{Streamable-304738e0.js → Streamable-1293e4f3.js} +1 -1
- chainlit/frontend/dist/assets/{Twitch-0fdf7e43.js → Twitch-c69660cd.js} +1 -1
- chainlit/frontend/dist/assets/{Vidyard-0fb7aefe.js → Vidyard-43bda599.js} +1 -1
- chainlit/frontend/dist/assets/{Vimeo-10a415ff.js → Vimeo-54150039.js} +1 -1
- chainlit/frontend/dist/assets/{Wistia-7239a75e.js → Wistia-aa3c721b.js} +1 -1
- chainlit/frontend/dist/assets/{YouTube-f2b37e5e.js → YouTube-dd0f3cc2.js} +1 -1
- chainlit/frontend/dist/assets/index-cf48bedd.js +729 -0
- chainlit/frontend/dist/assets/react-plotly-f52a41eb.js +3484 -0
- chainlit/frontend/dist/index.html +1 -1
- chainlit/langchain/callbacks.py +6 -1
- chainlit/llama_index/callbacks.py +21 -5
- chainlit/markdown.py +15 -9
- chainlit/message.py +0 -1
- chainlit/server.py +90 -36
- chainlit/session.py +4 -1
- chainlit/translations/bn.json +231 -0
- chainlit/translations/gu.json +231 -0
- chainlit/translations/he-IL.json +231 -0
- chainlit/translations/hi.json +231 -0
- chainlit/translations/kn.json +231 -0
- chainlit/translations/ml.json +231 -0
- chainlit/translations/mr.json +231 -0
- chainlit/translations/ta.json +231 -0
- chainlit/translations/te.json +231 -0
- chainlit/utils.py +1 -1
- {chainlit-1.1.403rc0.dist-info → chainlit-1.2.0.dist-info}/METADATA +5 -6
- chainlit-1.2.0.dist-info/RECORD +96 -0
- chainlit/frontend/dist/assets/index-b8952cd9.js +0 -730
- chainlit/frontend/dist/assets/react-plotly-cae83415.js +0 -3602
- chainlit-1.1.403rc0.dist-info/RECORD +0 -82
- {chainlit-1.1.403rc0.dist-info → chainlit-1.2.0.dist-info}/WHEEL +0 -0
- {chainlit-1.1.403rc0.dist-info → chainlit-1.2.0.dist-info}/entry_points.txt +0 -0
chainlit/data/__init__.py
CHANGED
|
@@ -1,525 +1,19 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import json
|
|
3
1
|
import os
|
|
4
|
-
from
|
|
5
|
-
from typing import (
|
|
6
|
-
TYPE_CHECKING,
|
|
7
|
-
Any,
|
|
8
|
-
Dict,
|
|
9
|
-
List,
|
|
10
|
-
Literal,
|
|
11
|
-
Optional,
|
|
12
|
-
Protocol,
|
|
13
|
-
Union,
|
|
14
|
-
cast,
|
|
15
|
-
)
|
|
2
|
+
from typing import Optional
|
|
16
3
|
|
|
17
|
-
import
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
|
|
21
|
-
from chainlit.types import (
|
|
22
|
-
Feedback,
|
|
23
|
-
PageInfo,
|
|
24
|
-
PaginatedResponse,
|
|
25
|
-
Pagination,
|
|
26
|
-
ThreadDict,
|
|
27
|
-
ThreadFilter,
|
|
4
|
+
from .base import BaseDataLayer
|
|
5
|
+
from .literalai import LiteralDataLayer
|
|
6
|
+
from .utils import (
|
|
7
|
+
queue_until_user_message as queue_until_user_message, # TODO: Consider deprecating re-export.; Redundant alias tells type checkers to STFU.
|
|
28
8
|
)
|
|
29
|
-
from chainlit.user import PersistedUser, User
|
|
30
|
-
from literalai import Attachment
|
|
31
|
-
from literalai import Score as LiteralScore
|
|
32
|
-
from literalai import Step as LiteralStep
|
|
33
|
-
from literalai.filter import threads_filters as LiteralThreadsFilters
|
|
34
|
-
from literalai.step import StepDict as LiteralStepDict
|
|
35
|
-
|
|
36
|
-
if TYPE_CHECKING:
|
|
37
|
-
from chainlit.element import Element, ElementDict
|
|
38
|
-
from chainlit.step import FeedbackDict, StepDict
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def queue_until_user_message():
|
|
42
|
-
def decorator(method):
|
|
43
|
-
@functools.wraps(method)
|
|
44
|
-
async def wrapper(self, *args, **kwargs):
|
|
45
|
-
if (
|
|
46
|
-
isinstance(context.session, WebsocketSession)
|
|
47
|
-
and not context.session.has_first_interaction
|
|
48
|
-
):
|
|
49
|
-
# Queue the method invocation waiting for the first user message
|
|
50
|
-
queues = context.session.thread_queues
|
|
51
|
-
method_name = method.__name__
|
|
52
|
-
if method_name not in queues:
|
|
53
|
-
queues[method_name] = deque()
|
|
54
|
-
queues[method_name].append((method, self, args, kwargs))
|
|
55
|
-
|
|
56
|
-
else:
|
|
57
|
-
# Otherwise, Execute the method immediately
|
|
58
|
-
return await method(self, *args, **kwargs)
|
|
59
|
-
|
|
60
|
-
return wrapper
|
|
61
|
-
|
|
62
|
-
return decorator
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class BaseDataLayer:
|
|
66
|
-
"""Base class for data persistence."""
|
|
67
|
-
|
|
68
|
-
async def get_user(self, identifier: str) -> Optional["PersistedUser"]:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
async def create_user(self, user: "User") -> Optional["PersistedUser"]:
|
|
72
|
-
pass
|
|
73
|
-
|
|
74
|
-
async def delete_feedback(
|
|
75
|
-
self,
|
|
76
|
-
feedback_id: str,
|
|
77
|
-
) -> bool:
|
|
78
|
-
return True
|
|
79
|
-
|
|
80
|
-
async def upsert_feedback(
|
|
81
|
-
self,
|
|
82
|
-
feedback: Feedback,
|
|
83
|
-
) -> str:
|
|
84
|
-
return ""
|
|
85
|
-
|
|
86
|
-
@queue_until_user_message()
|
|
87
|
-
async def create_element(self, element: "Element"):
|
|
88
|
-
pass
|
|
89
|
-
|
|
90
|
-
async def get_element(
|
|
91
|
-
self, thread_id: str, element_id: str
|
|
92
|
-
) -> Optional["ElementDict"]:
|
|
93
|
-
pass
|
|
94
|
-
|
|
95
|
-
@queue_until_user_message()
|
|
96
|
-
async def delete_element(self, element_id: str, thread_id: Optional[str] = None):
|
|
97
|
-
pass
|
|
98
|
-
|
|
99
|
-
@queue_until_user_message()
|
|
100
|
-
async def create_step(self, step_dict: "StepDict"):
|
|
101
|
-
pass
|
|
102
|
-
|
|
103
|
-
@queue_until_user_message()
|
|
104
|
-
async def update_step(self, step_dict: "StepDict"):
|
|
105
|
-
pass
|
|
106
|
-
|
|
107
|
-
@queue_until_user_message()
|
|
108
|
-
async def delete_step(self, step_id: str):
|
|
109
|
-
pass
|
|
110
|
-
|
|
111
|
-
async def get_thread_author(self, thread_id: str) -> str:
|
|
112
|
-
return ""
|
|
113
|
-
|
|
114
|
-
async def delete_thread(self, thread_id: str):
|
|
115
|
-
pass
|
|
116
|
-
|
|
117
|
-
async def list_threads(
|
|
118
|
-
self, pagination: "Pagination", filters: "ThreadFilter"
|
|
119
|
-
) -> "PaginatedResponse[ThreadDict]":
|
|
120
|
-
return PaginatedResponse(
|
|
121
|
-
data=[],
|
|
122
|
-
pageInfo=PageInfo(hasNextPage=False, startCursor=None, endCursor=None),
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
|
|
126
|
-
return None
|
|
127
|
-
|
|
128
|
-
async def update_thread(
|
|
129
|
-
self,
|
|
130
|
-
thread_id: str,
|
|
131
|
-
name: Optional[str] = None,
|
|
132
|
-
user_id: Optional[str] = None,
|
|
133
|
-
metadata: Optional[Dict] = None,
|
|
134
|
-
tags: Optional[List[str]] = None,
|
|
135
|
-
):
|
|
136
|
-
pass
|
|
137
|
-
|
|
138
|
-
async def delete_user_session(self, id: str) -> bool:
|
|
139
|
-
return True
|
|
140
|
-
|
|
141
|
-
async def build_debug_url(self) -> str:
|
|
142
|
-
return ""
|
|
143
|
-
|
|
144
9
|
|
|
145
10
|
_data_layer: Optional[BaseDataLayer] = None
|
|
146
11
|
|
|
147
12
|
|
|
148
|
-
class ChainlitDataLayer(BaseDataLayer):
|
|
149
|
-
def __init__(self, api_key: str, server: Optional[str]):
|
|
150
|
-
from literalai import AsyncLiteralClient
|
|
151
|
-
|
|
152
|
-
self.client = AsyncLiteralClient(api_key=api_key, url=server)
|
|
153
|
-
logger.info("Chainlit data layer initialized")
|
|
154
|
-
|
|
155
|
-
def attachment_to_element_dict(self, attachment: Attachment) -> "ElementDict":
|
|
156
|
-
metadata = attachment.metadata or {}
|
|
157
|
-
return {
|
|
158
|
-
"chainlitKey": None,
|
|
159
|
-
"display": metadata.get("display", "side"),
|
|
160
|
-
"language": metadata.get("language"),
|
|
161
|
-
"autoPlay": metadata.get("autoPlay", None),
|
|
162
|
-
"playerConfig": metadata.get("playerConfig", None),
|
|
163
|
-
"page": metadata.get("page"),
|
|
164
|
-
"size": metadata.get("size"),
|
|
165
|
-
"type": metadata.get("type", "file"),
|
|
166
|
-
"forId": attachment.step_id,
|
|
167
|
-
"id": attachment.id or "",
|
|
168
|
-
"mime": attachment.mime,
|
|
169
|
-
"name": attachment.name or "",
|
|
170
|
-
"objectKey": attachment.object_key,
|
|
171
|
-
"url": attachment.url,
|
|
172
|
-
"threadId": attachment.thread_id,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
def score_to_feedback_dict(
|
|
176
|
-
self, score: Optional[LiteralScore]
|
|
177
|
-
) -> "Optional[FeedbackDict]":
|
|
178
|
-
if not score:
|
|
179
|
-
return None
|
|
180
|
-
return {
|
|
181
|
-
"id": score.id or "",
|
|
182
|
-
"forId": score.step_id or "",
|
|
183
|
-
"value": cast(Literal[0, 1], score.value),
|
|
184
|
-
"comment": score.comment,
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
def step_to_step_dict(self, step: LiteralStep) -> "StepDict":
|
|
188
|
-
metadata = step.metadata or {}
|
|
189
|
-
input = (step.input or {}).get("content") or (
|
|
190
|
-
json.dumps(step.input) if step.input and step.input != {} else ""
|
|
191
|
-
)
|
|
192
|
-
output = (step.output or {}).get("content") or (
|
|
193
|
-
json.dumps(step.output) if step.output and step.output != {} else ""
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
user_feedback = (
|
|
197
|
-
next(
|
|
198
|
-
(
|
|
199
|
-
s
|
|
200
|
-
for s in step.scores
|
|
201
|
-
if s.type == "HUMAN" and s.name == "user-feedback"
|
|
202
|
-
),
|
|
203
|
-
None,
|
|
204
|
-
)
|
|
205
|
-
if step.scores
|
|
206
|
-
else None
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
"createdAt": step.created_at,
|
|
211
|
-
"id": step.id or "",
|
|
212
|
-
"threadId": step.thread_id or "",
|
|
213
|
-
"parentId": step.parent_id,
|
|
214
|
-
"feedback": self.score_to_feedback_dict(user_feedback),
|
|
215
|
-
"start": step.start_time,
|
|
216
|
-
"end": step.end_time,
|
|
217
|
-
"type": step.type or "undefined",
|
|
218
|
-
"name": step.name or "",
|
|
219
|
-
"generation": step.generation.to_dict() if step.generation else None,
|
|
220
|
-
"input": input,
|
|
221
|
-
"output": output,
|
|
222
|
-
"showInput": metadata.get("showInput", False),
|
|
223
|
-
"indent": metadata.get("indent"),
|
|
224
|
-
"language": metadata.get("language"),
|
|
225
|
-
"isError": bool(step.error),
|
|
226
|
-
"waitForAnswer": metadata.get("waitForAnswer", False),
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async def build_debug_url(self) -> str:
|
|
230
|
-
try:
|
|
231
|
-
project_id = await self.client.api.get_my_project_id()
|
|
232
|
-
return f"{self.client.api.url}/projects/{project_id}/logs/threads/[thread_id]?currentStepId=[step_id]"
|
|
233
|
-
except Exception as e:
|
|
234
|
-
logger.error(f"Error building debug url: {e}")
|
|
235
|
-
return ""
|
|
236
|
-
|
|
237
|
-
async def get_user(self, identifier: str) -> Optional[PersistedUser]:
|
|
238
|
-
user = await self.client.api.get_user(identifier=identifier)
|
|
239
|
-
if not user:
|
|
240
|
-
return None
|
|
241
|
-
return PersistedUser(
|
|
242
|
-
id=user.id or "",
|
|
243
|
-
identifier=user.identifier or "",
|
|
244
|
-
metadata=user.metadata,
|
|
245
|
-
createdAt=user.created_at or "",
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
async def create_user(self, user: User) -> Optional[PersistedUser]:
|
|
249
|
-
_user = await self.client.api.get_user(identifier=user.identifier)
|
|
250
|
-
if not _user:
|
|
251
|
-
_user = await self.client.api.create_user(
|
|
252
|
-
identifier=user.identifier, metadata=user.metadata
|
|
253
|
-
)
|
|
254
|
-
elif _user.id:
|
|
255
|
-
await self.client.api.update_user(id=_user.id, metadata=user.metadata)
|
|
256
|
-
return PersistedUser(
|
|
257
|
-
id=_user.id or "",
|
|
258
|
-
identifier=_user.identifier or "",
|
|
259
|
-
metadata=user.metadata,
|
|
260
|
-
createdAt=_user.created_at or "",
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
async def delete_feedback(
|
|
264
|
-
self,
|
|
265
|
-
feedback_id: str,
|
|
266
|
-
):
|
|
267
|
-
if feedback_id:
|
|
268
|
-
await self.client.api.delete_score(
|
|
269
|
-
id=feedback_id,
|
|
270
|
-
)
|
|
271
|
-
return True
|
|
272
|
-
return False
|
|
273
|
-
|
|
274
|
-
async def upsert_feedback(
|
|
275
|
-
self,
|
|
276
|
-
feedback: Feedback,
|
|
277
|
-
):
|
|
278
|
-
if feedback.id:
|
|
279
|
-
await self.client.api.update_score(
|
|
280
|
-
id=feedback.id,
|
|
281
|
-
update_params={
|
|
282
|
-
"comment": feedback.comment,
|
|
283
|
-
"value": feedback.value,
|
|
284
|
-
},
|
|
285
|
-
)
|
|
286
|
-
return feedback.id
|
|
287
|
-
else:
|
|
288
|
-
created = await self.client.api.create_score(
|
|
289
|
-
step_id=feedback.forId,
|
|
290
|
-
value=feedback.value,
|
|
291
|
-
comment=feedback.comment,
|
|
292
|
-
name="user-feedback",
|
|
293
|
-
type="HUMAN",
|
|
294
|
-
)
|
|
295
|
-
return created.id or ""
|
|
296
|
-
|
|
297
|
-
@queue_until_user_message()
|
|
298
|
-
async def create_element(self, element: "Element"):
|
|
299
|
-
metadata = {
|
|
300
|
-
"size": element.size,
|
|
301
|
-
"language": element.language,
|
|
302
|
-
"display": element.display,
|
|
303
|
-
"type": element.type,
|
|
304
|
-
"page": getattr(element, "page", None),
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if not element.for_id:
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
object_key = None
|
|
311
|
-
|
|
312
|
-
if not element.url:
|
|
313
|
-
if element.path:
|
|
314
|
-
async with aiofiles.open(element.path, "rb") as f:
|
|
315
|
-
content = await f.read() # type: Union[bytes, str]
|
|
316
|
-
elif element.content:
|
|
317
|
-
content = element.content
|
|
318
|
-
else:
|
|
319
|
-
raise ValueError("Either path or content must be provided")
|
|
320
|
-
uploaded = await self.client.api.upload_file(
|
|
321
|
-
content=content, mime=element.mime, thread_id=element.thread_id
|
|
322
|
-
)
|
|
323
|
-
object_key = uploaded["object_key"]
|
|
324
|
-
|
|
325
|
-
await self.client.api.send_steps(
|
|
326
|
-
[
|
|
327
|
-
{
|
|
328
|
-
"id": element.for_id,
|
|
329
|
-
"threadId": element.thread_id,
|
|
330
|
-
"attachments": [
|
|
331
|
-
{
|
|
332
|
-
"id": element.id,
|
|
333
|
-
"name": element.name,
|
|
334
|
-
"metadata": metadata,
|
|
335
|
-
"mime": element.mime,
|
|
336
|
-
"url": element.url,
|
|
337
|
-
"objectKey": object_key,
|
|
338
|
-
}
|
|
339
|
-
],
|
|
340
|
-
}
|
|
341
|
-
]
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
async def get_element(
|
|
345
|
-
self, thread_id: str, element_id: str
|
|
346
|
-
) -> Optional["ElementDict"]:
|
|
347
|
-
attachment = await self.client.api.get_attachment(id=element_id)
|
|
348
|
-
if not attachment:
|
|
349
|
-
return None
|
|
350
|
-
return self.attachment_to_element_dict(attachment)
|
|
351
|
-
|
|
352
|
-
@queue_until_user_message()
|
|
353
|
-
async def delete_element(self, element_id: str, thread_id: Optional[str] = None):
|
|
354
|
-
await self.client.api.delete_attachment(id=element_id)
|
|
355
|
-
|
|
356
|
-
@queue_until_user_message()
|
|
357
|
-
async def create_step(self, step_dict: "StepDict"):
|
|
358
|
-
metadata = dict(
|
|
359
|
-
step_dict.get("metadata", {}),
|
|
360
|
-
**{
|
|
361
|
-
"waitForAnswer": step_dict.get("waitForAnswer"),
|
|
362
|
-
"language": step_dict.get("language"),
|
|
363
|
-
"showInput": step_dict.get("showInput"),
|
|
364
|
-
},
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
step: LiteralStepDict = {
|
|
368
|
-
"createdAt": step_dict.get("createdAt"),
|
|
369
|
-
"startTime": step_dict.get("start"),
|
|
370
|
-
"endTime": step_dict.get("end"),
|
|
371
|
-
"generation": step_dict.get("generation"),
|
|
372
|
-
"id": step_dict.get("id"),
|
|
373
|
-
"parentId": step_dict.get("parentId"),
|
|
374
|
-
"name": step_dict.get("name"),
|
|
375
|
-
"threadId": step_dict.get("threadId"),
|
|
376
|
-
"type": step_dict.get("type"),
|
|
377
|
-
"tags": step_dict.get("tags"),
|
|
378
|
-
"metadata": metadata,
|
|
379
|
-
}
|
|
380
|
-
if step_dict.get("input"):
|
|
381
|
-
step["input"] = {"content": step_dict.get("input")}
|
|
382
|
-
if step_dict.get("output"):
|
|
383
|
-
step["output"] = {"content": step_dict.get("output")}
|
|
384
|
-
if step_dict.get("isError"):
|
|
385
|
-
step["error"] = step_dict.get("output")
|
|
386
|
-
|
|
387
|
-
await self.client.api.send_steps([step])
|
|
388
|
-
|
|
389
|
-
@queue_until_user_message()
|
|
390
|
-
async def update_step(self, step_dict: "StepDict"):
|
|
391
|
-
await self.create_step(step_dict)
|
|
392
|
-
|
|
393
|
-
@queue_until_user_message()
|
|
394
|
-
async def delete_step(self, step_id: str):
|
|
395
|
-
await self.client.api.delete_step(id=step_id)
|
|
396
|
-
|
|
397
|
-
async def get_thread_author(self, thread_id: str) -> str:
|
|
398
|
-
thread = await self.get_thread(thread_id)
|
|
399
|
-
if not thread:
|
|
400
|
-
return ""
|
|
401
|
-
user_identifier = thread.get("userIdentifier")
|
|
402
|
-
if not user_identifier:
|
|
403
|
-
return ""
|
|
404
|
-
|
|
405
|
-
return user_identifier
|
|
406
|
-
|
|
407
|
-
async def delete_thread(self, thread_id: str):
|
|
408
|
-
await self.client.api.delete_thread(id=thread_id)
|
|
409
|
-
|
|
410
|
-
async def list_threads(
|
|
411
|
-
self, pagination: "Pagination", filters: "ThreadFilter"
|
|
412
|
-
) -> "PaginatedResponse[ThreadDict]":
|
|
413
|
-
if not filters.userId:
|
|
414
|
-
raise ValueError("userId is required")
|
|
415
|
-
|
|
416
|
-
literal_filters: LiteralThreadsFilters = [
|
|
417
|
-
{
|
|
418
|
-
"field": "participantId",
|
|
419
|
-
"operator": "eq",
|
|
420
|
-
"value": filters.userId,
|
|
421
|
-
}
|
|
422
|
-
]
|
|
423
|
-
|
|
424
|
-
if filters.search:
|
|
425
|
-
literal_filters.append(
|
|
426
|
-
{
|
|
427
|
-
"field": "stepOutput",
|
|
428
|
-
"operator": "ilike",
|
|
429
|
-
"value": filters.search,
|
|
430
|
-
"path": "content",
|
|
431
|
-
}
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
if filters.feedback is not None:
|
|
435
|
-
literal_filters.append(
|
|
436
|
-
{
|
|
437
|
-
"field": "scoreValue",
|
|
438
|
-
"operator": "eq",
|
|
439
|
-
"value": filters.feedback,
|
|
440
|
-
"path": "user-feedback",
|
|
441
|
-
}
|
|
442
|
-
)
|
|
443
|
-
|
|
444
|
-
literal_response = await self.client.api.list_threads(
|
|
445
|
-
first=pagination.first,
|
|
446
|
-
after=pagination.cursor,
|
|
447
|
-
filters=literal_filters,
|
|
448
|
-
order_by={"column": "createdAt", "direction": "DESC"},
|
|
449
|
-
)
|
|
450
|
-
return PaginatedResponse(
|
|
451
|
-
pageInfo=PageInfo(
|
|
452
|
-
hasNextPage=literal_response.pageInfo.hasNextPage,
|
|
453
|
-
startCursor=literal_response.pageInfo.startCursor,
|
|
454
|
-
endCursor=literal_response.pageInfo.endCursor,
|
|
455
|
-
),
|
|
456
|
-
data=literal_response.data,
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
|
|
460
|
-
from chainlit.step import check_add_step_in_cot, stub_step
|
|
461
|
-
|
|
462
|
-
thread = await self.client.api.get_thread(id=thread_id)
|
|
463
|
-
if not thread:
|
|
464
|
-
return None
|
|
465
|
-
elements = [] # List[ElementDict]
|
|
466
|
-
steps = [] # List[StepDict]
|
|
467
|
-
if thread.steps:
|
|
468
|
-
for step in thread.steps:
|
|
469
|
-
for attachment in step.attachments:
|
|
470
|
-
elements.append(self.attachment_to_element_dict(attachment))
|
|
471
|
-
|
|
472
|
-
if check_add_step_in_cot(step):
|
|
473
|
-
steps.append(self.step_to_step_dict(step))
|
|
474
|
-
else:
|
|
475
|
-
steps.append(stub_step(step))
|
|
476
|
-
|
|
477
|
-
return {
|
|
478
|
-
"createdAt": thread.created_at or "",
|
|
479
|
-
"id": thread.id,
|
|
480
|
-
"name": thread.name or None,
|
|
481
|
-
"steps": steps,
|
|
482
|
-
"elements": elements,
|
|
483
|
-
"metadata": thread.metadata,
|
|
484
|
-
"userId": thread.participant_id,
|
|
485
|
-
"userIdentifier": thread.participant_identifier,
|
|
486
|
-
"tags": thread.tags,
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
async def update_thread(
|
|
490
|
-
self,
|
|
491
|
-
thread_id: str,
|
|
492
|
-
name: Optional[str] = None,
|
|
493
|
-
user_id: Optional[str] = None,
|
|
494
|
-
metadata: Optional[Dict] = None,
|
|
495
|
-
tags: Optional[List[str]] = None,
|
|
496
|
-
):
|
|
497
|
-
await self.client.api.upsert_thread(
|
|
498
|
-
id=thread_id,
|
|
499
|
-
name=name,
|
|
500
|
-
participant_id=user_id,
|
|
501
|
-
metadata=metadata,
|
|
502
|
-
tags=tags,
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
class BaseStorageClient(Protocol):
|
|
507
|
-
"""Base class for non-text data persistence like Azure Data Lake, S3, Google Storage, etc."""
|
|
508
|
-
|
|
509
|
-
async def upload_file(
|
|
510
|
-
self,
|
|
511
|
-
object_key: str,
|
|
512
|
-
data: Union[bytes, str],
|
|
513
|
-
mime: str = "application/octet-stream",
|
|
514
|
-
overwrite: bool = True,
|
|
515
|
-
) -> Dict[str, Any]:
|
|
516
|
-
pass
|
|
517
|
-
|
|
518
|
-
|
|
519
13
|
if api_key := os.environ.get("LITERAL_API_KEY"):
|
|
520
14
|
# support legacy LITERAL_SERVER variable as fallback
|
|
521
15
|
server = os.environ.get("LITERAL_API_URL") or os.environ.get("LITERAL_SERVER")
|
|
522
|
-
_data_layer =
|
|
16
|
+
_data_layer = LiteralDataLayer(api_key=api_key, server=server)
|
|
523
17
|
|
|
524
18
|
|
|
525
19
|
def get_data_layer():
|
chainlit/data/base.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from chainlit.types import (
|
|
5
|
+
Feedback,
|
|
6
|
+
PaginatedResponse,
|
|
7
|
+
Pagination,
|
|
8
|
+
ThreadDict,
|
|
9
|
+
ThreadFilter,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .utils import queue_until_user_message
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from chainlit.element import Element, ElementDict
|
|
16
|
+
from chainlit.step import StepDict
|
|
17
|
+
from chainlit.user import PersistedUser, User
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseDataLayer(ABC):
|
|
21
|
+
"""Base class for data persistence."""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
async def get_user(self, identifier: str) -> Optional["PersistedUser"]:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
async def create_user(self, user: "User") -> Optional["PersistedUser"]:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
async def delete_feedback(
|
|
33
|
+
self,
|
|
34
|
+
feedback_id: str,
|
|
35
|
+
) -> bool:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def upsert_feedback(
|
|
40
|
+
self,
|
|
41
|
+
feedback: Feedback,
|
|
42
|
+
) -> str:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@queue_until_user_message()
|
|
46
|
+
@abstractmethod
|
|
47
|
+
async def create_element(self, element: "Element"):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
async def get_element(
|
|
52
|
+
self, thread_id: str, element_id: str
|
|
53
|
+
) -> Optional["ElementDict"]:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@queue_until_user_message()
|
|
57
|
+
@abstractmethod
|
|
58
|
+
async def delete_element(self, element_id: str, thread_id: Optional[str] = None):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@queue_until_user_message()
|
|
62
|
+
@abstractmethod
|
|
63
|
+
async def create_step(self, step_dict: "StepDict"):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@queue_until_user_message()
|
|
67
|
+
@abstractmethod
|
|
68
|
+
async def update_step(self, step_dict: "StepDict"):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@queue_until_user_message()
|
|
72
|
+
@abstractmethod
|
|
73
|
+
async def delete_step(self, step_id: str):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
async def get_thread_author(self, thread_id: str) -> str:
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
async def delete_thread(self, thread_id: str):
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
async def list_threads(
|
|
86
|
+
self, pagination: "Pagination", filters: "ThreadFilter"
|
|
87
|
+
) -> "PaginatedResponse[ThreadDict]":
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
async def update_thread(
|
|
96
|
+
self,
|
|
97
|
+
thread_id: str,
|
|
98
|
+
name: Optional[str] = None,
|
|
99
|
+
user_id: Optional[str] = None,
|
|
100
|
+
metadata: Optional[Dict] = None,
|
|
101
|
+
tags: Optional[List[str]] = None,
|
|
102
|
+
):
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
async def build_debug_url(self) -> str:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class BaseStorageClient(ABC):
|
|
111
|
+
"""Base class for non-text data persistence like Azure Data Lake, S3, Google Storage, etc."""
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
async def upload_file(
|
|
115
|
+
self,
|
|
116
|
+
object_key: str,
|
|
117
|
+
data: Union[bytes, str],
|
|
118
|
+
mime: str = "application/octet-stream",
|
|
119
|
+
overwrite: bool = True,
|
|
120
|
+
) -> Dict[str, Any]:
|
|
121
|
+
pass
|
chainlit/data/dynamodb.py
CHANGED
|
@@ -12,7 +12,8 @@ import aiohttp
|
|
|
12
12
|
import boto3 # type: ignore
|
|
13
13
|
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
|
|
14
14
|
from chainlit.context import context
|
|
15
|
-
from chainlit.data import BaseDataLayer, BaseStorageClient
|
|
15
|
+
from chainlit.data.base import BaseDataLayer, BaseStorageClient
|
|
16
|
+
from chainlit.data.utils import queue_until_user_message
|
|
16
17
|
from chainlit.element import ElementDict
|
|
17
18
|
from chainlit.logger import logger
|
|
18
19
|
from chainlit.step import StepDict
|
|
@@ -36,7 +37,6 @@ _logger.setLevel(logging.WARNING)
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
class DynamoDBDataLayer(BaseDataLayer):
|
|
39
|
-
|
|
40
40
|
def __init__(
|
|
41
41
|
self,
|
|
42
42
|
table_name: str,
|
|
@@ -579,8 +579,5 @@ class DynamoDBDataLayer(BaseDataLayer):
|
|
|
579
579
|
updates=item,
|
|
580
580
|
)
|
|
581
581
|
|
|
582
|
-
async def delete_user_session(self, id: str) -> bool:
|
|
583
|
-
return True # Not sure why documentation wants this
|
|
584
|
-
|
|
585
582
|
async def build_debug_url(self) -> str:
|
|
586
583
|
return ""
|