# How to Define Tools Using the @needle.tool Decorator: A Complete Guide

> Define LLM-callable tools with Needle's @needle.tool decorator. This guide shows how it auto-generates JSON schemas from your Python function's type hints and docstrings for seamless integration.

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

---

**The `@needle.tool` decorator exposes any Python function as an LLM-callable tool by automatically generating a JSON schema from the function's type hints, docstring, and `Field` metadata.**

The decorator is implemented in the `cactus-compute/needle` repository and provides the bridge between Python code and agent-based systems. When applied, it attaches a `_needle_tool` attribute containing an OpenAI-compatible function schema that Needle's runtime can discover and invoke.

## How the @needle.tool Decorator Works

The core implementation lives in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). Understanding the internal mechanics helps you leverage the decorator effectively.

### Schema Generation Pipeline

The `build_schema(fn)` function (lines 20-50) drives the conversion process:

1. **Signature inspection** – extracts parameter names, type hints, and defaults
2. **Docstring parsing** – pulls the function description and parameter documentation
3. **Field metadata processing** – applies validation constraints from `Field` objects
4. **Type mapping** – converts Python types to JSON Schema via `_json_type` (lines 57-86)

The decorator itself is minimal (lines 73-76): it simply calls `build_schema(fn)` and stores the result as `fn._needle_tool`.

### Optional Parameter Handling

The `_is_optional` helper (lines 52-55) detects `Optional[T]` or `T | None` syntax. Optional parameters are excluded from the `required` array in the generated schema, matching OpenAI's function calling specification.

## Basic Tool Definition

Import `needle_tool` from `needle.agent.tools` and apply it to any function:

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

@needle_tool
def echo(message: str) -> str:
    """Return the same text that was given."""
    return message

```

The decorator populates `echo._needle_tool` with this schema:

```json
{
  "name": "echo",
  "description": "Return the same text that was given.",
  "parameters": {
    "type": "object",
    "properties": {
      "message": {"type": "string"}
    },
    "required": ["message"]
  }
}

```

The function name becomes the tool name, the docstring becomes the description, and type hints map to JSON Schema types automatically.

## Adding Validation with Field

Use `Field` objects to specify constraints, defaults, and metadata:

```python
from needle.agent.tools import needle_tool, Field

@needle_tool
def scale(value: float, factor: float = Field(default=1.0, ge=0.0, le=10.0)):
    """Multiply `value` by `factor`."""
    return value * factor

```

The generated schema includes validation rules:

```json
"factor": {
  "type": "number",
  "default": 1.0,
  "minimum": 0.0,
  "maximum": 10.0
}

```

Common `Field` parameters that translate to JSON Schema:
- `ge` / `le` → `minimum` / `maximum`
- `gt` / `lt` → `exclusiveMinimum` / `exclusiveMaximum`
- `min_length` / `max_length` → `minLength` / `maxLength`
- `pattern` → `pattern`
- `enum` → `enum`

## Using Enums and Literals

The decorator handles both `Enum` classes and `Literal` types for constrained choices:

```python
from enum import Enum
from typing import Literal
from needle.agent.tools import needle_tool, Field

class Color(Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

@needle_tool
def paint(surface: str, colour: Color | Literal["yellow"] = Field(default=Color.RED)):
    """Paint a surface with the chosen colour."""
    return f"{surface} painted {colour}"

```

The schema merges all allowed values: `["red", "green", "blue", "yellow"]`. Enum members serialize to their values, so `Color.RED` becomes `"red"` in the default field.

## Registering Multiple Tools from a Module

Organize tools in dedicated modules and register them collectively:

```python

# math_tools.py

from needle.agent.tools import needle_tool

@needle_tool
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

@needle_tool
def subtract(a: int, b: int) -> int:
    """Subtract b from a."""
    return a - b

```

```python

# main.py

import importlib
from needle.agent import register_tools

module = importlib.import_module("math_tools")
tools = register_tools(module)

```

`register_tools` scans the module's globals, identifies all objects with `_needle_tool` attributes, and returns a list ready for agent initialization.

## Accessing and Debugging Tool Schemas

Inspect the generated schema directly for debugging or external integration:

```python
>>> print(add._needle_tool)
{'name': 'add',
 'description': 'Add two integers.',
 'parameters': {'type': 'object',
                'properties': {'a': {'type': 'integer'},
                               'b': {'type': 'integer'}},
                'required': ['a', 'b']}}

```

This attribute is available immediately after decoration—no runtime registration required.

## Real-World Examples in the Repository

The `cactus-compute/needle` codebase demonstrates production usage:

- **[`needle/environments/smart_home.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/smart_home.py)** (lines 19-74) – Multiple device control tools with complex parameter validation
- **[`needle/environments/wearable.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/wearable.py)** (lines 17-54) – Health and activity tracking tools using enums for categorical data
- **[`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py)** – Comprehensive verification of schema structure and edge cases

These files illustrate patterns for naming conventions, docstring formatting, and validation design that align with LLM interpretation.

## Summary

- **`@needle_tool`** attaches a JSON schema to any function, making it discoverable by Needle agents
- **Schema generation** is automatic from type hints, docstrings, and `Field` metadata in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)
- **`Field` objects** add validation constraints and defaults that map directly to JSON Schema
- **Optional parameters** ( `T | None` or `Optional[T]`) are excluded from the `required` array
- **`register_tools`** collects all decorated functions from a module for agent initialization
- **`_needle_tool` attribute** provides direct access to the schema for debugging and integration

## Frequently Asked Questions

### What Python versions support the @needle.tool decorator?

The decorator uses modern Python features including `|` union syntax and `typing.Annotated`. According to the source code, it requires **Python 3.10 or newer**. Earlier versions may work with adjustments to type hint syntax.

### Can I use Pydantic models instead of individual parameters?

No—the current implementation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) builds schemas from function signatures directly. For Pydantic-style validation, use `Field` objects within the function parameters rather than model classes.

### How does the decorator handle complex return types?

Return type annotations are captured in the schema but not validated at runtime. The schema's `parameters` field describes inputs only; return type information is stored separately and may be used by agent orchestration layers for result formatting.

### What happens if two tools have the same name?

The schema uses the Python function name as the tool name. Duplicate names in the same scope will overwrite earlier registrations when `register_tools` scans the module. Use distinct function names or organize tools in separate namespaces.