deepagents 0.0.6rc1__py3-none-any.whl → 0.0.6rc3__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 +2 -6
- deepagents/graph.py +74 -127
- deepagents/middleware.py +198 -0
- deepagents/prompts.py +17 -13
- deepagents/state.py +9 -1
- deepagents/tools.py +7 -8
- deepagents/types.py +21 -0
- {deepagents-0.0.6rc1.dist-info → deepagents-0.0.6rc3.dist-info}/METADATA +31 -58
- deepagents-0.0.6rc3.dist-info/RECORD +17 -0
- {deepagents-0.0.6rc1.dist-info → deepagents-0.0.6rc3.dist-info}/top_level.txt +1 -0
- tests/test_deepagents.py +136 -0
- tests/test_hitl.py +51 -0
- tests/test_middleware.py +57 -0
- tests/utils.py +81 -0
- deepagents/builder.py +0 -84
- deepagents/interrupt.py +0 -122
- deepagents/sub_agent.py +0 -169
- deepagents-0.0.6rc1.dist-info/RECORD +0 -14
- {deepagents-0.0.6rc1.dist-info → deepagents-0.0.6rc3.dist-info}/WHEEL +0 -0
- {deepagents-0.0.6rc1.dist-info → deepagents-0.0.6rc3.dist-info}/licenses/LICENSE +0 -0
deepagents/interrupt.py
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
"""Interrupt configuration functionality for deep agents using LangGraph prebuilts."""
|
|
2
|
-
|
|
3
|
-
from typing import Dict, Any, List, Optional, Union
|
|
4
|
-
from langgraph.types import interrupt
|
|
5
|
-
from langgraph.prebuilt.interrupt import (
|
|
6
|
-
HumanInterruptConfig,
|
|
7
|
-
ActionRequest,
|
|
8
|
-
HumanInterrupt,
|
|
9
|
-
HumanResponse,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
ToolInterruptConfig = Dict[str, Union[HumanInterruptConfig, bool]]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def create_interrupt_hook(
|
|
16
|
-
tool_configs: ToolInterruptConfig,
|
|
17
|
-
message_prefix: str = "Tool execution requires approval",
|
|
18
|
-
) -> callable:
|
|
19
|
-
"""Create a post model hook that handles interrupts using native LangGraph schemas.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
tool_configs: Dict mapping tool names to HumanInterruptConfig objects or boolean values.
|
|
23
|
-
If True, uses default HumanInterruptConfig. If False, no interrupt.
|
|
24
|
-
message_prefix: Optional message prefix for interrupt descriptions
|
|
25
|
-
"""
|
|
26
|
-
# Right now we don't properly handle `ignore`
|
|
27
|
-
for tool, interrupt_config in tool_configs.items():
|
|
28
|
-
if isinstance(interrupt_config, dict):
|
|
29
|
-
if "allow_ignore" in interrupt_config and interrupt_config["allow_ignore"]:
|
|
30
|
-
raise ValueError(
|
|
31
|
-
f"For {tool} we get `allow_ignore = True` - we currently don't support `ignore`."
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
def interrupt_hook(state: Dict[str, Any]) -> Dict[str, Any]:
|
|
35
|
-
"""Post model hook that checks for tool calls and triggers interrupts if needed."""
|
|
36
|
-
messages = state.get("messages", [])
|
|
37
|
-
if not messages:
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
last_message = messages[-1]
|
|
41
|
-
|
|
42
|
-
if not hasattr(last_message, "tool_calls") or not last_message.tool_calls:
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
# Separate tool calls that need interrupts from those that don't
|
|
46
|
-
interrupt_tool_calls = []
|
|
47
|
-
auto_approved_tool_calls = []
|
|
48
|
-
|
|
49
|
-
for tool_call in last_message.tool_calls:
|
|
50
|
-
tool_name = tool_call["name"]
|
|
51
|
-
if tool_name in tool_configs and tool_configs[tool_name]:
|
|
52
|
-
interrupt_tool_calls.append(tool_call)
|
|
53
|
-
else:
|
|
54
|
-
auto_approved_tool_calls.append(tool_call)
|
|
55
|
-
|
|
56
|
-
# If no interrupts needed, return early
|
|
57
|
-
if not interrupt_tool_calls:
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
# Right now, no easy handling for when multiple tools need interrupt
|
|
61
|
-
if len(interrupt_tool_calls) > 1:
|
|
62
|
-
raise ValueError(
|
|
63
|
-
"Right now, interrupt hook only works when one tool requires interrupts"
|
|
64
|
-
)
|
|
65
|
-
tool_call = interrupt_tool_calls[0]
|
|
66
|
-
|
|
67
|
-
approved_tool_calls = auto_approved_tool_calls.copy()
|
|
68
|
-
|
|
69
|
-
tool_name = tool_call["name"]
|
|
70
|
-
tool_args = tool_call["args"]
|
|
71
|
-
description = f"{message_prefix}\n\nTool: {tool_name}\nArgs: {tool_args}"
|
|
72
|
-
tool_config = tool_configs[tool_name]
|
|
73
|
-
default_tool_config: HumanInterruptConfig = {
|
|
74
|
-
"allow_accept": True,
|
|
75
|
-
"allow_edit": True,
|
|
76
|
-
"allow_respond": True,
|
|
77
|
-
"allow_ignore": False,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
request: HumanInterrupt = {
|
|
81
|
-
"action_request": ActionRequest(
|
|
82
|
-
action=tool_name,
|
|
83
|
-
args=tool_args,
|
|
84
|
-
),
|
|
85
|
-
"config": tool_config
|
|
86
|
-
if isinstance(tool_config, dict)
|
|
87
|
-
else default_tool_config,
|
|
88
|
-
"description": description,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
responses: List[HumanResponse] = interrupt([request])
|
|
92
|
-
|
|
93
|
-
if len(responses) != 1:
|
|
94
|
-
raise ValueError(f"Expected a list of one response, got {responses}")
|
|
95
|
-
response = responses[0]
|
|
96
|
-
|
|
97
|
-
if response["type"] == "accept":
|
|
98
|
-
approved_tool_calls.append(tool_call)
|
|
99
|
-
elif response["type"] == "edit":
|
|
100
|
-
edited: ActionRequest = response["args"]
|
|
101
|
-
new_tool_call = {
|
|
102
|
-
"type": "tool_call",
|
|
103
|
-
"name": edited["action"],
|
|
104
|
-
"args": edited["args"],
|
|
105
|
-
"id": tool_call["id"],
|
|
106
|
-
}
|
|
107
|
-
approved_tool_calls.append(new_tool_call)
|
|
108
|
-
elif response["type"] == "response":
|
|
109
|
-
response_message = {
|
|
110
|
-
"type": "tool",
|
|
111
|
-
"tool_call_id": tool_call["id"],
|
|
112
|
-
"content": response["args"],
|
|
113
|
-
}
|
|
114
|
-
return {"messages": [response_message]}
|
|
115
|
-
else:
|
|
116
|
-
raise ValueError(f"Unknown response type: {response['type']}")
|
|
117
|
-
|
|
118
|
-
last_message.tool_calls = approved_tool_calls
|
|
119
|
-
|
|
120
|
-
return {"messages": [last_message]}
|
|
121
|
-
|
|
122
|
-
return interrupt_hook
|
deepagents/sub_agent.py
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
from deepagents.prompts import TASK_TOOL_DESCRIPTION
|
|
2
|
-
from deepagents.state import DeepAgentState
|
|
3
|
-
from langgraph.prebuilt import create_react_agent
|
|
4
|
-
from langchain_core.tools import BaseTool
|
|
5
|
-
from typing_extensions import TypedDict
|
|
6
|
-
from langchain_core.tools import tool, InjectedToolCallId
|
|
7
|
-
from langchain_core.messages import ToolMessage
|
|
8
|
-
from langchain_core.language_models import LanguageModelLike
|
|
9
|
-
from langchain.chat_models import init_chat_model
|
|
10
|
-
from typing import Annotated, NotRequired, Any, Union, Optional, Callable
|
|
11
|
-
from langgraph.types import Command
|
|
12
|
-
from langchain_core.runnables import Runnable
|
|
13
|
-
|
|
14
|
-
from langgraph.prebuilt import InjectedState
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class SubAgent(TypedDict):
|
|
18
|
-
name: str
|
|
19
|
-
description: str
|
|
20
|
-
prompt: str
|
|
21
|
-
tools: NotRequired[list[str]]
|
|
22
|
-
# Optional per-subagent model: can be either a model instance OR dict settings
|
|
23
|
-
model: NotRequired[Union[LanguageModelLike, dict[str, Any]]]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class CustomSubAgent(TypedDict):
|
|
27
|
-
name: str
|
|
28
|
-
description: str
|
|
29
|
-
graph: Runnable
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _get_agents(
|
|
33
|
-
tools,
|
|
34
|
-
instructions,
|
|
35
|
-
subagents: list[SubAgent | CustomSubAgent],
|
|
36
|
-
model,
|
|
37
|
-
state_schema,
|
|
38
|
-
post_model_hook: Optional[Callable] = None,
|
|
39
|
-
):
|
|
40
|
-
agents = {
|
|
41
|
-
"general-purpose": create_react_agent(
|
|
42
|
-
model,
|
|
43
|
-
prompt=instructions,
|
|
44
|
-
tools=tools,
|
|
45
|
-
checkpointer=False,
|
|
46
|
-
post_model_hook=post_model_hook,
|
|
47
|
-
state_schema=state_schema,
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
tools_by_name = {}
|
|
51
|
-
for tool_ in tools:
|
|
52
|
-
if not isinstance(tool_, BaseTool):
|
|
53
|
-
tool_ = tool(tool_)
|
|
54
|
-
tools_by_name[tool_.name] = tool_
|
|
55
|
-
for _agent in subagents:
|
|
56
|
-
if "graph" in _agent:
|
|
57
|
-
agents[_agent["name"]] = _agent["graph"]
|
|
58
|
-
continue
|
|
59
|
-
if "tools" in _agent:
|
|
60
|
-
_tools = [tools_by_name[t] for t in _agent["tools"]]
|
|
61
|
-
else:
|
|
62
|
-
_tools = tools
|
|
63
|
-
# Resolve per-subagent model: can be instance or dict
|
|
64
|
-
if "model" in _agent:
|
|
65
|
-
agent_model = _agent["model"]
|
|
66
|
-
if isinstance(agent_model, dict):
|
|
67
|
-
# Dictionary settings - create model from config
|
|
68
|
-
sub_model = init_chat_model(**agent_model)
|
|
69
|
-
else:
|
|
70
|
-
# Model instance - use directly
|
|
71
|
-
sub_model = agent_model
|
|
72
|
-
else:
|
|
73
|
-
# Fallback to main model
|
|
74
|
-
sub_model = model
|
|
75
|
-
agents[_agent["name"]] = create_react_agent(
|
|
76
|
-
sub_model,
|
|
77
|
-
prompt=_agent["prompt"],
|
|
78
|
-
tools=_tools,
|
|
79
|
-
state_schema=state_schema,
|
|
80
|
-
checkpointer=False,
|
|
81
|
-
post_model_hook=post_model_hook,
|
|
82
|
-
)
|
|
83
|
-
return agents
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _get_subagent_description(subagents: list[SubAgent | CustomSubAgent]):
|
|
87
|
-
return [f"- {_agent['name']}: {_agent['description']}" for _agent in subagents]
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _create_task_tool(
|
|
91
|
-
tools,
|
|
92
|
-
instructions,
|
|
93
|
-
subagents: list[SubAgent | CustomSubAgent],
|
|
94
|
-
model,
|
|
95
|
-
state_schema,
|
|
96
|
-
post_model_hook: Optional[Callable] = None,
|
|
97
|
-
):
|
|
98
|
-
agents = _get_agents(
|
|
99
|
-
tools, instructions, subagents, model, state_schema, post_model_hook
|
|
100
|
-
)
|
|
101
|
-
other_agents_string = _get_subagent_description(subagents)
|
|
102
|
-
|
|
103
|
-
@tool(
|
|
104
|
-
description=TASK_TOOL_DESCRIPTION.format(other_agents=other_agents_string)
|
|
105
|
-
)
|
|
106
|
-
async def task(
|
|
107
|
-
description: str,
|
|
108
|
-
subagent_type: str,
|
|
109
|
-
state: Annotated[DeepAgentState, InjectedState],
|
|
110
|
-
tool_call_id: Annotated[str, InjectedToolCallId],
|
|
111
|
-
):
|
|
112
|
-
if subagent_type not in agents:
|
|
113
|
-
return f"Error: invoked agent of type {subagent_type}, the only allowed types are {[f'`{k}`' for k in agents]}"
|
|
114
|
-
sub_agent = agents[subagent_type]
|
|
115
|
-
state["messages"] = [{"role": "user", "content": description}]
|
|
116
|
-
result = await sub_agent.ainvoke(state)
|
|
117
|
-
return Command(
|
|
118
|
-
update={
|
|
119
|
-
"files": result.get("files", {}),
|
|
120
|
-
"messages": [
|
|
121
|
-
ToolMessage(
|
|
122
|
-
result["messages"][-1].content, tool_call_id=tool_call_id
|
|
123
|
-
)
|
|
124
|
-
],
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
return task
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _create_sync_task_tool(
|
|
132
|
-
tools,
|
|
133
|
-
instructions,
|
|
134
|
-
subagents: list[SubAgent | CustomSubAgent],
|
|
135
|
-
model,
|
|
136
|
-
state_schema,
|
|
137
|
-
post_model_hook: Optional[Callable] = None,
|
|
138
|
-
):
|
|
139
|
-
agents = _get_agents(
|
|
140
|
-
tools, instructions, subagents, model, state_schema, post_model_hook
|
|
141
|
-
)
|
|
142
|
-
other_agents_string = _get_subagent_description(subagents)
|
|
143
|
-
|
|
144
|
-
@tool(
|
|
145
|
-
description=TASK_TOOL_DESCRIPTION.format(other_agents=other_agents_string)
|
|
146
|
-
)
|
|
147
|
-
def task(
|
|
148
|
-
description: str,
|
|
149
|
-
subagent_type: str,
|
|
150
|
-
state: Annotated[DeepAgentState, InjectedState],
|
|
151
|
-
tool_call_id: Annotated[str, InjectedToolCallId],
|
|
152
|
-
):
|
|
153
|
-
if subagent_type not in agents:
|
|
154
|
-
return f"Error: invoked agent of type {subagent_type}, the only allowed types are {[f'`{k}`' for k in agents]}"
|
|
155
|
-
sub_agent = agents[subagent_type]
|
|
156
|
-
state["messages"] = [{"role": "user", "content": description}]
|
|
157
|
-
result = sub_agent.invoke(state)
|
|
158
|
-
return Command(
|
|
159
|
-
update={
|
|
160
|
-
"files": result.get("files", {}),
|
|
161
|
-
"messages": [
|
|
162
|
-
ToolMessage(
|
|
163
|
-
result["messages"][-1].content, tool_call_id=tool_call_id
|
|
164
|
-
)
|
|
165
|
-
],
|
|
166
|
-
}
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
return task
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
deepagents/__init__.py,sha256=_u6fBfAwHlIeMsu5n95nMHz0WPhEglVlZ3ulm31WlwY,361
|
|
2
|
-
deepagents/builder.py,sha256=CTjEZpkubpGr2pippATsQaENxHGZgzddrUrsattU-QU,2794
|
|
3
|
-
deepagents/graph.py,sha256=tEdZ4WQHN0tsnXr60Lv9LIp55s6LYq0R-v2czAbmHlg,8035
|
|
4
|
-
deepagents/interrupt.py,sha256=08W7Zjk6S8HBoNepBvzSAXvpywM5sCDl0oGNim8YCJA,4449
|
|
5
|
-
deepagents/model.py,sha256=VyRIkdeXJH8HqLrudTKucHpBTtrwMFTQGRlXBj0kUyo,155
|
|
6
|
-
deepagents/prompts.py,sha256=PzHgIwy4gzHyiKHR3fCS7RtJR15NyGshCtPKuOS7XTQ,24743
|
|
7
|
-
deepagents/state.py,sha256=sCihH_OgYKibf6zGc2MdORFJqBqKNO60bWVjAenQ_xc,567
|
|
8
|
-
deepagents/sub_agent.py,sha256=4Q84OxoAznXKVVLbd6Y2RYDOnTF7THdYwtOLvCxaED4,5404
|
|
9
|
-
deepagents/tools.py,sha256=LFu6T1qP0DDtUBBKZUiZJt4O1rfpZ69BbdV1jx7IrK8,4765
|
|
10
|
-
deepagents-0.0.6rc1.dist-info/licenses/LICENSE,sha256=c__BaxUCK69leo2yEKynf8lWndu8iwYwge1CbyqAe-E,1071
|
|
11
|
-
deepagents-0.0.6rc1.dist-info/METADATA,sha256=9t_eBM4TTKxVCGXdz1M9v86rSSGeSOQ15-Yc7PJZxz0,17180
|
|
12
|
-
deepagents-0.0.6rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
deepagents-0.0.6rc1.dist-info/top_level.txt,sha256=drAzchOzPNePwpb3_pbPuvLuayXkN7SNqeIKMBWJoAo,11
|
|
14
|
-
deepagents-0.0.6rc1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|