diff --git a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py index 6a5e882e7..4ad5d1908 100644 --- a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py +++ b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py @@ -140,7 +140,9 @@ def _get_prompt_runnable(prompt: Optional[Prompt]) -> Runnable: return prompt_runnable -def _should_bind_tools(model: LanguageModelLike, tools: Sequence[BaseTool]) -> bool: +def _should_bind_tools( + model: LanguageModelLike, tools: Sequence[BaseTool], num_builtin: int = 0 +) -> bool: if isinstance(model, RunnableSequence): model = next( ( @@ -158,9 +160,10 @@ def _should_bind_tools(model: LanguageModelLike, tools: Sequence[BaseTool]) -> b return True bound_tools = model.kwargs["tools"] - if len(tools) != len(bound_tools): + if len(tools) != len(bound_tools) - num_builtin: raise ValueError( "Number of tools in the model.bind_tools() and tools passed to create_react_agent must match" + f" Got {len(tools)} tools, expected {len(bound_tools) - num_builtin}" ) tool_names = set(tool.name for tool in tools) @@ -444,9 +447,8 @@ def create_react_agent( tool_calling_enabled = len(tool_classes) > 0 if ( - _should_bind_tools(model, tool_classes) + _should_bind_tools(model, tool_classes, num_builtin=len(llm_builtin_tools)) and len(tool_classes) > 0 - or (len(llm_builtin_tools) > 0) ): model = cast(BaseChatModel, model).bind_tools(tool_classes + llm_builtin_tools) # type: ignore[operator] diff --git a/libs/prebuilt/tests/model.py b/libs/prebuilt/tests/model.py index 8e9857675..7daf80431 100644 --- a/libs/prebuilt/tests/model.py +++ b/libs/prebuilt/tests/model.py @@ -73,9 +73,12 @@ class FakeToolCallingModel(BaseChatModel): tool_dicts = [] for tool in tools: + if isinstance(tool, dict): + tool_dicts.append(tool) + continue if not isinstance(tool, BaseTool): raise TypeError( - "Only BaseTool is supported by FakeToolCallingModel.bind_tools" + "Only BaseTool and dict is supported by FakeToolCallingModel.bind_tools" ) # NOTE: this is a simplified tool spec for testing purposes only diff --git a/libs/prebuilt/tests/test_react_agent.py b/libs/prebuilt/tests/test_react_agent.py index 64f4d0187..0a0cc0599 100644 --- a/libs/prebuilt/tests/test_react_agent.py +++ b/libs/prebuilt/tests/test_react_agent.py @@ -275,7 +275,8 @@ async def test_prompt_with_store_async(): @pytest.mark.parametrize("tool_style", ["openai", "anthropic"]) @pytest.mark.parametrize("version", REACT_TOOL_CALL_VERSIONS) -def test_model_with_tools(tool_style: str, version: str): +@pytest.mark.parametrize("include_builtin", [True, False]) +def test_model_with_tools(tool_style: str, version: str, include_builtin: bool): model = FakeToolCallingModel(tool_style=tool_style) @dec_tool @@ -288,10 +289,27 @@ def test_model_with_tools(tool_style: str, version: str): """Tool 2 docstring.""" return f"Tool 2: {some_val}" + tools = [tool1, tool2] + if include_builtin: + tools.append( + { + "type": "mcp", + "server_label": "atest_sever", + "server_url": "https://some.mcp.somewhere.com/sse", + "headers": {"foo": "bar"}, + "allowed_tools": [ + "mcp_tool_1", + "set_active_account", + "get_url_markdown", + "get_url_screenshot", + ], + "require_approval": "never", + } + ) # check valid agent constructor agent = create_react_agent( - model.bind_tools([tool1, tool2]), - [tool1, tool2], + model.bind_tools(tools), + tools, version=version, ) result = agent.nodes["tools"].invoke(