langroid 0.24.1__py3-none-any.whl → 0.26.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.
- langroid/agent/base.py +339 -125
- langroid/agent/callbacks/chainlit.py +57 -129
- langroid/agent/chat_agent.py +19 -0
- langroid/agent/tools/orchestration.py +8 -3
- {langroid-0.24.1.dist-info → langroid-0.26.0.dist-info}/METADATA +2 -2
- {langroid-0.24.1.dist-info → langroid-0.26.0.dist-info}/RECORD +9 -9
- pyproject.toml +3 -3
- {langroid-0.24.1.dist-info → langroid-0.26.0.dist-info}/LICENSE +0 -0
- {langroid-0.24.1.dist-info → langroid-0.26.0.dist-info}/WHEEL +0 -0
langroid/agent/base.py
CHANGED
@@ -175,6 +175,7 @@ class Agent(ABC):
|
|
175
175
|
show_llm_response=noop_fn,
|
176
176
|
show_agent_response=noop_fn,
|
177
177
|
get_user_response=None,
|
178
|
+
get_user_response_async=None,
|
178
179
|
get_last_step=noop_fn,
|
179
180
|
set_parent_agent=noop_fn,
|
180
181
|
show_error_message=noop_fn,
|
@@ -322,6 +323,52 @@ class Agent(ABC):
|
|
322
323
|
lambda msg: message_class.handle_message_fallback(self, msg),
|
323
324
|
)
|
324
325
|
|
326
|
+
async_tool_name = f"{tool}_async"
|
327
|
+
if (
|
328
|
+
hasattr(message_class, "handle_async")
|
329
|
+
and inspect.isfunction(message_class.handle_async)
|
330
|
+
and not hasattr(self, async_tool_name)
|
331
|
+
):
|
332
|
+
has_chat_doc_arg = (
|
333
|
+
len(inspect.signature(message_class.handle_async).parameters) > 1
|
334
|
+
)
|
335
|
+
|
336
|
+
if has_chat_doc_arg:
|
337
|
+
|
338
|
+
@no_type_check
|
339
|
+
async def handler(obj, chat_doc):
|
340
|
+
return await obj.handle_async(chat_doc)
|
341
|
+
|
342
|
+
else:
|
343
|
+
|
344
|
+
@no_type_check
|
345
|
+
async def handler(obj):
|
346
|
+
return await obj.handle_async()
|
347
|
+
|
348
|
+
setattr(self, async_tool_name, handler)
|
349
|
+
elif (
|
350
|
+
hasattr(message_class, "response_async")
|
351
|
+
and inspect.isfunction(message_class.response_async)
|
352
|
+
and not hasattr(self, async_tool_name)
|
353
|
+
):
|
354
|
+
has_chat_doc_arg = (
|
355
|
+
len(inspect.signature(message_class.response_async).parameters) > 2
|
356
|
+
)
|
357
|
+
|
358
|
+
if has_chat_doc_arg:
|
359
|
+
|
360
|
+
@no_type_check
|
361
|
+
async def handler(obj, chat_doc):
|
362
|
+
return await obj.response_async(self, chat_doc)
|
363
|
+
|
364
|
+
else:
|
365
|
+
|
366
|
+
@no_type_check
|
367
|
+
async def handler(obj):
|
368
|
+
return await obj.response_async(self)
|
369
|
+
|
370
|
+
setattr(self, async_tool_name, handler)
|
371
|
+
|
325
372
|
return [tool]
|
326
373
|
|
327
374
|
def enable_message_handling(
|
@@ -393,32 +440,14 @@ class Agent(ABC):
|
|
393
440
|
recipient=recipient,
|
394
441
|
)
|
395
442
|
|
396
|
-
|
443
|
+
def _agent_response_final(
|
397
444
|
self,
|
398
|
-
msg: Optional[str | ChatDocument]
|
399
|
-
|
400
|
-
return self.agent_response(msg)
|
401
|
-
|
402
|
-
def agent_response(
|
403
|
-
self,
|
404
|
-
msg: Optional[str | ChatDocument] = None,
|
445
|
+
msg: Optional[str | ChatDocument],
|
446
|
+
results: Optional[str | OrderedDict[str, str] | ChatDocument],
|
405
447
|
) -> Optional[ChatDocument]:
|
406
448
|
"""
|
407
|
-
|
408
|
-
used to handle LLM's "tool message" or `function_call`
|
409
|
-
(e.g. OpenAI `function_call`).
|
410
|
-
Args:
|
411
|
-
msg (str|ChatDocument): the input to respond to: if msg is a string,
|
412
|
-
and it contains a valid JSON-structured "tool message", or
|
413
|
-
if msg is a ChatDocument, and it contains a `function_call`.
|
414
|
-
Returns:
|
415
|
-
Optional[ChatDocument]: the response, packaged as a ChatDocument
|
416
|
-
|
449
|
+
Convert results to final response.
|
417
450
|
"""
|
418
|
-
if msg is None:
|
419
|
-
return None
|
420
|
-
|
421
|
-
results = self.handle_message(msg)
|
422
451
|
if results is None:
|
423
452
|
return None
|
424
453
|
if not settings.quiet:
|
@@ -438,7 +467,7 @@ class Agent(ABC):
|
|
438
467
|
if isinstance(results, ChatDocument):
|
439
468
|
# Preserve trail of tool_ids for OpenAI Assistant fn-calls
|
440
469
|
results.metadata.tool_ids = (
|
441
|
-
[] if isinstance(msg, str) else msg.metadata.tool_ids
|
470
|
+
[] if msg is None or isinstance(msg, str) else msg.metadata.tool_ids
|
442
471
|
)
|
443
472
|
return results
|
444
473
|
sender_name = self.config.name
|
@@ -461,10 +490,49 @@ class Agent(ABC):
|
|
461
490
|
sender_name=sender_name,
|
462
491
|
oai_tool_id=oai_tool_id,
|
463
492
|
# preserve trail of tool_ids for OpenAI Assistant fn-calls
|
464
|
-
tool_ids=
|
493
|
+
tool_ids=(
|
494
|
+
[] if msg is None or isinstance(msg, str) else msg.metadata.tool_ids
|
495
|
+
),
|
465
496
|
),
|
466
497
|
)
|
467
498
|
|
499
|
+
async def agent_response_async(
|
500
|
+
self,
|
501
|
+
msg: Optional[str | ChatDocument] = None,
|
502
|
+
) -> Optional[ChatDocument]:
|
503
|
+
"""
|
504
|
+
Asynch version of `agent_response`. See there for details.
|
505
|
+
"""
|
506
|
+
if msg is None:
|
507
|
+
return None
|
508
|
+
|
509
|
+
results = await self.handle_message_async(msg)
|
510
|
+
|
511
|
+
return self._agent_response_final(msg, results)
|
512
|
+
|
513
|
+
def agent_response(
|
514
|
+
self,
|
515
|
+
msg: Optional[str | ChatDocument] = None,
|
516
|
+
) -> Optional[ChatDocument]:
|
517
|
+
"""
|
518
|
+
Response from the "agent itself", typically (but not only)
|
519
|
+
used to handle LLM's "tool message" or `function_call`
|
520
|
+
(e.g. OpenAI `function_call`).
|
521
|
+
Args:
|
522
|
+
msg (str|ChatDocument): the input to respond to: if msg is a string,
|
523
|
+
and it contains a valid JSON-structured "tool message", or
|
524
|
+
if msg is a ChatDocument, and it contains a `function_call`.
|
525
|
+
Returns:
|
526
|
+
Optional[ChatDocument]: the response, packaged as a ChatDocument
|
527
|
+
|
528
|
+
"""
|
529
|
+
if msg is None:
|
530
|
+
return None
|
531
|
+
|
532
|
+
results = self.handle_message(msg)
|
533
|
+
|
534
|
+
return self._agent_response_final(msg, results)
|
535
|
+
|
468
536
|
def process_tool_results(
|
469
537
|
self,
|
470
538
|
results: str,
|
@@ -625,60 +693,46 @@ class Agent(ABC):
|
|
625
693
|
recipient=recipient,
|
626
694
|
)
|
627
695
|
|
628
|
-
|
629
|
-
self,
|
630
|
-
msg: Optional[str | ChatDocument] = None,
|
631
|
-
) -> Optional[ChatDocument]:
|
632
|
-
return self.user_response(msg)
|
633
|
-
|
634
|
-
def user_response(
|
635
|
-
self,
|
636
|
-
msg: Optional[str | ChatDocument] = None,
|
637
|
-
) -> Optional[ChatDocument]:
|
696
|
+
def user_can_respond(self, msg: Optional[str | ChatDocument] = None) -> bool:
|
638
697
|
"""
|
639
|
-
|
640
|
-
with an actual answer, or quit using "q" or "x"
|
698
|
+
Whether the user can respond to a message.
|
641
699
|
|
642
700
|
Args:
|
643
701
|
msg (str|ChatDocument): the string to respond to.
|
644
702
|
|
645
703
|
Returns:
|
646
|
-
(str) User response, packaged as a ChatDocument
|
647
704
|
|
648
705
|
"""
|
649
|
-
|
650
706
|
# When msg explicitly addressed to user, this means an actual human response
|
651
707
|
# is being sought.
|
652
708
|
need_human_response = (
|
653
709
|
isinstance(msg, ChatDocument) and msg.metadata.recipient == Entity.USER
|
654
710
|
)
|
655
|
-
default_user_msg = (
|
656
|
-
(self.default_human_response or "null") if need_human_response else ""
|
657
|
-
)
|
658
711
|
|
659
712
|
if not self.interactive and not need_human_response:
|
660
|
-
return
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
713
|
+
return False
|
714
|
+
|
715
|
+
return True
|
716
|
+
|
717
|
+
def _user_response_final(
|
718
|
+
self, msg: Optional[str | ChatDocument], user_msg: str
|
719
|
+
) -> Optional[ChatDocument]:
|
720
|
+
"""
|
721
|
+
Convert user_msg to final response.
|
722
|
+
"""
|
723
|
+
if not user_msg:
|
724
|
+
need_human_response = (
|
725
|
+
isinstance(msg, ChatDocument) and msg.metadata.recipient == Entity.USER
|
726
|
+
)
|
727
|
+
user_msg = (
|
728
|
+
(self.default_human_response or "null") if need_human_response else ""
|
729
|
+
)
|
730
|
+
user_msg = user_msg.strip()
|
676
731
|
|
677
732
|
tool_ids = []
|
678
733
|
if msg is not None and isinstance(msg, ChatDocument):
|
679
734
|
tool_ids = msg.metadata.tool_ids
|
680
735
|
|
681
|
-
user_msg = user_msg.strip() or default_user_msg.strip()
|
682
736
|
# only return non-None result if user_msg not empty
|
683
737
|
if not user_msg:
|
684
738
|
return None
|
@@ -700,6 +754,72 @@ class Agent(ABC):
|
|
700
754
|
),
|
701
755
|
)
|
702
756
|
|
757
|
+
async def user_response_async(
|
758
|
+
self,
|
759
|
+
msg: Optional[str | ChatDocument] = None,
|
760
|
+
) -> Optional[ChatDocument]:
|
761
|
+
"""
|
762
|
+
Asynch version of `user_response`. See there for details.
|
763
|
+
"""
|
764
|
+
if not self.user_can_respond(msg):
|
765
|
+
return None
|
766
|
+
|
767
|
+
if self.default_human_response is not None:
|
768
|
+
user_msg = self.default_human_response
|
769
|
+
else:
|
770
|
+
if (
|
771
|
+
self.callbacks.get_user_response_async is not None
|
772
|
+
and self.callbacks.get_user_response_async is not async_noop_fn
|
773
|
+
):
|
774
|
+
user_msg = await self.callbacks.get_user_response_async(prompt="")
|
775
|
+
elif self.callbacks.get_user_response is not None:
|
776
|
+
user_msg = self.callbacks.get_user_response(prompt="")
|
777
|
+
else:
|
778
|
+
user_msg = Prompt.ask(
|
779
|
+
f"[blue]{self.indent}"
|
780
|
+
+ self.config.human_prompt
|
781
|
+
+ f"\n{self.indent}"
|
782
|
+
)
|
783
|
+
|
784
|
+
return self._user_response_final(msg, user_msg)
|
785
|
+
|
786
|
+
def user_response(
|
787
|
+
self,
|
788
|
+
msg: Optional[str | ChatDocument] = None,
|
789
|
+
) -> Optional[ChatDocument]:
|
790
|
+
"""
|
791
|
+
Get user response to current message. Could allow (human) user to intervene
|
792
|
+
with an actual answer, or quit using "q" or "x"
|
793
|
+
|
794
|
+
Args:
|
795
|
+
msg (str|ChatDocument): the string to respond to.
|
796
|
+
|
797
|
+
Returns:
|
798
|
+
(str) User response, packaged as a ChatDocument
|
799
|
+
|
800
|
+
"""
|
801
|
+
|
802
|
+
if not self.user_can_respond(msg):
|
803
|
+
return None
|
804
|
+
|
805
|
+
if self.default_human_response is not None:
|
806
|
+
user_msg = self.default_human_response
|
807
|
+
else:
|
808
|
+
if self.callbacks.get_user_response is not None:
|
809
|
+
# ask user with empty prompt: no need for prompt
|
810
|
+
# since user has seen the conversation so far.
|
811
|
+
# But non-empty prompt can be useful when Agent
|
812
|
+
# uses a tool that requires user input, or in other scenarios.
|
813
|
+
user_msg = self.callbacks.get_user_response(prompt="")
|
814
|
+
else:
|
815
|
+
user_msg = Prompt.ask(
|
816
|
+
f"[blue]{self.indent}"
|
817
|
+
+ self.config.human_prompt
|
818
|
+
+ f"\n{self.indent}"
|
819
|
+
)
|
820
|
+
|
821
|
+
return self._user_response_final(msg, user_msg)
|
822
|
+
|
703
823
|
@no_type_check
|
704
824
|
def llm_can_respond(self, message: Optional[str | ChatDocument] = None) -> bool:
|
705
825
|
"""
|
@@ -791,7 +911,7 @@ class Agent(ABC):
|
|
791
911
|
f"""
|
792
912
|
Requested output length has been shortened to {output_len}
|
793
913
|
so that the total length of Prompt + Output is less than
|
794
|
-
the completion context length of the LLM.
|
914
|
+
the completion context length of the LLM.
|
795
915
|
"""
|
796
916
|
)
|
797
917
|
|
@@ -865,7 +985,7 @@ class Agent(ABC):
|
|
865
985
|
f"""
|
866
986
|
Requested output length has been shortened to {output_len}
|
867
987
|
so that the total length of Prompt + Output is less than
|
868
|
-
the completion context length of the LLM.
|
988
|
+
the completion context length of the LLM.
|
869
989
|
"""
|
870
990
|
)
|
871
991
|
if self.llm.get_stream() and not settings.quiet:
|
@@ -1075,7 +1195,7 @@ class Agent(ABC):
|
|
1075
1195
|
if tool_name not in self.llm_tools_handled:
|
1076
1196
|
logger.warning(
|
1077
1197
|
f"""
|
1078
|
-
The function_call '{tool_name}' is not handled
|
1198
|
+
The function_call '{tool_name}' is not handled
|
1079
1199
|
by the agent named '{self.config.name}'!
|
1080
1200
|
If you intended this agent to handle this function_call,
|
1081
1201
|
either the fn-call name is incorrectly generated by the LLM,
|
@@ -1110,7 +1230,7 @@ class Agent(ABC):
|
|
1110
1230
|
if tool_name not in self.llm_tools_handled:
|
1111
1231
|
logger.warning(
|
1112
1232
|
f"""
|
1113
|
-
The tool_call '{tool_name}' is not handled
|
1233
|
+
The tool_call '{tool_name}' is not handled
|
1114
1234
|
by the agent named '{self.config.name}'!
|
1115
1235
|
If you intended this agent to handle this function_call,
|
1116
1236
|
either the fn-call name is incorrectly generated by the LLM,
|
@@ -1145,65 +1265,18 @@ class Agent(ABC):
|
|
1145
1265
|
[f"{e['loc']}: {e['msg']}" for e in ve.errors() if "loc" in e]
|
1146
1266
|
)
|
1147
1267
|
return f"""
|
1148
|
-
There were one or more errors in your attempt to use the
|
1149
|
-
TOOL or function_call named '{tool_name}':
|
1268
|
+
There were one or more errors in your attempt to use the
|
1269
|
+
TOOL or function_call named '{tool_name}':
|
1150
1270
|
{bad_field_errors}
|
1151
1271
|
Please write your message again, correcting the errors.
|
1152
1272
|
"""
|
1153
1273
|
|
1154
|
-
def
|
1155
|
-
self,
|
1156
|
-
) ->
|
1274
|
+
def _get_multiple_orch_tool_errs(
|
1275
|
+
self, tools: List[ToolMessage]
|
1276
|
+
) -> List[str | ChatDocument | None]:
|
1157
1277
|
"""
|
1158
|
-
|
1159
|
-
valid "tool" JSON substrings, or a
|
1160
|
-
ChatDocument containing a `function_call` attribute.
|
1161
|
-
Handle with the corresponding handler method, and return
|
1162
|
-
the results as a combined string.
|
1163
|
-
|
1164
|
-
Args:
|
1165
|
-
msg (str | ChatDocument): The string or ChatDocument to handle
|
1166
|
-
|
1167
|
-
Returns:
|
1168
|
-
The result of the handler method can be:
|
1169
|
-
- None if no tools successfully handled, or no tools present
|
1170
|
-
- str if langroid-native JSON tools were handled, and results concatenated,
|
1171
|
-
OR there's a SINGLE OpenAI tool-call.
|
1172
|
-
(We do this so the common scenario of a single tool/fn-call
|
1173
|
-
has a simple behavior).
|
1174
|
-
- Dict[str, str] if multiple OpenAI tool-calls were handled
|
1175
|
-
(dict is an id->result map)
|
1176
|
-
- ChatDocument if a handler returned a ChatDocument, intended to be the
|
1177
|
-
final response of the `agent_response` method.
|
1278
|
+
Return error document if the message contains multiple orchestration tools
|
1178
1279
|
"""
|
1179
|
-
try:
|
1180
|
-
tools = self.get_tool_messages(msg)
|
1181
|
-
tools = [t for t in tools if self._tool_recipient_match(t)]
|
1182
|
-
except ValidationError as ve:
|
1183
|
-
# correct tool name but bad fields
|
1184
|
-
return self.tool_validation_error(ve)
|
1185
|
-
except XMLException as xe: # from XMLToolMessage parsing
|
1186
|
-
return str(xe)
|
1187
|
-
except ValueError:
|
1188
|
-
# invalid tool name
|
1189
|
-
# We return None since returning "invalid tool name" would
|
1190
|
-
# be considered a valid result in task loop, and would be treated
|
1191
|
-
# as a response to the tool message even though the tool was not intended
|
1192
|
-
# for this agent.
|
1193
|
-
return None
|
1194
|
-
if len(tools) > 1 and not self.config.allow_multiple_tools:
|
1195
|
-
return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
|
1196
|
-
if len(tools) == 0:
|
1197
|
-
fallback_result = self.handle_message_fallback(msg)
|
1198
|
-
if fallback_result is None:
|
1199
|
-
return None
|
1200
|
-
return self.to_ChatDocument(
|
1201
|
-
fallback_result,
|
1202
|
-
chat_doc=msg if isinstance(msg, ChatDocument) else None,
|
1203
|
-
)
|
1204
|
-
has_ids = all([t.id != "" for t in tools])
|
1205
|
-
chat_doc = msg if isinstance(msg, ChatDocument) else None
|
1206
|
-
|
1207
1280
|
# check whether there are multiple orchestration-tools (e.g. DoneTool etc),
|
1208
1281
|
# in which case set result to error-string since we don't yet support
|
1209
1282
|
# multi-tools with one or more orch tools.
|
@@ -1230,20 +1303,24 @@ class Agent(ABC):
|
|
1230
1303
|
)
|
1231
1304
|
|
1232
1305
|
has_orch = any(isinstance(t, ORCHESTRATION_TOOLS) for t in tools)
|
1233
|
-
results: List[str | ChatDocument | None]
|
1234
1306
|
if has_orch and len(tools) > 1:
|
1235
1307
|
err_str = "ERROR: Use ONE tool at a time!"
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1308
|
+
return [err_str for _ in tools]
|
1309
|
+
|
1310
|
+
return []
|
1311
|
+
|
1312
|
+
def _handle_message_final(
|
1313
|
+
self, tools: List[ToolMessage], results: List[str | ChatDocument | None]
|
1314
|
+
) -> None | str | OrderedDict[str, str] | ChatDocument:
|
1315
|
+
"""
|
1316
|
+
Convert results to final response
|
1317
|
+
"""
|
1318
|
+
# extract content from ChatDocument results so we have all str|None
|
1319
|
+
results = [r.content if isinstance(r, ChatDocument) else r for r in results]
|
1244
1320
|
|
1245
|
-
# now all results are str|None
|
1246
1321
|
tool_names = [t.default_value("request") for t in tools]
|
1322
|
+
|
1323
|
+
has_ids = all([t.id != "" for t in tools])
|
1247
1324
|
if has_ids:
|
1248
1325
|
id2result = OrderedDict(
|
1249
1326
|
(t.id, r)
|
@@ -1278,6 +1355,112 @@ class Agent(ABC):
|
|
1278
1355
|
final = "\n\n".join(str_results)
|
1279
1356
|
return final
|
1280
1357
|
|
1358
|
+
async def handle_message_async(
|
1359
|
+
self, msg: str | ChatDocument
|
1360
|
+
) -> None | str | OrderedDict[str, str] | ChatDocument:
|
1361
|
+
"""
|
1362
|
+
Asynch version of `handle_message`. See there for details.
|
1363
|
+
"""
|
1364
|
+
try:
|
1365
|
+
tools = self.get_tool_messages(msg)
|
1366
|
+
tools = [t for t in tools if self._tool_recipient_match(t)]
|
1367
|
+
except ValidationError as ve:
|
1368
|
+
# correct tool name but bad fields
|
1369
|
+
return self.tool_validation_error(ve)
|
1370
|
+
except XMLException as xe: # from XMLToolMessage parsing
|
1371
|
+
return str(xe)
|
1372
|
+
except ValueError:
|
1373
|
+
# invalid tool name
|
1374
|
+
# We return None since returning "invalid tool name" would
|
1375
|
+
# be considered a valid result in task loop, and would be treated
|
1376
|
+
# as a response to the tool message even though the tool was not intended
|
1377
|
+
# for this agent.
|
1378
|
+
return None
|
1379
|
+
if len(tools) > 1 and not self.config.allow_multiple_tools:
|
1380
|
+
return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
|
1381
|
+
if len(tools) == 0:
|
1382
|
+
fallback_result = self.handle_message_fallback(msg)
|
1383
|
+
if fallback_result is None:
|
1384
|
+
return None
|
1385
|
+
return self.to_ChatDocument(
|
1386
|
+
fallback_result,
|
1387
|
+
chat_doc=msg if isinstance(msg, ChatDocument) else None,
|
1388
|
+
)
|
1389
|
+
chat_doc = msg if isinstance(msg, ChatDocument) else None
|
1390
|
+
|
1391
|
+
results = self._get_multiple_orch_tool_errs(tools)
|
1392
|
+
if not results:
|
1393
|
+
results = [
|
1394
|
+
await self.handle_tool_message_async(t, chat_doc=chat_doc)
|
1395
|
+
for t in tools
|
1396
|
+
]
|
1397
|
+
# if there's a solitary ChatDocument|str result, return it as is
|
1398
|
+
if len(results) == 1 and isinstance(results[0], (str, ChatDocument)):
|
1399
|
+
return results[0]
|
1400
|
+
|
1401
|
+
return self._handle_message_final(tools, results)
|
1402
|
+
|
1403
|
+
def handle_message(
|
1404
|
+
self, msg: str | ChatDocument
|
1405
|
+
) -> None | str | OrderedDict[str, str] | ChatDocument:
|
1406
|
+
"""
|
1407
|
+
Handle a "tool" message either a string containing one or more
|
1408
|
+
valid "tool" JSON substrings, or a
|
1409
|
+
ChatDocument containing a `function_call` attribute.
|
1410
|
+
Handle with the corresponding handler method, and return
|
1411
|
+
the results as a combined string.
|
1412
|
+
|
1413
|
+
Args:
|
1414
|
+
msg (str | ChatDocument): The string or ChatDocument to handle
|
1415
|
+
|
1416
|
+
Returns:
|
1417
|
+
The result of the handler method can be:
|
1418
|
+
- None if no tools successfully handled, or no tools present
|
1419
|
+
- str if langroid-native JSON tools were handled, and results concatenated,
|
1420
|
+
OR there's a SINGLE OpenAI tool-call.
|
1421
|
+
(We do this so the common scenario of a single tool/fn-call
|
1422
|
+
has a simple behavior).
|
1423
|
+
- Dict[str, str] if multiple OpenAI tool-calls were handled
|
1424
|
+
(dict is an id->result map)
|
1425
|
+
- ChatDocument if a handler returned a ChatDocument, intended to be the
|
1426
|
+
final response of the `agent_response` method.
|
1427
|
+
"""
|
1428
|
+
try:
|
1429
|
+
tools = self.get_tool_messages(msg)
|
1430
|
+
tools = [t for t in tools if self._tool_recipient_match(t)]
|
1431
|
+
except ValidationError as ve:
|
1432
|
+
# correct tool name but bad fields
|
1433
|
+
return self.tool_validation_error(ve)
|
1434
|
+
except XMLException as xe: # from XMLToolMessage parsing
|
1435
|
+
return str(xe)
|
1436
|
+
except ValueError:
|
1437
|
+
# invalid tool name
|
1438
|
+
# We return None since returning "invalid tool name" would
|
1439
|
+
# be considered a valid result in task loop, and would be treated
|
1440
|
+
# as a response to the tool message even though the tool was not intended
|
1441
|
+
# for this agent.
|
1442
|
+
return None
|
1443
|
+
if len(tools) > 1 and not self.config.allow_multiple_tools:
|
1444
|
+
return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
|
1445
|
+
if len(tools) == 0:
|
1446
|
+
fallback_result = self.handle_message_fallback(msg)
|
1447
|
+
if fallback_result is None:
|
1448
|
+
return None
|
1449
|
+
return self.to_ChatDocument(
|
1450
|
+
fallback_result,
|
1451
|
+
chat_doc=msg if isinstance(msg, ChatDocument) else None,
|
1452
|
+
)
|
1453
|
+
chat_doc = msg if isinstance(msg, ChatDocument) else None
|
1454
|
+
|
1455
|
+
results = self._get_multiple_orch_tool_errs(tools)
|
1456
|
+
if not results:
|
1457
|
+
results = [self.handle_tool_message(t, chat_doc=chat_doc) for t in tools]
|
1458
|
+
# if there's a solitary ChatDocument|str result, return it as is
|
1459
|
+
if len(results) == 1 and isinstance(results[0], (str, ChatDocument)):
|
1460
|
+
return results[0]
|
1461
|
+
|
1462
|
+
return self._handle_message_final(tools, results)
|
1463
|
+
|
1281
1464
|
@property
|
1282
1465
|
def all_llm_tools_known(self) -> set[str]:
|
1283
1466
|
"""All known tools; this may extend self.llm_tools_known."""
|
@@ -1546,6 +1729,37 @@ class Agent(ABC):
|
|
1546
1729
|
) + truncate_warning
|
1547
1730
|
return result
|
1548
1731
|
|
1732
|
+
async def handle_tool_message_async(
|
1733
|
+
self,
|
1734
|
+
tool: ToolMessage,
|
1735
|
+
chat_doc: Optional[ChatDocument] = None,
|
1736
|
+
) -> None | str | ChatDocument:
|
1737
|
+
"""
|
1738
|
+
Asynch version of `handle_tool_message`. See there for details.
|
1739
|
+
"""
|
1740
|
+
tool_name = tool.default_value("request")
|
1741
|
+
handler_method = getattr(self, tool_name + "_async", None)
|
1742
|
+
if handler_method is None:
|
1743
|
+
return self.handle_tool_message(tool, chat_doc=chat_doc)
|
1744
|
+
has_chat_doc_arg = (
|
1745
|
+
chat_doc is not None
|
1746
|
+
and "chat_doc" in inspect.signature(handler_method).parameters
|
1747
|
+
)
|
1748
|
+
try:
|
1749
|
+
if has_chat_doc_arg:
|
1750
|
+
maybe_result = await handler_method(tool, chat_doc=chat_doc)
|
1751
|
+
else:
|
1752
|
+
maybe_result = await handler_method(tool)
|
1753
|
+
result = self.to_ChatDocument(maybe_result, tool_name, chat_doc)
|
1754
|
+
except Exception as e:
|
1755
|
+
# raise the error here since we are sure it's
|
1756
|
+
# not a pydantic validation error,
|
1757
|
+
# which we check in `handle_message`
|
1758
|
+
raise e
|
1759
|
+
return self._maybe_truncate_result(
|
1760
|
+
result, tool._max_result_tokens
|
1761
|
+
) # type: ignore
|
1762
|
+
|
1549
1763
|
def handle_tool_message(
|
1550
1764
|
self,
|
1551
1765
|
tool: ToolMessage,
|
@@ -16,7 +16,6 @@ except ImportError:
|
|
16
16
|
raise LangroidImportError("chainlit", "chainlit")
|
17
17
|
|
18
18
|
from chainlit import run_sync
|
19
|
-
from chainlit.config import config
|
20
19
|
from chainlit.logger import logger
|
21
20
|
|
22
21
|
import langroid as lr
|
@@ -227,7 +226,6 @@ class ChainlitAgentCallbacks:
|
|
227
226
|
def __init__(
|
228
227
|
self,
|
229
228
|
agent: lr.Agent,
|
230
|
-
msg: cl.Message = None,
|
231
229
|
config: ChainlitCallbackConfig = ChainlitCallbackConfig(),
|
232
230
|
):
|
233
231
|
"""Add callbacks to the agent, and save the initial message,
|
@@ -240,6 +238,7 @@ class ChainlitAgentCallbacks:
|
|
240
238
|
agent.callbacks.show_llm_response = self.show_llm_response
|
241
239
|
agent.callbacks.show_agent_response = self.show_agent_response
|
242
240
|
agent.callbacks.get_user_response = self.get_user_response
|
241
|
+
agent.callbacks.get_user_response_async = self.get_user_response_async
|
243
242
|
agent.callbacks.get_last_step = self.get_last_step
|
244
243
|
agent.callbacks.set_parent_agent = self.set_parent_agent
|
245
244
|
agent.callbacks.show_error_message = self.show_error_message
|
@@ -250,8 +249,6 @@ class ChainlitAgentCallbacks:
|
|
250
249
|
# We don't want to suppress LLM output in async + streaming,
|
251
250
|
# since we often use chainlit async callbacks to display LLM output
|
252
251
|
self.agent.llm.config.async_stream_quiet = False
|
253
|
-
if msg is not None:
|
254
|
-
self.show_first_user_message(msg)
|
255
252
|
|
256
253
|
def _get_parent_id(self) -> str | None:
|
257
254
|
"""Get step id under which we need to nest the current step:
|
@@ -281,10 +278,11 @@ class ChainlitAgentCallbacks:
|
|
281
278
|
|
282
279
|
def start_llm_stream(self) -> Callable[[str], None]:
|
283
280
|
"""Returns a streaming fn that can be passed to the LLM class"""
|
284
|
-
self.stream = cl.
|
281
|
+
self.stream = cl.Message(
|
282
|
+
content="",
|
285
283
|
id=self.curr_step.id if self.curr_step is not None else None,
|
286
|
-
|
287
|
-
type="
|
284
|
+
author=self._entity_name("llm"),
|
285
|
+
type="assistant_message",
|
288
286
|
parent_id=self._get_parent_id(),
|
289
287
|
)
|
290
288
|
self.last_step = self.stream
|
@@ -296,7 +294,6 @@ class ChainlitAgentCallbacks:
|
|
296
294
|
under parent {self._get_parent_id()}
|
297
295
|
"""
|
298
296
|
)
|
299
|
-
run_sync(self.stream.send()) # type: ignore
|
300
297
|
|
301
298
|
def stream_token(t: str) -> None:
|
302
299
|
if self.stream is None:
|
@@ -307,10 +304,11 @@ class ChainlitAgentCallbacks:
|
|
307
304
|
|
308
305
|
async def start_llm_stream_async(self) -> Callable[[str], None]:
|
309
306
|
"""Returns a streaming fn that can be passed to the LLM class"""
|
310
|
-
self.stream = cl.
|
307
|
+
self.stream = cl.Message(
|
308
|
+
content="",
|
311
309
|
id=self.curr_step.id if self.curr_step is not None else None,
|
312
|
-
|
313
|
-
type="
|
310
|
+
author=self._entity_name("llm"),
|
311
|
+
type="assistant_message",
|
314
312
|
parent_id=self._get_parent_id(),
|
315
313
|
)
|
316
314
|
self.last_step = self.stream
|
@@ -320,9 +318,8 @@ class ChainlitAgentCallbacks:
|
|
320
318
|
Starting LLM stream for {self.agent.config.name}
|
321
319
|
id = {self.stream.id}
|
322
320
|
under parent {self._get_parent_id()}
|
323
|
-
|
321
|
+
"""
|
324
322
|
)
|
325
|
-
await self.stream.send() # type: ignore
|
326
323
|
|
327
324
|
async def stream_token(t: str) -> None:
|
328
325
|
if self.stream is None:
|
@@ -346,14 +343,14 @@ class ChainlitAgentCallbacks:
|
|
346
343
|
else:
|
347
344
|
run_sync(self.stream.update()) # type: ignore
|
348
345
|
stream_id = self.stream.id if content else None
|
349
|
-
step = cl.
|
346
|
+
step = cl.Message(
|
347
|
+
content=textwrap.dedent(content) or NO_ANSWER,
|
350
348
|
id=stream_id,
|
351
|
-
|
352
|
-
type="
|
349
|
+
author=self._entity_name("llm", tool=is_tool),
|
350
|
+
type="assistant_message",
|
353
351
|
parent_id=self._get_parent_id(),
|
354
352
|
language="json" if is_tool else None,
|
355
353
|
)
|
356
|
-
step.output = textwrap.dedent(content) or NO_ANSWER
|
357
354
|
logger.info(
|
358
355
|
f"""
|
359
356
|
Finish STREAM LLM response for {self.agent.config.name}
|
@@ -371,16 +368,16 @@ class ChainlitAgentCallbacks:
|
|
371
368
|
language: str | None = None,
|
372
369
|
) -> None:
|
373
370
|
"""Show non-streaming LLM response."""
|
374
|
-
step = cl.
|
371
|
+
step = cl.Message(
|
372
|
+
content=textwrap.dedent(content) or NO_ANSWER,
|
375
373
|
id=self.curr_step.id if self.curr_step is not None else None,
|
376
|
-
|
377
|
-
type="
|
378
|
-
parent_id=self._get_parent_id(),
|
374
|
+
author=self._entity_name("llm", tool=is_tool, cached=cached),
|
375
|
+
type="assistant_message",
|
379
376
|
language=language or ("json" if is_tool else None),
|
377
|
+
parent_id=self._get_parent_id(),
|
380
378
|
)
|
381
379
|
self.last_step = step
|
382
380
|
self.curr_step = None
|
383
|
-
step.output = textwrap.dedent(content) or NO_ANSWER
|
384
381
|
logger.info(
|
385
382
|
f"""
|
386
383
|
Showing NON-STREAM LLM response for {self.agent.config.name}
|
@@ -391,34 +388,31 @@ class ChainlitAgentCallbacks:
|
|
391
388
|
run_sync(step.send()) # type: ignore
|
392
389
|
|
393
390
|
def show_error_message(self, error: str) -> None:
|
394
|
-
"""Show error message
|
395
|
-
step = cl.
|
396
|
-
|
391
|
+
"""Show error message."""
|
392
|
+
step = cl.Message(
|
393
|
+
content=error,
|
394
|
+
author=self.agent.config.name + f"({ERROR})",
|
397
395
|
type="run",
|
398
|
-
parent_id=self._get_parent_id(),
|
399
396
|
language="text",
|
397
|
+
parent_id=self._get_parent_id(),
|
400
398
|
)
|
401
399
|
self.last_step = step
|
402
|
-
step.output = error
|
403
400
|
run_sync(step.send())
|
404
401
|
|
405
402
|
def show_agent_response(self, content: str, language="text") -> None:
|
406
|
-
"""Show message from agent (typically tool handler).
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
403
|
+
"""Show message from agent (typically tool handler)."""
|
404
|
+
if language == "text":
|
405
|
+
content = wrap_text_preserving_structure(content, width=90)
|
406
|
+
step = cl.Message(
|
407
|
+
content=content,
|
411
408
|
id=self.curr_step.id if self.curr_step is not None else None,
|
412
|
-
|
409
|
+
author=self._entity_name("agent"),
|
413
410
|
type="tool",
|
414
|
-
parent_id=self._get_parent_id(),
|
415
411
|
language=language,
|
412
|
+
parent_id=self._get_parent_id(),
|
416
413
|
)
|
417
|
-
if language == "text":
|
418
|
-
content = wrap_text_preserving_structure(content, width=90)
|
419
414
|
self.last_step = step
|
420
415
|
self.curr_step = None
|
421
|
-
step.output = content
|
422
416
|
logger.info(
|
423
417
|
f"""
|
424
418
|
Showing AGENT response for {self.agent.config.name}
|
@@ -433,13 +427,13 @@ class ChainlitAgentCallbacks:
|
|
433
427
|
so that the UI displays a spinner while the process is running."""
|
434
428
|
if self.curr_step is not None:
|
435
429
|
run_sync(self.curr_step.remove()) # type: ignore
|
436
|
-
step = cl.
|
437
|
-
|
430
|
+
step = cl.Message(
|
431
|
+
content="",
|
432
|
+
author=self._entity_name(entity),
|
438
433
|
type="run",
|
439
434
|
parent_id=self._get_parent_id(),
|
440
435
|
language="text",
|
441
436
|
)
|
442
|
-
step.output = ""
|
443
437
|
self.last_step = step
|
444
438
|
self.curr_step = step
|
445
439
|
logger.info(
|
@@ -503,54 +497,23 @@ class ChainlitAgentCallbacks:
|
|
503
497
|
return "" # process the "feedback" case here
|
504
498
|
|
505
499
|
def get_user_response(self, prompt: str) -> str:
|
506
|
-
"""Ask for user response, wait for it, and return it
|
507
|
-
as a cl.Step rather than as a cl.Message so we can nest it
|
508
|
-
under the parent step.
|
509
|
-
"""
|
510
|
-
return run_sync(self.ask_user_step(prompt=prompt, suppress_values=["c"]))
|
500
|
+
"""Ask for user response, wait for it, and return it"""
|
511
501
|
|
512
|
-
|
513
|
-
"""Show user response as a step."""
|
514
|
-
step = cl.Step(
|
515
|
-
id=cl.context.current_step.id,
|
516
|
-
name=self._entity_name("user"),
|
517
|
-
type="run",
|
518
|
-
parent_id=self._get_parent_id(),
|
519
|
-
)
|
520
|
-
step.output = message
|
521
|
-
logger.info(
|
522
|
-
f"""
|
523
|
-
Showing USER response for {self.agent.config.name}
|
524
|
-
id = {step.id}
|
525
|
-
under parent {self._get_parent_id()}
|
526
|
-
"""
|
527
|
-
)
|
528
|
-
run_sync(step.send())
|
502
|
+
return run_sync(self.ask_user(prompt=prompt, suppress_values=["c"]))
|
529
503
|
|
530
|
-
def
|
531
|
-
"""
|
532
|
-
|
533
|
-
|
534
|
-
name=self._entity_name("user"),
|
535
|
-
type="run",
|
536
|
-
parent_id=self._get_parent_id(),
|
537
|
-
)
|
538
|
-
self.last_step = step
|
539
|
-
step.output = msg.content
|
540
|
-
run_sync(step.update())
|
504
|
+
async def get_user_response_async(self, prompt: str) -> str:
|
505
|
+
"""Ask for user response, wait for it, and return it"""
|
506
|
+
|
507
|
+
return await self.ask_user(prompt=prompt, suppress_values=["c"])
|
541
508
|
|
542
|
-
async def
|
509
|
+
async def ask_user(
|
543
510
|
self,
|
544
511
|
prompt: str,
|
545
512
|
timeout: int = USER_TIMEOUT,
|
546
513
|
suppress_values: List[str] = ["c"],
|
547
514
|
) -> str:
|
548
515
|
"""
|
549
|
-
Ask user for input
|
550
|
-
Rather than rely entirely on AskUserMessage (which doesn't let us
|
551
|
-
nest the question + answer under a step), we instead create fake
|
552
|
-
steps for the question and answer, and only rely on AskUserMessage
|
553
|
-
with an empty prompt to await user response.
|
516
|
+
Ask user for input.
|
554
517
|
|
555
518
|
Args:
|
556
519
|
prompt (str): Prompt to display to user
|
@@ -561,31 +524,16 @@ class ChainlitAgentCallbacks:
|
|
561
524
|
Returns:
|
562
525
|
str: User response
|
563
526
|
"""
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
# force hide_cot to False so that the user question + response is visible
|
570
|
-
config.ui.hide_cot = False
|
571
|
-
|
572
|
-
if prompt != "":
|
573
|
-
# Create a question step to ask user
|
574
|
-
question_step = cl.Step(
|
575
|
-
name=f"{self.agent.config.name} (AskUser ❓)",
|
576
|
-
type="run",
|
577
|
-
parent_id=self._get_parent_id(),
|
578
|
-
)
|
579
|
-
question_step.output = prompt
|
580
|
-
await question_step.send() # type: ignore
|
581
|
-
|
582
|
-
# Use AskUserMessage to await user response,
|
583
|
-
# but with an empty prompt so the question is not visible,
|
584
|
-
# but still pauses for user input in the input box.
|
585
|
-
res = await cl.AskUserMessage(
|
586
|
-
content="",
|
527
|
+
ask_msg = cl.AskUserMessage(
|
528
|
+
content=prompt,
|
529
|
+
author=f"{self.agent.config.name}(Awaiting user input...)",
|
530
|
+
type="assistant_message",
|
587
531
|
timeout=timeout,
|
588
|
-
)
|
532
|
+
)
|
533
|
+
res = await ask_msg.send()
|
534
|
+
if prompt == "":
|
535
|
+
# if there was no actual prompt, clear the row from the UI for clarity.
|
536
|
+
await ask_msg.remove()
|
589
537
|
|
590
538
|
if res is None:
|
591
539
|
run_sync(
|
@@ -595,31 +543,10 @@ class ChainlitAgentCallbacks:
|
|
595
543
|
)
|
596
544
|
return "x"
|
597
545
|
|
598
|
-
# The above will try to display user response in res
|
599
|
-
# but we create fake step with same id as res and
|
600
|
-
# erase it using empty output so it's not displayed
|
601
|
-
step = cl.Step(
|
602
|
-
id=res["id"],
|
603
|
-
name="TempUserResponse",
|
604
|
-
type="run",
|
605
|
-
parent_id=self._get_parent_id(),
|
606
|
-
)
|
607
|
-
step.output = ""
|
608
|
-
await step.update() # type: ignore
|
609
|
-
|
610
546
|
# Finally, reproduce the user response at right nesting level
|
611
547
|
if res["output"] in suppress_values:
|
612
|
-
config.ui.hide_cot = hide_cot # restore original value
|
613
548
|
return ""
|
614
549
|
|
615
|
-
step = cl.Step(
|
616
|
-
name=self._entity_name(entity="user"),
|
617
|
-
type="run",
|
618
|
-
parent_id=self._get_parent_id(),
|
619
|
-
)
|
620
|
-
step.output = res["output"]
|
621
|
-
await step.send() # type: ignore
|
622
|
-
config.ui.hide_cot = hide_cot # restore original value
|
623
550
|
return res["output"]
|
624
551
|
|
625
552
|
|
@@ -632,13 +559,12 @@ class ChainlitTaskCallbacks(ChainlitAgentCallbacks):
|
|
632
559
|
def __init__(
|
633
560
|
self,
|
634
561
|
task: lr.Task,
|
635
|
-
msg: cl.Message = None,
|
636
562
|
config: ChainlitCallbackConfig = ChainlitCallbackConfig(),
|
637
563
|
):
|
638
564
|
"""Inject callbacks recursively, ensuring msg is passed to the
|
639
565
|
top-level agent"""
|
640
566
|
|
641
|
-
super().__init__(task.agent,
|
567
|
+
super().__init__(task.agent, config)
|
642
568
|
self._inject_callbacks(task)
|
643
569
|
self.task = task
|
644
570
|
if config.show_subtask_response:
|
@@ -659,12 +585,14 @@ class ChainlitTaskCallbacks(ChainlitAgentCallbacks):
|
|
659
585
|
"""Show sub-task response as a step, nested at the right level."""
|
660
586
|
|
661
587
|
# The step should nest under the calling agent's last step
|
662
|
-
step = cl.
|
663
|
-
|
588
|
+
step = cl.Message(
|
589
|
+
content=content or NO_ANSWER,
|
590
|
+
author=(
|
591
|
+
self.task.agent.config.name + f"( ⏎ From {task.agent.config.name})"
|
592
|
+
),
|
664
593
|
type="run",
|
665
594
|
parent_id=self._get_parent_id(),
|
666
595
|
language="json" if is_tool else None,
|
667
596
|
)
|
668
|
-
step.output = content or NO_ANSWER
|
669
597
|
self.last_step = step
|
670
598
|
run_sync(step.send())
|
langroid/agent/chat_agent.py
CHANGED
@@ -1077,6 +1077,25 @@ class ChatAgent(Agent):
|
|
1077
1077
|
|
1078
1078
|
return agent.handle_tool_message(tool)
|
1079
1079
|
|
1080
|
+
async def response_async(
|
1081
|
+
self, agent: ChatAgent
|
1082
|
+
) -> None | str | ChatDocument:
|
1083
|
+
# One-time use
|
1084
|
+
agent.set_output_format(None)
|
1085
|
+
|
1086
|
+
if self.tool is None:
|
1087
|
+
return None
|
1088
|
+
|
1089
|
+
# As the ToolMessage schema accepts invalid
|
1090
|
+
# `tool.request` values, reparse with the
|
1091
|
+
# corresponding tool
|
1092
|
+
request = self.tool.request
|
1093
|
+
if request not in agent.llm_tools_map:
|
1094
|
+
return None
|
1095
|
+
tool = agent.llm_tools_map[request].parse_raw(self.tool.to_json())
|
1096
|
+
|
1097
|
+
return await agent.handle_tool_message_async(tool)
|
1098
|
+
|
1080
1099
|
return AnyTool
|
1081
1100
|
|
1082
1101
|
def _strict_recovery_instructions(
|
@@ -110,7 +110,7 @@ class FinalResultTool(ToolMessage):
|
|
110
110
|
(b) be returned as the final result of the root task, i.e. this tool would appear
|
111
111
|
in the final ChatDocument's `tool_messages` list.
|
112
112
|
See test_tool_handlers_and_results in test_tool_messages.py, and
|
113
|
-
examples/basic/tool-
|
113
|
+
examples/basic/chat-tool-function.py.
|
114
114
|
|
115
115
|
Note:
|
116
116
|
- when defining a tool handler or agent_response, you can directly return
|
@@ -118,8 +118,13 @@ class FinalResultTool(ToolMessage):
|
|
118
118
|
where the values can be arbitrary data structures, including nested
|
119
119
|
Pydantic objs, or you can define a subclass of FinalResultTool with the
|
120
120
|
fields you want to return.
|
121
|
-
- This is a special ToolMessage that is NOT meant to be used
|
122
|
-
by
|
121
|
+
- This is a special ToolMessage that is NOT meant to be used by an agent's
|
122
|
+
llm_response, but only by agent_response or tool handlers.
|
123
|
+
- A subclass of this tool can be defined, with specific fields, and
|
124
|
+
with _allow_llm_use = True, to allow the LLM to generate this tool,
|
125
|
+
and have the effect of terminating the current and all parent tasks,
|
126
|
+
with the tool appearing in the final ChatDocument's `tool_messages` list.
|
127
|
+
See examples/basic/multi-agent-return-result.py.
|
123
128
|
"""
|
124
129
|
|
125
130
|
request: str = ""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.26.0
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -42,7 +42,7 @@ Requires-Dist: arango-datasets (>=1.2.2,<2.0.0) ; extra == "all" or extra == "ar
|
|
42
42
|
Requires-Dist: async-generator (>=1.10,<2.0)
|
43
43
|
Requires-Dist: bs4 (>=0.0.1,<0.0.2)
|
44
44
|
Requires-Dist: cerebras-cloud-sdk (>=1.1.0,<2.0.0)
|
45
|
-
Requires-Dist: chainlit (
|
45
|
+
Requires-Dist: chainlit (>=1.3.2,<2.0.0) ; extra == "all" or extra == "chainlit"
|
46
46
|
Requires-Dist: chromadb (>=0.4.21,<=0.4.23) ; extra == "vecdbs" or extra == "all" or extra == "chromadb"
|
47
47
|
Requires-Dist: colorlog (>=6.7.0,<7.0.0)
|
48
48
|
Requires-Dist: docstring-parser (>=0.15,<0.16)
|
@@ -1,10 +1,10 @@
|
|
1
1
|
langroid/__init__.py,sha256=z_fCOLQJPOw3LLRPBlFB5-2HyCjpPgQa4m4iY5Fvb8Y,1800
|
2
2
|
langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,786
|
3
|
-
langroid/agent/base.py,sha256=
|
3
|
+
langroid/agent/base.py,sha256=jAt7tbyPIWoGJDe6Xi75nthl-JY47yWB9Q5O1m9QJq0,76798
|
4
4
|
langroid/agent/batch.py,sha256=QZdlt1563hx4l3AXrCaGovE-PNG93M3DsvQAbDzdiS8,13705
|
5
5
|
langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
langroid/agent/callbacks/chainlit.py,sha256=
|
7
|
-
langroid/agent/chat_agent.py,sha256=
|
6
|
+
langroid/agent/callbacks/chainlit.py,sha256=C6zzzYC30qC4eMA7al7eFpRoTgoe3475kaMKyXgQM0Q,20695
|
7
|
+
langroid/agent/chat_agent.py,sha256=jZaeMOQsGZlrnWj7RBT7RR17Bd0zR9H4D6n_F4rPUn0,79517
|
8
8
|
langroid/agent/chat_document.py,sha256=xPUMGzR83rn4iAEXIw2jy5LQ6YJ6Y0TiZ78XRQeDnJQ,17778
|
9
9
|
langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
|
@@ -45,7 +45,7 @@ langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6
|
|
45
45
|
langroid/agent/tools/file_tools.py,sha256=GjPB5YDILucYapElnvvoYpGJuZQ25ecLs2REv7edPEo,7292
|
46
46
|
langroid/agent/tools/google_search_tool.py,sha256=y7b-3FtgXf0lfF4AYxrZ3K5pH2dhidvibUOAGBE--WI,1456
|
47
47
|
langroid/agent/tools/metaphor_search_tool.py,sha256=qj4gt453cLEX3EGW7nVzVu6X7LCdrwjSlcNY0qJW104,2489
|
48
|
-
langroid/agent/tools/orchestration.py,sha256=
|
48
|
+
langroid/agent/tools/orchestration.py,sha256=851nZQOE1HpGBwH5om_TNP_qCMxxatXYWFZUrpjSfKk,11421
|
49
49
|
langroid/agent/tools/recipient_tool.py,sha256=dr0yTxgNEIoxUYxH6TtaExC4G_8WdJ0xGohIa4dFLhY,9808
|
50
50
|
langroid/agent/tools/retrieval_tool.py,sha256=zcAV20PP_6VzSd-UE-IJcabaBseFL_QNz59Bnig8-lE,946
|
51
51
|
langroid/agent/tools/rewind_tool.py,sha256=XAXL3BpNhCmBGYq_qi_sZfHJuIw7NY2jp4wnojJ7WRs,5606
|
@@ -142,8 +142,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
|
|
142
142
|
langroid/vector_store/momento.py,sha256=qR-zBF1RKVHQZPZQYW_7g-XpTwr46p8HJuYPCkfJbM4,10534
|
143
143
|
langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
|
144
144
|
langroid/vector_store/qdrantdb.py,sha256=v88lqFkepADvlN6lByUj9I4NEKa9X9lWH16uTPPbYrE,17457
|
145
|
-
pyproject.toml,sha256=
|
146
|
-
langroid-0.
|
147
|
-
langroid-0.
|
148
|
-
langroid-0.
|
149
|
-
langroid-0.
|
145
|
+
pyproject.toml,sha256=hm5QzEMcabDq_y8pe7dEvEfkVvJQe6Bx7qVdtB1A8ro,7495
|
146
|
+
langroid-0.26.0.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
147
|
+
langroid-0.26.0.dist-info/METADATA,sha256=E7vU0iufbOSBvgaDtCUEUgEquSch0eMfWsYyXtTJIE0,57519
|
148
|
+
langroid-0.26.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
149
|
+
langroid-0.26.0.dist-info/RECORD,,
|
pyproject.toml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "langroid"
|
3
|
-
version = "0.
|
3
|
+
version = "0.26.0"
|
4
4
|
description = "Harness LLMs with Multi-Agent Programming"
|
5
5
|
authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
|
6
6
|
readme = "README.md"
|
@@ -22,7 +22,7 @@ pymysql = {version = "^1.1.0", optional = true}
|
|
22
22
|
meilisearch-python-sdk = {version="^2.2.3", optional=true}
|
23
23
|
litellm = {version = "^1.30.1", optional = true}
|
24
24
|
metaphor-python = {version = "^0.1.23", optional = true}
|
25
|
-
chainlit = {version = "1.
|
25
|
+
chainlit = {version = "^1.3.2", optional = true}
|
26
26
|
python-socketio = {version="^5.11.0", optional=true}
|
27
27
|
neo4j = {version = "^5.14.1", optional = true}
|
28
28
|
huggingface-hub = {version="^0.21.2", optional=true}
|
@@ -242,7 +242,7 @@ lint.select = [
|
|
242
242
|
lint.exclude = ["docs/**", ".venv", "venv", "examples/**", "examples_dev", "langroid/utils/web", "notebooks", "__init__.py", "langroid/embedding_models/protoc/*"]
|
243
243
|
lint.fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
|
244
244
|
lint.unfixable = []
|
245
|
-
lint.extend-ignore = ["F821","F401"]
|
245
|
+
lint.extend-ignore = ["F821", "F401"]
|
246
246
|
|
247
247
|
[tool.pytest.ini_options]
|
248
248
|
filterwarnings = ["ignore::DeprecationWarning"]
|
File without changes
|
File without changes
|