# How the `@needle.tool` Decorator Works: Registering Python Functions as LLM Tools

> Learn how the @needle.tool decorator registers Python functions as LLM tools. Explore schema generation and runtime execution within the Needle agent framework. Enhance your LLM applications.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: deep-dive
- Published: 2026-08-24

---

**The `@needle.tool` decorator transforms regular Python functions into LLM-callable tools by registering them in a global registry, generating JSON-serializable schemas, and wrapping them for runtime execution within the Needle agent framework.**

The `@needle.tool` decorator serves as the primary integration point for the [cactus-compute/needle](https://github.com/cactus-compute/needle) agent architecture. When applied to any Python callable, it marks that function as an available operation that large language models can invoke during multi-step reasoning, automatically handling serialization, validation, and registry management.

## Core Mechanisms of the @needle.tool Decorator

The decorator performs three critical actions at import time to bridge the gap between Python functions and LLM tool-use protocols.

### Global Tool Registration

When `@needle.tool` is applied to a function, the decorator immediately adds the callable to Needle’s internal `tools` dictionary (exposed as `TOOL_REGISTRY` in the public API). This global registry maps function names to their corresponding callables, allowing the `Agent` class to look up and execute tools by name when parsing model outputs.

### JSON Schema Generation

The decorator introspects the function signature—extracting parameter names, type hints, and docstrings—to construct a standardized JSON schema. This schema is sent to the LLM as part of the system prompt, informing the model exactly **what** operations are available and **how** to structure its tool-call arguments.

### Runtime Function Wrapping

The original function is wrapped in a lightweight shim that performs minimal pre-flight checks and normalizes return values. This ensures that whether the tool returns a string, integer, or complex object, the output is safely serializable for injection back into the LLM's context window.

## Implementation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)

The core logic resides in **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** between lines 168 and 210. Here, the `tool` function acts as both the decorator and the registration engine:

```python

# Conceptual structure based on needle/agent/tools.py#L168-L210

from typing import Callable
import functools

def tool(func: Callable) -> Callable:
    """Decorator to register a function as a Needle agent tool."""
    # Register in global lookup

    TOOL_REGISTRY[func.__name__] = func
    
    # Generate and store JSON schema from type hints/docstring

    _schemas[func.__name__] = _build_schema(func)
    
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Runtime validation and execution logic

        result = func(*args, **kwargs)
        return _normalize_output(result)
    
    return wrapper

```

According to the cactus-compute/needle source code, this implementation ensures that decorated functions are available for automatic discovery by the `Agent` class without requiring manual registration steps.

## Practical Usage Examples

### Basic Tool Definition

Define a tool by importing the decorator and applying it to any function with type hints:

```python
from needle.agent.tools import tool

@tool
def search_web(query: str) -> str:
    """Perform a web search and return the top result."""
    # Implementation details omitted

    return f"Results for: {query}"

@tool
def calculate_sum(a: int, b: int) -> int:
    """Return the sum of two integers."""
    return a + b

```

### Integrating with an Agent

Once decorated, tools can be passed to an Agent instance or automatically discovered if the module is imported:

```python
from needle.agent import Agent

agent = Agent()
agent.add_tool(search_web)  # Explicit addition

agent.add_tool(calculate_sum)

# The LLM may output: {"name": "calculate_sum", "arguments": {"a": 5, "b": 3}}

# The agent executes the function and feeds "8" back into the context

```

### Inspecting the Tool Registry

You can view all registered tools by accessing the global registry directly:

```python
from needle.agent.tools import TOOL_REGISTRY

print(TOOL_REGISTRY.keys())

# Output: dict_keys(['search_web', 'calculate_sum', ...])

# Access the raw function

sum_func = TOOL_REGISTRY['calculate_sum']
result = sum_func(10, 20)  # Returns 30

```

## Summary

- **The `@needle.tool` decorator** is defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (lines 168–210) and serves as the entry point for tool registration.
- **Global registration** occurs at decoration time, populating the `TOOL_REGISTRY` dictionary used by the Agent to resolve tool calls.
- **Schema generation** automatically creates LLM-compatible descriptions from Python type hints and docstrings.
- **Runtime wrapping** ensures consistent output formatting and basic validation when the LLM invokes the tool.
- **No manual registration** is required beyond applying the decorator; functions become immediately available to any Needle agent instance.

## Frequently Asked Questions

### Where is the `@needle.tool` decorator implemented?

The decorator is implemented in **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** starting at line 168. This file contains the `tool` function definition, the global `TOOL_REGISTRY` dictionary, and the schema generation logic used to expose Python functions to the LLM.

### How does the decorator generate the tool schema?

The decorator introspects the function’s `__annotations__` and `__doc__` attributes to build a JSON-serializable description. As implemented in cactus-compute/needle, it extracts parameter types from type hints and the description from the docstring, then stores this metadata in an internal schemas dictionary for later inclusion in system prompts.

### Can I register a tool manually without using the decorator?

Yes. While the decorator is the idiomatic approach, you can manually add functions to the registry by importing `TOOL_REGISTRY` from `needle.agent.tools` and assigning the function: `TOOL_REGISTRY['my_tool'] = my_function`. However, this bypasses automatic schema generation and wrapping, requiring you to manually ensure the function conforms to the expected interface.

### What happens when the LLM calls a registered tool?

When the agent parses a tool-use request from the model’s output, it looks up the function name in `TOOL_REGISTRY`, validates the provided arguments against the stored schema, executes the wrapped function, and serializes the return value for re-injection into the LLM’s context window.