chainlit 0.2.111__py3-none-any.whl → 0.3.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/message.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from typing import List, Dict, Union
2
+ from abc import ABC, abstractmethod
2
3
  import uuid
3
4
  import time
4
- from abc import ABC, abstractmethod
5
- from chainlit.telemetry import trace_event
6
- from chainlit.logger import logger
7
- from chainlit.sdk import get_sdk, Chainlit
5
+ import asyncio
6
+
8
7
  from chainlit.telemetry import trace_event
8
+ from chainlit.emitter import get_emitter
9
9
  from chainlit.config import config
10
10
  from chainlit.types import (
11
11
  LLMSettings,
@@ -27,27 +27,30 @@ class MessageBase(ABC):
27
27
  id: int = None
28
28
  temp_id: str = None
29
29
  streaming = False
30
+ created_at: int = None
31
+
32
+ def __post_init__(self) -> None:
33
+ trace_event(f"init {self.__class__.__name__}")
34
+ self.temp_id = uuid.uuid4().hex
35
+ self.created_at = current_milli_time()
36
+ self.emitter = get_emitter()
37
+ if not self.emitter:
38
+ raise RuntimeError("Message should be instantiated in a Chainlit context")
30
39
 
31
40
  @abstractmethod
32
41
  def to_dict(self):
33
42
  pass
34
43
 
35
- def _create(self, sdk: Chainlit):
44
+ async def _create(self):
36
45
  msg_dict = self.to_dict()
37
- if sdk.client:
38
- self.id = sdk.client.create_message(msg_dict)
46
+ if self.emitter.client and not self.id:
47
+ self.id = await self.emitter.client.create_message(msg_dict)
39
48
  if self.id:
40
49
  msg_dict["id"] = self.id
41
50
 
42
- if not "id" in msg_dict:
43
- self.temp_id = uuid.uuid4().hex
44
- msg_dict["tempId"] = self.temp_id
45
-
46
- msg_dict["createdAt"] = current_milli_time()
47
-
48
51
  return msg_dict
49
52
 
50
- def update(
53
+ async def update(
51
54
  self,
52
55
  author: str = None,
53
56
  content: str = None,
@@ -60,12 +63,6 @@ class MessageBase(ABC):
60
63
  """
61
64
  trace_event("update_message")
62
65
 
63
- sdk = get_sdk()
64
-
65
- if not sdk:
66
- logger.warning("No SDK found, cannot update message")
67
- return False
68
-
69
66
  if author:
70
67
  self.author = author
71
68
  if content:
@@ -79,77 +76,54 @@ class MessageBase(ABC):
79
76
 
80
77
  msg_dict = self.to_dict()
81
78
 
82
- if sdk.client and self.id:
83
- sdk.client.update_message(self.id, msg_dict)
79
+ if self.emitter.client and self.id:
80
+ self.emitter.client.update_message(self.id, msg_dict)
84
81
  msg_dict["id"] = self.id
85
- elif self.temp_id:
86
- msg_dict["tempId"] = self.temp_id
87
- else:
88
- logger.error("Cannot update a message that has no ID")
89
- return False
90
82
 
91
- sdk.update_message(msg_dict)
83
+ await self.emitter.update_message(msg_dict)
92
84
 
93
85
  return True
94
86
 
95
- def remove(self):
87
+ async def remove(self):
96
88
  """
97
89
  Remove a message already sent to the UI.
98
90
  This will not automatically remove potential nested messages and could lead to undesirable side effects in the UI.
99
91
  """
100
92
  trace_event("remove_message")
101
93
 
102
- sdk = get_sdk()
94
+ if self.emitter.client and self.id:
95
+ await self.emitter.client.delete_message(self.id)
103
96
 
104
- if not sdk:
105
- logger.warning("No SDK found, cannot delete message")
106
- return False
107
- if sdk.client and self.id:
108
- sdk.client.delete_message(self.id)
109
- sdk.delete_message(self.id)
110
- elif self.temp_id:
111
- sdk.delete_message(self.temp_id)
112
- else:
113
- logger.error("Cannot delete a message that has no ID")
114
- return False
97
+ await self.emitter.delete_message(self.to_dict())
115
98
 
116
99
  return True
117
100
 
118
- def send(self) -> Union[str, int]:
119
- sdk = get_sdk()
101
+ async def send(self) -> Union[str, int]:
102
+ if self.content is None:
103
+ self.content = ""
120
104
 
121
- if not sdk:
122
- logger.warning("No SDK found, cannot send message")
123
- return
124
-
125
- msg_dict = self._create(sdk)
105
+ msg_dict = await self._create()
126
106
 
127
107
  if self.streaming:
128
108
  self.streaming = False
129
- sdk.stream_end(msg_dict)
130
- else:
131
- sdk.send_message(msg_dict)
109
+
110
+ await self.emitter.send_message(msg_dict)
132
111
 
133
112
  return self.id or self.temp_id
134
113
 
135
- def stream_token(self, token: str):
114
+ async def stream_token(self, token: str):
136
115
  """
137
116
  Sends a token to the UI. This is useful for streaming messages.
138
117
  Once all tokens have been streamed, call .send() to persist the message.
139
118
  """
140
- sdk = get_sdk()
141
-
142
- if not sdk:
143
- logger.warning("No SDK found, cannot send message")
144
- return
145
119
 
146
120
  if not self.streaming:
147
121
  self.streaming = True
148
122
  msg_dict = self.to_dict()
149
- sdk.stream_start(msg_dict)
123
+ await self.emitter.stream_start(msg_dict)
150
124
 
151
125
  self.content += token
152
- sdk.send_token(token)
126
+ await self.emitter.send_token(id=self.id or self.temp_id, token=token)
153
127
 
154
128
 
155
129
  class Message(MessageBase):
@@ -194,8 +168,12 @@ class Message(MessageBase):
194
168
  if llm_settings:
195
169
  self.llmSettings = llm_settings.to_dict()
196
170
 
171
+ super().__post_init__()
172
+
197
173
  def to_dict(self):
198
174
  return {
175
+ "tempId": self.temp_id,
176
+ "createdAt": self.created_at,
199
177
  "content": self.content,
200
178
  "author": self.author,
201
179
  "prompt": self.prompt,
@@ -204,19 +182,18 @@ class Message(MessageBase):
204
182
  "indent": self.indent,
205
183
  }
206
184
 
207
- def send(self):
185
+ async def send(self):
208
186
  """
209
187
  Send the message to the UI and persist it in the cloud if a project ID is configured.
210
188
  Return the ID of the message.
211
189
  """
212
190
  trace_event("send_message")
213
- id = super().send()
191
+ id = await super().send()
214
192
 
215
- for action in self.actions:
216
- action.send(for_id=str(id))
217
-
218
- for element in self.elements:
219
- element.send(for_id=str(id))
193
+ action_coros = [action.send(for_id=str(id)) for action in self.actions]
194
+ element_coros = [element.send(for_id=str(id)) for element in self.elements]
195
+ all_coros = action_coros + element_coros
196
+ await asyncio.gather(*all_coros)
220
197
 
221
198
  return id
222
199
 
@@ -242,35 +219,32 @@ class ErrorMessage(MessageBase):
242
219
  self.author = author
243
220
  self.indent = indent
244
221
 
222
+ super().__post_init__()
223
+
245
224
  def to_dict(self):
246
225
  return {
226
+ "tempId": self.temp_id,
227
+ "createdAt": self.created_at,
247
228
  "content": self.content,
248
229
  "author": self.author,
249
230
  "indent": self.indent,
250
231
  "isError": True,
251
232
  }
252
233
 
253
- def send(self):
234
+ async def send(self):
254
235
  """
255
236
  Send the error message to the UI and persist it in the cloud if a project ID is configured.
256
237
  Return the ID of the message.
257
238
  """
258
239
  trace_event("send_error_message")
259
- id = super().send()
260
-
261
- return id
240
+ return await super().send()
262
241
 
263
242
 
264
243
  class AskMessageBase(MessageBase):
265
244
  def remove(self):
266
245
  removed = super().remove()
267
246
  if removed:
268
- sdk = get_sdk()
269
-
270
- if not sdk:
271
- return
272
-
273
- sdk.clear_ask()
247
+ self.emitter.clear_ask()
274
248
 
275
249
 
276
250
  class AskUserMessage(AskMessageBase):
@@ -298,33 +272,33 @@ class AskUserMessage(AskMessageBase):
298
272
  self.timeout = timeout
299
273
  self.raise_on_timeout = raise_on_timeout
300
274
 
275
+ super().__post_init__()
276
+
301
277
  def to_dict(self):
302
278
  return {
279
+ "tempId": self.temp_id,
280
+ "createdAt": self.created_at,
303
281
  "content": self.content,
304
282
  "author": self.author,
305
283
  "waitForAnswer": True,
306
284
  }
307
285
 
308
- def send(self) -> Union[AskResponse, None]:
286
+ async def send(self) -> Union[AskResponse, None]:
309
287
  """
310
288
  Sends the question to ask to the UI and waits for the reply.
311
289
  """
312
290
  trace_event("send_ask_user")
313
291
 
314
- sdk = get_sdk()
315
-
316
- if not sdk:
317
- logger.warning("No SDK found, cannot send message")
318
- return
319
-
320
292
  if self.streaming:
321
293
  self.streaming = False
322
294
 
323
- msg_dict = self._create(sdk)
295
+ msg_dict = await self._create()
324
296
 
325
297
  spec = AskSpec(type="text", timeout=self.timeout)
326
298
 
327
- return sdk.send_ask_user(msg_dict, spec, self.raise_on_timeout)
299
+ res = await self.emitter.send_ask_user(msg_dict, spec, self.raise_on_timeout)
300
+
301
+ return res
328
302
 
329
303
 
330
304
  class AskFileMessage(AskMessageBase):
@@ -336,7 +310,8 @@ class AskFileMessage(AskMessageBase):
336
310
  Args:
337
311
  content (str): Text displayed above the upload button.
338
312
  accept (Union[List[str], Dict[str, List[str]]]): List of mime type to accept like ["text/csv", "application/pdf"] or a dict like {"text/plain": [".txt", ".py"]}.
339
- max_size_mb (int, optional): Maximum file size in MB. Maximum value is 100.
313
+ max_size_mb (int, optional): Maximum size per file in MB. Maximum value is 100.
314
+ max_files (int, optional): Maximum number of files to upload. Maximum value is 10.
340
315
  author (str, optional): The author of the message, this will be used in the UI. Defaults to the chatbot name (see config).
341
316
  timeout (int, optional): The number of seconds to wait for an answer before raising a TimeoutError.
342
317
  raise_on_timeout (bool, optional): Whether to raise a socketio TimeoutError if the user does not answer in time.
@@ -347,51 +322,52 @@ class AskFileMessage(AskMessageBase):
347
322
  content: str,
348
323
  accept: Union[List[str], Dict[str, List[str]]],
349
324
  max_size_mb=2,
325
+ max_files=1,
350
326
  author=config.chatbot_name,
351
327
  timeout=90,
352
328
  raise_on_timeout=False,
353
329
  ):
354
330
  self.content = content
355
331
  self.max_size_mb = max_size_mb
332
+ self.max_files = max_files
356
333
  self.accept = accept
357
334
  self.author = author
358
335
  self.timeout = timeout
359
336
  self.raise_on_timeout = raise_on_timeout
360
337
 
338
+ super().__post_init__()
339
+
361
340
  def to_dict(self):
362
341
  return {
342
+ "tempId": self.temp_id,
343
+ "createdAt": self.created_at,
363
344
  "content": self.content,
364
345
  "author": self.author,
365
346
  "waitForAnswer": True,
366
347
  }
367
348
 
368
- def send(self) -> Union[AskFileResponse, None]:
349
+ async def send(self) -> Union[List[AskFileResponse], None]:
369
350
  """
370
351
  Sends the message to request a file from the user to the UI and waits for the reply.
371
352
  """
372
353
  trace_event("send_ask_file")
373
354
 
374
- sdk = get_sdk()
375
-
376
- if not sdk:
377
- logger.warning("No SDK found, cannot send message")
378
- return
379
-
380
355
  if self.streaming:
381
356
  self.streaming = False
382
357
 
383
- msg_dict = self._create(sdk)
358
+ msg_dict = await self._create()
384
359
 
385
360
  spec = AskFileSpec(
386
361
  type="file",
387
362
  accept=self.accept,
388
363
  max_size_mb=self.max_size_mb,
364
+ max_files=self.max_files,
389
365
  timeout=self.timeout,
390
366
  )
391
367
 
392
- res = sdk.send_ask_user(msg_dict, spec, self.raise_on_timeout)
368
+ res = await self.emitter.send_ask_user(msg_dict, spec, self.raise_on_timeout)
393
369
 
394
370
  if res:
395
- return AskFileResponse(**res)
371
+ return [AskFileResponse(**r) for r in res]
396
372
  else:
397
373
  return None