meshagent-agents 0.0.36__py3-none-any.whl → 0.0.38__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 meshagent-agents might be problematic. Click here for more details.

meshagent/agents/chat.py CHANGED
@@ -1,6 +1,14 @@
1
- from .agent import SingleRoomAgent, AgentChatContext, AgentCallContext
1
+ from .agent import SingleRoomAgent, AgentChatContext
2
2
  from meshagent.api.chan import Chan
3
- from meshagent.api import RoomMessage, RoomException, RoomClient, RemoteParticipant, RequiredSchema, Requirement, Element, MeshDocument
3
+ from meshagent.api import (
4
+ RoomMessage,
5
+ RoomClient,
6
+ RemoteParticipant,
7
+ RequiredSchema,
8
+ Requirement,
9
+ Element,
10
+ MeshDocument,
11
+ )
4
12
  from meshagent.tools import Toolkit, ToolContext
5
13
  from .adapter import LLMAdapter, ToolResponseAdapter
6
14
  from meshagent.openai.tools.responses_adapter import ImageGenerationTool
@@ -8,11 +16,9 @@ import asyncio
8
16
  from typing import Optional
9
17
  import logging
10
18
  from meshagent.tools import MultiToolkit
11
- import urllib
12
19
  import uuid
13
20
  import datetime
14
- import json
15
- from typing import Literal, Optional
21
+ from typing import Literal
16
22
  import base64
17
23
  from openai.types.responses import ResponseStreamEvent
18
24
  from asyncio import CancelledError
@@ -24,20 +30,20 @@ tracer = trace.get_tracer("meshagent.chatbot")
24
30
  logger = logging.getLogger("chat")
25
31
 
26
32
 
27
-
28
33
  class ChatBotThreadOpenAIImageGenerationTool(ImageGenerationTool):
29
- def __init__(self,
34
+ def __init__(
35
+ self,
30
36
  *,
31
- background: Literal["transparent","opaque","auto"] = None,
37
+ background: Literal["transparent", "opaque", "auto"] = None,
32
38
  input_image_mask_url: Optional[str] = None,
33
39
  model: Optional[str] = None,
34
40
  moderation: Optional[str] = None,
35
41
  output_compression: Optional[int] = None,
36
- output_format: Optional[Literal["png","webp","jpeg"]] = None,
42
+ output_format: Optional[Literal["png", "webp", "jpeg"]] = None,
37
43
  partial_images: Optional[int] = None,
38
44
  quality: Optional[Literal["auto", "low", "medium", "high"]] = None,
39
- size: Optional[Literal["1024x1024","1024x1536","1536x1024","auto"]] = None,
40
- thread_context: 'ChatThreadContext'
45
+ size: Optional[Literal["1024x1024", "1024x1536", "1536x1024", "auto"]] = None,
46
+ thread_context: "ChatThreadContext",
41
47
  ):
42
48
  super().__init__(
43
49
  background=background,
@@ -48,57 +54,80 @@ class ChatBotThreadOpenAIImageGenerationTool(ImageGenerationTool):
48
54
  output_format=output_format,
49
55
  partial_images=partial_images,
50
56
  quality=quality,
51
- size=size
57
+ size=size,
52
58
  )
53
59
 
54
60
  self.thread_context = thread_context
55
61
 
56
- async def on_image_generation_partial(self, context, *, item_id, output_index, sequence_number, type, partial_image_b64, partial_image_index, size, quality, background, output_format, **extra):
57
-
62
+ async def on_image_generation_partial(
63
+ self,
64
+ context,
65
+ *,
66
+ item_id,
67
+ output_index,
68
+ sequence_number,
69
+ type,
70
+ partial_image_b64,
71
+ partial_image_index,
72
+ size,
73
+ quality,
74
+ background,
75
+ output_format,
76
+ **extra,
77
+ ):
58
78
  output_format = self.output_format
59
- if output_format == None:
79
+ if output_format is None:
60
80
  output_format = "png"
61
81
 
62
82
  image_name = f"{str(uuid.uuid4())}.{output_format}"
63
83
 
64
84
  handle = await context.room.storage.open(path=image_name)
65
- await context.room.storage.write(handle=handle, data=base64.b64decode(partial_image_b64))
85
+ await context.room.storage.write(
86
+ handle=handle, data=base64.b64decode(partial_image_b64)
87
+ )
66
88
  await context.room.storage.close(handle=handle)
67
89
 
68
-
69
90
  messages = None
70
91
 
71
92
  for prop in self.thread_context.thread.root.get_children():
72
-
73
93
  if prop.tag_name == "messages":
74
-
75
94
  messages = prop
76
95
 
77
96
  for child in messages.get_children():
78
-
79
97
  if child.get_attribute("id") == item_id:
80
-
81
98
  for file in child.get_children():
82
99
  file.set_attribute("path", image_name)
83
100
 
84
101
  return
85
102
 
86
103
  message_element = messages.append_child(
87
- tag_name="message",
104
+ tag_name="message",
88
105
  attributes={
89
- "id" : item_id,
90
- "text" : "",
91
- "created_at" : datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00","Z"),
92
- "author_name" : context.room.local_participant.get_attribute("name"),
93
- }
106
+ "id": item_id,
107
+ "text": "",
108
+ "created_at": datetime.datetime.now(datetime.timezone.utc)
109
+ .isoformat()
110
+ .replace("+00:00", "Z"),
111
+ "author_name": context.room.local_participant.get_attribute("name"),
112
+ },
94
113
  )
95
- message_element.append_child(tag_name="file", attributes={ "path" : image_name })
114
+ message_element.append_child(tag_name="file", attributes={"path": image_name})
96
115
 
97
-
98
- async def on_image_generated(self, context: ToolContext, *, item_id: str, data: bytes, status: str, size: str, quality: str, background: str, output_format: str, **extra):
99
-
116
+ async def on_image_generated(
117
+ self,
118
+ context: ToolContext,
119
+ *,
120
+ item_id: str,
121
+ data: bytes,
122
+ status: str,
123
+ size: str,
124
+ quality: str,
125
+ background: str,
126
+ output_format: str,
127
+ **extra,
128
+ ):
100
129
  output_format = self.output_format
101
- if output_format == None:
130
+ if output_format is None:
102
131
  output_format = "png"
103
132
 
104
133
  image_name = f"{str(uuid.uuid4())}.{output_format}"
@@ -107,80 +136,94 @@ class ChatBotThreadOpenAIImageGenerationTool(ImageGenerationTool):
107
136
  await context.room.storage.write(handle=handle, data=data)
108
137
  await context.room.storage.close(handle=handle)
109
138
 
110
-
111
139
  messages = None
112
140
 
113
141
  for prop in self.thread_context.thread.root.get_children():
114
-
115
142
  if prop.tag_name == "messages":
116
-
117
143
  messages = prop
118
144
 
119
145
  for child in messages.get_children():
120
-
121
146
  if child.get_attribute("id") == item_id:
122
-
123
147
  for file in child.get_children():
124
148
  file.set_attribute("path", image_name)
125
149
 
126
150
  return
127
-
151
+
128
152
  message_element = messages.append_child(
129
- tag_name="message",
153
+ tag_name="message",
130
154
  attributes={
131
- "id" : item_id,
132
- "text" : "",
133
- "created_at" : datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00","Z"),
134
- "author_name" : context.room.local_participant.get_attribute("name"),
135
- }
155
+ "id": item_id,
156
+ "text": "",
157
+ "created_at": datetime.datetime.now(datetime.timezone.utc)
158
+ .isoformat()
159
+ .replace("+00:00", "Z"),
160
+ "author_name": context.room.local_participant.get_attribute("name"),
161
+ },
136
162
  )
137
- message_element.append_child(tag_name="file", attributes={ "path" : image_name })
138
-
139
- self.thread_context.chat.append_assistant_message(f"An image was saved at the path {image_name} and displayed to the user")
140
-
163
+ message_element.append_child(tag_name="file", attributes={"path": image_name})
141
164
 
165
+ self.thread_context.chat.append_assistant_message(
166
+ f"An image was saved at the path {image_name} and displayed to the user"
167
+ )
142
168
 
143
- def get_thread_participants(*, room: RoomClient, thread: MeshDocument) -> list[RemoteParticipant]:
144
169
 
170
+ def get_thread_participants(
171
+ *, room: RoomClient, thread: MeshDocument
172
+ ) -> list[RemoteParticipant]:
145
173
  results = list[RemoteParticipant]()
146
174
 
147
175
  for prop in thread.root.get_children():
148
-
149
176
  if prop.tag_name == "members":
150
-
151
177
  for member in prop.get_children():
152
-
153
178
  for online in room.messaging.get_participants():
154
-
155
179
  if online.get_attribute("name") == member.get_attribute("name"):
156
-
157
180
  results.append(online)
158
181
 
159
182
  return results
160
183
 
161
184
 
162
185
  class ChatThreadContext:
163
- def __init__(self, *, chat: AgentChatContext, thread: MeshDocument, participants: Optional[list[RemoteParticipant]] = None):
186
+ def __init__(
187
+ self,
188
+ *,
189
+ chat: AgentChatContext,
190
+ thread: MeshDocument,
191
+ participants: Optional[list[RemoteParticipant]] = None,
192
+ ):
164
193
  self.thread = thread
165
- if participants == None:
194
+ if participants is None:
166
195
  participants = []
167
196
 
168
197
  self.participants = participants
169
198
  self.chat = chat
170
199
 
200
+
171
201
  # todo: thread should stop when participant stops?
172
202
  class ChatBot(SingleRoomAgent):
173
- def __init__(self, *, name, title = None, description = None, requires : Optional[list[Requirement]] = None, llm_adapter: LLMAdapter, tool_adapter: Optional[ToolResponseAdapter] = None, toolkits: Optional[list[Toolkit]] = None, rules : Optional[list[str]] = None, auto_greet_message : Optional[str] = None, empty_state_title : Optional[str] = None, labels: Optional[str] = None):
174
-
203
+ def __init__(
204
+ self,
205
+ *,
206
+ name,
207
+ title=None,
208
+ description=None,
209
+ requires: Optional[list[Requirement]] = None,
210
+ llm_adapter: LLMAdapter,
211
+ tool_adapter: Optional[ToolResponseAdapter] = None,
212
+ toolkits: Optional[list[Toolkit]] = None,
213
+ rules: Optional[list[str]] = None,
214
+ auto_greet_message: Optional[str] = None,
215
+ empty_state_title: Optional[str] = None,
216
+ labels: Optional[str] = None,
217
+ ):
175
218
  super().__init__(
176
219
  name=name,
177
220
  title=title,
178
221
  description=description,
179
222
  requires=requires,
180
- labels=labels
223
+ labels=labels,
181
224
  )
182
225
 
183
- if toolkits == None:
226
+ if toolkits is None:
184
227
  toolkits = []
185
228
 
186
229
  self._llm_adapter = llm_adapter
@@ -188,180 +231,202 @@ class ChatBot(SingleRoomAgent):
188
231
 
189
232
  self._message_channels = dict[str, Chan[RoomMessage]]()
190
233
 
191
- self._room : RoomClient | None = None
234
+ self._room: RoomClient | None = None
192
235
  self._toolkits = toolkits
193
236
 
194
- if rules == None:
237
+ if rules is None:
195
238
  rules = []
196
239
 
197
240
  self._rules = rules
198
- self._is_typing = dict[str,asyncio.Task]()
241
+ self._is_typing = dict[str, asyncio.Task]()
199
242
  self._auto_greet_message = auto_greet_message
200
-
201
- if empty_state_title == None:
243
+
244
+ if empty_state_title is None:
202
245
  empty_state_title = "How can I help you?"
203
246
  self._empty_state_title = empty_state_title
204
247
 
205
- self._thread_tasks = dict[str,asyncio.Task]()
248
+ self._thread_tasks = dict[str, asyncio.Task]()
206
249
 
207
250
  def init_requirements(self, requires: list[Requirement]):
208
- if requires == None:
209
-
210
- requires = [
211
- RequiredSchema(
212
- name="thread"
213
- )
214
- ]
251
+ if requires is None:
252
+ requires = [RequiredSchema(name="thread")]
215
253
 
216
254
  else:
217
-
218
- thread_schema = list(n for n in requires if (isinstance(n, RequiredSchema) and n.name == "thread"))
255
+ thread_schema = list(
256
+ n
257
+ for n in requires
258
+ if (isinstance(n, RequiredSchema) and n.name == "thread")
259
+ )
219
260
  if len(thread_schema) == 0:
220
- requires.append(
221
- RequiredSchema(
222
- name="thread"
223
- )
224
- )
225
-
226
- async def _send_and_save_chat(self, messages: Element, path: str, to: RemoteParticipant, id: str, text: str, thread_attributes: dict):
227
-
261
+ requires.append(RequiredSchema(name="thread"))
262
+
263
+ async def _send_and_save_chat(
264
+ self,
265
+ messages: Element,
266
+ path: str,
267
+ to: RemoteParticipant,
268
+ id: str,
269
+ text: str,
270
+ thread_attributes: dict,
271
+ ):
228
272
  with tracer.start_as_current_span("chatbot.thread.message") as span:
229
-
230
273
  span.set_attributes(thread_attributes)
231
274
  span.set_attribute("role", "assistant")
232
- span.set_attribute("from_participant_name", self.room.local_participant.get_attribute("name"))
233
- span.set_attributes({
234
- "id" : id,
235
- "text" : text
236
- })
237
-
238
- await self.room.messaging.send_message(to=to, type="chat", message={ "path" : path, "text" : text })
239
-
240
- messages.append_child(tag_name="message", attributes={
241
- "id" : id,
242
- "text" : text,
243
- "created_at" : datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00","Z"),
244
- "author_name" : self.room.local_participant.get_attribute("name"),
245
- })
246
-
247
-
248
- async def greet(self, *, messages: Element, path: str, chat_context: AgentChatContext, participant: RemoteParticipant, thread_attributes: dict):
249
-
250
- if self._auto_greet_message != None:
275
+ span.set_attribute(
276
+ "from_participant_name",
277
+ self.room.local_participant.get_attribute("name"),
278
+ )
279
+ span.set_attributes({"id": id, "text": text})
280
+
281
+ await self.room.messaging.send_message(
282
+ to=to, type="chat", message={"path": path, "text": text}
283
+ )
284
+
285
+ messages.append_child(
286
+ tag_name="message",
287
+ attributes={
288
+ "id": id,
289
+ "text": text,
290
+ "created_at": datetime.datetime.now(datetime.timezone.utc)
291
+ .isoformat()
292
+ .replace("+00:00", "Z"),
293
+ "author_name": self.room.local_participant.get_attribute("name"),
294
+ },
295
+ )
296
+
297
+ async def greet(
298
+ self,
299
+ *,
300
+ messages: Element,
301
+ path: str,
302
+ chat_context: AgentChatContext,
303
+ participant: RemoteParticipant,
304
+ thread_attributes: dict,
305
+ ):
306
+ if self._auto_greet_message is not None:
251
307
  chat_context.append_user_message(self._auto_greet_message)
252
- await self._send_and_save_chat(id=str(uuid.uuid4()), to=RemoteParticipant(id=participant.id), messages=messages, path=path, text= self._auto_greet_message, thread_attributes=thread_attributes)
253
-
308
+ await self._send_and_save_chat(
309
+ id=str(uuid.uuid4()),
310
+ to=RemoteParticipant(id=participant.id),
311
+ messages=messages,
312
+ path=path,
313
+ text=self._auto_greet_message,
314
+ thread_attributes=thread_attributes,
315
+ )
254
316
 
255
317
  async def get_thread_participants(self, *, thread: MeshDocument):
256
318
  return get_thread_participants(room=self._room, thread=thread)
257
-
258
- async def get_thread_toolkits(self, *, thread_context: ChatThreadContext, participant: RemoteParticipant) -> list[Toolkit]:
259
319
 
260
- toolkits = await self.get_required_toolkits(context=ToolContext(room=self.room, caller=participant, caller_context={ "chat": thread_context.chat.to_json() }))
320
+ async def get_thread_toolkits(
321
+ self, *, thread_context: ChatThreadContext, participant: RemoteParticipant
322
+ ) -> list[Toolkit]:
323
+ toolkits = await self.get_required_toolkits(
324
+ context=ToolContext(
325
+ room=self.room,
326
+ caller=participant,
327
+ caller_context={"chat": thread_context.chat.to_json()},
328
+ )
329
+ )
261
330
  toaster = None
262
-
263
- for toolkit in toolkits:
264
331
 
332
+ for toolkit in toolkits:
265
333
  if toolkit.name == "ui":
266
-
267
334
  for tool in toolkit.tools:
268
-
269
335
  if tool.name == "show_toast":
270
-
271
336
  toaster = tool
272
337
 
273
- if toaster != None:
338
+ if toaster is not None:
274
339
 
275
340
  def multi_tool(toolkit: Toolkit):
276
341
  if toaster in toolkit.tools:
277
342
  return toolkit
278
-
279
- return MultiToolkit(required=[ toaster ], base_toolkit=toolkit )
343
+
344
+ return MultiToolkit(required=[toaster], base_toolkit=toolkit)
280
345
 
281
346
  toolkits = list(map(multi_tool, toolkits))
282
-
283
-
284
- return [
285
- *self._toolkits,
286
- *toolkits
287
- ]
288
-
347
+
348
+ return [*self._toolkits, *toolkits]
349
+
289
350
  async def init_chat_context(self) -> AgentChatContext:
290
- context = self._llm_adapter.create_chat_context()
351
+ context = self._llm_adapter.create_chat_context()
291
352
  context.append_rules(self._rules)
292
353
  return context
293
354
 
294
355
  async def open_thread(self, *, path: str):
295
356
  return await self.room.sync.open(path=path)
296
-
357
+
297
358
  async def close_thread(self, *, path: str):
298
359
  return await self.room.sync.close(path=path)
299
360
 
300
-
301
361
  async def _spawn_thread(self, path: str, messages: Chan[RoomMessage]):
302
-
303
- self.room.developer.log_nowait(type="chatbot.thread.started", data={ "path" : path })
362
+ self.room.developer.log_nowait(
363
+ type="chatbot.thread.started", data={"path": path}
364
+ )
304
365
  chat_context = await self.init_chat_context()
305
366
  opened = False
306
-
367
+
307
368
  doc_messages = None
308
369
  current_file = None
309
370
  llm_messages = Chan[ResponseStreamEvent]()
310
371
  thread_context = None
311
372
 
312
373
  thread_attributes = None
313
-
314
374
 
315
375
  def done_processing_llm_events(task: asyncio.Task):
316
376
  try:
317
377
  task.result()
318
- except CancelledError as e:
378
+ except CancelledError:
319
379
  pass
320
380
  except Exception as e:
321
381
  logger.error("error sending delta", exc_info=e)
322
382
 
323
383
  async def process_llm_events():
324
-
325
384
  partial = ""
326
385
  content_element = None
327
386
  context_message = None
328
387
 
329
388
  async for evt in llm_messages:
330
-
331
389
  for participant in self._room.messaging.get_participants():
332
- logger.debug(f"sending event {evt.type} to {participant.get_attribute("name")}")
390
+ logger.debug(
391
+ f"sending event {evt.type} to {participant.get_attribute('name')}"
392
+ )
333
393
 
334
394
  # self.room.messaging.send_message_nowait(to=participant, type="llm.event", message=json.loads(evt.to_json()))
335
395
 
336
396
  if evt.type == "response.content_part.added":
337
397
  partial = ""
338
- content_element = doc_messages.append_child(tag_name="message", attributes={
339
- "text" : "",
340
- "created_at" : datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00","Z"),
341
- "author_name" : self.room.local_participant.get_attribute("name"),
342
- })
343
-
344
- context_message = {
345
- "role" : "assistant",
346
- "content" : ""
347
- }
398
+ content_element = doc_messages.append_child(
399
+ tag_name="message",
400
+ attributes={
401
+ "text": "",
402
+ "created_at": datetime.datetime.now(datetime.timezone.utc)
403
+ .isoformat()
404
+ .replace("+00:00", "Z"),
405
+ "author_name": self.room.local_participant.get_attribute(
406
+ "name"
407
+ ),
408
+ },
409
+ )
410
+
411
+ context_message = {"role": "assistant", "content": ""}
348
412
  chat_context.messages.append(context_message)
349
-
413
+
350
414
  elif evt.type == "response.output_text.delta":
351
415
  partial += evt.delta
352
416
  content_element["text"] = partial
353
417
  context_message["content"] = partial
354
-
418
+
355
419
  elif evt.type == "response.output_text.done":
356
420
  content_element = None
357
-
421
+
358
422
  with tracer.start_as_current_span("chatbot.thread.message") as span:
359
- span.set_attribute("from_participant_name", self.room.local_participant.get_attribute("name"))
423
+ span.set_attribute(
424
+ "from_participant_name",
425
+ self.room.local_participant.get_attribute("name"),
426
+ )
360
427
  span.set_attribute("role", "assistant")
361
428
  span.set_attributes(thread_attributes)
362
- span.set_attributes({
363
- "text" : evt.text
364
- })
429
+ span.set_attributes({"text": evt.text})
365
430
 
366
431
  llm_task = asyncio.create_task(process_llm_events())
367
432
  llm_task.add_done_callback(done_processing_llm_events)
@@ -369,13 +434,10 @@ class ChatBot(SingleRoomAgent):
369
434
  thread = None
370
435
 
371
436
  try:
372
-
373
437
  received = None
374
438
 
375
439
  while True:
376
-
377
440
  while True:
378
-
379
441
  logger.info(f"waiting for message on thread {path}")
380
442
  received = await messages.recv()
381
443
  logger.info(f"received message on thread {path}: {received.type}")
@@ -385,169 +447,228 @@ class ChatBot(SingleRoomAgent):
385
447
  if participant.id == received.from_participant_id:
386
448
  chat_with_participant = participant
387
449
  break
388
-
389
- if chat_with_participant == None:
390
- logger.warning("participant does not have messaging enabled, skipping message")
450
+
451
+ if chat_with_participant is None:
452
+ logger.warning(
453
+ "participant does not have messaging enabled, skipping message"
454
+ )
391
455
  continue
392
456
 
393
457
  thread_attributes = {
394
- "agent_name" : self.name,
395
- "agent_participant_id" : self.room.local_participant.id,
396
- "agent_participant_name" : self.room.local_participant.get_attribute("name"),
397
- "remote_participant_id" : chat_with_participant.id,
398
- "remote_participant_name" : chat_with_participant.get_attribute("name"),
399
- "path" : path,
458
+ "agent_name": self.name,
459
+ "agent_participant_id": self.room.local_participant.id,
460
+ "agent_participant_name": self.room.local_participant.get_attribute(
461
+ "name"
462
+ ),
463
+ "remote_participant_id": chat_with_participant.id,
464
+ "remote_participant_name": chat_with_participant.get_attribute(
465
+ "name"
466
+ ),
467
+ "path": path,
400
468
  }
401
-
402
- if current_file != chat_with_participant.get_attribute("current_file"):
403
- logger.info(f"participant is now looking at {chat_with_participant.get_attribute("current_file")}")
404
- current_file = chat_with_participant.get_attribute("current_file")
405
-
406
- if current_file != None:
407
- chat_context.append_assistant_message(message=f"the user is currently viewing the file at the path: {current_file}")
408
-
409
- elif current_file != None:
410
- chat_context.append_assistant_message(message=f"the user is not current viewing any files")
411
-
412
- if thread == None:
413
-
414
- with tracer.start_as_current_span("chatbot.thread.open") as span:
415
469
 
470
+ if current_file != chat_with_participant.get_attribute(
471
+ "current_file"
472
+ ):
473
+ logger.info(
474
+ f"participant is now looking at {chat_with_participant.get_attribute('current_file')}"
475
+ )
476
+ current_file = chat_with_participant.get_attribute(
477
+ "current_file"
478
+ )
479
+
480
+ if current_file is not None:
481
+ chat_context.append_assistant_message(
482
+ message=f"the user is currently viewing the file at the path: {current_file}"
483
+ )
484
+
485
+ elif current_file is not None:
486
+ chat_context.append_assistant_message(
487
+ message="the user is not current viewing any files"
488
+ )
489
+
490
+ if thread is None:
491
+ with tracer.start_as_current_span(
492
+ "chatbot.thread.open"
493
+ ) as span:
416
494
  span.set_attributes(thread_attributes)
417
495
 
418
496
  thread = await self.open_thread(path=path)
419
-
497
+
420
498
  for prop in thread.root.get_children():
421
-
422
499
  if prop.tag_name == "messages":
423
-
424
500
  doc_messages = prop
425
-
426
- for element in doc_messages.get_children():
427
501
 
502
+ for element in doc_messages.get_children():
428
503
  if isinstance(element, Element):
429
-
430
504
  msg = element["text"]
431
- if element["author_name"] == self.room.local_participant.get_attribute("name"):
432
- chat_context.append_assistant_message(msg)
505
+ if (
506
+ element["author_name"]
507
+ == self.room.local_participant.get_attribute(
508
+ "name"
509
+ )
510
+ ):
511
+ chat_context.append_assistant_message(
512
+ msg
513
+ )
433
514
  else:
434
515
  chat_context.append_user_message(msg)
435
516
 
436
517
  for child in element.get_children():
437
518
  if child.tag_name == "file":
438
- chat_context.append_assistant_message(f"the user attached a file with the path '{child.get_attribute("path")}'")
439
-
440
- if doc_messages == None:
441
- raise Exception("thread was not properly initialized")
519
+ chat_context.append_assistant_message(
520
+ f"the user attached a file with the path '{child.get_attribute('path')}'"
521
+ )
442
522
 
523
+ if doc_messages is None:
524
+ raise Exception("thread was not properly initialized")
443
525
 
444
526
  if received.type == "opened":
445
-
446
- if opened == False:
447
-
527
+ if not opened:
448
528
  opened = True
449
-
450
- await self.greet(path=path, chat_context=chat_context, participant=chat_with_participant, messages=doc_messages, thread_attributes=thread_attributes)
451
529
 
452
- if received.type == "chat":
453
-
454
-
455
- if thread == None:
530
+ await self.greet(
531
+ path=path,
532
+ chat_context=chat_context,
533
+ participant=chat_with_participant,
534
+ messages=doc_messages,
535
+ thread_attributes=thread_attributes,
536
+ )
456
537
 
457
- self.room.developer.log_nowait(type="thread is not open", data={})
538
+ if received.type == "chat":
539
+ if thread is None:
540
+ self.room.developer.log_nowait(
541
+ type="thread is not open", data={}
542
+ )
458
543
  break
459
544
 
460
-
461
- for participant in get_thread_participants(room=self._room, thread=thread):
545
+ for participant in get_thread_participants(
546
+ room=self._room, thread=thread
547
+ ):
462
548
  # TODO: async gather
463
- self._room.messaging.send_message_nowait(to=participant, type="thinking", message={"thinking":True, "path": path})
549
+ self._room.messaging.send_message_nowait(
550
+ to=participant,
551
+ type="thinking",
552
+ message={"thinking": True, "path": path},
553
+ )
464
554
 
465
555
  if chat_with_participant.id == received.from_participant_id:
466
- self.room.developer.log_nowait(type="llm.message", data={ "context" : chat_context.id, "participant_id" : self.room.local_participant.id, "participant_name" : self.room.local_participant.get_attribute("name"), "message" : { "content" : { "role" : "user", "text" : received.message["text"] } } })
556
+ self.room.developer.log_nowait(
557
+ type="llm.message",
558
+ data={
559
+ "context": chat_context.id,
560
+ "participant_id": self.room.local_participant.id,
561
+ "participant_name": self.room.local_participant.get_attribute(
562
+ "name"
563
+ ),
564
+ "message": {
565
+ "content": {
566
+ "role": "user",
567
+ "text": received.message["text"],
568
+ }
569
+ },
570
+ },
571
+ )
467
572
 
468
573
  attachments = received.message.get("attachments", [])
469
574
  text = received.message["text"]
470
575
 
471
576
  for attachment in attachments:
472
-
473
- chat_context.append_assistant_message(message=f"the user attached a file at the path '{attachment["path"]}'")
577
+ chat_context.append_assistant_message(
578
+ message=f"the user attached a file at the path '{attachment['path']}'"
579
+ )
474
580
 
475
581
  chat_context.append_user_message(message=text)
476
-
477
582
 
478
583
  # if user is typing, wait for typing to stop
479
584
  while True:
480
-
481
585
  if chat_with_participant.id not in self._is_typing:
482
586
  break
483
-
484
- await asyncio.sleep(.5)
485
587
 
486
- if messages.empty() == True:
588
+ await asyncio.sleep(0.5)
589
+
590
+ if messages.empty():
487
591
  break
488
-
489
- if received != None:
490
592
 
593
+ if received is not None:
491
594
  with tracer.start_as_current_span("chatbot.thread.message") as span:
492
-
493
595
  span.set_attributes(thread_attributes)
494
596
  span.set_attribute("role", "user")
495
- span.set_attribute("from_participant_name", chat_with_participant.get_attribute("name"))
597
+ span.set_attribute(
598
+ "from_participant_name",
599
+ chat_with_participant.get_attribute("name"),
600
+ )
496
601
 
497
602
  attachments = received.message.get("attachments", [])
498
603
  span.set_attribute("attachments", attachments)
499
604
 
500
605
  text = received.message["text"]
501
- span.set_attributes({
502
- "text" : text
503
- })
606
+ span.set_attributes({"text": text})
504
607
 
505
608
  try:
506
-
507
- if thread_context == None:
508
-
609
+ if thread_context is None:
509
610
  thread_context = ChatThreadContext(
510
611
  chat=chat_context,
511
612
  thread=thread,
512
- participants=get_thread_participants(room=self.room, thread=thread)
613
+ participants=get_thread_participants(
614
+ room=self.room, thread=thread
615
+ ),
513
616
  )
514
617
 
515
618
  def handle_event(evt):
516
619
  llm_messages.send_nowait(evt)
517
620
 
518
- with tracer.start_as_current_span("chatbot.llm") as span:
519
-
621
+ with tracer.start_as_current_span("chatbot.llm") as span:
520
622
  try:
521
-
522
- with tracer.start_as_current_span("get_thread_toolkits") as span:
523
-
524
- thread_toolkits = await self.get_thread_toolkits(thread_context=thread_context, participant=participant)
525
-
526
- response = await self._llm_adapter.next(
623
+ with tracer.start_as_current_span(
624
+ "get_thread_toolkits"
625
+ ) as span:
626
+ thread_toolkits = (
627
+ await self.get_thread_toolkits(
628
+ thread_context=thread_context,
629
+ participant=participant,
630
+ )
631
+ )
632
+
633
+ await self._llm_adapter.next(
527
634
  context=chat_context,
528
635
  room=self._room,
529
636
  toolkits=thread_toolkits,
530
637
  tool_adapter=self._tool_adapter,
531
- event_handler=handle_event
638
+ event_handler=handle_event,
532
639
  )
533
640
  except Exception as e:
534
641
  logger.error("An error was encountered", exc_info=e)
535
- await self._send_and_save_chat(messages=doc_messages, to=chat_with_participant, path=path, id=str(uuid.uuid4()), text="There was an error while communicating with the LLM. Please try again later.", thread_attributes=thread_attributes)
642
+ await self._send_and_save_chat(
643
+ messages=doc_messages,
644
+ to=chat_with_participant,
645
+ path=path,
646
+ id=str(uuid.uuid4()),
647
+ text="There was an error while communicating with the LLM. Please try again later.",
648
+ thread_attributes=thread_attributes,
649
+ )
536
650
 
537
651
  finally:
538
- for participant in get_thread_participants(room=self._room, thread=thread):
652
+ for participant in get_thread_participants(
653
+ room=self._room, thread=thread
654
+ ):
539
655
  # TODO: async gather
540
- self._room.messaging.send_message_nowait(to=participant, type="thinking", message={"thinking":False, "path" : path})
656
+ self._room.messaging.send_message_nowait(
657
+ to=participant,
658
+ type="thinking",
659
+ message={"thinking": False, "path": path},
660
+ )
541
661
 
542
662
  finally:
543
-
544
663
  llm_messages.close()
545
664
 
546
- if self.room != None:
665
+ if self.room is not None:
547
666
  logger.info("thread was ended {path}")
548
- self.room.developer.log_nowait(type="chatbot.thread.ended", data={ "path" : path })
549
-
550
- if thread != None:
667
+ self.room.developer.log_nowait(
668
+ type="chatbot.thread.ended", data={"path": path}
669
+ )
670
+
671
+ if thread is not None:
551
672
  await self.close_thread(path=path)
552
673
 
553
674
  def _get_message_channel(self, key: str) -> Chan[RoomMessage]:
@@ -556,64 +677,67 @@ class ChatBot(SingleRoomAgent):
556
677
  self._message_channels[key] = chan
557
678
 
558
679
  chan = self._message_channels[key]
559
-
680
+
560
681
  return chan
561
-
682
+
562
683
  async def stop(self):
563
684
  await super().stop()
564
685
 
565
686
  for thread in self._thread_tasks.values():
566
687
  thread.cancel()
567
-
688
+
568
689
  self._thread_tasks.clear()
569
690
 
570
691
  async def start(self, *, room):
571
-
572
692
  await super().start(room=room)
573
693
 
574
694
  logger.debug("Starting chatbot")
575
-
576
- await self.room.local_participant.set_attribute("empty_state_title", self._empty_state_title)
577
695
 
578
- def on_message(message: RoomMessage):
696
+ await self.room.local_participant.set_attribute(
697
+ "empty_state_title", self._empty_state_title
698
+ )
579
699
 
580
-
700
+ def on_message(message: RoomMessage):
581
701
  if message.type == "chat" or message.type == "opened":
582
-
583
702
  path = message.message["path"]
584
-
703
+
585
704
  messages = self._get_message_channel(path)
586
705
 
587
- logger.info(f"queued incoming message for thread {path}: {message.type}")
588
-
706
+ logger.info(
707
+ f"queued incoming message for thread {path}: {message.type}"
708
+ )
709
+
589
710
  messages.send_nowait(message)
590
711
 
591
712
  if path not in self._thread_tasks or self._thread_tasks[path].done():
592
-
593
- def thread_done(task: asyncio.Task):
594
713
 
714
+ def thread_done(task: asyncio.Task):
595
715
  self._message_channels.pop(path)
596
716
  try:
597
717
  task.result()
598
- except CancelledError as e:
718
+ except CancelledError:
599
719
  pass
600
720
  except Exception as e:
601
- logger.error(f"The chat thread ended with an error {e}", exc_info=e)
602
-
603
-
721
+ logger.error(
722
+ f"The chat thread ended with an error {e}", exc_info=e
723
+ )
724
+
604
725
  logger.info(f"spawning chat thread for {path}")
605
- task = asyncio.create_task(self._spawn_thread(messages=messages, path=path))
726
+ task = asyncio.create_task(
727
+ self._spawn_thread(messages=messages, path=path)
728
+ )
606
729
  task.add_done_callback(thread_done)
607
730
 
608
731
  self._thread_tasks[path] = task
609
732
 
610
733
  elif message.type == "typing":
734
+
611
735
  def callback(task: asyncio.Task):
612
736
  try:
613
737
  task.result()
614
- except:
738
+ except Exception:
615
739
  pass
616
-
740
+
617
741
  async def remove_timeout(id: str):
618
742
  await asyncio.sleep(1)
619
743
  self._is_typing.pop(id)
@@ -621,23 +745,22 @@ class ChatBot(SingleRoomAgent):
621
745
  if message.from_participant_id in self._is_typing:
622
746
  self._is_typing[message.from_participant_id].cancel()
623
747
 
624
- timeout = asyncio.create_task(remove_timeout(id=message.from_participant_id))
748
+ timeout = asyncio.create_task(
749
+ remove_timeout(id=message.from_participant_id)
750
+ )
625
751
  timeout.add_done_callback(callback)
626
752
 
627
753
  self._is_typing[message.from_participant_id] = timeout
628
754
 
629
755
  room.messaging.on("message", on_message)
630
-
631
- if self._auto_greet_message != None:
632
- def on_participant_added(participant:RemoteParticipant):
633
-
756
+
757
+ if self._auto_greet_message is not None:
758
+
759
+ def on_participant_added(participant: RemoteParticipant):
634
760
  # will spawn the initial thread
635
761
  self._get_message_channel(participant_id=participant.id)
636
-
637
762
 
638
763
  room.messaging.on("participant_added", on_participant_added)
639
764
 
640
-
641
765
  logger.debug("Enabling chatbot messaging")
642
766
  await room.messaging.enable()
643
-