mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
fix(sdk): propagate goto and graph in Commands returned by tools (#3391)
Resolves https://github.com/langchain-ai/deepagents/issues/2500 Co-authored-by: Mason Daugherty <github@mdrxy.com> Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
@@ -2004,7 +2004,11 @@ class FilesystemMiddleware(AgentMiddleware[FilesystemState, ContextT, ResponseT]
|
||||
resolved_backend,
|
||||
)
|
||||
processed_messages.append(processed_message)
|
||||
return Command(update={**update, "messages": processed_messages})
|
||||
return Command(
|
||||
goto=tool_result.goto,
|
||||
graph=tool_result.graph,
|
||||
update={**update, "messages": processed_messages},
|
||||
)
|
||||
msg = f"Unreachable code reached in _intercept_large_tool_result: for tool_result of type {type(tool_result)}"
|
||||
raise AssertionError(msg)
|
||||
|
||||
@@ -2039,7 +2043,11 @@ class FilesystemMiddleware(AgentMiddleware[FilesystemState, ContextT, ResponseT]
|
||||
resolved_backend,
|
||||
)
|
||||
processed_messages.append(processed_message)
|
||||
return Command(update={**update, "messages": processed_messages})
|
||||
return Command(
|
||||
goto=tool_result.goto,
|
||||
graph=tool_result.graph,
|
||||
update={**update, "messages": processed_messages},
|
||||
)
|
||||
msg = f"Unreachable code reached in _aintercept_large_tool_result: for tool_result of type {type(tool_result)}"
|
||||
raise AssertionError(msg)
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ from langchain_core.runnables import Runnable
|
||||
from langchain_core.tools import BaseTool, tool
|
||||
from langgraph.channels.delta import DeltaChannel
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
from langgraph.types import Command
|
||||
from pydantic import Field
|
||||
|
||||
from deepagents.backends import CompositeBackend, FilesystemBackend
|
||||
@@ -3608,6 +3610,53 @@ class TestDeltaChannels:
|
||||
assert any("hello.txt" in k for k in files)
|
||||
|
||||
|
||||
def test_tool_command_parent_handoff_preserved() -> None:
|
||||
# A tool returning Command(goto=..., graph=Command.PARENT) must propagate routing
|
||||
# through FilesystemMiddleware so multi-agent handoffs reach the sibling node.
|
||||
@tool
|
||||
def transfer_to_b(runtime: ToolRuntime) -> Command:
|
||||
"""Transfer to agent_b."""
|
||||
return Command(
|
||||
goto="agent_b",
|
||||
graph=Command.PARENT,
|
||||
update={
|
||||
"messages": [
|
||||
ToolMessage(content="transferred", tool_call_id=runtime.tool_call_id),
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
fake_model = FakeChatModelWithHistory(
|
||||
messages=iter(
|
||||
[
|
||||
AIMessage(
|
||||
content="",
|
||||
tool_calls=[{"id": "call_xfer", "name": "transfer_to_b", "args": {}}],
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
agent_a = create_deep_agent(model=fake_model, tools=[transfer_to_b])
|
||||
|
||||
visited: list[str] = []
|
||||
|
||||
def agent_b_node(_state: dict) -> dict:
|
||||
visited.append("agent_b")
|
||||
return {}
|
||||
|
||||
parent = StateGraph(dict)
|
||||
parent.add_node("agent_a", agent_a)
|
||||
parent.add_node("agent_b", agent_b_node)
|
||||
parent.add_edge(START, "agent_a")
|
||||
parent.add_edge("agent_a", END)
|
||||
parent.add_edge("agent_b", END)
|
||||
compiled = parent.compile()
|
||||
|
||||
compiled.invoke({"messages": [HumanMessage(content="hi")]})
|
||||
|
||||
assert visited == ["agent_b"]
|
||||
|
||||
|
||||
def test_invalid_tool_call_patched_on_next_turn() -> None:
|
||||
# Turn 1: model truncates and emits an invalid tool call (no matching ToolMessage
|
||||
# will be produced because agents only route on `tool_calls`).
|
||||
|
||||
Reference in New Issue
Block a user