deepagents 0.0.11rc1__py3-none-any.whl → 0.0.12rc2__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.
- deepagents/__init__.py +7 -5
- deepagents/graph.py +112 -114
- deepagents/middleware/__init__.py +6 -0
- deepagents/middleware/filesystem.py +1125 -0
- deepagents/middleware/subagents.py +481 -0
- {deepagents-0.0.11rc1.dist-info → deepagents-0.0.12rc2.dist-info}/METADATA +13 -12
- deepagents-0.0.12rc2.dist-info/RECORD +10 -0
- {deepagents-0.0.11rc1.dist-info → deepagents-0.0.12rc2.dist-info}/top_level.txt +0 -1
- deepagents/middleware.py +0 -198
- deepagents/model.py +0 -5
- deepagents/prompts.py +0 -423
- deepagents/state.py +0 -33
- deepagents/tools.py +0 -201
- deepagents/types.py +0 -21
- deepagents-0.0.11rc1.dist-info/RECORD +0 -17
- tests/test_deepagents.py +0 -136
- tests/test_hitl.py +0 -51
- tests/test_middleware.py +0 -57
- tests/utils.py +0 -81
- {deepagents-0.0.11rc1.dist-info → deepagents-0.0.12rc2.dist-info}/WHEEL +0 -0
- {deepagents-0.0.11rc1.dist-info → deepagents-0.0.12rc2.dist-info}/licenses/LICENSE +0 -0
deepagents/tools.py
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
from re import L
|
|
2
|
-
from langchain_core.tools import tool, InjectedToolCallId
|
|
3
|
-
from langchain_core.messages import ToolMessage
|
|
4
|
-
from langgraph.types import Command
|
|
5
|
-
from langchain.tools.tool_node import InjectedState
|
|
6
|
-
from typing import Annotated, Union
|
|
7
|
-
from deepagents.state import Todo, FilesystemState
|
|
8
|
-
from deepagents.prompts import (
|
|
9
|
-
WRITE_TODOS_TOOL_DESCRIPTION,
|
|
10
|
-
LIST_FILES_TOOL_DESCRIPTION,
|
|
11
|
-
READ_FILE_TOOL_DESCRIPTION,
|
|
12
|
-
WRITE_FILE_TOOL_DESCRIPTION,
|
|
13
|
-
EDIT_FILE_TOOL_DESCRIPTION,
|
|
14
|
-
)
|
|
15
|
-
from ai_filesystem import FilesystemClient
|
|
16
|
-
import os
|
|
17
|
-
|
|
18
|
-
def has_memories_prefix(file_path: str) -> bool:
|
|
19
|
-
return file_path.startswith("memories/")
|
|
20
|
-
|
|
21
|
-
def append_memories_prefix(file_path: str) -> str:
|
|
22
|
-
return f"memories/{file_path}"
|
|
23
|
-
|
|
24
|
-
def strip_memories_prefix(file_path: str) -> str:
|
|
25
|
-
return file_path.replace("memories/", "")
|
|
26
|
-
|
|
27
|
-
@tool(description=WRITE_TODOS_TOOL_DESCRIPTION)
|
|
28
|
-
def write_todos(
|
|
29
|
-
todos: list[Todo], tool_call_id: Annotated[str, InjectedToolCallId]
|
|
30
|
-
) -> Command:
|
|
31
|
-
return Command(
|
|
32
|
-
update={
|
|
33
|
-
"todos": todos,
|
|
34
|
-
"messages": [
|
|
35
|
-
ToolMessage(f"Updated todo list to {todos}", tool_call_id=tool_call_id)
|
|
36
|
-
],
|
|
37
|
-
}
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@tool(description=LIST_FILES_TOOL_DESCRIPTION)
|
|
42
|
-
def ls(state: Annotated[FilesystemState, InjectedState]) -> list[str]:
|
|
43
|
-
"""List all files"""
|
|
44
|
-
files = []
|
|
45
|
-
files.extend(list(state.get("files", {}).keys()))
|
|
46
|
-
# Special handling for longterm filesystem
|
|
47
|
-
if os.getenv("LONGTERM_FILESYSTEM_NAME") and os.getenv("AGENT_FS_API_KEY"):
|
|
48
|
-
filesystem_client = FilesystemClient(
|
|
49
|
-
filesystem=os.getenv("LONGTERM_FILESYSTEM_NAME")
|
|
50
|
-
)
|
|
51
|
-
file_data_list = filesystem_client._list_files()
|
|
52
|
-
memories_files = [f"memories/{f.path}" for f in file_data_list]
|
|
53
|
-
files.extend(memories_files)
|
|
54
|
-
return files
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@tool(description=READ_FILE_TOOL_DESCRIPTION)
|
|
58
|
-
def read_file(
|
|
59
|
-
file_path: str,
|
|
60
|
-
state: Annotated[FilesystemState, InjectedState],
|
|
61
|
-
offset: int = 0,
|
|
62
|
-
limit: int = 2000,
|
|
63
|
-
) -> str:
|
|
64
|
-
# Special handling for longterm filesystem
|
|
65
|
-
if os.getenv("LONGTERM_FILESYSTEM_NAME") and os.getenv("AGENT_FS_API_KEY") and has_memories_prefix(file_path):
|
|
66
|
-
filesystem_client = FilesystemClient(
|
|
67
|
-
filesystem=os.getenv("LONGTERM_FILESYSTEM_NAME")
|
|
68
|
-
)
|
|
69
|
-
file_path = strip_memories_prefix(file_path)
|
|
70
|
-
content = filesystem_client.read_file(file_path)
|
|
71
|
-
return content
|
|
72
|
-
|
|
73
|
-
mock_filesystem = state.get("files", {})
|
|
74
|
-
if file_path not in mock_filesystem:
|
|
75
|
-
return f"Error: File '{file_path}' not found"
|
|
76
|
-
|
|
77
|
-
# Get file content
|
|
78
|
-
content = mock_filesystem[file_path]
|
|
79
|
-
|
|
80
|
-
# Handle empty file
|
|
81
|
-
if not content or content.strip() == "":
|
|
82
|
-
return "System reminder: File exists but has empty contents"
|
|
83
|
-
|
|
84
|
-
# Split content into lines
|
|
85
|
-
lines = content.splitlines()
|
|
86
|
-
|
|
87
|
-
# Apply line offset and limit
|
|
88
|
-
start_idx = offset
|
|
89
|
-
end_idx = min(start_idx + limit, len(lines))
|
|
90
|
-
|
|
91
|
-
# Handle case where offset is beyond file length
|
|
92
|
-
if start_idx >= len(lines):
|
|
93
|
-
return f"Error: Line offset {offset} exceeds file length ({len(lines)} lines)"
|
|
94
|
-
|
|
95
|
-
# Format output with line numbers (cat -n format)
|
|
96
|
-
result_lines = []
|
|
97
|
-
for i in range(start_idx, end_idx):
|
|
98
|
-
line_content = lines[i]
|
|
99
|
-
|
|
100
|
-
# Truncate lines longer than 2000 characters
|
|
101
|
-
if len(line_content) > 2000:
|
|
102
|
-
line_content = line_content[:2000]
|
|
103
|
-
|
|
104
|
-
# Line numbers start at 1, so add 1 to the index
|
|
105
|
-
line_number = i + 1
|
|
106
|
-
result_lines.append(f"{line_number:6d}\t{line_content}")
|
|
107
|
-
|
|
108
|
-
return "\n".join(result_lines)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@tool(description=WRITE_FILE_TOOL_DESCRIPTION)
|
|
112
|
-
def write_file(
|
|
113
|
-
file_path: str,
|
|
114
|
-
content: str,
|
|
115
|
-
state: Annotated[FilesystemState, InjectedState],
|
|
116
|
-
tool_call_id: Annotated[str, InjectedToolCallId],
|
|
117
|
-
) -> Command:
|
|
118
|
-
# Special handling for longterm filesystem
|
|
119
|
-
if os.getenv("LONGTERM_FILESYSTEM_NAME") and os.getenv("AGENT_FS_API_KEY") and has_memories_prefix(file_path):
|
|
120
|
-
filesystem_client = FilesystemClient(
|
|
121
|
-
filesystem=os.getenv("LONGTERM_FILESYSTEM_NAME")
|
|
122
|
-
)
|
|
123
|
-
short_file_path = strip_memories_prefix(file_path)
|
|
124
|
-
filesystem_client.create_file(short_file_path, content)
|
|
125
|
-
return Command(
|
|
126
|
-
update={
|
|
127
|
-
"messages": [ToolMessage(f"Updated longterm memories file {file_path}", tool_call_id=tool_call_id)]
|
|
128
|
-
}
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
files = state.get("files", {})
|
|
132
|
-
return Command(
|
|
133
|
-
update={
|
|
134
|
-
"files": files,
|
|
135
|
-
"messages": [ToolMessage(f"Updated file {file_path}", tool_call_id=tool_call_id)]
|
|
136
|
-
}
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
@tool(description=EDIT_FILE_TOOL_DESCRIPTION)
|
|
141
|
-
def edit_file(
|
|
142
|
-
file_path: str,
|
|
143
|
-
old_string: str,
|
|
144
|
-
new_string: str,
|
|
145
|
-
state: Annotated[FilesystemState, InjectedState],
|
|
146
|
-
tool_call_id: Annotated[str, InjectedToolCallId],
|
|
147
|
-
replace_all: bool = False,
|
|
148
|
-
) -> Union[Command, str]:
|
|
149
|
-
"""Write to a file."""
|
|
150
|
-
# Special handling for longterm filesystem
|
|
151
|
-
if os.getenv("LONGTERM_FILESYSTEM_NAME") and os.getenv("AGENT_FS_API_KEY") and has_memories_prefix(file_path):
|
|
152
|
-
filesystem_client = FilesystemClient(
|
|
153
|
-
filesystem=os.getenv("LONGTERM_FILESYSTEM_NAME")
|
|
154
|
-
)
|
|
155
|
-
short_file_path = strip_memories_prefix(file_path)
|
|
156
|
-
filesystem_client.edit_file(short_file_path, old_string, new_string, replace_all)
|
|
157
|
-
return Command(
|
|
158
|
-
update={
|
|
159
|
-
"messages": [ToolMessage(f"Successfully edited longterm memories file {file_path}", tool_call_id=tool_call_id)]
|
|
160
|
-
}
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
mock_filesystem = state.get("files", {})
|
|
164
|
-
# Check if file exists in mock filesystem
|
|
165
|
-
if file_path not in mock_filesystem:
|
|
166
|
-
return f"Error: File '{file_path}' not found"
|
|
167
|
-
|
|
168
|
-
# Get current file content
|
|
169
|
-
content = mock_filesystem[file_path]
|
|
170
|
-
|
|
171
|
-
# Check if old_string exists in the file
|
|
172
|
-
if old_string not in content:
|
|
173
|
-
return f"Error: String not found in file: '{old_string}'"
|
|
174
|
-
|
|
175
|
-
# If not replace_all, check for uniqueness
|
|
176
|
-
if not replace_all:
|
|
177
|
-
occurrences = content.count(old_string)
|
|
178
|
-
if occurrences > 1:
|
|
179
|
-
return f"Error: String '{old_string}' appears {occurrences} times in file. Use replace_all=True to replace all instances, or provide a more specific string with surrounding context."
|
|
180
|
-
elif occurrences == 0:
|
|
181
|
-
return f"Error: String not found in file: '{old_string}'"
|
|
182
|
-
|
|
183
|
-
# Perform the replacement
|
|
184
|
-
if replace_all:
|
|
185
|
-
new_content = content.replace(old_string, new_string)
|
|
186
|
-
replacement_count = content.count(old_string)
|
|
187
|
-
result_msg = f"Successfully replaced {replacement_count} instance(s) of the string in '{file_path}'"
|
|
188
|
-
else:
|
|
189
|
-
new_content = content.replace(
|
|
190
|
-
old_string, new_string, 1
|
|
191
|
-
) # Replace only first occurrence
|
|
192
|
-
result_msg = f"Successfully replaced string in '{file_path}'"
|
|
193
|
-
|
|
194
|
-
# Update the mock filesystem
|
|
195
|
-
mock_filesystem[file_path] = new_content
|
|
196
|
-
return Command(
|
|
197
|
-
update={
|
|
198
|
-
"files": mock_filesystem,
|
|
199
|
-
"messages": [ToolMessage(result_msg, tool_call_id=tool_call_id)],
|
|
200
|
-
}
|
|
201
|
-
)
|
deepagents/types.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from typing import NotRequired, Union, Any
|
|
2
|
-
from typing_extensions import TypedDict
|
|
3
|
-
from langchain_core.language_models import LanguageModelLike
|
|
4
|
-
from langchain.agents.middleware import AgentMiddleware
|
|
5
|
-
from langchain_core.runnables import Runnable
|
|
6
|
-
from langchain_core.tools import BaseTool
|
|
7
|
-
|
|
8
|
-
class SubAgent(TypedDict):
|
|
9
|
-
name: str
|
|
10
|
-
description: str
|
|
11
|
-
prompt: str
|
|
12
|
-
tools: NotRequired[list[BaseTool]]
|
|
13
|
-
# Optional per-subagent model: can be either a model instance OR dict settings
|
|
14
|
-
model: NotRequired[Union[LanguageModelLike, dict[str, Any]]]
|
|
15
|
-
middleware: NotRequired[list[AgentMiddleware]]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class CustomSubAgent(TypedDict):
|
|
19
|
-
name: str
|
|
20
|
-
description: str
|
|
21
|
-
graph: Runnable
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
deepagents/__init__.py,sha256=fA_91ByxPb3e8aPfci43zOXrWz8ylh_CFQALo7EUKi8,312
|
|
2
|
-
deepagents/graph.py,sha256=sLN0gWJma9dGgJI_RHDK-I1cEZMCo8nfxuF4a8vN7Po,6288
|
|
3
|
-
deepagents/middleware.py,sha256=P2y1crsedTH1uZlKS3fpc7pd4jwz8dR8jnw7repUjLk,7254
|
|
4
|
-
deepagents/model.py,sha256=VyRIkdeXJH8HqLrudTKucHpBTtrwMFTQGRlXBj0kUyo,155
|
|
5
|
-
deepagents/prompts.py,sha256=hiyiUriv-B3IQ0lwRqiJsvtgvwSS4wdeMNdXojHUisc,25603
|
|
6
|
-
deepagents/state.py,sha256=8so3MgL-zRPYP8Ci_OuVg4wHrs5uAXCErKF1AjjCSt8,726
|
|
7
|
-
deepagents/tools.py,sha256=kTS-O8jWJsUnX0tBl19Iyg8fSR2jhD8c7jDz_xNrw0I,7138
|
|
8
|
-
deepagents/types.py,sha256=5KBSUPlWOnv9It3SnJCMHrOtp9Y4_NQGtGCp69JsEjE,694
|
|
9
|
-
deepagents-0.0.11rc1.dist-info/licenses/LICENSE,sha256=c__BaxUCK69leo2yEKynf8lWndu8iwYwge1CbyqAe-E,1071
|
|
10
|
-
tests/test_deepagents.py,sha256=SwtOiJF4c1O3r_Q3AiM7XZu6tVq4uMIcZlnsfRjx8Ig,7648
|
|
11
|
-
tests/test_hitl.py,sha256=B16ZFiyaVSOcDLz7mh1RTaQZ93EMTKOPUY-IEslkcfM,2460
|
|
12
|
-
tests/test_middleware.py,sha256=3HYmTx0Jw4XTNJjqLYeyGS_QZzcqkFuKfShtajIDhF4,2146
|
|
13
|
-
tests/utils.py,sha256=Ln_DYaMkwAVBo4XQ-QKwlCWP8zZYMenWTcFhsneoL0g,2913
|
|
14
|
-
deepagents-0.0.11rc1.dist-info/METADATA,sha256=nWOCcZljXdWY9tm3Gc_0Wyt7ZK0WScU0MhZY-jlJKuY,17331
|
|
15
|
-
deepagents-0.0.11rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
-
deepagents-0.0.11rc1.dist-info/top_level.txt,sha256=_w9VMQtG4YDNg5A5eAeUre7dF7x7hk9zRpT9zsFaukY,17
|
|
17
|
-
deepagents-0.0.11rc1.dist-info/RECORD,,
|
tests/test_deepagents.py
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
from deepagents.graph import create_deep_agent
|
|
2
|
-
from langchain.agents import create_agent
|
|
3
|
-
from tests.utils import assert_all_deepagent_qualities, SAMPLE_MODEL, sample_tool, get_weather, get_soccer_scores, SampleMiddlewareWithTools, SampleMiddlewareWithToolsAndState, WeatherToolMiddleware, ResearchMiddleware, ResearchMiddlewareWithTools, TOY_BASKETBALL_RESEARCH
|
|
4
|
-
|
|
5
|
-
class TestDeepAgents:
|
|
6
|
-
def test_base_deep_agent(self):
|
|
7
|
-
agent = create_deep_agent()
|
|
8
|
-
assert_all_deepagent_qualities(agent)
|
|
9
|
-
|
|
10
|
-
def test_deep_agent_with_tool(self):
|
|
11
|
-
agent = create_deep_agent(tools=[sample_tool])
|
|
12
|
-
assert_all_deepagent_qualities(agent)
|
|
13
|
-
assert "sample_tool" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
14
|
-
|
|
15
|
-
def test_deep_agent_with_middleware_with_tool(self):
|
|
16
|
-
agent = create_deep_agent(middleware=[SampleMiddlewareWithTools()])
|
|
17
|
-
assert_all_deepagent_qualities(agent)
|
|
18
|
-
assert "sample_tool" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
19
|
-
|
|
20
|
-
def test_deep_agent_with_middleware_with_tool_and_state(self):
|
|
21
|
-
agent = create_deep_agent(middleware=[SampleMiddlewareWithToolsAndState()])
|
|
22
|
-
assert_all_deepagent_qualities(agent)
|
|
23
|
-
assert "sample_tool" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
24
|
-
assert "sample_input" in agent.stream_channels
|
|
25
|
-
|
|
26
|
-
def test_deep_agent_with_subagents(self):
|
|
27
|
-
subagents = [
|
|
28
|
-
{
|
|
29
|
-
"name": "weather_agent",
|
|
30
|
-
"description": "Use this agent to get the weather",
|
|
31
|
-
"prompt": "You are a weather agent.",
|
|
32
|
-
"tools": [get_weather],
|
|
33
|
-
"model": SAMPLE_MODEL,
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
agent = create_deep_agent(tools=[sample_tool], subagents=subagents)
|
|
37
|
-
assert_all_deepagent_qualities(agent)
|
|
38
|
-
result = agent.invoke({"messages": [{"role": "user", "content": "What is the weather in Tokyo?"}]})
|
|
39
|
-
agent_messages = [msg for msg in result.get("messages", []) if msg.type == "ai"]
|
|
40
|
-
tool_calls = [tool_call for msg in agent_messages for tool_call in msg.tool_calls]
|
|
41
|
-
assert any([tool_call["name"] == "task" and tool_call["args"].get("subagent_type") == "weather_agent" for tool_call in tool_calls])
|
|
42
|
-
|
|
43
|
-
def test_deep_agent_with_subagents_gen_purpose(self):
|
|
44
|
-
subagents = [
|
|
45
|
-
{
|
|
46
|
-
"name": "weather_agent",
|
|
47
|
-
"description": "Use this agent to get the weather",
|
|
48
|
-
"prompt": "You are a weather agent.",
|
|
49
|
-
"tools": [get_weather],
|
|
50
|
-
"model": SAMPLE_MODEL,
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
agent = create_deep_agent(tools=[sample_tool], subagents=subagents)
|
|
54
|
-
assert_all_deepagent_qualities(agent)
|
|
55
|
-
result = agent.invoke({"messages": [{"role": "user", "content": "Use the general purpose subagent to call the sample tool"}]})
|
|
56
|
-
agent_messages = [msg for msg in result.get("messages", []) if msg.type == "ai"]
|
|
57
|
-
tool_calls = [tool_call for msg in agent_messages for tool_call in msg.tool_calls]
|
|
58
|
-
assert any([tool_call["name"] == "task" and tool_call["args"].get("subagent_type") == "general-purpose" for tool_call in tool_calls])
|
|
59
|
-
|
|
60
|
-
def test_deep_agent_with_subagents_with_middleware(self):
|
|
61
|
-
subagents = [
|
|
62
|
-
{
|
|
63
|
-
"name": "weather_agent",
|
|
64
|
-
"description": "Use this agent to get the weather",
|
|
65
|
-
"prompt": "You are a weather agent.",
|
|
66
|
-
"tools": [],
|
|
67
|
-
"model": SAMPLE_MODEL,
|
|
68
|
-
"middleware": [WeatherToolMiddleware()],
|
|
69
|
-
}
|
|
70
|
-
]
|
|
71
|
-
agent = create_deep_agent(tools=[sample_tool], subagents=subagents)
|
|
72
|
-
assert_all_deepagent_qualities(agent)
|
|
73
|
-
result = agent.invoke({"messages": [{"role": "user", "content": "What is the weather in Tokyo?"}]})
|
|
74
|
-
agent_messages = [msg for msg in result.get("messages", []) if msg.type == "ai"]
|
|
75
|
-
tool_calls = [tool_call for msg in agent_messages for tool_call in msg.tool_calls]
|
|
76
|
-
assert any([tool_call["name"] == "task" and tool_call["args"].get("subagent_type") == "weather_agent" for tool_call in tool_calls])
|
|
77
|
-
|
|
78
|
-
def test_deep_agent_with_custom_subagents(self):
|
|
79
|
-
subagents = [
|
|
80
|
-
{
|
|
81
|
-
"name": "weather_agent",
|
|
82
|
-
"description": "Use this agent to get the weather",
|
|
83
|
-
"prompt": "You are a weather agent.",
|
|
84
|
-
"tools": [get_weather],
|
|
85
|
-
"model": SAMPLE_MODEL,
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"name": "soccer_agent",
|
|
89
|
-
"description": "Use this agent to get the latest soccer scores",
|
|
90
|
-
"graph": create_agent(
|
|
91
|
-
model=SAMPLE_MODEL,
|
|
92
|
-
tools=[get_soccer_scores],
|
|
93
|
-
prompt="You are a soccer agent.",
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
]
|
|
97
|
-
agent = create_deep_agent(tools=[sample_tool], subagents=subagents)
|
|
98
|
-
assert_all_deepagent_qualities(agent)
|
|
99
|
-
result = agent.invoke({"messages": [{"role": "user", "content": "Look up the weather in Tokyo, and the latest scores for Manchester City!"}]})
|
|
100
|
-
agent_messages = [msg for msg in result.get("messages", []) if msg.type == "ai"]
|
|
101
|
-
tool_calls = [tool_call for msg in agent_messages for tool_call in msg.tool_calls]
|
|
102
|
-
assert any([tool_call["name"] == "task" and tool_call["args"].get("subagent_type") == "weather_agent" for tool_call in tool_calls])
|
|
103
|
-
assert any([tool_call["name"] == "task" and tool_call["args"].get("subagent_type") == "soccer_agent" for tool_call in tool_calls])
|
|
104
|
-
|
|
105
|
-
def test_deep_agent_with_extended_state_and_subagents(self):
|
|
106
|
-
subagents = [
|
|
107
|
-
{
|
|
108
|
-
"name": "basketball_info_agent",
|
|
109
|
-
"description": "Use this agent to get surface level info on any basketball topic",
|
|
110
|
-
"prompt": "You are a basketball info agent.",
|
|
111
|
-
"middleware": [ResearchMiddlewareWithTools()],
|
|
112
|
-
}
|
|
113
|
-
]
|
|
114
|
-
agent = create_deep_agent(tools=[sample_tool], subagents=subagents, middleware=[ResearchMiddleware()])
|
|
115
|
-
assert_all_deepagent_qualities(agent)
|
|
116
|
-
assert "research" in agent.stream_channels
|
|
117
|
-
result = agent.invoke({"messages": [{"role": "user", "content": "Get surface level info on lebron james"}]}, config={"recursion_limit": 100})
|
|
118
|
-
agent_messages = [msg for msg in result.get("messages", []) if msg.type == "ai"]
|
|
119
|
-
tool_calls = [tool_call for msg in agent_messages for tool_call in msg.tool_calls]
|
|
120
|
-
assert any([tool_call["name"] == "task" and tool_call["args"].get("subagent_type") == "basketball_info_agent" for tool_call in tool_calls])
|
|
121
|
-
assert TOY_BASKETBALL_RESEARCH in result["research"]
|
|
122
|
-
|
|
123
|
-
def test_deep_agent_with_subagents_no_tools(self):
|
|
124
|
-
subagents = [
|
|
125
|
-
{
|
|
126
|
-
"name": "basketball_info_agent",
|
|
127
|
-
"description": "Use this agent to get surface level info on any basketball topic",
|
|
128
|
-
"prompt": "You are a basketball info agent.",
|
|
129
|
-
}
|
|
130
|
-
]
|
|
131
|
-
agent = create_deep_agent(tools=[sample_tool], subagents=subagents)
|
|
132
|
-
assert_all_deepagent_qualities(agent)
|
|
133
|
-
result = agent.invoke({"messages": [{"role": "user", "content": "Use the basketball info subagent to call the sample tool"}]}, config={"recursion_limit": 100})
|
|
134
|
-
agent_messages = [msg for msg in result.get("messages", []) if msg.type == "ai"]
|
|
135
|
-
tool_calls = [tool_call for msg in agent_messages for tool_call in msg.tool_calls]
|
|
136
|
-
assert any([tool_call["name"] == "task" and tool_call["args"].get("subagent_type") == "basketball_info_agent" for tool_call in tool_calls])
|
tests/test_hitl.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from deepagents.graph import create_deep_agent
|
|
2
|
-
from tests.utils import assert_all_deepagent_qualities, get_weather, sample_tool, get_soccer_scores
|
|
3
|
-
from langgraph.checkpoint.memory import MemorySaver
|
|
4
|
-
from langgraph.types import Command
|
|
5
|
-
import uuid
|
|
6
|
-
|
|
7
|
-
SAMPLE_TOOL_CONFIG = {
|
|
8
|
-
"sample_tool": True,
|
|
9
|
-
"get_weather": False,
|
|
10
|
-
"get_soccer_scores": {
|
|
11
|
-
"allow_accept": True,
|
|
12
|
-
"allow_reject": True,
|
|
13
|
-
"allow_respond": False,
|
|
14
|
-
"description": "Ohohohooooo"
|
|
15
|
-
},
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class TestHITL:
|
|
19
|
-
def test_hitl_agent(self):
|
|
20
|
-
checkpointer = MemorySaver()
|
|
21
|
-
agent = create_deep_agent(tools=[sample_tool, get_weather, get_soccer_scores], tool_configs=SAMPLE_TOOL_CONFIG, checkpointer=checkpointer)
|
|
22
|
-
config = {
|
|
23
|
-
"configurable": {
|
|
24
|
-
"thread_id": uuid.uuid4()
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
assert_all_deepagent_qualities(agent)
|
|
28
|
-
result = agent.invoke({"messages": [{"role": "user", "content": "Call the sample tool, get the weather in New York and get scores for the latest soccer games in parallel"}]}, config=config)
|
|
29
|
-
agent_messages = [msg for msg in result.get("messages", []) if msg.type == "ai"]
|
|
30
|
-
tool_calls = [tool_call for msg in agent_messages for tool_call in msg.tool_calls]
|
|
31
|
-
assert any([tool_call["name"] == "sample_tool" for tool_call in tool_calls])
|
|
32
|
-
assert any([tool_call["name"] == "get_weather" for tool_call in tool_calls])
|
|
33
|
-
assert any([tool_call["name"] == "get_soccer_scores" for tool_call in tool_calls])
|
|
34
|
-
|
|
35
|
-
assert result["__interrupt__"] is not None
|
|
36
|
-
interrupts = result["__interrupt__"][0].value
|
|
37
|
-
assert len(interrupts) == 2
|
|
38
|
-
assert any([interrupt["action_request"]["action"] == "sample_tool" for interrupt in interrupts])
|
|
39
|
-
assert any([interrupt["action_request"]["action"] == "get_soccer_scores" for interrupt in interrupts])
|
|
40
|
-
|
|
41
|
-
result2 = agent.invoke(
|
|
42
|
-
Command(
|
|
43
|
-
resume=[{"type": "accept"}, {"type": "accept"}]
|
|
44
|
-
),
|
|
45
|
-
config=config
|
|
46
|
-
)
|
|
47
|
-
tool_results = [msg for msg in result2.get("messages", []) if msg.type == "tool"]
|
|
48
|
-
assert any([tool_result.name == "sample_tool" for tool_result in tool_results])
|
|
49
|
-
assert any([tool_result.name == "get_weather" for tool_result in tool_results])
|
|
50
|
-
assert any([tool_result.name == "get_soccer_scores" for tool_result in tool_results])
|
|
51
|
-
assert "__interrupt__" not in result2
|
tests/test_middleware.py
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
from langchain.agents import create_agent
|
|
2
|
-
from deepagents.middleware import (
|
|
3
|
-
PlanningMiddleware,
|
|
4
|
-
FilesystemMiddleware,
|
|
5
|
-
SubAgentMiddleware,
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
SAMPLE_MODEL = "claude-3-5-sonnet-20240620"
|
|
9
|
-
|
|
10
|
-
class TestAddMiddleware:
|
|
11
|
-
def test_planning_middleware(self):
|
|
12
|
-
middleware = [PlanningMiddleware()]
|
|
13
|
-
agent = create_agent(model=SAMPLE_MODEL, middleware=middleware, tools=[])
|
|
14
|
-
assert "todos" in agent.stream_channels
|
|
15
|
-
assert "write_todos" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
16
|
-
|
|
17
|
-
def test_filesystem_middleware(self):
|
|
18
|
-
middleware = [FilesystemMiddleware()]
|
|
19
|
-
agent = create_agent(model=SAMPLE_MODEL, middleware=middleware, tools=[])
|
|
20
|
-
assert "files" in agent.stream_channels
|
|
21
|
-
agent_tools = agent.nodes["tools"].bound._tools_by_name.keys()
|
|
22
|
-
assert "ls" in agent_tools
|
|
23
|
-
assert "read_file" in agent_tools
|
|
24
|
-
assert "write_file" in agent_tools
|
|
25
|
-
assert "edit_file" in agent_tools
|
|
26
|
-
|
|
27
|
-
def test_subagent_middleware(self):
|
|
28
|
-
middleware = [
|
|
29
|
-
SubAgentMiddleware(
|
|
30
|
-
default_subagent_tools=[],
|
|
31
|
-
subagents=[],
|
|
32
|
-
model=SAMPLE_MODEL
|
|
33
|
-
)
|
|
34
|
-
]
|
|
35
|
-
agent = create_agent(model=SAMPLE_MODEL, middleware=middleware, tools=[])
|
|
36
|
-
assert "task" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
37
|
-
|
|
38
|
-
def test_multiple_middleware(self):
|
|
39
|
-
middleware = [
|
|
40
|
-
PlanningMiddleware(),
|
|
41
|
-
FilesystemMiddleware(),
|
|
42
|
-
SubAgentMiddleware(
|
|
43
|
-
default_subagent_tools=[],
|
|
44
|
-
subagents=[],
|
|
45
|
-
model=SAMPLE_MODEL
|
|
46
|
-
)
|
|
47
|
-
]
|
|
48
|
-
agent = create_agent(model=SAMPLE_MODEL, middleware=middleware, tools=[])
|
|
49
|
-
assert "todos" in agent.stream_channels
|
|
50
|
-
assert "files" in agent.stream_channels
|
|
51
|
-
agent_tools = agent.nodes["tools"].bound._tools_by_name.keys()
|
|
52
|
-
assert "write_todos" in agent_tools
|
|
53
|
-
assert "ls" in agent_tools
|
|
54
|
-
assert "read_file" in agent_tools
|
|
55
|
-
assert "write_file" in agent_tools
|
|
56
|
-
assert "edit_file" in agent_tools
|
|
57
|
-
assert "task" in agent_tools
|
tests/utils.py
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
from langchain_core.tools import tool, InjectedToolCallId
|
|
2
|
-
from langchain.agents.middleware import AgentMiddleware
|
|
3
|
-
from typing import Annotated
|
|
4
|
-
from langchain.agents.tool_node import InjectedState
|
|
5
|
-
from langchain.agents.middleware import AgentMiddleware, AgentState
|
|
6
|
-
from langgraph.types import Command
|
|
7
|
-
from langchain_core.messages import ToolMessage
|
|
8
|
-
|
|
9
|
-
def assert_all_deepagent_qualities(agent):
|
|
10
|
-
assert "todos" in agent.stream_channels
|
|
11
|
-
assert "files" in agent.stream_channels
|
|
12
|
-
assert "write_todos" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
13
|
-
assert "ls" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
14
|
-
assert "read_file" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
15
|
-
assert "write_file" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
16
|
-
assert "edit_file" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
17
|
-
assert "task" in agent.nodes["tools"].bound._tools_by_name.keys()
|
|
18
|
-
|
|
19
|
-
###########################
|
|
20
|
-
# Mock tools and middleware
|
|
21
|
-
###########################
|
|
22
|
-
|
|
23
|
-
SAMPLE_MODEL = "claude-3-5-sonnet-20240620"
|
|
24
|
-
|
|
25
|
-
@tool(description="Use this tool to get the weather")
|
|
26
|
-
def get_weather(location: str):
|
|
27
|
-
return f"The weather in {location} is sunny."
|
|
28
|
-
|
|
29
|
-
@tool(description="Use this tool to get the latest soccer scores")
|
|
30
|
-
def get_soccer_scores(team: str):
|
|
31
|
-
return f"The latest soccer scores for {team} are 2-1."
|
|
32
|
-
|
|
33
|
-
@tool(description="Sample tool")
|
|
34
|
-
def sample_tool(sample_input: str):
|
|
35
|
-
return sample_input
|
|
36
|
-
|
|
37
|
-
@tool(description="Sample tool with injected state")
|
|
38
|
-
def sample_tool_with_injected_state(sample_input: str, state: Annotated[dict, InjectedState]):
|
|
39
|
-
return sample_input + state["sample_input"]
|
|
40
|
-
|
|
41
|
-
TOY_BASKETBALL_RESEARCH = "Lebron James is the best basketball player of all time with over 40k points and 21 seasons in the NBA."
|
|
42
|
-
|
|
43
|
-
@tool(description="Use this tool to conduct research into basketball and save it to state")
|
|
44
|
-
def research_basketball(
|
|
45
|
-
topic: str,
|
|
46
|
-
state: Annotated[dict, InjectedState],
|
|
47
|
-
tool_call_id: Annotated[str, InjectedToolCallId]
|
|
48
|
-
):
|
|
49
|
-
current_research = state.get("research", "")
|
|
50
|
-
research = f"{current_research}\n\nResearching on {topic}... Done! {TOY_BASKETBALL_RESEARCH}"
|
|
51
|
-
return Command(
|
|
52
|
-
update={
|
|
53
|
-
"research": research,
|
|
54
|
-
"messages": [
|
|
55
|
-
ToolMessage(research, tool_call_id=tool_call_id)
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
class ResearchState(AgentState):
|
|
61
|
-
research: str
|
|
62
|
-
|
|
63
|
-
class ResearchMiddlewareWithTools(AgentMiddleware):
|
|
64
|
-
state_schema = ResearchState
|
|
65
|
-
tools = [research_basketball]
|
|
66
|
-
|
|
67
|
-
class ResearchMiddleware(AgentMiddleware):
|
|
68
|
-
state_schema = ResearchState
|
|
69
|
-
|
|
70
|
-
class SampleMiddlewareWithTools(AgentMiddleware):
|
|
71
|
-
tools = [sample_tool]
|
|
72
|
-
|
|
73
|
-
class SampleState(AgentState):
|
|
74
|
-
sample_input: str
|
|
75
|
-
|
|
76
|
-
class SampleMiddlewareWithToolsAndState(AgentMiddleware):
|
|
77
|
-
state_schema = SampleState
|
|
78
|
-
tools = [sample_tool]
|
|
79
|
-
|
|
80
|
-
class WeatherToolMiddleware(AgentMiddleware):
|
|
81
|
-
tools = [get_weather]
|
|
File without changes
|
|
File without changes
|