# How to Declare Tools for Needle 2 Using Decorated Functions

> Learn to declare tools for Needle 2 with decorated functions. Automatically generate JSON schemas from type hints and docstrings for precise tool calls.

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

---

**Needle 2 discovers callable tools from Python functions wrapped with the `@needle.tool` decorator, automatically generating JSON schemas from type hints and docstrings to constrain the model's token-level grammar and guarantee syntactically correct tool calls.**

The `cactus-compute/needle` framework enables reliable tool use on resource-constrained devices by validating model outputs against strict schemas. When you declare tools for Needle 2 using decorated functions, the library introspects your Python code to build validation contracts that drive the agent's constrained decoding process.

## How the @needle.tool Decorator Works

The implementation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) attaches a JSON schema description to your function as `_needle_tool`. When you instantiate `needle.Needle(tools=[...])`, the agent's tool-retrieval head scans supplied callables for this attribute, builds a contract schema, and constructs a byte-level grammar that permits only valid JSON tool calls during generation.

### Inspecting Function Signatures

The decorator uses Python's `inspect.signature` and `typing.get_type_hints` to extract parameter names, type annotations, and default values from your function definition. This introspection occurs at decoration time, ensuring zero overhead during inference.

### Mapping Python Types to JSON Schema

An internal `_json_type` function handles conversion of Python primitives, `enum.Enum` classes, `typing.Literal` definitions, container types (`list`, `dict`), `Optional` unions, and Pydantic models into compliant JSON Schema definitions. This mapping preserves nested structures and validation constraints across complex type hierarchies.

### Extracting Documentation

The `_parse_doc` helper parses your function's docstring to populate the tool's top-level description. If your docstring includes an `Args:` block, the parser extracts per-parameter descriptions to enrich the schema metadata consumed by the model.

### Enforcing Constraints with needle.Field

For parameters wrapped in `typing.Annotated[..., needle.Field(...)]`, the decorator merges validation rules directly into the JSON schema. Supported constraints include numeric bounds (`gt`, `le`, `ge`, `lt`), string patterns (`pattern`), length limits (`max_length`, `min_length`), and enumerated values (`enum`).

### Handling Required vs Optional Parameters

The decorator marks parameters as required in the schema only if they lack default values and are not typed as `Optional[...]` or `Union[..., None]`. This ensures the model understands which arguments must be present in every tool call.

## Declaring a Simple Tool

The most basic tool requires only a function signature and docstring. The decorator extracts the name, parameters, and description automatically.

```python
import needle

@needle.tool
def get_weather(city: str):
    """Get the current weather for a city."""
    return {"city": city, "temp_c": 23, "sky": "clear"}

```

This generates a schema requiring a single string argument `city` and attaches it to `get_weather._needle_tool`.

## Adding Validation Constraints with needle.Field

Use `typing.Annotated` combined with `needle.Field` to enforce business logic at the schema level, preventing invalid values from reaching your implementation.

```python
import needle
from typing import Annotated

@needle.tool
def send_money(
    amount: Annotated[float, needle.Field(
        gt=0, 
        le=10_000,
        description="USD amount, up to 10k"
    )],
    to: Annotated[str, needle.Field(
        pattern=r"^@[a-z0-9_]+$",
        description="Recipient handle"
    )],
    memo: Annotated[str, needle.Field(max_length=80)] = "",
):
    """Transfer money to a user handle."""
    return {"sent": amount, "to": to, "memo": memo}

```

Here, `amount` must be between 0 and 10,000, `to` must match the regex pattern for handles, and `memo` is optional but limited to 80 characters when provided.

## Using Literal Types for Fixed Choices

When an argument accepts only specific values, use `typing.Literal` to constrain the model to valid options without writing validation code.

```python
from typing import Literal
import needle

@needle.tool
def set_thermostat(
    temperature: int,
    mode: Literal["heat", "cool", "auto"] = "auto"
):
    """Set the thermostat."""
    return {"temperature": temperature, "mode": mode}

```

The generated schema restricts `mode` to the three enumerated strings, defaulting to `"auto"` when omitted.

## Registering and Running Tools

Pass your decorated functions to the `Needle` constructor to activate them. The agent uses the stored schemas to build its constrained grammar before running inference.

```python

# Build an agent with the declared tools

agent = needle.Needle(tools=[get_weather, send_money, set_thermostat])

# Run a query - the model emits JSON calls matching your schemas

result = agent.run("Send $42 to @alice and tell me the weather in Paris")
print(result["results"])

# Output:

# [{'sent': 42.0, 'to': '@alice', 'memo': ''},

#  {'city': 'Paris', 'temp_c': 23, 'sky': 'clear'}]

```

The tool-retrieval head selects relevant tools from your list and embeds their schemas in the context, while the grammar constraint ensures emitted calls are syntactically valid against those schemas.

## Summary

- The `@needle.tool` decorator in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) generates JSON schemas and attaches them to functions via the `_needle_tool` attribute.
- It uses `inspect.signature` and `typing.get_type_hints` for introspection, plus `_json_type` for type mapping and `_parse_doc` for documentation extraction.
- Validation rules are defined using `typing.Annotated` with `needle.Field` parameters such as `gt`, `le`, `pattern`, and `max_length`.
- The agent consumes these schemas to build a byte-level grammar that constrains model output to valid tool calls.
- Tools are registered by passing the decorated function objects directly to `needle.Needle(tools=[...])`.

## Frequently Asked Questions

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

According to the cactus-compute/needle source code, the complete decorator logic resides in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). The implementation is re-exported through [`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py) to provide the public `needle.tool` interface.

### How does Needle 2 handle optional parameters?

Parameters with default values or typed as `Optional[...]` (or `Union[..., None]`) are marked as non-required in the JSON schema. The model may omit these arguments during tool calls, and the Python function will receive its default value.

### Can I use Pydantic models as tool arguments?

Yes. The `_json_type` mapper in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) natively supports Pydantic models, recursively converting them into nested JSON Schema objects while preserving field-level constraints and descriptions defined in your model classes.

### What happens if I forget to use the decorator?

Without `@needle.tool`, the function lacks the `_needle_tool` attribute. When you pass such a function to `needle.Needle(tools=[...])`, the agent will not recognize it as a valid tool, and it will be excluded from the tool-retrieval head and constrained grammar.