mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-27 09:45:05 +08:00
fix: Skip built-in tool when external tool has same name (#13036)
* fix duplicate tool call * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * [autofix.ci] apply automated fixes (attempt 3/3) * chore: auto-bake note keys and regenerate backend locales/en.json [skip ci] --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b90433a9ae
commit
6691ac7ff3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -503,6 +503,48 @@ class TestAgentComponent(ComponentTestBaseWithoutClient):
|
||||
|
||||
assert tools == []
|
||||
|
||||
async def test_should_register_calculator_tool_only_once_when_external_calculator_connected_and_toggle_enabled(
|
||||
self, component_class, default_kwargs
|
||||
):
|
||||
"""Internal toggle must not register a tool whose name already comes from an external connection.
|
||||
|
||||
Bug: Agent has add_calculator_tool=True (default). When a Calculator component is also
|
||||
wired into the external Tools input, the StructuredTool 'evaluate_expression' is registered
|
||||
twice. Anthropic and Gemini reject duplicate tool names with HTTP 400
|
||||
('Tool names must be unique' / 'Duplicate function declaration found: evaluate_expression').
|
||||
|
||||
Given add_calculator_tool=True AND an external Calculator-derived tool already in self.tools,
|
||||
When get_agent_requirements runs,
|
||||
Then the resulting tools list contains 'evaluate_expression' exactly once.
|
||||
"""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from lfx.components.utilities.calculator_core import CalculatorComponent
|
||||
|
||||
# An external connection delivers exactly the StructuredTool that
|
||||
# CalculatorComponent.to_toolkit() produces — re-use the same path here.
|
||||
external_calc_tool = (await CalculatorComponent().to_toolkit()).pop(0)
|
||||
assert external_calc_tool.name == "evaluate_expression"
|
||||
|
||||
default_kwargs["add_calculator_tool"] = True
|
||||
default_kwargs["add_current_date_tool"] = False
|
||||
default_kwargs["tools"] = [external_calc_tool]
|
||||
component = await self.component_setup(component_class, default_kwargs)
|
||||
component.model = [{"name": "gpt-4o", "provider": "OpenAI", "metadata": {}}]
|
||||
component.get_memory_data = AsyncMock(return_value=[])
|
||||
component._get_shared_callbacks = list
|
||||
component.set_tools_callbacks = lambda *_: None
|
||||
|
||||
with patch("lfx.components.models_and_agents.agent.get_llm") as mock_get_llm:
|
||||
mock_get_llm.return_value = MockLanguageModel()
|
||||
_, _, tools = await component.get_agent_requirements()
|
||||
|
||||
tool_names = [t.name for t in tools]
|
||||
assert tool_names.count("evaluate_expression") == 1, (
|
||||
f"'evaluate_expression' must be registered exactly once; got {tool_names!r}. "
|
||||
"Duplicate tool names are rejected by Anthropic/Gemini with HTTP 400."
|
||||
)
|
||||
|
||||
def test_should_replace_current_date_and_model_name_when_both_placeholders_present(self, component_class):
|
||||
"""Unit test: helper replaces both placeholders with concrete values."""
|
||||
component = component_class()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -325,7 +325,10 @@ class AgentComponent(ToolCallingAgentComponent):
|
||||
if not isinstance(current_date_tool, StructuredTool):
|
||||
msg = "CurrentDateComponent must be converted to a StructuredTool"
|
||||
raise TypeError(msg)
|
||||
self.tools.append(current_date_tool)
|
||||
# Skip if an externally-connected tool already provides the same name.
|
||||
# Duplicate tool names are rejected by Anthropic/Gemini with HTTP 400.
|
||||
if not any(getattr(t, "name", None) == current_date_tool.name for t in self.tools):
|
||||
self.tools.append(current_date_tool)
|
||||
|
||||
# Add calculator tool if enabled (zero-config arithmetic)
|
||||
if getattr(self, "add_calculator_tool", False):
|
||||
@ -336,7 +339,10 @@ class AgentComponent(ToolCallingAgentComponent):
|
||||
if not isinstance(calculator_tool, StructuredTool):
|
||||
msg = "CalculatorComponent must be converted to a StructuredTool"
|
||||
raise TypeError(msg)
|
||||
self.tools.append(calculator_tool)
|
||||
# Skip if an externally-connected tool already provides the same name.
|
||||
# Duplicate tool names are rejected by Anthropic/Gemini with HTTP 400.
|
||||
if not any(getattr(t, "name", None) == calculator_tool.name for t in self.tools):
|
||||
self.tools.append(calculator_tool)
|
||||
|
||||
# Set shared callbacks for tracing the tools used by the agent
|
||||
self.set_tools_callbacks(self.tools, self._get_shared_callbacks())
|
||||
|
||||
Reference in New Issue
Block a user