chainlit 0.7.700__py3-none-any.whl → 1.0.0rc1__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.

Files changed (38) hide show
  1. chainlit/__init__.py +32 -23
  2. chainlit/auth.py +9 -10
  3. chainlit/cli/__init__.py +1 -2
  4. chainlit/config.py +13 -12
  5. chainlit/context.py +7 -3
  6. chainlit/data/__init__.py +375 -9
  7. chainlit/data/acl.py +6 -5
  8. chainlit/element.py +86 -123
  9. chainlit/emitter.py +117 -50
  10. chainlit/frontend/dist/assets/{index-71698725.js → index-6aee009a.js} +118 -292
  11. chainlit/frontend/dist/assets/{react-plotly-2c0acdf0.js → react-plotly-2f07c02a.js} +1 -1
  12. chainlit/frontend/dist/index.html +1 -1
  13. chainlit/haystack/callbacks.py +45 -43
  14. chainlit/hello.py +1 -1
  15. chainlit/langchain/callbacks.py +132 -120
  16. chainlit/llama_index/callbacks.py +68 -48
  17. chainlit/message.py +179 -207
  18. chainlit/oauth_providers.py +39 -34
  19. chainlit/playground/provider.py +44 -30
  20. chainlit/playground/providers/anthropic.py +4 -4
  21. chainlit/playground/providers/huggingface.py +2 -2
  22. chainlit/playground/providers/langchain.py +8 -10
  23. chainlit/playground/providers/openai.py +19 -13
  24. chainlit/server.py +155 -99
  25. chainlit/session.py +109 -40
  26. chainlit/socket.py +47 -36
  27. chainlit/step.py +393 -0
  28. chainlit/types.py +78 -21
  29. chainlit/user.py +32 -0
  30. chainlit/user_session.py +1 -5
  31. {chainlit-0.7.700.dist-info → chainlit-1.0.0rc1.dist-info}/METADATA +12 -31
  32. chainlit-1.0.0rc1.dist-info/RECORD +60 -0
  33. chainlit/client/base.py +0 -169
  34. chainlit/client/cloud.py +0 -502
  35. chainlit/prompt.py +0 -40
  36. chainlit-0.7.700.dist-info/RECORD +0 -61
  37. {chainlit-0.7.700.dist-info → chainlit-1.0.0rc1.dist-info}/WHEEL +0 -0
  38. {chainlit-0.7.700.dist-info → chainlit-1.0.0rc1.dist-info}/entry_points.txt +0 -0
chainlit/data/__init__.py CHANGED
@@ -1,13 +1,379 @@
1
+ import functools
1
2
  import os
2
- from typing import Optional
3
+ from collections import deque
4
+ from typing import TYPE_CHECKING, Dict, List, Optional
3
5
 
4
- from chainlit.client.cloud import ChainlitCloudClient
5
- from chainlit.config import config
6
+ from chainlit.context import context
7
+ from chainlit.logger import logger
8
+ from chainlit.session import WebsocketSession
9
+ from chainlit.types import Feedback, Pagination, ThreadDict, ThreadFilter
10
+ from chainlit.user import PersistedUser, User, UserDict
11
+ from chainlit_client import Attachment
12
+ from chainlit_client import Feedback as ClientFeedback
13
+ from chainlit_client import PageInfo, PaginatedResponse
14
+ from chainlit_client import Step as ClientStep
15
+ from chainlit_client.thread import NumberListFilter, StringFilter, StringListFilter
16
+ from chainlit_client.thread import ThreadFilter as ClientThreadFilter
6
17
 
7
- chainlit_client = None # type: Optional[ChainlitCloudClient]
18
+ if TYPE_CHECKING:
19
+ from chainlit.element import Element, ElementDict
20
+ from chainlit.step import FeedbackDict, StepDict
8
21
 
9
- if config.data_persistence:
10
- chainlit_client = ChainlitCloudClient(
11
- api_key=os.environ.get("CHAINLIT_API_KEY", ""),
12
- chainlit_server=config.chainlit_server,
13
- )
22
+ _data_layer = None
23
+
24
+
25
+ def queue_until_user_message():
26
+ def decorator(method):
27
+ @functools.wraps(method)
28
+ async def wrapper(self, *args, **kwargs):
29
+ if (
30
+ isinstance(context.session, WebsocketSession)
31
+ and not context.session.has_user_message
32
+ ):
33
+ # Queue the method invocation waiting for the first user message
34
+ queues = context.session.thread_queues
35
+ method_name = method.__name__
36
+ if method_name not in queues:
37
+ queues[method_name] = deque()
38
+ queues[method_name].append((method, self, args, kwargs))
39
+
40
+ else:
41
+ # Otherwise, Execute the method immediately
42
+ return await method(self, *args, **kwargs)
43
+
44
+ return wrapper
45
+
46
+ return decorator
47
+
48
+
49
+ class BaseDataLayer:
50
+ """Base class for data persistence."""
51
+
52
+ async def get_user(self, identifier: str) -> Optional["PersistedUser"]:
53
+ return None
54
+
55
+ async def create_user(self, user: "User") -> Optional["PersistedUser"]:
56
+ pass
57
+
58
+ async def upsert_feedback(
59
+ self,
60
+ feedback: Feedback,
61
+ ) -> str:
62
+ return ""
63
+
64
+ @queue_until_user_message()
65
+ async def create_element(self, element_dict: "ElementDict"):
66
+ pass
67
+
68
+ async def get_element(
69
+ self, thread_id: str, element_id: str
70
+ ) -> Optional["ElementDict"]:
71
+ pass
72
+
73
+ @queue_until_user_message()
74
+ async def delete_element(self, element_id: str):
75
+ pass
76
+
77
+ @queue_until_user_message()
78
+ async def create_step(self, step_dict: "StepDict"):
79
+ pass
80
+
81
+ @queue_until_user_message()
82
+ async def update_step(self, step_dict: "StepDict"):
83
+ pass
84
+
85
+ @queue_until_user_message()
86
+ async def delete_step(self, step_id: str):
87
+ pass
88
+
89
+ async def get_thread_author(self, thread_id: str) -> str:
90
+ return ""
91
+
92
+ async def delete_thread(self, thread_id: str):
93
+ pass
94
+
95
+ async def list_threads(
96
+ self, pagination: "Pagination", filters: "ThreadFilter"
97
+ ) -> "PaginatedResponse[ThreadDict]":
98
+ return PaginatedResponse(
99
+ data=[], pageInfo=PageInfo(hasNextPage=False, endCursor=None)
100
+ )
101
+
102
+ async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
103
+ return None
104
+
105
+ async def update_thread(
106
+ self,
107
+ thread_id: str,
108
+ user_id: Optional[str] = None,
109
+ metadata: Optional[Dict] = None,
110
+ tags: Optional[List[str]] = None,
111
+ ):
112
+ pass
113
+
114
+
115
+ class ChainlitDataLayer:
116
+ def __init__(
117
+ self, api_key: str, chainlit_server: Optional[str] = "https://cloud.chainlit.io"
118
+ ):
119
+ from chainlit_client import ChainlitClient
120
+
121
+ self.client = ChainlitClient(api_key=api_key, url=chainlit_server)
122
+ logger.info("Chainlit data layer initialized")
123
+
124
+ def attachment_to_element_dict(self, attachment: Attachment) -> "ElementDict":
125
+ metadata = attachment.metadata or {}
126
+ return {
127
+ "chainlitKey": None,
128
+ "display": metadata.get("display", "side"),
129
+ "language": metadata.get("language"),
130
+ "size": metadata.get("size"),
131
+ "type": metadata.get("type", "file"),
132
+ "forId": attachment.step_id,
133
+ "id": attachment.id or "",
134
+ "mime": attachment.mime,
135
+ "name": attachment.name or "",
136
+ "objectKey": attachment.object_key,
137
+ "url": attachment.url,
138
+ "threadId": attachment.thread_id,
139
+ }
140
+
141
+ def feedback_to_feedback_dict(
142
+ self, feedback: Optional[ClientFeedback]
143
+ ) -> "Optional[FeedbackDict]":
144
+ if not feedback:
145
+ return None
146
+ return {
147
+ "id": feedback.id or "",
148
+ "forId": feedback.step_id or "",
149
+ "value": feedback.value or 0, # type: ignore
150
+ "comment": feedback.comment,
151
+ "strategy": "BINARY",
152
+ }
153
+
154
+ def step_to_step_dict(self, step: ClientStep) -> "StepDict":
155
+ metadata = step.metadata or {}
156
+ return {
157
+ "createdAt": step.created_at,
158
+ "id": step.id or "",
159
+ "threadId": step.thread_id or "",
160
+ "parentId": step.parent_id,
161
+ "feedback": self.feedback_to_feedback_dict(step.feedback),
162
+ "start": step.start_time,
163
+ "end": step.end_time,
164
+ "type": step.type or "undefined",
165
+ "name": step.name or "",
166
+ "generation": step.generation.to_dict() if step.generation else None,
167
+ "input": step.input or "",
168
+ "output": step.output or "",
169
+ "showInput": metadata.get("showInput", False),
170
+ "disableFeedback": metadata.get("disableFeedback", False),
171
+ "indent": metadata.get("indent"),
172
+ "language": metadata.get("language"),
173
+ "isError": metadata.get("isError", False),
174
+ "waitForAnswer": metadata.get("waitForAnswer", False),
175
+ "feedback": self.feedback_to_feedback_dict(step.feedback),
176
+ }
177
+
178
+ async def get_user(self, identifier: str) -> Optional[PersistedUser]:
179
+ user = await self.client.api.get_user(identifier=identifier)
180
+ if not user:
181
+ return None
182
+ return PersistedUser(
183
+ id=user.id or "",
184
+ identifier=user.identifier or "",
185
+ metadata=user.metadata,
186
+ createdAt=user.created_at or "",
187
+ )
188
+
189
+ async def create_user(self, user: User) -> Optional[PersistedUser]:
190
+ _user = await self.client.api.get_user(identifier=user.identifier)
191
+ if not _user:
192
+ _user = await self.client.api.create_user(
193
+ identifier=user.identifier, metadata=user.metadata
194
+ )
195
+ return PersistedUser(
196
+ id=_user.id or "",
197
+ identifier=_user.identifier or "",
198
+ metadata=_user.metadata,
199
+ createdAt=_user.created_at or "",
200
+ )
201
+
202
+ async def upsert_feedback(
203
+ self,
204
+ feedback: Feedback,
205
+ ):
206
+ if feedback.id:
207
+ await self.client.api.update_feedback(
208
+ id=feedback.id,
209
+ update_params={
210
+ "comment": feedback.comment,
211
+ "strategy": feedback.strategy,
212
+ "value": feedback.value,
213
+ },
214
+ )
215
+ return feedback.id
216
+ else:
217
+ created = await self.client.api.create_feedback(
218
+ step_id=feedback.forId,
219
+ value=feedback.value,
220
+ comment=feedback.comment,
221
+ strategy=feedback.strategy,
222
+ )
223
+ return created.id or ""
224
+
225
+ @queue_until_user_message()
226
+ async def create_element(self, element: "Element"):
227
+ metadata = {
228
+ "size": element.size,
229
+ "language": element.language,
230
+ "display": element.display,
231
+ "type": element.type,
232
+ }
233
+
234
+ await self.client.api.create_attachment(
235
+ thread_id=element.thread_id,
236
+ step_id=element.for_id or "",
237
+ mime=element.mime,
238
+ name=element.name,
239
+ url=element.url,
240
+ content=element.content,
241
+ path=element.path,
242
+ metadata=metadata,
243
+ )
244
+
245
+ async def get_element(
246
+ self, thread_id: str, element_id: str
247
+ ) -> Optional["ElementDict"]:
248
+ attachment = await self.client.api.get_attachment(id=element_id)
249
+ if not attachment:
250
+ return None
251
+ return self.attachment_to_element_dict(attachment)
252
+
253
+ @queue_until_user_message()
254
+ async def delete_element(self, element_id: str):
255
+ await self.client.api.delete_attachment(id=element_id)
256
+
257
+ @queue_until_user_message()
258
+ async def create_step(self, step_dict: "StepDict"):
259
+ metadata = {
260
+ "disableFeedback": step_dict.get("disableFeedback"),
261
+ "isError": step_dict.get("isError"),
262
+ "waitForAnswer": step_dict.get("waitForAnswer"),
263
+ "language": step_dict.get("language"),
264
+ "showInput": step_dict.get("showInput"),
265
+ }
266
+
267
+ await self.client.api.send_steps(
268
+ [
269
+ {
270
+ "createdAt": step_dict.get("createdAt"),
271
+ "startTime": step_dict.get("start"),
272
+ "endTime": step_dict.get("end"),
273
+ "generation": step_dict.get("generation"),
274
+ "id": step_dict.get("id"),
275
+ "parentId": step_dict.get("parentId"),
276
+ "input": step_dict.get("input"),
277
+ "output": step_dict.get("output"),
278
+ "name": step_dict.get("name"),
279
+ "threadId": step_dict.get("threadId"),
280
+ "type": step_dict.get("type"),
281
+ "metadata": metadata,
282
+ }
283
+ ]
284
+ )
285
+
286
+ @queue_until_user_message()
287
+ async def update_step(self, step_dict: "StepDict"):
288
+ await self.create_step(step_dict)
289
+
290
+ @queue_until_user_message()
291
+ async def delete_step(self, step_id: str):
292
+ await self.client.api.delete_step(id=step_id)
293
+
294
+ async def get_thread_author(self, thread_id: str) -> str:
295
+ thread = await self.get_thread(thread_id)
296
+ if not thread:
297
+ return ""
298
+ user = thread.get("user")
299
+ if not user:
300
+ return ""
301
+ return user.get("identifier") or ""
302
+
303
+ async def delete_thread(self, thread_id: str):
304
+ await self.client.api.delete_thread(id=thread_id)
305
+
306
+ async def list_threads(
307
+ self, pagination: "Pagination", filters: "ThreadFilter"
308
+ ) -> "PaginatedResponse[ThreadDict]":
309
+ if not filters.userIdentifier:
310
+ raise ValueError("userIdentifier is required")
311
+
312
+ client_filters = ClientThreadFilter(
313
+ participantsIdentifier=StringListFilter(
314
+ operator="in", value=[filters.userIdentifier]
315
+ ),
316
+ )
317
+ if filters.search:
318
+ client_filters.search = StringFilter(operator="ilike", value=filters.search)
319
+ if filters.feedback:
320
+ client_filters.feedbacksValue = NumberListFilter(
321
+ operator="in", value=[filters.feedback]
322
+ )
323
+ return await self.client.api.list_threads(
324
+ first=pagination.first, after=pagination.cursor, filters=client_filters
325
+ )
326
+
327
+ async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
328
+ thread = await self.client.api.get_thread(id=thread_id)
329
+ if not thread:
330
+ return None
331
+ elements = [] # List[ElementDict]
332
+ steps = [] # List[StepDict]
333
+ if thread.steps:
334
+ for step in thread.steps:
335
+ for attachment in step.attachments:
336
+ elements.append(self.attachment_to_element_dict(attachment))
337
+ steps.append(self.step_to_step_dict(step))
338
+
339
+ user = None # type: Optional["UserDict"]
340
+
341
+ if thread.user:
342
+ user = {
343
+ "id": thread.user.id or "",
344
+ "identifier": thread.user.identifier or "",
345
+ "metadata": thread.user.metadata,
346
+ }
347
+
348
+ return {
349
+ "createdAt": thread.created_at or "",
350
+ "id": thread.id,
351
+ "steps": steps,
352
+ "elements": elements,
353
+ "metadata": thread.metadata,
354
+ "user": user,
355
+ "tags": thread.tags,
356
+ }
357
+
358
+ async def update_thread(
359
+ self,
360
+ thread_id: str,
361
+ user_id: Optional[str] = None,
362
+ metadata: Optional[Dict] = None,
363
+ tags: Optional[List[str]] = None,
364
+ ):
365
+ await self.client.api.upsert_thread(
366
+ thread_id=thread_id,
367
+ participant_id=user_id,
368
+ metadata=metadata,
369
+ tags=tags,
370
+ )
371
+
372
+
373
+ if api_key := os.environ.get("CHAINLIT_API_KEY"):
374
+ chainlit_server = os.environ.get("CHAINLIT_SERVER")
375
+ _data_layer = ChainlitDataLayer(api_key=api_key, chainlit_server=chainlit_server)
376
+
377
+
378
+ def get_data_layer():
379
+ return _data_layer
chainlit/data/acl.py CHANGED
@@ -1,14 +1,15 @@
1
- from chainlit.data import chainlit_client
1
+ from chainlit.data import get_data_layer
2
2
  from fastapi import HTTPException
3
3
 
4
4
 
5
- async def is_conversation_author(username: str, conversation_id: str):
6
- if not chainlit_client:
5
+ async def is_thread_author(username: str, thread_id: str):
6
+ data_layer = get_data_layer()
7
+ if not data_layer:
7
8
  raise HTTPException(status_code=401, detail="Unauthorized")
8
9
 
9
- conversation_author = await chainlit_client.get_conversation_author(conversation_id)
10
+ thread_author = await data_layer.get_thread_author(thread_id)
10
11
 
11
- if conversation_author != username:
12
+ if thread_author != username:
12
13
  raise HTTPException(status_code=401, detail="Unauthorized")
13
14
  else:
14
15
  return True