langroid 0.33.4__py3-none-any.whl → 0.33.7__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 (129) hide show
  1. langroid/__init__.py +106 -0
  2. langroid/agent/__init__.py +41 -0
  3. langroid/agent/base.py +1983 -0
  4. langroid/agent/batch.py +398 -0
  5. langroid/agent/callbacks/__init__.py +0 -0
  6. langroid/agent/callbacks/chainlit.py +598 -0
  7. langroid/agent/chat_agent.py +1899 -0
  8. langroid/agent/chat_document.py +454 -0
  9. langroid/agent/openai_assistant.py +882 -0
  10. langroid/agent/special/__init__.py +59 -0
  11. langroid/agent/special/arangodb/__init__.py +0 -0
  12. langroid/agent/special/arangodb/arangodb_agent.py +656 -0
  13. langroid/agent/special/arangodb/system_messages.py +186 -0
  14. langroid/agent/special/arangodb/tools.py +107 -0
  15. langroid/agent/special/arangodb/utils.py +36 -0
  16. langroid/agent/special/doc_chat_agent.py +1466 -0
  17. langroid/agent/special/lance_doc_chat_agent.py +262 -0
  18. langroid/agent/special/lance_rag/__init__.py +9 -0
  19. langroid/agent/special/lance_rag/critic_agent.py +198 -0
  20. langroid/agent/special/lance_rag/lance_rag_task.py +82 -0
  21. langroid/agent/special/lance_rag/query_planner_agent.py +260 -0
  22. langroid/agent/special/lance_tools.py +61 -0
  23. langroid/agent/special/neo4j/__init__.py +0 -0
  24. langroid/agent/special/neo4j/csv_kg_chat.py +174 -0
  25. langroid/agent/special/neo4j/neo4j_chat_agent.py +433 -0
  26. langroid/agent/special/neo4j/system_messages.py +120 -0
  27. langroid/agent/special/neo4j/tools.py +32 -0
  28. langroid/agent/special/relevance_extractor_agent.py +127 -0
  29. langroid/agent/special/retriever_agent.py +56 -0
  30. langroid/agent/special/sql/__init__.py +17 -0
  31. langroid/agent/special/sql/sql_chat_agent.py +654 -0
  32. langroid/agent/special/sql/utils/__init__.py +21 -0
  33. langroid/agent/special/sql/utils/description_extractors.py +190 -0
  34. langroid/agent/special/sql/utils/populate_metadata.py +85 -0
  35. langroid/agent/special/sql/utils/system_message.py +35 -0
  36. langroid/agent/special/sql/utils/tools.py +64 -0
  37. langroid/agent/special/table_chat_agent.py +263 -0
  38. langroid/agent/task.py +2095 -0
  39. langroid/agent/tool_message.py +393 -0
  40. langroid/agent/tools/__init__.py +38 -0
  41. langroid/agent/tools/duckduckgo_search_tool.py +50 -0
  42. langroid/agent/tools/file_tools.py +234 -0
  43. langroid/agent/tools/google_search_tool.py +39 -0
  44. langroid/agent/tools/metaphor_search_tool.py +68 -0
  45. langroid/agent/tools/orchestration.py +303 -0
  46. langroid/agent/tools/recipient_tool.py +235 -0
  47. langroid/agent/tools/retrieval_tool.py +32 -0
  48. langroid/agent/tools/rewind_tool.py +137 -0
  49. langroid/agent/tools/segment_extract_tool.py +41 -0
  50. langroid/agent/xml_tool_message.py +382 -0
  51. langroid/cachedb/__init__.py +17 -0
  52. langroid/cachedb/base.py +58 -0
  53. langroid/cachedb/momento_cachedb.py +108 -0
  54. langroid/cachedb/redis_cachedb.py +153 -0
  55. langroid/embedding_models/__init__.py +39 -0
  56. langroid/embedding_models/base.py +74 -0
  57. langroid/embedding_models/models.py +461 -0
  58. langroid/embedding_models/protoc/__init__.py +0 -0
  59. langroid/embedding_models/protoc/embeddings.proto +19 -0
  60. langroid/embedding_models/protoc/embeddings_pb2.py +33 -0
  61. langroid/embedding_models/protoc/embeddings_pb2.pyi +50 -0
  62. langroid/embedding_models/protoc/embeddings_pb2_grpc.py +79 -0
  63. langroid/embedding_models/remote_embeds.py +153 -0
  64. langroid/exceptions.py +71 -0
  65. langroid/language_models/__init__.py +53 -0
  66. langroid/language_models/azure_openai.py +153 -0
  67. langroid/language_models/base.py +678 -0
  68. langroid/language_models/config.py +18 -0
  69. langroid/language_models/mock_lm.py +124 -0
  70. langroid/language_models/openai_gpt.py +1964 -0
  71. langroid/language_models/prompt_formatter/__init__.py +16 -0
  72. langroid/language_models/prompt_formatter/base.py +40 -0
  73. langroid/language_models/prompt_formatter/hf_formatter.py +132 -0
  74. langroid/language_models/prompt_formatter/llama2_formatter.py +75 -0
  75. langroid/language_models/utils.py +151 -0
  76. langroid/mytypes.py +84 -0
  77. langroid/parsing/__init__.py +52 -0
  78. langroid/parsing/agent_chats.py +38 -0
  79. langroid/parsing/code_parser.py +121 -0
  80. langroid/parsing/document_parser.py +718 -0
  81. langroid/parsing/para_sentence_split.py +62 -0
  82. langroid/parsing/parse_json.py +155 -0
  83. langroid/parsing/parser.py +313 -0
  84. langroid/parsing/repo_loader.py +790 -0
  85. langroid/parsing/routing.py +36 -0
  86. langroid/parsing/search.py +275 -0
  87. langroid/parsing/spider.py +102 -0
  88. langroid/parsing/table_loader.py +94 -0
  89. langroid/parsing/url_loader.py +111 -0
  90. langroid/parsing/urls.py +273 -0
  91. langroid/parsing/utils.py +373 -0
  92. langroid/parsing/web_search.py +156 -0
  93. langroid/prompts/__init__.py +9 -0
  94. langroid/prompts/dialog.py +17 -0
  95. langroid/prompts/prompts_config.py +5 -0
  96. langroid/prompts/templates.py +141 -0
  97. langroid/pydantic_v1/__init__.py +10 -0
  98. langroid/pydantic_v1/main.py +4 -0
  99. langroid/utils/__init__.py +19 -0
  100. langroid/utils/algorithms/__init__.py +3 -0
  101. langroid/utils/algorithms/graph.py +103 -0
  102. langroid/utils/configuration.py +98 -0
  103. langroid/utils/constants.py +30 -0
  104. langroid/utils/git_utils.py +252 -0
  105. langroid/utils/globals.py +49 -0
  106. langroid/utils/logging.py +135 -0
  107. langroid/utils/object_registry.py +66 -0
  108. langroid/utils/output/__init__.py +20 -0
  109. langroid/utils/output/citations.py +41 -0
  110. langroid/utils/output/printing.py +99 -0
  111. langroid/utils/output/status.py +40 -0
  112. langroid/utils/pandas_utils.py +30 -0
  113. langroid/utils/pydantic_utils.py +602 -0
  114. langroid/utils/system.py +286 -0
  115. langroid/utils/types.py +93 -0
  116. langroid/vector_store/__init__.py +50 -0
  117. langroid/vector_store/base.py +359 -0
  118. langroid/vector_store/chromadb.py +214 -0
  119. langroid/vector_store/lancedb.py +406 -0
  120. langroid/vector_store/meilisearch.py +299 -0
  121. langroid/vector_store/momento.py +278 -0
  122. langroid/vector_store/qdrantdb.py +468 -0
  123. {langroid-0.33.4.dist-info → langroid-0.33.7.dist-info}/METADATA +95 -94
  124. langroid-0.33.7.dist-info/RECORD +127 -0
  125. {langroid-0.33.4.dist-info → langroid-0.33.7.dist-info}/WHEEL +1 -1
  126. langroid-0.33.4.dist-info/RECORD +0 -7
  127. langroid-0.33.4.dist-info/entry_points.txt +0 -4
  128. pyproject.toml +0 -356
  129. {langroid-0.33.4.dist-info → langroid-0.33.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,234 @@
1
+ from contextlib import chdir
2
+ from pathlib import Path
3
+ from textwrap import dedent
4
+ from typing import Callable, List, Tuple, Type
5
+
6
+ import git
7
+
8
+ from langroid.agent.tool_message import ToolMessage
9
+ from langroid.agent.xml_tool_message import XMLToolMessage
10
+ from langroid.pydantic_v1 import Field
11
+ from langroid.utils.git_utils import git_commit_file
12
+ from langroid.utils.system import create_file, list_dir, read_file
13
+
14
+
15
+ class ReadFileTool(ToolMessage):
16
+ request: str = "read_file_tool"
17
+ purpose: str = "Read the contents of a <file_path>"
18
+ file_path: str
19
+
20
+ _line_nums: bool = True # whether to add line numbers to the content
21
+ _curr_dir: Callable[[], str] | None = None
22
+
23
+ @classmethod
24
+ def create(
25
+ cls,
26
+ get_curr_dir: Callable[[], str] | None,
27
+ ) -> Type["ReadFileTool"]:
28
+ """
29
+ Create a subclass of ReadFileTool for a specific directory
30
+
31
+ Args:
32
+ get_curr_dir (callable): A function that returns the current directory.
33
+
34
+ Returns:
35
+ Type[ReadFileTool]: A subclass of the ReadFileTool class, specifically
36
+ for the current directory.
37
+ """
38
+
39
+ class CustomReadFileTool(cls): # type: ignore
40
+ _curr_dir: Callable[[], str] | None = (
41
+ staticmethod(get_curr_dir) if get_curr_dir else None
42
+ )
43
+
44
+ return CustomReadFileTool
45
+
46
+ @classmethod
47
+ def examples(cls) -> List[ToolMessage | tuple[str, ToolMessage]]:
48
+ return [
49
+ cls(file_path="src/lib.rs"),
50
+ (
51
+ "I want to read the contents of src/main.rs",
52
+ cls(file_path="src/main.rs"),
53
+ ),
54
+ ]
55
+
56
+ def handle(self) -> str:
57
+ # return contents as str for LLM to read
58
+ # ASSUME: file_path should be relative to the curr_dir
59
+ try:
60
+ dir = (self._curr_dir and self._curr_dir()) or Path.cwd()
61
+ with chdir(dir):
62
+ # if file doesn't exist, return an error message
63
+ content = read_file(self.file_path, self._line_nums)
64
+ line_num_str = ""
65
+ if self._line_nums:
66
+ line_num_str = "(Line numbers added for reference only!)"
67
+ return f"""
68
+ CONTENTS of {self.file_path}:
69
+ {line_num_str}
70
+ ---------------------------
71
+ {content}
72
+ """
73
+ except FileNotFoundError:
74
+ return f"File not found: {self.file_path}"
75
+
76
+
77
+ class WriteFileTool(XMLToolMessage):
78
+ request: str = "write_file_tool"
79
+ purpose: str = """
80
+ Tool for writing <content> in a certain <language> to a <file_path>
81
+ """
82
+
83
+ file_path: str = Field(..., description="The path to the file to write the content")
84
+
85
+ language: str = Field(
86
+ default="",
87
+ description="""
88
+ The language of the content; could be human language or programming language
89
+ """,
90
+ )
91
+ content: str = Field(
92
+ ...,
93
+ description="The content to write to the file",
94
+ verbatim=True, # preserve the content as is; uses CDATA section in XML
95
+ )
96
+ _curr_dir: Callable[[], str] | None = None
97
+ _git_repo: Callable[[], git.Repo] | None = None
98
+ _commit_message: str = "Agent write file tool"
99
+
100
+ @classmethod
101
+ def create(
102
+ cls,
103
+ get_curr_dir: Callable[[], str] | None,
104
+ get_git_repo: Callable[[], str] | None,
105
+ ) -> Type["WriteFileTool"]:
106
+ """
107
+ Create a subclass of WriteFileTool with the current directory and git repo.
108
+
109
+ Args:
110
+ get_curr_dir (callable): A function that returns the current directory.
111
+ get_git_repo (callable): A function that returns the git repo.
112
+
113
+ Returns:
114
+ Type[WriteFileTool]: A subclass of the WriteFileTool class, specifically
115
+ for the current directory and git repo.
116
+ """
117
+
118
+ class CustomWriteFileTool(cls): # type: ignore
119
+ _curr_dir: Callable[[], str] | None = (
120
+ staticmethod(get_curr_dir) if get_curr_dir else None
121
+ )
122
+ _git_repo: Callable[[], str] | None = (
123
+ staticmethod(get_git_repo) if get_git_repo else None
124
+ )
125
+
126
+ return CustomWriteFileTool
127
+
128
+ @classmethod
129
+ def examples(cls) -> List[ToolMessage | Tuple[str, ToolMessage]]:
130
+ return [
131
+ (
132
+ """
133
+ I want to define a simple hello world python function
134
+ in a file "mycode/hello.py"
135
+ """,
136
+ cls(
137
+ file_path="mycode/hello.py",
138
+ language="python",
139
+ content="""
140
+ def hello():
141
+ print("Hello, World!")
142
+ """,
143
+ ),
144
+ ),
145
+ cls(
146
+ file_path="src/lib.rs",
147
+ language="rust",
148
+ content="""
149
+ fn main() {
150
+ println!("Hello, World!");
151
+ }
152
+ """,
153
+ ),
154
+ cls(
155
+ file_path="docs/intro.txt",
156
+ content="""
157
+ # Introduction
158
+ This is the first sentence of the introduction.
159
+ """,
160
+ ),
161
+ ]
162
+
163
+ def handle(self) -> str:
164
+ curr_dir = (self._curr_dir and self._curr_dir()) or Path.cwd()
165
+ with chdir(curr_dir):
166
+ create_file(self.file_path, self.content)
167
+ msg = f"Content written to {self.file_path}"
168
+ # possibly commit the file
169
+ if self._git_repo:
170
+ git_commit_file(
171
+ self._git_repo(),
172
+ self.file_path,
173
+ self._commit_message,
174
+ )
175
+ msg += " and committed"
176
+ return msg
177
+
178
+
179
+ class ListDirTool(ToolMessage):
180
+ request: str = "list_dir_tool"
181
+ purpose: str = "List the contents of a <dir_path>"
182
+ dir_path: str
183
+
184
+ _curr_dir: Callable[[], str] | None = None
185
+
186
+ @classmethod
187
+ def create(
188
+ cls,
189
+ get_curr_dir: Callable[[], str] | None,
190
+ ) -> Type["ReadFileTool"]:
191
+ """
192
+ Create a subclass of ListDirTool for a specific directory
193
+
194
+ Args:
195
+ get_curr_dir (callable): A function that returns the current directory.
196
+
197
+ Returns:
198
+ Type[ReadFileTool]: A subclass of the ReadFileTool class, specifically
199
+ for the current directory.
200
+ """
201
+
202
+ class CustomListDirTool(cls): # type: ignore
203
+ _curr_dir: Callable[[], str] | None = (
204
+ staticmethod(get_curr_dir) if get_curr_dir else None
205
+ )
206
+
207
+ return CustomListDirTool
208
+
209
+ @classmethod
210
+ def examples(cls) -> List[ToolMessage | tuple[str, ToolMessage]]:
211
+ return [
212
+ cls(dir_path="src"),
213
+ (
214
+ "I want to list the contents of src",
215
+ cls(dir_path="src"),
216
+ ),
217
+ ]
218
+
219
+ def handle(self) -> str:
220
+ # ASSUME: dir_path should be relative to the curr_dir_path
221
+ dir = (self._curr_dir and self._curr_dir()) or Path.cwd()
222
+ with chdir(dir):
223
+ contents = list_dir(self.dir_path)
224
+
225
+ if not contents:
226
+ return f"Directory not found or empty: {self.dir_path}"
227
+ contents_str = "\n".join(contents)
228
+ return dedent(
229
+ f"""
230
+ LISTING of directory {self.dir_path}:
231
+ ---------------------------
232
+ {contents_str}
233
+ """.strip()
234
+ )
@@ -0,0 +1,39 @@
1
+ """
2
+ A tool to trigger a Google search for a given query, and return the top results with
3
+ their titles, links, summaries. Since the tool is stateless (i.e. does not need
4
+ access to agent state), it can be enabled for any agent, without having to define a
5
+ special method inside the agent: `agent.enable_message(GoogleSearchTool)`
6
+
7
+ NOTE: Using this tool requires setting the GOOGLE_API_KEY and GOOGLE_CSE_ID
8
+ environment variables in your `.env` file, as explained in the
9
+ [README](https://github.com/langroid/langroid#gear-installation-and-setup).
10
+ """
11
+
12
+ from typing import List, Tuple
13
+
14
+ from langroid.agent.tool_message import ToolMessage
15
+ from langroid.parsing.web_search import google_search
16
+
17
+
18
+ class GoogleSearchTool(ToolMessage):
19
+ request: str = "web_search"
20
+ purpose: str = """
21
+ To search the web and return up to <num_results> links relevant to
22
+ the given <query>.
23
+ """
24
+ query: str
25
+ num_results: int
26
+
27
+ def handle(self) -> str:
28
+ search_results = google_search(self.query, self.num_results)
29
+ # return Title, Link, Summary of each result, separated by two newlines
30
+ return "\n\n".join(str(result) for result in search_results)
31
+
32
+ @classmethod
33
+ def examples(cls) -> List["ToolMessage" | Tuple[str, "ToolMessage"]]:
34
+ return [
35
+ cls(
36
+ query="When was the Llama2 Large Language Model (LLM) released?",
37
+ num_results=3,
38
+ ),
39
+ ]
@@ -0,0 +1,68 @@
1
+ """
2
+ A tool to trigger a Metaphor search for a given query,
3
+ (https://docs.exa.ai/reference/getting-started)
4
+ and return the top results with their titles, links, summaries.
5
+ Since the tool is stateless (i.e. does not need
6
+ access to agent state), it can be enabled for any agent, without having to define a
7
+ special method inside the agent: `agent.enable_message(MetaphorSearchTool)`
8
+
9
+ NOTE: To use this tool, you need to:
10
+
11
+ * set the METAPHOR_API_KEY environment variables in
12
+ your `.env` file, e.g. `METAPHOR_API_KEY=your_api_key_here`
13
+ (Note as of 28 Jan 2023, Metaphor renamed to Exa, so you can also use
14
+ `EXA_API_KEY=your_api_key_here`)
15
+
16
+ * install langroid with the `metaphor` extra, e.g.
17
+ `pip install langroid[metaphor]` or `uv pip install langroid[metaphor]`
18
+ or `poetry add langroid[metaphor]` or `uv add langroid[metaphor]`
19
+ (it installs the `metaphor-python` package from pypi).
20
+
21
+ For more information, please refer to the official docs:
22
+ https://metaphor.systems/
23
+ """
24
+
25
+ from typing import List, Tuple
26
+
27
+ from langroid.agent.tool_message import ToolMessage
28
+ from langroid.parsing.web_search import metaphor_search
29
+
30
+
31
+ class MetaphorSearchTool(ToolMessage):
32
+ request: str = "metaphor_search"
33
+ purpose: str = """
34
+ To search the web and return up to <num_results>
35
+ links relevant to the given <query>. When using this tool,
36
+ ONLY show the required JSON, DO NOT SAY ANYTHING ELSE.
37
+ Wait for the results of the web search, and then use them to
38
+ compose your response.
39
+ """
40
+ query: str
41
+ num_results: int
42
+
43
+ def handle(self) -> str:
44
+ """
45
+ Conducts a search using the metaphor API based on the provided query
46
+ and number of results by triggering a metaphor_search.
47
+
48
+ Returns:
49
+ str: A formatted string containing the titles, links, and
50
+ summaries of each search result, separated by two newlines.
51
+ """
52
+
53
+ search_results = metaphor_search(self.query, self.num_results)
54
+ # return Title, Link, Summary of each result, separated by two newlines
55
+ results_str = "\n\n".join(str(result) for result in search_results)
56
+ return f"""
57
+ BELOW ARE THE RESULTS FROM THE WEB SEARCH. USE THESE TO COMPOSE YOUR RESPONSE:
58
+ {results_str}
59
+ """
60
+
61
+ @classmethod
62
+ def examples(cls) -> List["ToolMessage" | Tuple[str, "ToolMessage"]]:
63
+ return [
64
+ cls(
65
+ query="When was the Llama2 Large Language Model (LLM) released?",
66
+ num_results=3,
67
+ ),
68
+ ]
@@ -0,0 +1,303 @@
1
+ """
2
+ Various tools to for agents to be able to control flow of Task, e.g.
3
+ termination, routing to another agent, etc.
4
+ """
5
+
6
+ from typing import Any, List, Tuple
7
+
8
+ from langroid.agent.chat_agent import ChatAgent
9
+ from langroid.agent.chat_document import ChatDocument
10
+ from langroid.agent.tool_message import ToolMessage
11
+ from langroid.mytypes import Entity
12
+ from langroid.pydantic_v1 import Extra
13
+ from langroid.utils.types import to_string
14
+
15
+
16
+ class AgentDoneTool(ToolMessage):
17
+ """Tool for AGENT entity (i.e. agent_response or downstream tool handling fns) to
18
+ signal the current task is done."""
19
+
20
+ purpose: str = """
21
+ To signal the current task is done, along with an optional message <content>
22
+ of arbitrary type (default None) and an
23
+ optional list of <tools> (default empty list).
24
+ """
25
+ request: str = "agent_done_tool"
26
+ content: Any = None
27
+ tools: List[ToolMessage] = []
28
+ # only meant for agent_response or tool-handlers, not for LLM generation:
29
+ _allow_llm_use: bool = False
30
+
31
+ def response(self, agent: ChatAgent) -> ChatDocument:
32
+ content_str = "" if self.content is None else to_string(self.content)
33
+ return agent.create_agent_response(
34
+ content=content_str,
35
+ content_any=self.content,
36
+ tool_messages=[self] + self.tools,
37
+ )
38
+
39
+
40
+ class DoneTool(ToolMessage):
41
+ """Tool for Agent Entity (i.e. agent_response) or LLM entity (i.e. llm_response) to
42
+ signal the current task is done, with some content as the result."""
43
+
44
+ purpose: str = """
45
+ To signal the current task is done, along with an optional message <content>
46
+ of arbitrary type (default None).
47
+ """
48
+ request: str = "done_tool"
49
+ content: str = ""
50
+
51
+ def response(self, agent: ChatAgent) -> ChatDocument:
52
+ return agent.create_agent_response(
53
+ content=self.content,
54
+ content_any=self.content,
55
+ tool_messages=[self],
56
+ )
57
+
58
+ @classmethod
59
+ def instructions(cls) -> str:
60
+ tool_name = cls.default_value("request")
61
+ return f"""
62
+ When you determine your task is finished,
63
+ use the tool `{tool_name}` to signal this,
64
+ along with any message or result, in the `content` field.
65
+ """
66
+
67
+
68
+ class ResultTool(ToolMessage):
69
+ """Class to use as a wrapper for sending arbitrary results from an Agent's
70
+ agent_response or tool handlers, to:
71
+ (a) trigger completion of the current task (similar to (Agent)DoneTool), and
72
+ (b) be returned as the result of the current task, i.e. this tool would appear
73
+ in the resulting ChatDocument's `tool_messages` list.
74
+ See test_tool_handlers_and_results in test_tool_messages.py, and
75
+ examples/basic/tool-extract-short-example.py.
76
+
77
+ Note:
78
+ - when defining a tool handler or agent_response, you can directly return
79
+ ResultTool(field1 = val1, ...),
80
+ where the values can be arbitrary data structures, including nested
81
+ Pydantic objs, or you can define a subclass of ResultTool with the
82
+ fields you want to return.
83
+ - This is a special ToolMessage that is NOT meant to be used or handled
84
+ by an agent.
85
+ - AgentDoneTool is more restrictive in that you can only send a `content`
86
+ or `tools` in the result.
87
+ """
88
+
89
+ request: str = "result_tool"
90
+ purpose: str = "Ignored; Wrapper for a structured message"
91
+ id: str = "" # placeholder for OpenAI-API tool_call_id
92
+
93
+ class Config:
94
+ extra = Extra.allow
95
+ arbitrary_types_allowed = False
96
+ validate_all = True
97
+ validate_assignment = True
98
+ # do not include these fields in the generated schema
99
+ # since we don't require the LLM to specify them
100
+ schema_extra = {"exclude": {"purpose", "id", "strict"}}
101
+
102
+ def handle(self) -> AgentDoneTool:
103
+ return AgentDoneTool(tools=[self])
104
+
105
+
106
+ class FinalResultTool(ToolMessage):
107
+ """Class to use as a wrapper for sending arbitrary results from an Agent's
108
+ agent_response or tool handlers, to:
109
+ (a) trigger completion of the current task as well as all parent tasks, and
110
+ (b) be returned as the final result of the root task, i.e. this tool would appear
111
+ in the final ChatDocument's `tool_messages` list.
112
+ See test_tool_handlers_and_results in test_tool_messages.py, and
113
+ examples/basic/chat-tool-function.py.
114
+
115
+ Note:
116
+ - when defining a tool handler or agent_response, you can directly return
117
+ FinalResultTool(field1 = val1, ...),
118
+ where the values can be arbitrary data structures, including nested
119
+ Pydantic objs, or you can define a subclass of FinalResultTool with the
120
+ fields you want to return.
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.
128
+ """
129
+
130
+ request: str = ""
131
+ purpose: str = "Ignored; Wrapper for a structured message"
132
+ id: str = "" # placeholder for OpenAI-API tool_call_id
133
+ _allow_llm_use: bool = False
134
+
135
+ class Config:
136
+ extra = Extra.allow
137
+ arbitrary_types_allowed = False
138
+ validate_all = True
139
+ validate_assignment = True
140
+ # do not include these fields in the generated schema
141
+ # since we don't require the LLM to specify them
142
+ schema_extra = {"exclude": {"purpose", "id", "strict"}}
143
+
144
+
145
+ class PassTool(ToolMessage):
146
+ """Tool for "passing" on the received msg (ChatDocument),
147
+ so that an as-yet-unspecified agent can handle it.
148
+ Similar to ForwardTool, but without specifying the recipient agent.
149
+ """
150
+
151
+ purpose: str = """
152
+ To pass the current message so that other agents can handle it.
153
+ """
154
+ request: str = "pass_tool"
155
+
156
+ def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
157
+ """When this tool is enabled for an Agent, this will result in a method
158
+ added to the Agent with signature:
159
+ `forward_tool(self, tool: PassTool, chat_doc: ChatDocument) -> ChatDocument:`
160
+ """
161
+ # if PassTool is in chat_doc, pass its parent, else pass chat_doc itself
162
+ doc = chat_doc
163
+ while True:
164
+ tools = agent.get_tool_messages(doc)
165
+ if not any(isinstance(t, type(self)) for t in tools):
166
+ break
167
+ if doc.parent is None:
168
+ break
169
+ doc = doc.parent
170
+ assert doc is not None, "PassTool: parent of chat_doc must not be None"
171
+ new_doc = ChatDocument.deepcopy(doc)
172
+ new_doc.metadata.sender = Entity.AGENT
173
+ return new_doc
174
+
175
+ @classmethod
176
+ def instructions(cls) -> str:
177
+ return """
178
+ Use the `pass_tool` to PASS the current message
179
+ so that another agent can handle it.
180
+ """
181
+
182
+
183
+ class DonePassTool(PassTool):
184
+ """Tool to signal DONE, AND Pass incoming/current msg as result.
185
+ Similar to PassTool, except we append a DoneTool to the result tool_messages.
186
+ """
187
+
188
+ purpose: str = """
189
+ To signal the current task is done, with results set to the current/incoming msg.
190
+ """
191
+ request: str = "done_pass_tool"
192
+
193
+ def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
194
+ # use PassTool to get the right ChatDocument to pass...
195
+ new_doc = PassTool.response(self, agent, chat_doc)
196
+ tools = agent.get_tool_messages(new_doc)
197
+ # ...then return an AgentDoneTool with content, tools from this ChatDocument
198
+ return AgentDoneTool(content=new_doc.content, tools=tools) # type: ignore
199
+
200
+ @classmethod
201
+ def instructions(cls) -> str:
202
+ return """
203
+ When you determine your task is finished,
204
+ and want to pass the current message as the result of the task,
205
+ use the `done_pass_tool` to signal this.
206
+ """
207
+
208
+
209
+ class ForwardTool(PassTool):
210
+ """Tool for forwarding the received msg (ChatDocument) to another agent or entity.
211
+ Similar to PassTool, but with a specified recipient agent.
212
+ """
213
+
214
+ purpose: str = """
215
+ To forward the current message to an <agent>, where <agent>
216
+ could be the name of an agent, or an entity such as "user", "llm".
217
+ """
218
+ request: str = "forward_tool"
219
+ agent: str
220
+
221
+ def response(self, agent: ChatAgent, chat_doc: ChatDocument) -> ChatDocument:
222
+ """When this tool is enabled for an Agent, this will result in a method
223
+ added to the Agent with signature:
224
+ `forward_tool(self, tool: ForwardTool, chat_doc: ChatDocument) -> ChatDocument:`
225
+ """
226
+ # if chat_doc contains ForwardTool, then we forward its parent ChatDocument;
227
+ # else forward chat_doc itself
228
+ new_doc = PassTool.response(self, agent, chat_doc)
229
+ new_doc.metadata.recipient = self.agent
230
+ return new_doc
231
+
232
+ @classmethod
233
+ def instructions(cls) -> str:
234
+ return """
235
+ If you need to forward the current message to another agent,
236
+ use the `forward_tool` to do so,
237
+ setting the `recipient` field to the name of the recipient agent.
238
+ """
239
+
240
+
241
+ class SendTool(ToolMessage):
242
+ """Tool for agent or LLM to send content to a specified agent.
243
+ Similar to RecipientTool.
244
+ """
245
+
246
+ purpose: str = """
247
+ To send message <content> to agent specified in <to> field.
248
+ """
249
+ request: str = "send_tool"
250
+ to: str
251
+ content: str = ""
252
+
253
+ def response(self, agent: ChatAgent) -> ChatDocument:
254
+ return agent.create_agent_response(
255
+ self.content,
256
+ recipient=self.to,
257
+ )
258
+
259
+ @classmethod
260
+ def instructions(cls) -> str:
261
+ return """
262
+ If you need to send a message to another agent,
263
+ use the `send_tool` to do so, with these field values:
264
+ - `to` field = name of the recipient agent,
265
+ - `content` field = the message to send.
266
+ """
267
+
268
+ @classmethod
269
+ def examples(cls) -> List["ToolMessage" | Tuple[str, "ToolMessage"]]:
270
+ return [
271
+ cls(to="agent1", content="Hello, agent1!"),
272
+ (
273
+ """
274
+ I need to send the content 'Who built the Gemini model?',
275
+ to the 'Searcher' agent.
276
+ """,
277
+ cls(to="Searcher", content="Who built the Gemini model?"),
278
+ ),
279
+ ]
280
+
281
+
282
+ class AgentSendTool(ToolMessage):
283
+ """Tool for Agent (i.e. agent_response) to send content or tool_messages
284
+ to a specified agent. Similar to SendTool except that AgentSendTool is only
285
+ usable by agent_response (or handler of another tool), to send content or
286
+ tools to another agent. SendTool does not allow sending tools.
287
+ """
288
+
289
+ purpose: str = """
290
+ To send message <content> and <tools> to agent specified in <to> field.
291
+ """
292
+ request: str = "agent_send_tool"
293
+ to: str
294
+ content: str = ""
295
+ tools: List[ToolMessage] = []
296
+ _allow_llm_use: bool = False
297
+
298
+ def response(self, agent: ChatAgent) -> ChatDocument:
299
+ return agent.create_agent_response(
300
+ self.content,
301
+ tool_messages=self.tools,
302
+ recipient=self.to,
303
+ )