langroid 0.23.2__py3-none-any.whl → 0.24.1__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 +42 -7
- langroid/agent/chat_agent.py +667 -16
- langroid/agent/chat_document.py +8 -3
- langroid/agent/openai_assistant.py +1 -1
- langroid/agent/special/sql/sql_chat_agent.py +20 -6
- langroid/agent/task.py +62 -3
- langroid/agent/tool_message.py +82 -2
- langroid/agent/tools/orchestration.py +2 -2
- langroid/agent/xml_tool_message.py +43 -28
- langroid/language_models/azure_openai.py +18 -5
- langroid/language_models/base.py +22 -0
- langroid/language_models/mock_lm.py +3 -0
- langroid/language_models/openai_gpt.py +81 -4
- langroid/utils/pydantic_utils.py +11 -0
- {langroid-0.23.2.dist-info → langroid-0.24.1.dist-info}/METADATA +3 -1
- {langroid-0.23.2.dist-info → langroid-0.24.1.dist-info}/RECORD +19 -19
- pyproject.toml +2 -2
- {langroid-0.23.2.dist-info → langroid-0.24.1.dist-info}/LICENSE +0 -0
- {langroid-0.23.2.dist-info → langroid-0.24.1.dist-info}/WHEEL +0 -0
langroid/agent/base.py
CHANGED
@@ -142,12 +142,19 @@ class Agent(ABC):
|
|
142
142
|
self.llm_tools_handled: Set[str] = set()
|
143
143
|
self.llm_tools_usable: Set[str] = set()
|
144
144
|
self.llm_tools_known: Set[str] = set() # all known tools, handled/used or not
|
145
|
+
# Indicates which tool-names are allowed to be inferred when
|
146
|
+
# the LLM "forgets" to include the request field in its
|
147
|
+
# tool-call.
|
148
|
+
self.enabled_requests_for_inference: Optional[Set[str]] = (
|
149
|
+
None # If None, we allow all
|
150
|
+
)
|
145
151
|
self.interactive: bool = True # may be modified by Task wrapper
|
146
152
|
self.token_stats_str = ""
|
147
153
|
self.default_human_response: Optional[str] = None
|
148
154
|
self._indent = ""
|
149
155
|
self.llm = LanguageModel.create(config.llm)
|
150
156
|
self.vecdb = VectorStore.create(config.vecdb) if config.vecdb else None
|
157
|
+
self.tool_error = False
|
151
158
|
if config.parsing is not None and self.config.llm is not None:
|
152
159
|
# token_encoding_model is used to obtain the tokenizer,
|
153
160
|
# so in case it's an OpenAI model, we ensure that the tokenizer
|
@@ -899,7 +906,7 @@ class Agent(ABC):
|
|
899
906
|
try:
|
900
907
|
tools = self.get_tool_messages(msg)
|
901
908
|
return len(tools) > 0
|
902
|
-
except ValidationError:
|
909
|
+
except (ValidationError, XMLException):
|
903
910
|
# there is a tool/fn-call attempt but had a validation error,
|
904
911
|
# so we still consider this a tool message "attempt"
|
905
912
|
return True
|
@@ -934,7 +941,7 @@ class Agent(ABC):
|
|
934
941
|
) -> List[ToolMessage]:
|
935
942
|
try:
|
936
943
|
return self.get_tool_messages(msg, all_tools)
|
937
|
-
except ValidationError:
|
944
|
+
except (ValidationError, XMLException):
|
938
945
|
return []
|
939
946
|
|
940
947
|
def get_tool_messages(
|
@@ -1040,6 +1047,7 @@ class Agent(ABC):
|
|
1040
1047
|
Returns:
|
1041
1048
|
List[ToolMessage]: list of ToolMessage objects
|
1042
1049
|
"""
|
1050
|
+
self.tool_error = False
|
1043
1051
|
substrings = XMLToolMessage.find_candidates(input_str)
|
1044
1052
|
is_json = False
|
1045
1053
|
if len(substrings) == 0:
|
@@ -1049,7 +1057,11 @@ class Agent(ABC):
|
|
1049
1057
|
return []
|
1050
1058
|
|
1051
1059
|
results = [self._get_one_tool_message(j, is_json) for j in substrings]
|
1052
|
-
|
1060
|
+
valid_results = [r for r in results if r is not None]
|
1061
|
+
# If any tool is correctly formed we do not set the flag
|
1062
|
+
if len(valid_results) > 0:
|
1063
|
+
self.tool_error = False
|
1064
|
+
return valid_results
|
1053
1065
|
|
1054
1066
|
def get_function_call_class(self, msg: ChatDocument) -> Optional[ToolMessage]:
|
1055
1067
|
"""
|
@@ -1071,7 +1083,10 @@ class Agent(ABC):
|
|
1071
1083
|
or you need to enable this agent to handle this fn-call.
|
1072
1084
|
"""
|
1073
1085
|
)
|
1086
|
+
if tool_name not in self.all_llm_tools_known:
|
1087
|
+
self.tool_error = True
|
1074
1088
|
return None
|
1089
|
+
self.tool_error = False
|
1075
1090
|
tool_class = self.llm_tools_map[tool_name]
|
1076
1091
|
tool_msg.update(dict(request=tool_name))
|
1077
1092
|
tool = tool_class.parse_obj(tool_msg)
|
@@ -1086,6 +1101,7 @@ class Agent(ABC):
|
|
1086
1101
|
if msg.oai_tool_calls is None:
|
1087
1102
|
return []
|
1088
1103
|
tools = []
|
1104
|
+
all_errors = True
|
1089
1105
|
for tc in msg.oai_tool_calls:
|
1090
1106
|
if tc.function is None:
|
1091
1107
|
continue
|
@@ -1103,11 +1119,14 @@ class Agent(ABC):
|
|
1103
1119
|
"""
|
1104
1120
|
)
|
1105
1121
|
continue
|
1122
|
+
all_errors = False
|
1106
1123
|
tool_class = self.llm_tools_map[tool_name]
|
1107
1124
|
tool_msg.update(dict(request=tool_name))
|
1108
1125
|
tool = tool_class.parse_obj(tool_msg)
|
1109
1126
|
tool.id = tc.id or ""
|
1110
1127
|
tools.append(tool)
|
1128
|
+
# When no tool is valid, set the recovery flag
|
1129
|
+
self.tool_error = all_errors
|
1111
1130
|
return tools
|
1112
1131
|
|
1113
1132
|
def tool_validation_error(self, ve: ValidationError) -> str:
|
@@ -1259,6 +1278,11 @@ class Agent(ABC):
|
|
1259
1278
|
final = "\n\n".join(str_results)
|
1260
1279
|
return final
|
1261
1280
|
|
1281
|
+
@property
|
1282
|
+
def all_llm_tools_known(self) -> set[str]:
|
1283
|
+
"""All known tools; this may extend self.llm_tools_known."""
|
1284
|
+
return self.llm_tools_known
|
1285
|
+
|
1262
1286
|
def handle_message_fallback(self, msg: str | ChatDocument) -> Any:
|
1263
1287
|
"""
|
1264
1288
|
Fallback method for the "no-tools" scenario.
|
@@ -1278,7 +1302,7 @@ class Agent(ABC):
|
|
1278
1302
|
) -> Optional[ToolMessage]:
|
1279
1303
|
"""
|
1280
1304
|
Parse the tool_candidate_str into ANY ToolMessage KNOWN to agent --
|
1281
|
-
This includes non-used/handled tools, i.e. any tool in self.
|
1305
|
+
This includes non-used/handled tools, i.e. any tool in self.all_llm_tools_known.
|
1282
1306
|
The exception to this is below where we try our best to infer the tool
|
1283
1307
|
when the LLM has "forgotten" to include the "request" field in the tool str ---
|
1284
1308
|
in this case we ONLY look at the possible set of HANDLED tools, i.e.
|
@@ -1311,6 +1335,7 @@ class Agent(ABC):
|
|
1311
1335
|
# }
|
1312
1336
|
|
1313
1337
|
if not isinstance(maybe_tool_dict, dict):
|
1338
|
+
self.tool_error = True
|
1314
1339
|
return None
|
1315
1340
|
|
1316
1341
|
properties = maybe_tool_dict.get("properties")
|
@@ -1318,7 +1343,14 @@ class Agent(ABC):
|
|
1318
1343
|
maybe_tool_dict = properties
|
1319
1344
|
request = maybe_tool_dict.get("request")
|
1320
1345
|
if request is None:
|
1321
|
-
|
1346
|
+
if self.enabled_requests_for_inference is None:
|
1347
|
+
possible = [self.llm_tools_map[r] for r in self.llm_tools_handled]
|
1348
|
+
else:
|
1349
|
+
allowable = self.enabled_requests_for_inference.intersection(
|
1350
|
+
self.llm_tools_handled
|
1351
|
+
)
|
1352
|
+
possible = [self.llm_tools_map[r] for r in allowable]
|
1353
|
+
|
1322
1354
|
default_keys = set(ToolMessage.__fields__.keys())
|
1323
1355
|
request_keys = set(maybe_tool_dict.keys())
|
1324
1356
|
|
@@ -1351,19 +1383,23 @@ class Agent(ABC):
|
|
1351
1383
|
if len(candidate_tools) == 1:
|
1352
1384
|
return candidate_tools[0]
|
1353
1385
|
else:
|
1386
|
+
self.tool_error = True
|
1354
1387
|
return None
|
1355
1388
|
|
1356
|
-
if not isinstance(request, str) or request not in self.
|
1389
|
+
if not isinstance(request, str) or request not in self.all_llm_tools_known:
|
1390
|
+
self.tool_error = True
|
1357
1391
|
return None
|
1358
1392
|
|
1359
1393
|
message_class = self.llm_tools_map.get(request)
|
1360
1394
|
if message_class is None:
|
1361
1395
|
logger.warning(f"No message class found for request '{request}'")
|
1396
|
+
self.tool_error = True
|
1362
1397
|
return None
|
1363
1398
|
|
1364
1399
|
try:
|
1365
1400
|
message = message_class.parse_obj(maybe_tool_dict)
|
1366
1401
|
except ValidationError as ve:
|
1402
|
+
self.tool_error = True
|
1367
1403
|
raise ve
|
1368
1404
|
return message
|
1369
1405
|
|
@@ -1474,7 +1510,6 @@ class Agent(ABC):
|
|
1474
1510
|
value = tool.get_value_of_type(output_type)
|
1475
1511
|
if value is not None:
|
1476
1512
|
return cast(T, value)
|
1477
|
-
|
1478
1513
|
return None
|
1479
1514
|
|
1480
1515
|
def _maybe_truncate_result(
|