# How to Debug "Unknown Tool" Errors in Needle: A Complete Troubleshooting Guide

> Troubleshoot 'unknown tool' errors in Needle. Learn why these errors occur when functions are defined and how to resolve them with this complete guide.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: how-to-guide
- Published: 2026-08-18

---

**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`](https://github.com/cactus-compute/needle/blob/main/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:

```python

# 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 `@tool` decorator** – Defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), this decorator attaches a JSON schema dict to the function via the `_needle_tool` attribute. The `_resolve()` method extracts this schema and uses its `"name"` field as the registry key.

- **Passing plain callables** – If a function lacks the `_needle_tool` attribute, `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`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py):

```python
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:

```python
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:

```python
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:

```python
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:

```python
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:

```python
from mymodule import my_tool  # Ensure fresh import

agent = Needle(tools=[my_tool])

```

### Unsupported Type Signatures

If `build_schema()` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/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:

```python
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:

```python
print(my_tool._needle_tool)

# Verify: {'name': 'my_tool', 'description': '...', ...}

```

### Enable Verbose Logging

Set the environment variable before instantiation to see internal library messages:

```python
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:

```python

# Example problematic LLM response

{"name": "GetWeather", "arguments": {"city": "NYC"}}  # vs "get_weather"

```

## Code Examples for Fixing Registration Issues

**Proper registration with decorator:**

```python
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:**

```python
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:**

```python
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 in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)), populated only at agent instantiation via the `_resolve()` method.
- The **`@tool` decorator** from [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) attaches the `_needle_tool` schema attribute; without it, `build_schema()` attempts automatic inference.
- **"Unknown tool" errors** occur when `self._functions.get(call.get("name"))` returns `None`, 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 `Needle` instance.
- Debug by printing `agent._functions.keys()`, inspecting `function._needle_tool`, and enabling `NEEDLE_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`](https://github.com/cactus-compute/needle/blob/main/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`.