diff --git a/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py b/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py index a4556e232..a09385c82 100644 --- a/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py +++ b/libs/langgraph/langgraph/prebuilt/chat_agent_executor.py @@ -248,9 +248,7 @@ def create_react_agent( interrupt_before: Optional[list[str]] = None, interrupt_after: Optional[list[str]] = None, debug: bool = False, - tool_call_parallelism: Literal[ - "single_tool_node", "parallel_tool_nodes" - ] = "single_tool_node", + version: Literal["v1", "v2"] = "v1", ) -> CompiledGraph: """Creates a graph that works with a chat model that utilizes tool calling. @@ -300,12 +298,12 @@ def create_react_agent( Should be one of the following: "agent", "tools". This is useful if you want to return directly or run additional processing on an output. debug: A flag indicating whether to enable debug mode. - tool_call_parallelism: Determines what state is sent to the tool node. + version: Determines the version of the graph to create. Can be one of: - - `"single_tool_node"`: The tool node processes a single message. All tool + - `"v1"`: The tool node processes a single message. All tool calls in the message are executed in parallel within the tool node. - - `"parallel_tool_nodes"`: The tool node processes a tool call. + - `"v2"`: The tool node processes a tool call. Tool calls are distributed across multiple instances of the tool node using the [Send](https://langchain-ai.github.io/langgraph/concepts/low_level/#send) API. @@ -584,10 +582,9 @@ def create_react_agent( TimeoutError: Timed out at step 2 ``` """ - if tool_call_parallelism not in ("single_tool_node", "parallel_tool_nodes"): + if version not in ("v1", "v2"): raise ValueError( - f"Invalid version {tool_call_parallelism}. Supported versions are " - "'single_tool_node' and 'parallel_tool_nodes'." + f"Invalid version {version}. Supported versions are 'v1' and 'v2'." ) if state_schema is not None: @@ -772,9 +769,9 @@ def create_react_agent( return END if response_format is None else "generate_structured_response" # Otherwise if there is, we continue else: - if tool_call_parallelism == "single_tool_node": + if version == "v1": return "tools" - elif tool_call_parallelism == "parallel_tool_nodes": + elif version == "v2": tool_calls = [ tool_node.inject_tool_args(call, state, store) # type: ignore[arg-type] for call in last_message.tool_calls diff --git a/libs/langgraph/tests/test_prebuilt.py b/libs/langgraph/tests/test_prebuilt.py index 3c4a518b2..b0a19d530 100644 --- a/libs/langgraph/tests/test_prebuilt.py +++ b/libs/langgraph/tests/test_prebuilt.py @@ -73,7 +73,7 @@ from tests.messages import _AnyIdHumanMessage, _AnyIdToolMessage pytestmark = pytest.mark.anyio -REACT_TOOL_CALL_PARALLELISM = ["single_tool_node", "parallel_tool_nodes"] +REACT_TOOL_CALL_VERSIONS = ["v1", "v2"] class FakeToolCallingModel(BaseChatModel): @@ -150,9 +150,9 @@ class FakeToolCallingModel(BaseChatModel): @pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC) -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) def test_no_prompt( - request: pytest.FixtureRequest, checkpointer_name: str, tool_call_parallelism: str + request: pytest.FixtureRequest, checkpointer_name: str, version: str ) -> None: checkpointer: BaseCheckpointSaver = request.getfixturevalue( "checkpointer_" + checkpointer_name @@ -163,7 +163,7 @@ def test_no_prompt( model, [], checkpointer=checkpointer, - tool_call_parallelism=tool_call_parallelism, + version=version, ) inputs = [HumanMessage("hi?")] thread = {"configurable": {"thread_id": "123"}} @@ -326,8 +326,8 @@ def test_runnable_prompt(): assert response == expected_response -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_prompt_with_store(tool_call_parallelism: str): +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_prompt_with_store(version: str): def add(a: int, b: int): """Adds a and b""" return a + b @@ -352,7 +352,7 @@ def test_prompt_with_store(tool_call_parallelism: str): [add], prompt=prompt, store=in_memory_store, - tool_call_parallelism=tool_call_parallelism, + version=version, ) response = agent.invoke( {"messages": [("user", "hi")]}, {"configurable": {"user_id": "1"}} @@ -365,7 +365,7 @@ def test_prompt_with_store(tool_call_parallelism: str): [add], prompt=prompt_no_store, store=in_memory_store, - tool_call_parallelism=tool_call_parallelism, + version=version, ) response = agent.invoke( {"messages": [("user", "hi")]}, {"configurable": {"user_id": "2"}} @@ -416,8 +416,8 @@ async def test_prompt_with_store_async(): @pytest.mark.parametrize("tool_style", ["openai", "anthropic"]) -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_model_with_tools(tool_style: str, tool_call_parallelism: str): +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_model_with_tools(tool_style: str, version: str): model = FakeToolCallingModel(tool_style=tool_style) @dec_tool @@ -434,7 +434,7 @@ def test_model_with_tools(tool_style: str, tool_call_parallelism: str): agent = create_react_agent( model.bind_tools([tool1, tool2]), [tool1, tool2], - tool_call_parallelism=tool_call_parallelism, + version=version, ) result = agent.nodes["tools"].invoke( { @@ -602,8 +602,8 @@ def test__infer_handled_types() -> None: not IS_LANGCHAIN_CORE_030_OR_GREATER, reason="Pydantic v1 is required for this test to pass in langchain-core < 0.3", ) -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_react_agent_with_structured_response(tool_call_parallelism: str) -> None: +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_react_agent_with_structured_response(version: str) -> None: class WeatherResponse(BaseModel): temperature: float = Field(description="The temperature in fahrenheit") @@ -622,7 +622,7 @@ def test_react_agent_with_structured_response(tool_call_parallelism: str) -> Non model, [get_weather], response_format=response_format, - tool_call_parallelism=tool_call_parallelism, + version=version, ) response = agent.invoke({"messages": [HumanMessage("What's the weather?")]}) assert response["structured_response"] == expected_structured_response @@ -1070,8 +1070,8 @@ def test_tool_node_incorrect_tool_name(): assert tool_message.tool_call_id == "some 0" -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_tool_node_node_interrupt(tool_call_parallelism: str): +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_tool_node_node_interrupt(version: str): def tool_normal(some_val: int) -> str: """Tool docstring.""" return "normal" @@ -1120,7 +1120,7 @@ def test_tool_node_node_interrupt(tool_call_parallelism: str): model, [tool_interrupt, tool_normal], checkpointer=checkpointer, - tool_call_parallelism=tool_call_parallelism, + version=version, ) result = agent.invoke({"messages": [HumanMessage("hi?")]}, config) expected_messages = [ @@ -1145,10 +1145,10 @@ def test_tool_node_node_interrupt(tool_call_parallelism: str): ), _AnyIdToolMessage(content="normal", name="tool_normal", tool_call_id="2"), ] - if tool_call_parallelism == "single_tool_node": + if version == "v1": # Interrupt blocks second tool result assert result["messages"] == expected_messages[:-1] - elif tool_call_parallelism == "parallel_tool_nodes": + elif version == "v2": assert result["messages"] == expected_messages state = agent.get_state(config) @@ -1723,8 +1723,8 @@ async def test_tool_node_command_list_input(): not IS_LANGCHAIN_CORE_030_OR_GREATER, reason="Langchain core 0.3.0 or greater is required", ) -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_react_agent_update_state(tool_call_parallelism: str): +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_react_agent_update_state(version: str): from langchain_core.tools.base import InjectedToolCallId class State(AgentState): @@ -1762,7 +1762,7 @@ def test_react_agent_update_state(tool_call_parallelism: str): state_schema=State, prompt=prompt, checkpointer=checkpointer, - tool_call_parallelism=tool_call_parallelism, + version=version, ) config = {"configurable": {"thread_id": "1"}} # run until interrpupted @@ -1782,8 +1782,8 @@ def test_react_agent_update_state(tool_call_parallelism: str): not IS_LANGCHAIN_CORE_030_OR_GREATER, reason="Langchain core 0.3.0 or greater is required", ) -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_react_agent_parallel_tool_calls(tool_call_parallelism: str): +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_react_agent_parallel_tool_calls(version: str): human_assistance_execution_count = 0 @dec_tool @@ -1816,7 +1816,7 @@ def test_react_agent_parallel_tool_calls(tool_call_parallelism: str): model, [human_assistance, get_weather], checkpointer=checkpointer, - tool_call_parallelism=tool_call_parallelism, + version=version, ) config = {"configurable": {"thread_id": "1"}} query = "Get user assistance and also check the weather" @@ -1826,12 +1826,12 @@ def test_react_agent_parallel_tool_calls(tool_call_parallelism: str): ): message_types.append([message.type for message in event["messages"]]) - if tool_call_parallelism == "single_tool_node": + if version == "v1": assert message_types == [ ["human"], ["human", "ai"], ] - elif tool_call_parallelism == "parallel_tool_nodes": + elif version == "v2": assert message_types == [ ["human"], ["human", "ai"], @@ -1851,10 +1851,10 @@ def test_react_agent_parallel_tool_calls(tool_call_parallelism: str): ["human", "ai", "tool", "tool", "ai"], ] - if tool_call_parallelism == "single_tool_node": + if version == "v1": assert human_assistance_execution_count == 1 assert get_weather_execution_count == 2 - elif tool_call_parallelism == "parallel_tool_nodes": + elif version == "v2": assert human_assistance_execution_count == 1 assert get_weather_execution_count == 1 @@ -2047,8 +2047,8 @@ def test_tool_node_inject_state(schema_: Type[T]) -> None: assert tool_message.content == "hi?" -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_create_react_agent_inject_vars(tool_call_parallelism: str) -> None: +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_create_react_agent_inject_vars(version: str) -> None: class AgentStateExtraKey(AgentState): foo: int @@ -2077,7 +2077,7 @@ def test_create_react_agent_inject_vars(tool_call_parallelism: str) -> None: [tool1], state_schema=AgentStateExtraKey, store=store, - tool_call_parallelism=tool_call_parallelism, + version=version, ) input_message = HumanMessage("hi") result = agent.invoke({"messages": [input_message], "foo": 2}) @@ -2223,8 +2223,8 @@ def test_tool_node_messages_key() -> None: ] -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -async def test_return_direct(tool_call_parallelism: str) -> None: +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +async def test_return_direct(version: str) -> None: @dec_tool(return_direct=True) def tool_return_direct(input: str) -> str: """A tool that returns directly.""" @@ -2251,7 +2251,7 @@ async def test_return_direct(tool_call_parallelism: str) -> None: agent = create_react_agent( model, [tool_return_direct, tool_normal], - tool_call_parallelism=tool_call_parallelism, + version=version, ) # Test direct return for tool_return_direct @@ -2346,8 +2346,8 @@ def test_inspect_react() -> None: inspect.getclosurevars(agent.nodes["agent"].bound.func) -@pytest.mark.parametrize("tool_call_parallelism", REACT_TOOL_CALL_PARALLELISM) -def test_react_with_subgraph_tools(tool_call_parallelism: str) -> None: +@pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) +def test_react_with_subgraph_tools(version: str) -> None: class State(TypedDict): a: int b: int @@ -2400,7 +2400,7 @@ def test_react_with_subgraph_tools(tool_call_parallelism: str) -> None: model, tool_node, checkpointer=checkpointer, - tool_call_parallelism=tool_call_parallelism, + version=version, ) result = agent.invoke( {"messages": [HumanMessage(content="What's 2 + 3 and 2 * 3?")]},