# How to Define a Tool Using the Decorator Pattern in Needle

> Learn how to define a tool using the decorator pattern in Needle. Effortlessly convert Python functions into LLM-callable tools with automatic JSON schema generation.

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

---

**Needle's `@tool` decorator automatically converts any Python function into an LLM-callable tool by attaching an OpenAI-compatible JSON schema to the function via a private `_needle_tool` attribute.**

The Needle framework provides a clean, decorator-based API for exposing Python functions to AI agents. By applying a single decorator, developers can transform ordinary callables into well-documented tools that language models can discover and invoke. This article walks through the implementation details in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) and demonstrates practical usage patterns.

## The Core Mechanism: `@tool` Decorator

The `tool` decorator defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (lines 73-76) is the entry point for tool definition. When applied to a function, it:

1. Inspects the function signature and type hints
2. Extracts parameter metadata from docstrings
3. Generates an OpenAI-style JSON schema
4. Attaches the schema to the function as `_needle_tool`

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

@tool
def translate(text: str, target_lang: str = "en") -> str:
    """Translate the given text to the target language.

    Args:
        text: The source text to translate.
        target_lang: ISO code of the language to translate into (default "en").
    """
    return f"[{target_lang}] {text}"

```

After decoration, the function carries its complete schema:

```python
>>> translate._needle_tool
{
    "name": "translate",
    "description": "Translate the given text to the target language.",
    "parameters": {
        "type": "object",
        "properties": {
            "text": {"type": "string", "description": "The source text to translate."},
            "target_lang": {"type": "string", "default": "en", "description": "ISO code of the language to translate into (default \"en\")."}
        },
        "required": ["text"]
    }
}

```

## Schema Generation Internals

The `build_schema` function (lines 20-51 in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)) powers the decorator's automatic schema creation. It uses Python's `inspect` module to analyze function signatures and produces a complete OpenAI-compatible description.

### Key Components

- **`inspect.signature`** — extracts parameter names, types, and defaults
- **`_json_type` (lines 57-86)** — maps Python types to JSON Schema types
- **`_field_of`** — extracts `Field` metadata including constraints and descriptions

The builder distinguishes between required parameters (no default) and optional parameters (has default), populating the `required` array accordingly. Type hints are translated through `_json_type`, which handles `str`, `int`, `float`, `bool`, `list`, `dict`, and Pydantic models.

## Adding Parameter Constraints with Field

The `Field` class allows you to specify validation constraints that flow into the generated schema. Annotate parameters with `Field(default=..., ge=..., le=..., enum=...)` to enforce bounds or restrict values.

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

@tool
def resize(
    width: int = Field(default=256, ge=1, le=4096),
    height: int = Field(default=256, ge=1, le=4096)
):
    """Resize an image to the given dimensions in pixels."""
    return f"Resized to {width}x{height}"

```

This produces a schema with `minimum`, `maximum`, and `default` values:

```python
{
    "name": "resize",
    "parameters": {
        "properties": {
            "width": {"type": "integer", "default": 256, "minimum": 1, "maximum": 4096},
            "height": {"type": "integer", "default": 256, "minimum": 1, "maximum": 4096}
        },
        "required": []
    }
}

```

## Runtime Tool Invocation

At runtime, Needle agents locate tools by checking for the `_needle_tool` attribute. The original function remains callable as standard Python while also serving the LLM's structured interface.

```python
from needle.agent import fetch, tools

# Tools decorated with @tool are automatically discoverable

registered_tools = {translate.__name__: translate}

def call_tool(name: str, **kwargs):
    """Execute a tool by name with parsed arguments."""
    fn = registered_tools[name]
    return fn(**kwargs)

# Simulated agent call

result = call_tool("translate", text="Hola", target_lang="en")
print(result)  # → [en] Hola

```

The separation between schema generation and function execution lets you test tools independently of the agent framework.

## File Structure Reference

Understanding where the decorator pattern is implemented helps when extending or debugging tools:

| File | Purpose | Key Functions |
|------|---------|---------------|
| [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) | Core implementation | `tool`, `build_schema`, `_json_type`, `Field` |
| [`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py) | Runtime integration | Tool registration for agent loop |
| [`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) | Validation suite | Unit tests for schema generation |

## Summary

- **Apply `@tool`** from `needle/agent.tools` to any function to expose it to LLM agents
- **Type hints and docstrings** automatically populate the OpenAI-compatible JSON schema
- **`Field`** annotations add validation constraints like `ge`, `le`, `enum`
- **`_needle_tool`** stores the complete schema as a function attribute for runtime discovery
- Schema generation runs once at import time with no overhead during tool execution

## Frequently Asked Questions

### What Python types does the `@tool` decorator support?

The decorator supports all JSON-serializable primitives (`str`, `int`, `float`, `bool`), collections (`list`, `dict`), and Pydantic models. The `_json_type` helper in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) maps these to JSON Schema types automatically. Custom classes require either Pydantic integration or manual schema construction.

### Can I use the `@tool` decorator on class methods?

Yes, though the current implementation treats `self` as a regular parameter. For instance methods, you'll typically define tools as module-level functions or static methods to avoid including `self` in the LLM-facing schema. The `build_schema` function inspects all parameters including `self` unless excluded.

### How does Needle handle default values in the generated schema?

Parameters with default values are marked as optional in the `required` array, and their defaults appear in the property definitions. This matches the OpenAI function calling specification where missing arguments assume documented defaults.

### Where can I customize the generated description?

The function docstring drives the `description` field. Use Google-style docstrings with `Args:` sections; `build_schema` parses these to populate individual parameter descriptions. There's no separate API to override the description—keep docstrings informative and current.