# How to Define Tools Using the @needle.tool Decorator in Needle

> Learn how to define tools using the @needle.tool decorator. Convert Python functions into JSON-schema tools for your Needle agent to invoke automatically during conversations.

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

---

**The `@needle.tool` decorator converts any Python function into a JSON-schema tool definition that the Needle agent can automatically invoke during conversations.**

The `cactus-compute/needle` library provides a lightweight way to equip LLM agents with capabilities through typed Python functions. By applying the `@needle.tool` decorator, you transform regular functions into structured tools using native type hints and docstrings, eliminating the need to write raw JSON schemas manually.

## How the @needle.tool Decorator Works

Internally, the decorator leverages schema generation to make functions discoverable by the model. When you decorate a function, Needle calls `build_schema` to parse the signature and docstring, then attaches the resulting JSON schema to the function object as the `_needle_tool` attribute.

According to the source code in [`/needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main//needle/agent/tools.py) (lines 62-65), this metadata attachment happens immediately at decoration time. The schema captures parameter types, default values, and descriptions extracted from your docstring's Args section, creating a complete specification that compatible LLMs can interpret.

## Basic Usage: Creating Your First Tool

Define a tool by decorating a function with standard Python type hints and a Google-style docstring. The decorator introspects these annotations to generate the required schema automatically.

```python
import needle

@needle.tool
def set_thermostat(temperature: int, mode: str = "auto"):
    """Set the thermostat.

    Args:
        temperature: Desired temperature in Celsius.
        mode: Heating mode – one of "heat", "cool", or "auto".
    """
    # In a real app this would issue a command to a device.

    return {"temperature": temperature, "mode": mode}

```

The function above becomes a valid tool entry that describes its parameters to the model. For additional constraints, you can use `needle.Field` (documented in [`doc/apis.md`](https://github.com/cactus-compute/needle/blob/main/doc/apis.md)) to specify requirements like value ranges or enums directly in the function signature.

## Registering Tools with the Needle Agent

To make tools available during an agent session, pass them as a list when instantiating the `Needle` class. The constructor examines each item, extracts the attached `_needle_tool` schema (or builds one on the fly if provided as a raw dictionary), and stores an internal mapping from tool name to callable.

As implemented in [`/needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main//needle/__init__.py) (lines 84-94), this initialization step creates the `_functions` registry that maps string names to the actual Python callables. This registry enables the runtime to look up and execute the correct function when the model requests a tool call.

```python

# Create an agent bound to the tool

agent = needle.Needle(tools=[set_thermostat])

# Run a query; the model decides whether to call the tool

result = agent.run("Make it 21°C and set cooling mode")
print(result["results"])

# → [{'temperature': 21, 'mode': 'cool'}]

```

## Runtime Execution Flow

During a conversation, the Needle agent presents the registered tool schemas to the model within the system context. When the model determines it needs external data or action, it returns a JSON response containing a `function_calls` array with the target tool name and populated arguments.

The agent then resolves the call by looking up the name in its `_functions` mapping and executing the corresponding Python function with the provided arguments. As shown in [`/needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main//needle/__init__.py) (lines 107-126), this execution loop handles the tool invocation, feeds the result back into the conversation context, and aggregates final outputs for the user.

## Manual Tool Calling with complete()

For lower-level control, you can use the `complete()` method to handle tool execution manually. This approach exposes the raw `function_calls` structure, allowing you to inspect the model's intent before invoking the function or to implement custom error handling.

```python
response = agent.complete("Dim the living room lights to 30%")
if response["type"] == "call":
    # The model chose a tool; extract the call details

    call = response["function_calls"][0]
    tool_fn = agent._functions[call["name"]]
    tool_result = tool_fn(**call["arguments"])
    
    # Feed the result back to continue the dialogue

    import json
    next_resp = agent.complete(json.dumps([tool_result]))

```

This pattern accesses the internal `_functions` dictionary (populated during initialization) to retrieve the callable by name, then dispatches it with the keyword arguments supplied by the model.

## Summary

- The `@needle.tool` decorator attaches a JSON schema (generated by `build_schema`) to functions as the `_needle_tool` attribute in [`/needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main//needle/agent/tools.py).
- Tool registration occurs in `Needle.__init__` at lines 84-94 of [`/needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main//needle/__init__.py), which builds a mapping from tool names to callables.
- At runtime, the agent presents these schemas to the model and executes requested calls by looking up functions in the `_functions` registry (lines 107-126).
- You can rely on Python type hints and docstrings for automatic schema generation, or manually handle tool calls via the `complete()` method for advanced workflows.

## Frequently Asked Questions

### What is the `_needle_tool` attribute used for?

The `_needle_tool` attribute stores the generated JSON schema representation of your function. Needle uses this metadata internally to register the tool when you pass the function to the `Needle` constructor, ensuring the model receives a complete description of the function's parameters and return type.

### Can I use the @needle.tool decorator without type hints?

While the decorator may still function, omitting type hints results in an incomplete JSON schema that limits the model's ability to provide correct arguments. The `build_schema` utility relies on Python annotations to generate accurate type information, so adding type hints is strongly recommended for reliable tool calling performance.

### How does Needle handle multiple tool calls in a single response?

According to the execution loop in [`/needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main//needle/__init__.py) (lines 107-126), Needle processes the `function_calls` array sequentially. It iterates through each call object, resolves the corresponding function from the `_functions` mapping, executes it with the parsed arguments, and aggregates the results before returning the final output to the user or continuing the conversation.

### Is it possible to define tools without using the decorator?

Yes. You can supply raw JSON schema dictionaries directly to the `tools` parameter when creating a `Needle` instance. However, using the `@needle.tool` decorator provides a type-safe, maintainable approach that keeps your Python function definitions and their LLM-facing schemas synchronized automatically.