Cherry-pick prebuilt (#5204)

fix: [prebuilt] Checks for pre-bound model with builtin tools (#5203)
This commit is contained in:
William FH
2025-06-25 18:36:52 -07:00
committed by GitHub
parent d00181cebd
commit e442a9cb97
3 changed files with 31 additions and 8 deletions
@@ -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]
+4 -1
View File
@@ -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
+21 -3
View File
@@ -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(