langroid 0.6.7__py3-none-any.whl → 0.9.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.
Files changed (37) hide show
  1. langroid/agent/base.py +499 -55
  2. langroid/agent/callbacks/chainlit.py +1 -1
  3. langroid/agent/chat_agent.py +191 -37
  4. langroid/agent/chat_document.py +142 -29
  5. langroid/agent/openai_assistant.py +20 -4
  6. langroid/agent/special/lance_doc_chat_agent.py +25 -18
  7. langroid/agent/special/lance_rag/critic_agent.py +37 -5
  8. langroid/agent/special/lance_rag/query_planner_agent.py +102 -63
  9. langroid/agent/special/lance_tools.py +10 -2
  10. langroid/agent/special/sql/sql_chat_agent.py +69 -13
  11. langroid/agent/task.py +179 -43
  12. langroid/agent/tool_message.py +19 -7
  13. langroid/agent/tools/__init__.py +5 -0
  14. langroid/agent/tools/orchestration.py +216 -0
  15. langroid/agent/tools/recipient_tool.py +6 -11
  16. langroid/agent/tools/rewind_tool.py +1 -1
  17. langroid/agent/typed_task.py +19 -0
  18. langroid/language_models/.chainlit/config.toml +121 -0
  19. langroid/language_models/.chainlit/translations/en-US.json +231 -0
  20. langroid/language_models/base.py +114 -12
  21. langroid/language_models/mock_lm.py +10 -1
  22. langroid/language_models/openai_gpt.py +260 -36
  23. langroid/mytypes.py +0 -1
  24. langroid/parsing/parse_json.py +19 -2
  25. langroid/utils/pydantic_utils.py +19 -0
  26. langroid/vector_store/base.py +3 -1
  27. langroid/vector_store/lancedb.py +2 -0
  28. {langroid-0.6.7.dist-info → langroid-0.9.0.dist-info}/METADATA +4 -1
  29. {langroid-0.6.7.dist-info → langroid-0.9.0.dist-info}/RECORD +32 -33
  30. pyproject.toml +2 -1
  31. langroid/agent/special/lance_rag_new/__init__.py +0 -9
  32. langroid/agent/special/lance_rag_new/critic_agent.py +0 -171
  33. langroid/agent/special/lance_rag_new/lance_rag_task.py +0 -144
  34. langroid/agent/special/lance_rag_new/query_planner_agent.py +0 -222
  35. langroid/agent/team.py +0 -1758
  36. {langroid-0.6.7.dist-info → langroid-0.9.0.dist-info}/LICENSE +0 -0
  37. {langroid-0.6.7.dist-info → langroid-0.9.0.dist-info}/WHEEL +0 -0
langroid/agent/task.py CHANGED
@@ -5,7 +5,7 @@ import copy
5
5
  import logging
6
6
  import re
7
7
  import threading
8
- from collections import Counter, deque
8
+ from collections import Counter, OrderedDict, deque
9
9
  from pathlib import Path
10
10
  from types import SimpleNamespace
11
11
  from typing import (
@@ -33,6 +33,8 @@ from langroid.agent.chat_document import (
33
33
  ChatDocument,
34
34
  StatusCode,
35
35
  )
36
+ from langroid.agent.tool_message import ToolMessage
37
+ from langroid.agent.tools.orchestration import AgentDoneTool, DoneTool
36
38
  from langroid.cachedb.redis_cachedb import RedisCache, RedisCacheConfig
37
39
  from langroid.exceptions import InfiniteLoopException
38
40
  from langroid.mytypes import Entity
@@ -89,6 +91,8 @@ class TaskConfig(BaseModel):
89
91
  this is always enabled since this is a critical way for responders to
90
92
  indicate that the message should be sent to a specific entity/agent.
91
93
  (Search for "SEND_TO" in the examples/ dir to see how this is used.)
94
+ allow_subtask_multi_oai_tools (bool): whether to allow multiple OpenAI
95
+ tool-calls to be sent to a sub-task.
92
96
  """
93
97
 
94
98
  inf_loop_cycle_len: int = 10
@@ -97,6 +101,7 @@ class TaskConfig(BaseModel):
97
101
  restart_as_subtask: bool = False
98
102
  logs_dir: str = "logs"
99
103
  addressing_prefix: str = ""
104
+ allow_subtask_multi_oai_tools: bool = True
100
105
 
101
106
 
102
107
  class Task:
@@ -570,9 +575,13 @@ class Task:
570
575
  return self.pending_message
571
576
 
572
577
  def reset_all_sub_tasks(self) -> None:
573
- """Recursively reset message history of own agent and all sub-tasks"""
578
+ """
579
+ Recursively reset message history & state of own agent and
580
+ those of all sub-tasks.
581
+ """
574
582
  self.agent.clear_history(0)
575
583
  self.agent.clear_dialog()
584
+ self.agent.init_state()
576
585
  for t in self.sub_tasks:
577
586
  t.reset_all_sub_tasks()
578
587
 
@@ -584,11 +593,13 @@ class Task:
584
593
  max_cost: float = 0,
585
594
  max_tokens: int = 0,
586
595
  session_id: str = "",
596
+ allow_restart: bool = True,
587
597
  ) -> Optional[ChatDocument]:
588
598
  """Synchronous version of `run_async()`.
589
599
  See `run_async()` for details."""
590
- if (self.restart and caller is None) or (
591
- self.config_sub_task.restart_as_subtask and caller is not None
600
+ if allow_restart and (
601
+ (self.restart and caller is None)
602
+ or (self.config_sub_task.restart_as_subtask and caller is not None)
592
603
  ):
593
604
  # We are either at top level, with restart = True, OR
594
605
  # we are a sub-task with restart_as_subtask = True,
@@ -679,6 +690,7 @@ class Task:
679
690
  max_cost: float = 0,
680
691
  max_tokens: int = 0,
681
692
  session_id: str = "",
693
+ allow_restart: bool = True,
682
694
  ) -> Optional[ChatDocument]:
683
695
  """
684
696
  Loop over `step()` until task is considered done or `turns` is reached.
@@ -700,6 +712,7 @@ class Task:
700
712
  max_cost (float): max cost allowed for the task (default 0 -> no limit)
701
713
  max_tokens (int): max tokens allowed for the task (default 0 -> no limit)
702
714
  session_id (str): session id for the task
715
+ allow_restart (bool): whether to allow restarting the task
703
716
 
704
717
  Returns:
705
718
  Optional[ChatDocument]: valid result of the task.
@@ -710,11 +723,9 @@ class Task:
710
723
  # message can be considered to be from the USER
711
724
  # (from the POV of this agent's LLM).
712
725
 
713
- if (
714
- self.restart
715
- and caller is None
716
- or self.config_sub_task.restart_as_subtask
717
- and caller is not None
726
+ if allow_restart and (
727
+ (self.restart and caller is None)
728
+ or (self.config_sub_task.restart_as_subtask and caller is not None)
718
729
  ):
719
730
  # We are either at top level, with restart = True, OR
720
731
  # we are a sub-task with restart_as_subtask = True,
@@ -923,7 +934,6 @@ class Task:
923
934
  # create dummy msg for logging
924
935
  log_doc = ChatDocument(
925
936
  content="[CANNOT RESPOND]",
926
- function_call=None,
927
937
  metadata=ChatDocMetaData(
928
938
  sender=r if isinstance(r, Entity) else Entity.USER,
929
939
  sender_name=str(r),
@@ -1023,11 +1033,11 @@ class Task:
1023
1033
  # (responder, result) from a responder who explicitly said NO_ANSWER
1024
1034
  no_answer_response: None | Tuple[Responder, ChatDocument] = None
1025
1035
  for r in responders:
1036
+ self.is_pass_thru = False
1026
1037
  if not self._can_respond(r):
1027
1038
  # create dummy msg for logging
1028
1039
  log_doc = ChatDocument(
1029
1040
  content="[CANNOT RESPOND]",
1030
- function_call=None,
1031
1041
  metadata=ChatDocMetaData(
1032
1042
  sender=r if isinstance(r, Entity) else Entity.USER,
1033
1043
  sender_name=str(r),
@@ -1097,10 +1107,7 @@ class Task:
1097
1107
  # Contrast this with self.pending_message.metadata.sender, which is an ENTITY
1098
1108
  # of this agent, or a sub-task's agent.
1099
1109
  if not self.is_pass_thru:
1100
- if (
1101
- self.pending_message is not None
1102
- and self.pending_message.metadata.agent_id == self.agent.id
1103
- ):
1110
+ if self.pending_message is not None and not isinstance(r, Task):
1104
1111
  # when pending msg is from our own agent, respect the sender set there,
1105
1112
  # since sometimes a response may "mock" as if the response is from
1106
1113
  # another entity (e.g when using RewindTool, the agent handler
@@ -1111,7 +1118,7 @@ class Task:
1111
1118
  self.pending_sender = r
1112
1119
  self.pending_message = result
1113
1120
  # set the parent/child links ONLY if not already set by agent internally,
1114
- # which may happen when using the RewindTool
1121
+ # which may happen when using the RewindTool, or in other scenarios.
1115
1122
  if parent is not None and not result.metadata.parent_id:
1116
1123
  result.metadata.parent_id = parent.id()
1117
1124
  if parent is not None and not parent.metadata.child_id:
@@ -1166,6 +1173,26 @@ class Task:
1166
1173
  msg_str = escape(str(self.pending_message))
1167
1174
  print(f"[grey37][{sender_str}]{msg_str}[/grey37]")
1168
1175
 
1176
+ def _forbid_multi_oai_tools(self, e: Responder) -> ChatDocument:
1177
+ # Passing multiple OpenAI Tools to be handled by another agent
1178
+ # is not supported yet (we need to carefully establish correspondence
1179
+ # between the original tool-calls of agent A, and the returned results,
1180
+ # which may involve recursive-called tools by agent B).
1181
+ # So we set an error result corresponding to each tool-call.
1182
+ assert isinstance(
1183
+ e, Task
1184
+ ), "Forbidding multiple OAI tools only applies to a responder of type Task"
1185
+ err_str = """
1186
+ ERROR: cannot pass multiple tools to another agent!
1187
+ Please use ONE tool at a time!
1188
+ """
1189
+ id2result = OrderedDict((tc.id, err_str) for tc in self.agent.oai_tool_calls)
1190
+ result = e.agent.create_user_response(
1191
+ content="",
1192
+ oai_tool_id2result=id2result,
1193
+ )
1194
+ return result
1195
+
1169
1196
  def response(
1170
1197
  self,
1171
1198
  e: Responder,
@@ -1178,15 +1205,40 @@ class Task:
1178
1205
  actual_turns = e.turns if e.turns > 0 else turns
1179
1206
  e.agent.callbacks.set_parent_agent(self.agent)
1180
1207
  # e.callbacks.set_parent_agent(self.agent)
1181
- result = e.run(
1182
- self.pending_message,
1183
- turns=actual_turns,
1184
- caller=self,
1185
- max_cost=self.max_cost,
1186
- max_tokens=self.max_tokens,
1187
- )
1208
+ pending_tools = self.agent.get_tool_messages(self.pending_message)
1209
+ # TODO disable this
1210
+ if (
1211
+ len(pending_tools) > 1
1212
+ and len(self.agent.oai_tool_calls) > 1
1213
+ and not self.config.allow_subtask_multi_oai_tools
1214
+ ):
1215
+ result = self._forbid_multi_oai_tools(e)
1216
+ else:
1217
+ result = e.run(
1218
+ self.pending_message,
1219
+ turns=actual_turns,
1220
+ caller=self,
1221
+ max_cost=self.max_cost,
1222
+ max_tokens=self.max_tokens,
1223
+ )
1224
+ if result is not None:
1225
+ content, id2result, oai_tool_id = self.agent.process_tool_results(
1226
+ result.content,
1227
+ result.oai_tool_id2result,
1228
+ (
1229
+ self.pending_message.oai_tool_calls
1230
+ if isinstance(self.pending_message, ChatDocument)
1231
+ else None
1232
+ ),
1233
+ )
1234
+ result.content = content
1235
+ result.oai_tool_id2result = id2result
1236
+ result.metadata.oai_tool_id = oai_tool_id
1237
+
1188
1238
  result_str = ( # only used by callback to display content and possible tool
1189
- "NONE" if result is None else str(ChatDocument.to_LLMMessage(result))
1239
+ "NONE"
1240
+ if result is None
1241
+ else "\n\n".join(str(m) for m in ChatDocument.to_LLMMessage(result))
1190
1242
  )
1191
1243
  maybe_tool = len(extract_top_level_json(result_str)) > 0
1192
1244
  self.callbacks.show_subtask_response(
@@ -1197,14 +1249,26 @@ class Task:
1197
1249
  else:
1198
1250
  response_fn = self._entity_responder_map[cast(Entity, e)]
1199
1251
  result = response_fn(self.pending_message)
1200
- return self._process_result_routing(result)
1252
+ return self._process_result_routing(result, e)
1201
1253
 
1202
1254
  def _process_result_routing(
1203
- self, result: ChatDocument | None
1255
+ self, result: ChatDocument | None, e: Responder
1204
1256
  ) -> ChatDocument | None:
1205
1257
  # process result in case there is a routing instruction
1206
1258
  if result is None:
1207
1259
  return None
1260
+ if isinstance(result, ToolMessage):
1261
+ # this supports Agent responders and Task.run() to
1262
+ # return a ToolMessage, in addition str, ChatDocument
1263
+ if isinstance(e, Task):
1264
+ # With the curr defn of Task.result(),
1265
+ # Task.run() can't return a ToolMessage, so this case doesn't occur,
1266
+ # but we leave it here in case a
1267
+ # Task subclass overrides default behavior
1268
+ return e.agent.create_user_response(tool_messages=[result])
1269
+ else:
1270
+ # e must be this agent's Entity (LLM, AGENT or USER)
1271
+ return self.agent.response_template(e=e, tool_messages=[result])
1208
1272
  # if result content starts with @name, set recipient to name
1209
1273
  is_pass, recipient, content = parse_routing(
1210
1274
  result,
@@ -1258,15 +1322,42 @@ class Task:
1258
1322
  if isinstance(e, Task):
1259
1323
  actual_turns = e.turns if e.turns > 0 else turns
1260
1324
  e.agent.callbacks.set_parent_agent(self.agent)
1261
- # e.callbacks.set_parent_agent(self.agent)
1262
- result = await e.run_async(
1263
- self.pending_message,
1264
- turns=actual_turns,
1265
- caller=self,
1266
- max_cost=self.max_cost,
1267
- max_tokens=self.max_tokens,
1325
+ pending_tools = self.agent.get_tool_messages(self.pending_message)
1326
+ # TODO disable this
1327
+ if (
1328
+ len(pending_tools) > 1
1329
+ and len(self.agent.oai_tool_calls) > 1
1330
+ and not self.config.allow_subtask_multi_oai_tools
1331
+ ):
1332
+ result = self._forbid_multi_oai_tools(e)
1333
+ else:
1334
+ # e.callbacks.set_parent_agent(self.agent)
1335
+ result = await e.run_async(
1336
+ self.pending_message,
1337
+ turns=actual_turns,
1338
+ caller=self,
1339
+ max_cost=self.max_cost,
1340
+ max_tokens=self.max_tokens,
1341
+ )
1342
+ if result is not None:
1343
+ content, id2result, oai_tool_id = self.agent.process_tool_results(
1344
+ result.content,
1345
+ result.oai_tool_id2result,
1346
+ (
1347
+ self.pending_message.oai_tool_calls
1348
+ if isinstance(self.pending_message, ChatDocument)
1349
+ else None
1350
+ ),
1351
+ )
1352
+ result.content = content
1353
+ result.oai_tool_id2result = id2result
1354
+ result.metadata.oai_tool_id = oai_tool_id
1355
+
1356
+ result_str = ( # only used by callback to display content and possible tool
1357
+ "NONE"
1358
+ if result is None
1359
+ else "\n\n".join(str(m) for m in ChatDocument.to_LLMMessage(result))
1268
1360
  )
1269
- result_str = str(ChatDocument.to_LLMMessage(result))
1270
1361
  maybe_tool = len(extract_top_level_json(result_str)) > 0
1271
1362
  self.callbacks.show_subtask_response(
1272
1363
  task=e,
@@ -1276,7 +1367,7 @@ class Task:
1276
1367
  else:
1277
1368
  response_fn = self._entity_responder_async_map[cast(Entity, e)]
1278
1369
  result = await response_fn(self.pending_message)
1279
- return self._process_result_routing(result)
1370
+ return self._process_result_routing(result, e)
1280
1371
 
1281
1372
  def result(self, status: StatusCode | None = None) -> ChatDocument | None:
1282
1373
  """
@@ -1301,8 +1392,24 @@ class Task:
1301
1392
  if DONE in content:
1302
1393
  # assuming it is of the form "DONE: <content>"
1303
1394
  content = content.replace(DONE, "").strip()
1395
+ oai_tool_calls = result_msg.oai_tool_calls if result_msg else None
1396
+ oai_tool_id2result = result_msg.oai_tool_id2result if result_msg else None
1304
1397
  fun_call = result_msg.function_call if result_msg else None
1305
1398
  tool_messages = result_msg.tool_messages if result_msg else []
1399
+ # if there is an LLMDoneTool or AgentDoneTool among these,
1400
+ # we extract content and tools from here, and ignore all others
1401
+ for t in tool_messages:
1402
+ if isinstance(t, (AgentDoneTool, DoneTool)):
1403
+ # there shouldn't be multiple tools like this; just take the first
1404
+ content = t.content
1405
+ if isinstance(t, AgentDoneTool):
1406
+ tool_messages = t.tools
1407
+ break
1408
+ # drop the "Done" tools since they should not be part of the task result,
1409
+ # or else they would cause the parent task to get done!
1410
+ tool_messages = [
1411
+ t for t in tool_messages if not isinstance(t, (DoneTool, AgentDoneTool))
1412
+ ]
1306
1413
  block = result_msg.metadata.block if result_msg else None
1307
1414
  recipient = result_msg.metadata.recipient if result_msg else ""
1308
1415
  tool_ids = result_msg.metadata.tool_ids if result_msg else []
@@ -1312,6 +1419,8 @@ class Task:
1312
1419
  # since to the "parent" task, this result is equivalent to a response from USER
1313
1420
  result_doc = ChatDocument(
1314
1421
  content=content,
1422
+ oai_tool_calls=oai_tool_calls,
1423
+ oai_tool_id2result=oai_tool_id2result,
1315
1424
  function_call=fun_call,
1316
1425
  tool_messages=tool_messages,
1317
1426
  metadata=ChatDocMetaData(
@@ -1346,6 +1455,8 @@ class Task:
1346
1455
  isinstance(msg, ChatDocument)
1347
1456
  and msg.content.strip() in [PASS, ""]
1348
1457
  and msg.function_call is None
1458
+ and msg.oai_tool_calls is None
1459
+ and msg.oai_tool_id2result is None
1349
1460
  and msg.tool_messages == []
1350
1461
  )
1351
1462
  )
@@ -1357,7 +1468,16 @@ class Task:
1357
1468
 
1358
1469
  response_says_done = result is not None and (
1359
1470
  (isinstance(result, str) and DONE in result)
1360
- or (isinstance(result, ChatDocument) and DONE in result.content)
1471
+ or (
1472
+ isinstance(result, ChatDocument)
1473
+ and (
1474
+ DONE in result.content
1475
+ or any(
1476
+ isinstance(t, (DoneTool, AgentDoneTool))
1477
+ for t in result.tool_messages
1478
+ )
1479
+ )
1480
+ )
1361
1481
  )
1362
1482
  return (
1363
1483
  (
@@ -1469,13 +1589,22 @@ class Task:
1469
1589
  if self._is_kill():
1470
1590
  return (True, StatusCode.KILL)
1471
1591
  result = result or self.pending_message
1592
+ # An entity decided task is done, either via DoneTool,
1593
+ # or by explicitly saying DONE
1594
+ done_result = result is not None and (
1595
+ DONE in (result.content if isinstance(result, str) else result.content)
1596
+ or any(
1597
+ isinstance(t, (DoneTool, AgentDoneTool)) for t in result.tool_messages
1598
+ )
1599
+ )
1600
+
1472
1601
  user_quit = (
1473
1602
  result is not None
1474
- and (result.content in USER_QUIT_STRINGS or DONE in result.content)
1603
+ and (result.content in USER_QUIT_STRINGS or done_result)
1475
1604
  and result.metadata.sender == Entity.USER
1476
1605
  )
1477
- if self._level == 0 and self.interactive and self.only_user_quits_root:
1478
- # for top-level task, in interactive mode, only user can quit out
1606
+ if self._level == 0 and self._user_can_respond() and self.only_user_quits_root:
1607
+ # for top-level task, only user can quit out
1479
1608
  return (user_quit, StatusCode.USER_QUIT if user_quit else StatusCode.OK)
1480
1609
 
1481
1610
  if self.is_done:
@@ -1510,8 +1639,7 @@ class Task:
1510
1639
  final = (
1511
1640
  # no valid response from any entity/agent in current turn
1512
1641
  result is None
1513
- # An entity decided task is done
1514
- or DONE in result.content
1642
+ or done_result
1515
1643
  or ( # current task is addressing message to caller task
1516
1644
  self.caller is not None
1517
1645
  and self.caller.name != ""
@@ -1630,14 +1758,17 @@ class Task:
1630
1758
  and recipient != self.name # case sensitive
1631
1759
  )
1632
1760
 
1633
- def _can_respond(self, e: Responder) -> bool:
1634
- user_can_respond = self.interactive or (
1761
+ def _user_can_respond(self) -> bool:
1762
+ return self.interactive or (
1635
1763
  # regardless of self.interactive, if a msg is explicitly addressed to
1636
1764
  # user, then wait for user response
1637
1765
  self.pending_message is not None
1638
1766
  and self.pending_message.metadata.recipient == Entity.USER
1639
1767
  )
1640
1768
 
1769
+ def _can_respond(self, e: Responder) -> bool:
1770
+ user_can_respond = self._user_can_respond()
1771
+
1641
1772
  if self.pending_sender == e or (e == Entity.USER and not user_can_respond):
1642
1773
  # sender is same as e (an entity cannot respond to its own msg),
1643
1774
  # or user cannot respond
@@ -1645,6 +1776,11 @@ class Task:
1645
1776
 
1646
1777
  if self.pending_message is None:
1647
1778
  return True
1779
+ if isinstance(e, Task) and e.agent.has_only_unhandled_tools(
1780
+ self.pending_message
1781
+ ):
1782
+ return False
1783
+
1648
1784
  if self._recipient_mismatch(e):
1649
1785
  # Cannot respond if not addressed to this entity
1650
1786
  return False
@@ -15,7 +15,7 @@ from typing import Any, Dict, List, Tuple, Type
15
15
  from docstring_parser import parse
16
16
 
17
17
  from langroid.language_models.base import LLMFunctionSpec
18
- from langroid.pydantic_v1 import BaseModel
18
+ from langroid.pydantic_v1 import BaseModel, ConfigDict, Extra
19
19
  from langroid.utils.pydantic_utils import (
20
20
  _recursive_purge_dict_key,
21
21
  generate_simple_schema,
@@ -39,14 +39,21 @@ class ToolMessage(ABC, BaseModel):
39
39
 
40
40
  request: str
41
41
  purpose: str
42
+ id: str = "" # placeholder for OpenAI-API tool_call_id
43
+
44
+ model_config = ConfigDict(extra=Extra.allow)
45
+
46
+ _handle_only: bool = False # only allow handling, but not use (LLM-generation)?
42
47
 
43
48
  class Config:
49
+ # only HANDLING allowed, NOT "use" (i.e LLM generation)
50
+ handle_only: bool = False
44
51
  arbitrary_types_allowed = False
45
52
  validate_all = True
46
53
  validate_assignment = True
47
54
  # do not include these fields in the generated schema
48
55
  # since we don't require the LLM to specify them
49
- schema_extra = {"exclude": {"purpose"}}
56
+ schema_extra = {"exclude": {"purpose", "id", "model_config"}}
50
57
 
51
58
  @classmethod
52
59
  def instructions(cls) -> str:
@@ -108,13 +115,13 @@ class ToolMessage(ABC, BaseModel):
108
115
  return "\n\n".join(examples_jsons)
109
116
 
110
117
  def to_json(self) -> str:
111
- return self.json(indent=4, exclude={"purpose"})
118
+ return self.json(indent=4, exclude=self.Config.schema_extra["exclude"])
112
119
 
113
120
  def json_example(self) -> str:
114
- return self.json(indent=4, exclude={"purpose"})
121
+ return self.json(indent=4, exclude=self.Config.schema_extra["exclude"])
115
122
 
116
123
  def dict_example(self) -> Dict[str, Any]:
117
- return self.dict(exclude={"purpose"})
124
+ return self.dict(exclude=self.Config.schema_extra["exclude"])
118
125
 
119
126
  @classmethod
120
127
  def default_value(cls, f: str) -> Any:
@@ -218,7 +225,9 @@ class ToolMessage(ABC, BaseModel):
218
225
  if "description" not in parameters["properties"][name]:
219
226
  parameters["properties"][name]["description"] = description
220
227
 
221
- excludes = ["purpose"] if request else ["request", "purpose"]
228
+ excludes = cls.Config.schema_extra["exclude"]
229
+ if not request:
230
+ excludes = excludes.union({"request"})
222
231
  # exclude 'excludes' from parameters["properties"]:
223
232
  parameters["properties"] = {
224
233
  field: details
@@ -259,5 +268,8 @@ class ToolMessage(ABC, BaseModel):
259
268
  Returns:
260
269
  Dict[str, Any]: simplified schema
261
270
  """
262
- schema = generate_simple_schema(cls, exclude=["purpose"])
271
+ schema = generate_simple_schema(
272
+ cls,
273
+ exclude=list(cls.Config.schema_extra["exclude"]),
274
+ )
263
275
  return schema
@@ -1,9 +1,11 @@
1
1
  from . import google_search_tool
2
2
  from . import recipient_tool
3
3
  from . import rewind_tool
4
+ from . import orchestration
4
5
  from .google_search_tool import GoogleSearchTool
5
6
  from .recipient_tool import AddRecipientTool, RecipientTool
6
7
  from .rewind_tool import RewindTool
8
+ from .orchestration import AgentDoneTool, DoneTool
7
9
 
8
10
  __all__ = [
9
11
  "GoogleSearchTool",
@@ -13,4 +15,7 @@ __all__ = [
13
15
  "recipient_tool",
14
16
  "rewind_tool",
15
17
  "RewindTool",
18
+ "orchestration",
19
+ "AgentDoneTool",
20
+ "DoneTool",
16
21
  ]