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

> Learn to use the @needle.tool decorator to define custom tools for your Needle agents. Automatically generate JSON schemas from Python functions, type hints, and docstrings for seamless agent integration.

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

---

**The `@needle.tool` decorator converts any Python function into a tool schema that Needle agents can invoke, automatically generating JSON schema from type hints and docstrings.**

The `@needle.tool` decorator is the primary mechanism for equipping Needle agents with callable capabilities. By applying this decorator to a function, you enable large language models to discover, understand, and execute that function during an agentic session. This article explains how the decorator works under the hood, how to structure your tool functions, and how Needle resolves tool calls at runtime.

---

## What the `@needle.tool` Decorator Actually Does

When you apply `@needle.tool` to a function, Needle invokes `build_schema` to generate a JSON Schema representation of that function's signature. This schema is attached to the function object as the `_needle_tool` attribute in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)【/needle/agent/tools.py#L62-L65】.

The schema captures:

- **Parameter names and types** from Python type hints
- **Default values** for optional parameters
- **Descriptions** from the function's docstring and `Args` section

This metadata is what the language model sees when deciding which tool to invoke. The decorator itself does not modify the function's behavior—it simply annotates it with machine-readable metadata.

---

## Defining a Tool with Type Hints and Docstrings

Needle follows standard Python conventions. The more precise your type hints and docstrings, the better the model's tool selection becomes.

```python
import needle

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

    Args:
        temperature: Desired temperature in Celsius. Must be between 15 and 30.
        mode: Operating mode — one of "heat", "cool", or "auto".
    """
    return {"temperature": temperature, "mode": mode}

```

Key requirements for effective tool definitions:

- **Use concrete types** (`int`, `str`, `float`, `bool`) rather than `Any`
- **Provide detailed docstrings** with an `Args` section explaining each parameter
- **Add constraints** via `needle.Field` for ranges, enums, or patterns when needed (documented in [`doc/apis.md`](https://github.com/cactus-compute/needle/blob/main/doc/apis.md))

---

## Registering Tools with a Needle Agent

Once decorated, tools are passed to the `Needle` constructor as a list. The agent extracts schemas from the `_needle_tool` attribute or builds them on the fly for plain functions.

```python

# Register multiple tools

agent = needle.Needle(tools=[set_thermostat, control_lights, check_weather])

```

During initialization in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), the agent walks the `tools` list and constructs an internal mapping from tool names to callables【/needle/__init__.py#L84-L94】. This mapping is stored in `agent._functions` for later lookup.

---

## How Tool Execution Works at Runtime

When `agent.run()` is called, Needle enters an inference loop:

1. The model receives all tool schemas in its context
2. The model either returns a direct answer or emits a `function_calls` JSON object
3. If tools are called, Needle looks up each tool name in `agent._functions`
4. The callable is executed with the model-provided arguments
5. Results are fed back to the model for potential follow-up calls
6. Final aggregated results are returned【/needle/__init__.py#L107-L126】

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

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

```

The loop continues until the model produces a final response without additional tool calls.

---

## Manual Tool Execution with `complete()`

For finer control, use `agent.complete()` directly. This exposes the raw response type (`"call"` vs `"text"`) and lets you handle tool execution manually:

```python
response = agent.complete("Dim the living room lights to 30%")

if response["type"] == "call":
    call = response["function_calls"][0]
    tool_fn = agent._functions[call["name"]]
    result = tool_fn(**call["arguments"])
    
    # Continue the conversation with tool results

    follow_up = agent.complete(f'<tool_response>{result}</tool_response>')

```

Manual execution is useful when you need to:
- Intercept or validate arguments before execution
- Handle tool failures with custom retry logic
- Log or audit tool calls separately from the main loop

---

## Alternative: Raw JSON Schemas Without the Decorator

The `@needle.tool` decorator is a convenience, not a requirement. You can supply raw JSON schemas directly to `Needle(tools=...)`. However, the decorator provides **type safety**, **automatic documentation extraction**, and **maintainability** that raw schemas lack.

---

## Summary

- **`@needle.tool`** generates JSON schema from Python functions and attaches it as `_needle_tool`【/needle/agent/tools.py#L62-L65】
- **Tool registration** happens via `Needle(tools=[...])`, which builds an internal callable mapping【/needle/__init__.py#L84-L94】
- **Runtime execution** involves model-driven tool selection, name-based lookup, and result feedback【/needle/__init__.py#L107-L126】
- **Manual control** is available through `complete()` for custom execution flows

---

## Frequently Asked Questions

### What Python types does `@needle.tool` support for schema generation?

`@needle.tool` relies on `build_schema` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), which handles standard JSON-encodable types: `str`, `int`, `float`, `bool`, `list`, `dict`, and `Optional` variants. Complex custom objects require manual schema construction using `needle.Field` or raw JSON schemas.

### Can I use the same decorated function with multiple Needle agents?

Yes. The `@needle.tool` decorator attaches immutable schema metadata to the function object. You can pass the same function to any number of `Needle` instances without side effects or re-decoration.

### How does Needle handle tool name collisions?

Tool names are derived from the function's `__name__`. If you pass multiple tools with identical names to `Needle()`, the later one overwrites the earlier in `agent._functions`. Use distinct function names or wrap functions with `functools.wraps` renaming to avoid collisions.

### What happens if the model provides invalid arguments to a tool?

Needle does not automatically validate arguments against the schema before calling the function. Invalid types will raise standard Python exceptions (e.g., `TypeError`). For production systems, add validation inside your tool functions or use `needle.Field` constraints to guide the model toward valid outputs.