mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-12 04:37:51 +02:00
feat(prebuilt): Split tool node to individual tool nodes (#5888)
Add option to split tool node to individual nodes. Summary: * User code (specifically streaming) may break if it's relying on the name of the `tools` node * The boolean flag in the interface is likely **temporary** (especially if there are no major breaking changes) * We'll need to decide if we can get rid of the version in create react agent. "v1" is not consistent conceptually with a node per tool.
This commit is contained in:
@@ -780,14 +780,24 @@ class AgentStateExtraKeyPydantic(AgentStatePydantic):
|
||||
foo: int
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
|
||||
@pytest.mark.parametrize("version", ["v1", "v2"])
|
||||
@pytest.mark.parametrize(
|
||||
"state_schema", [AgentStateExtraKey, AgentStateExtraKeyPydantic]
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"use_individual_tool_nodes",
|
||||
[False, True],
|
||||
ids=["single_tool_node", "node_per_tool"],
|
||||
)
|
||||
def test_create_react_agent_inject_vars(
|
||||
version: Literal["v1", "v2"], state_schema: StateSchemaType
|
||||
version: Literal["v1", "v2"],
|
||||
state_schema: StateSchemaType,
|
||||
use_individual_tool_nodes: bool,
|
||||
) -> None:
|
||||
"""Test that the agent can inject state and store into tool functions."""
|
||||
if version == "v1" and use_individual_tool_nodes:
|
||||
pytest.skip("v1 does not support individual tool nodes")
|
||||
|
||||
store = InMemoryStore()
|
||||
namespace = ("test",)
|
||||
store.put(namespace, "test_key", {"bar": 3})
|
||||
@@ -826,6 +836,7 @@ def test_create_react_agent_inject_vars(
|
||||
state_schema=state_schema,
|
||||
store=store,
|
||||
version=version,
|
||||
use_individual_tool_nodes=use_individual_tool_nodes,
|
||||
)
|
||||
result = agent.invoke({"messages": [{"role": "user", "content": "hi"}], "foo": 2})
|
||||
assert result["messages"] == [
|
||||
@@ -967,7 +978,17 @@ def test_tool_node_messages_key() -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
|
||||
async def test_return_direct(version: str) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
"use_individual_tool_nodes",
|
||||
[False, True],
|
||||
ids=["single_tool_node", "node_per_tool"],
|
||||
)
|
||||
async def test_return_direct(
|
||||
version: Literal["v1", "v2"], use_individual_tool_nodes: bool
|
||||
) -> None:
|
||||
if version == "v1" and use_individual_tool_nodes:
|
||||
pytest.skip("v1 does not support individual tool nodes")
|
||||
|
||||
@dec_tool(return_direct=True)
|
||||
def tool_return_direct(input: str) -> str:
|
||||
"""A tool that returns directly."""
|
||||
@@ -995,6 +1016,7 @@ async def test_return_direct(version: str) -> None:
|
||||
model,
|
||||
[tool_return_direct, tool_normal],
|
||||
version=version,
|
||||
use_individual_tool_nodes=use_individual_tool_nodes,
|
||||
)
|
||||
|
||||
# Test direct return for tool_return_direct
|
||||
@@ -1088,15 +1110,27 @@ def test__get_state_args() -> None:
|
||||
|
||||
|
||||
def test_inspect_react() -> None:
|
||||
"""Test that we can inspect the agent and its nodes."""
|
||||
model = FakeToolCallingModel(tool_calls=[])
|
||||
agent = create_react_agent(model, [])
|
||||
inspect.getclosurevars(agent.nodes["agent"].bound.func)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
|
||||
@pytest.mark.parametrize(
|
||||
"use_individual_tool_nodes",
|
||||
[False, True],
|
||||
ids=["single_tool_node", "node_per_tool"],
|
||||
)
|
||||
def test_react_with_subgraph_tools(
|
||||
sync_checkpointer: BaseCheckpointSaver, version: Literal["v1", "v2"]
|
||||
sync_checkpointer: BaseCheckpointSaver,
|
||||
version: Literal["v1", "v2"],
|
||||
use_individual_tool_nodes: bool,
|
||||
) -> None:
|
||||
"""Test React agent with subgraph tools."""
|
||||
if version == "v1" and use_individual_tool_nodes:
|
||||
pytest.skip("v1 does not support individual tool nodes")
|
||||
|
||||
class State(TypedDict):
|
||||
a: int
|
||||
b: int
|
||||
@@ -1152,6 +1186,7 @@ def test_react_with_subgraph_tools(
|
||||
tool_node,
|
||||
checkpointer=sync_checkpointer,
|
||||
version=version,
|
||||
use_individual_tool_nodes=use_individual_tool_nodes,
|
||||
)
|
||||
result = agent.invoke(
|
||||
{"messages": [HumanMessage(content="What's 2 + 3 and 2 * 3?")]},
|
||||
@@ -1237,8 +1272,17 @@ def test_tool_node_stream_writer() -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS)
|
||||
def test_react_agent_subgraph_streaming_sync(version: Literal["v1", "v2"]) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
"use_individual_tool_nodes",
|
||||
[False, True],
|
||||
ids=["single_tool_node", "node_per_tool"],
|
||||
)
|
||||
def test_react_agent_subgraph_streaming_sync(
|
||||
version: Literal["v1", "v2"], use_individual_tool_nodes: bool
|
||||
) -> None:
|
||||
"""Test React agent streaming when used as a subgraph node sync version"""
|
||||
if version == "v1" and use_individual_tool_nodes:
|
||||
pytest.skip("v1 does not support individual tool nodes")
|
||||
|
||||
@dec_tool
|
||||
def get_weather(city: str) -> str:
|
||||
@@ -1258,6 +1302,7 @@ def test_react_agent_subgraph_streaming_sync(version: Literal["v1", "v2"]) -> No
|
||||
tools=[get_weather],
|
||||
prompt="You are a helpful travel assistant.",
|
||||
version=version,
|
||||
use_individual_tool_nodes=use_individual_tool_nodes,
|
||||
)
|
||||
|
||||
# Create a subgraph that uses the React agent as a node
|
||||
|
||||
Reference in New Issue
Block a user