# How to Declare a Tool Using the `@needle.tool` Decorator in Needle

> Learn how to declare a tool using the @needle.tool decorator in Needle. Expose Python functions as LLM tools with automatic JSON schema generation.

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

---

**Use the `@needle.tool` decorator on any Python function with type hints to register it as an LLM-exposed tool that includes automatic JSON-schema generation.**

The **Needle** framework provides a lightweight decorator-based system for turning ordinary Python functions into tools that LLM agents can invoke. By applying `@needle.tool` to your function, you enable automatic schema generation, runtime discovery, and first-class integration with the agent's tool-calling API—all without manual boilerplate.

## How the `@needle.tool` Decorator Works

The decorator performs three key operations at decoration time, as implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (line 169 and surrounding code):

1. **Wraps the function** – creates a thin wrapper that forwards all arguments unchanged to the original callable.

2. **Generates a JSON schema** – the internal `build_schema(fn)` function inspects Python type hints and the docstring to produce a structured schema describing parameters, return types, and descriptions.

3. **Stores metadata** – attaches the schema to the function as `_needle_tool`, a hidden attribute that the runtime uses for discovery.

At agent startup, [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (line 109) scans for all callables with the `_needle_tool` attribute and registers them with the LLM's tool-calling interface.

## Declaring Your First Tool

### Basic Function Decoration

Any function with type-annotated parameters and a docstring can become a tool:

```python
import needle

@needle.tool
def greet(name: str) -> str:
    """Return a friendly greeting."""
    return f"Hello, {name}!"

```

The decorator immediately attaches the generated schema:

```python
>>> greet._needle_tool
{
  "name": "greet",
  "description": "Return a friendly greeting.",
  "parameters": {
    "type": "object",
    "properties": {
      "name": {"type": "string", "description": "Name of the person to greet"}
    },
    "required": ["name"]
  }
}

```

### Tools with Optional Parameters

Default values are automatically captured in the schema:

```python
@needle.tool
def get_weather(city: str, unit: str = "celsius") -> dict:
    """Fetch current weather for *city*."""
    # API implementation here

    return {"city": city, "temp": 22, "unit": unit}

```

The generated schema marks `unit` as non-required and includes its default, letting the LLM understand when parameters can be omitted.

## Real-World Usage in Environments

Built-in Needle environments demonstrate `@needle.tool` in practice. In [`needle/environments/smart_home.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/smart_home.py), tools expose device control capabilities:

```python

# needle/environments/smart_home.py

@needle.tool
def turn_on_light(room: str) -> str:
    """Turn on the light in the specified room."""
    return f"Light in {room} turned on."

```

These environment tools are automatically collected into the `TOOLS` list at import time (verified in [`tests/test_environments.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_environments.py), line 17), making them available to any agent running in that environment context.

## Schema Generation Details

The `build_schema` function (in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), line 169) handles:

- **Type mapping** – converts Python types (`str`, `int`, `float`, `bool`, `list`, `dict`) to JSON Schema types.
- **Docstring parsing** – extracts parameter descriptions from Google-style or plain docstrings.
- **Return type annotation** – captures the return type for LLM context, though currently informational.

The decorator returns the original function object, so decorated tools remain fully callable in standard Python code:

```python

# Direct Python call works normally

result = greet("Alice")  # "Hello, Alice!"

# LLM call uses the same function via tool-calling API

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) | Core `@needle.tool` decorator and `build_schema` implementation (line 169) |
| [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) | Runtime tool discovery via `_needle_tool` attribute scanning (line 109) |
| `needle/environments/*.py` | Production examples: [`smart_home.py`](https://github.com/cactus-compute/needle/blob/main/smart_home.py), [`wearable.py`](https://github.com/cactus-compute/needle/blob/main/wearable.py) |
| [`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) | Unit tests validating schema generation (line 20) |

## Summary

- Apply `@needle.tool` to any type-annotated function to register it as an LLM tool.
- The decorator automatically generates JSON schemas from type hints and docstrings, storing them in `_needle_tool`.
- Registered tools are discovered at runtime in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) and exposed through the agent's tool-calling API.
- Decorated functions remain ordinary Python callables—no runtime overhead or calling convention changes.

## Frequently Asked Questions

### What happens if I forget type hints?

The `build_schema` function in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) relies on `inspect.get_annotations` to extract type information. Without hints, parameter types default to `any` in the JSON schema, reducing the LLM's ability to generate correct call arguments. Always include type hints for reliable tool behavior.

### Can I use `@needle.tool` on class methods?

Currently, the decorator is designed for module-level functions. For class-based tools, define them as static methods or module functions that accept the instance state as an explicit parameter. The test suite in [`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) focuses on function-based usage.

### How do I inspect a tool's schema after decoration?

Access the `_needle_tool` attribute directly on the decorated function: `my_function._needle_tool`. This returns the complete JSON schema dictionary that the LLM receives during tool-calling negotiations.