How to Debug "Unknown Tool" Errors in Needle: A Complete Troubleshooting Guide
Needle raises "unknown tool" errors when a function call name returned by the LLM cannot be found in the agent's internal _functions registry, which is populated at instantiation from the tools argument or via the @tool decorator.
The needle library from cactus-compute/needle resolves tool calls through a central registry that maps function names to callables. When the LLM returns a tool invocation that doesn't match any entry in this registry, Needle immediately returns an error instead of executing code. Understanding how this registry is built and queried is essential for resolving these debugging scenarios.
How Needle Registers Tools
Needle constructs the tool registry during agent instantiation in needle/__init__.py. The Needle class initializes a dictionary called _functions that stores all available tools for the session.
When you pass a list to the tools parameter, the _resolve() method processes each entry:
# From needle/__init__.py - the _resolve() logic
def _resolve(self, tool):
if callable(tool):
if hasattr(tool, "_needle_tool"):
# From @tool decorator
schema = tool._needle_tool
else:
# Plain function - build schema dynamically
schema = build_schema(tool)
self._functions[schema["name"]] = tool
Tools enter the registry in two ways:
-
Using the
@tooldecorator – Defined inneedle/agent/tools.py, this decorator attaches a JSON schema dict to the function via the_needle_toolattribute. The_resolve()method extracts this schema and uses its"name"field as the registry key. -
Passing plain callables – If a function lacks the
_needle_toolattribute,build_schema()automatically generates a schema from the function's type hints and docstring.
Why "Unknown Tool" Errors Occur
When the LLM returns a function call, Needle performs a direct dictionary lookup in needle/__init__.py:
fn = self._functions.get(call.get("name"))
if fn is None:
results.append({"error": "unknown tool: " + str(call.get("name"))})
If the "name" field from the LLM's JSON payload doesn't exist as a key in self._functions, Needle cannot locate the callable and returns the error. This lookup is case-sensitive and requires exact string matching.
Common Causes and Solutions
Tool Not Passed to Agent Constructor
The most common cause is forgetting to include the function in the tools list when creating the Needle instance.
Fix: Explicitly pass all tools to the constructor:
from needle import Needle
def my_function(x: int) -> int:
return x * 2
# Missing tools=[my_function] causes the error
agent = Needle() # Wrong
# Correct - function is registered in _functions
agent = Needle(tools=[my_function])
Name Mismatch Between Schema and LLM Call
The schema's "name" field must exactly match the name the LLM uses in its function call, including case and underscores.
Fix: Verify the registered name matches your prompt instructions:
print(agent._functions.keys())
# If the LLM calls "getWeather" but the key is "get_weather", the lookup fails
If you need a custom name that differs from the function name, pass a schema dict explicitly:
custom_schema = {
"name": "weather_lookup", # Must match what LLM uses
"description": "Look up weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
def get_weather(city: str):
return f"Sunny in {city}"
agent = Needle(tools=[{"schema": custom_schema, "func": get_weather}])
Missing @tool Decorator
Without the @tool decorator, build_schema() attempts to infer the schema, but complex signatures or missing type hints may cause incomplete registration.
Fix: Always use the decorator for explicit schema control:
from needle import tool
@tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
agent = Needle(tools=[add])
Function Defined After Agent Creation
The _functions dictionary is immutable after instantiation. Adding new functions to your Python scope after creating the agent doesn't update the registry.
Fix: Define all tools before instantiation, or create a new Needle instance when tools change.
Incorrect Import Path or Stale Reference
Passing a module path that hasn't been imported, or using a stale cached object, results in the function reference not resolving correctly during _resolve().
Fix: Import the module explicitly before passing:
from mymodule import my_tool # Ensure fresh import
agent = Needle(tools=[my_tool])
Unsupported Type Signatures
If build_schema() in needle/agent/tools.py encounters unsupported types (complex custom classes without Pydantic models), it may fail to generate a valid schema, preventing the tool from entering _functions.
Fix: Use supported type hints (int, str, list, dict, typing.Literal, typing.Optional) or wrap complex types in Pydantic models.
Debugging Steps
Inspect the Registry with agent._functions
Immediately after construction, dump the registry keys to verify registration:
agent = Needle(tools=[my_tool])
print("Registered tools:", list(agent._functions.keys()))
# Output should include: ['my_tool']
Verify the Generated Schema
Check that the _needle_tool attribute contains a valid name field:
print(my_tool._needle_tool)
# Verify: {'name': 'my_tool', 'description': '...', ...}
Enable Verbose Logging
Set the environment variable before instantiation to see internal library messages:
import os
os.environ["NEEDLE_LOG"] = "1"
from needle import Needle
agent = Needle(tools=[my_tool])
Validate the LLM Response
Inspect the raw LLM response to ensure the "name" field in the function call exactly matches your registered keys, including casing:
# Example problematic LLM response
{"name": "GetWeather", "arguments": {"city": "NYC"}} # vs "get_weather"
Code Examples for Fixing Registration Issues
Proper registration with decorator:
from needle import Needle, tool, Field
@tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
agent = Needle(tools=[add])
response = agent.run("What is 3 plus 4?")
print(response["results"]) # → [{'result': 7}]
Registering a plain callable without decorator:
def greet(name: str) -> str:
"""Greet someone."""
return f"Hello, {name}!"
# build_schema() creates the schema automatically
agent = Needle(tools=[greet])
Handling custom schema names:
custom_schema = {
"name": "weather_lookup",
"description": "Look up weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}
def get_weather(city: str):
return f"Sunny in {city}"
agent = Needle(tools=[{"schema": custom_schema, "func": get_weather}])
Summary
- Needle stores tools in
self._functions(defined inneedle/__init__.py), populated only at agent instantiation via the_resolve()method. - The
@tooldecorator fromneedle/agent/tools.pyattaches the_needle_toolschema attribute; without it,build_schema()attempts automatic inference. - "Unknown tool" errors occur when
self._functions.get(call.get("name"))returnsNone, meaning the LLM's requested name isn't in the registry. - Common fixes include: ensuring exact name matching, passing tools to the constructor, using supported type hints, and defining tools before creating the
Needleinstance. - Debug by printing
agent._functions.keys(), inspectingfunction._needle_tool, and enablingNEEDLE_LOG=1.
Frequently Asked Questions
Why does Needle say "unknown tool" when my function is clearly defined?
The function exists in your Python code but was never added to the agent's internal _functions dictionary. According to the source code in needle/__init__.py, this registry is built only during __init__ from the tools argument. If you defined the function after creating the agent or forgot to include it in the tools list, Needle cannot find it during the LLM function call lookup.
Can I add tools to a Needle agent after instantiation?
No. The _functions dictionary is populated once during construction and is not dynamic. If you need to add new capabilities, you must create a new Needle instance with the updated tools list. This design ensures the function registry remains immutable during the agent's execution lifecycle.
How do I check what name Needle expects for a tool?
Inspect the _needle_tool attribute attached by the decorator, or print the keys of agent._functions. The string key in that dictionary must exactly match the "name" field the LLM sends in its function call. For undecorated functions, build_schema() uses the function's __name__ attribute as the default.
What types are supported in tool function signatures?
Needle's build_schema() supports int, str, float, bool, list, dict, typing.Literal, typing.Optional, and Pydantic models. Complex custom classes without Pydantic definitions may cause schema generation to fail silently, preventing the tool from being registered in _functions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →