Fix: Tool name consistency across TOOLS registry and TOOL_FUNCTIONS
Browse filesResolved mismatch between:
- TOOLS registry (src/tools/__init__.py): "web_search", "calculator", "vision"
- TOOL_FUNCTIONS (src/agent/graph.py): Was using "search", "safe_eval", "analyze_image"
Changes:
- Updated TOOL_FUNCTIONS to match TOOLS registry names
- Updated fallback_tool_selection() to use correct names
- Fixed test mock to use "web_search" instead of "search"
Result: HuggingFace LLM function calling now works correctly
Tests: 99/99 passing ✅
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- src/agent/graph.py +13 -11
- test/test_llm_integration.py +1 -1
src/agent/graph.py
CHANGED
|
@@ -108,10 +108,10 @@ def fallback_tool_selection(question: str, plan: str) -> List[dict]:
|
|
| 108 |
# Extract search query - use first sentence or full question
|
| 109 |
query = question.split('.')[0] if '.' in question else question
|
| 110 |
tool_calls.append({
|
| 111 |
-
"tool": "
|
| 112 |
"params": {"query": query}
|
| 113 |
})
|
| 114 |
-
logger.info(f"[fallback_tool_selection] Added
|
| 115 |
|
| 116 |
# Math tool: keywords like "calculate", "compute", "+", "-", "*", "/", "="
|
| 117 |
math_keywords = ["calculate", "compute", "math", "sum", "multiply", "divide", "+", "-", "*", "/", "="]
|
|
@@ -123,10 +123,10 @@ def fallback_tool_selection(question: str, plan: str) -> List[dict]:
|
|
| 123 |
if expr_match:
|
| 124 |
expression = expr_match.group().strip()
|
| 125 |
tool_calls.append({
|
| 126 |
-
"tool": "
|
| 127 |
"params": {"expression": expression}
|
| 128 |
})
|
| 129 |
-
logger.info(f"[fallback_tool_selection] Added
|
| 130 |
|
| 131 |
# File tool: keywords like "file", "parse", "read", "csv", "json", "txt"
|
| 132 |
file_keywords = ["file", "parse", "read", "csv", "json", "txt", "document"]
|
|
@@ -144,7 +144,7 @@ def fallback_tool_selection(question: str, plan: str) -> List[dict]:
|
|
| 144 |
logger.warning("[fallback_tool_selection] No tools selected by fallback - adding default search")
|
| 145 |
# Default: just search the question
|
| 146 |
tool_calls.append({
|
| 147 |
-
"tool": "
|
| 148 |
"params": {"query": question}
|
| 149 |
})
|
| 150 |
|
|
@@ -220,11 +220,12 @@ def execute_node(state: AgentState) -> AgentState:
|
|
| 220 |
logger.info(f"[execute_node] Question: {state['question']}")
|
| 221 |
|
| 222 |
# Map tool names to actual functions
|
|
|
|
| 223 |
TOOL_FUNCTIONS = {
|
| 224 |
-
"
|
| 225 |
"parse_file": parse_file,
|
| 226 |
-
"
|
| 227 |
-
"
|
| 228 |
}
|
| 229 |
|
| 230 |
# Initialize results lists
|
|
@@ -315,11 +316,12 @@ def execute_node(state: AgentState) -> AgentState:
|
|
| 315 |
logger.info(f"[execute_node] Fallback after exception returned {len(tool_calls)} tool(s)")
|
| 316 |
|
| 317 |
# Try to execute fallback tools
|
|
|
|
| 318 |
TOOL_FUNCTIONS = {
|
| 319 |
-
"
|
| 320 |
"parse_file": parse_file,
|
| 321 |
-
"
|
| 322 |
-
"
|
| 323 |
}
|
| 324 |
|
| 325 |
for tool_call in tool_calls:
|
|
|
|
| 108 |
# Extract search query - use first sentence or full question
|
| 109 |
query = question.split('.')[0] if '.' in question else question
|
| 110 |
tool_calls.append({
|
| 111 |
+
"tool": "web_search",
|
| 112 |
"params": {"query": query}
|
| 113 |
})
|
| 114 |
+
logger.info(f"[fallback_tool_selection] Added web_search tool with query: {query}")
|
| 115 |
|
| 116 |
# Math tool: keywords like "calculate", "compute", "+", "-", "*", "/", "="
|
| 117 |
math_keywords = ["calculate", "compute", "math", "sum", "multiply", "divide", "+", "-", "*", "/", "="]
|
|
|
|
| 123 |
if expr_match:
|
| 124 |
expression = expr_match.group().strip()
|
| 125 |
tool_calls.append({
|
| 126 |
+
"tool": "calculator",
|
| 127 |
"params": {"expression": expression}
|
| 128 |
})
|
| 129 |
+
logger.info(f"[fallback_tool_selection] Added calculator tool with expression: {expression}")
|
| 130 |
|
| 131 |
# File tool: keywords like "file", "parse", "read", "csv", "json", "txt"
|
| 132 |
file_keywords = ["file", "parse", "read", "csv", "json", "txt", "document"]
|
|
|
|
| 144 |
logger.warning("[fallback_tool_selection] No tools selected by fallback - adding default search")
|
| 145 |
# Default: just search the question
|
| 146 |
tool_calls.append({
|
| 147 |
+
"tool": "web_search",
|
| 148 |
"params": {"query": question}
|
| 149 |
})
|
| 150 |
|
|
|
|
| 220 |
logger.info(f"[execute_node] Question: {state['question']}")
|
| 221 |
|
| 222 |
# Map tool names to actual functions
|
| 223 |
+
# NOTE: Keys must match TOOLS registry in src/tools/__init__.py
|
| 224 |
TOOL_FUNCTIONS = {
|
| 225 |
+
"web_search": search,
|
| 226 |
"parse_file": parse_file,
|
| 227 |
+
"calculator": safe_eval,
|
| 228 |
+
"vision": analyze_image,
|
| 229 |
}
|
| 230 |
|
| 231 |
# Initialize results lists
|
|
|
|
| 316 |
logger.info(f"[execute_node] Fallback after exception returned {len(tool_calls)} tool(s)")
|
| 317 |
|
| 318 |
# Try to execute fallback tools
|
| 319 |
+
# NOTE: Keys must match TOOLS registry in src/tools/__init__.py
|
| 320 |
TOOL_FUNCTIONS = {
|
| 321 |
+
"web_search": search,
|
| 322 |
"parse_file": parse_file,
|
| 323 |
+
"calculator": safe_eval,
|
| 324 |
+
"vision": analyze_image,
|
| 325 |
}
|
| 326 |
|
| 327 |
for tool_call in tool_calls:
|
test/test_llm_integration.py
CHANGED
|
@@ -234,7 +234,7 @@ class TestEndToEndWorkflow:
|
|
| 234 |
mock_tool_response = MagicMock()
|
| 235 |
mock_tool_use = MagicMock()
|
| 236 |
mock_tool_use.type = "tool_use"
|
| 237 |
-
mock_tool_use.name = "
|
| 238 |
mock_tool_use.input = {"query": "capital of France"}
|
| 239 |
mock_tool_use.id = "call_001"
|
| 240 |
mock_tool_response.content = [mock_tool_use]
|
|
|
|
| 234 |
mock_tool_response = MagicMock()
|
| 235 |
mock_tool_use = MagicMock()
|
| 236 |
mock_tool_use.type = "tool_use"
|
| 237 |
+
mock_tool_use.name = "web_search"
|
| 238 |
mock_tool_use.input = {"query": "capital of France"}
|
| 239 |
mock_tool_use.id = "call_001"
|
| 240 |
mock_tool_response.content = [mock_tool_use]
|