# How to Declare Tools and Generate JSON Schemas Using the @needle.tool Decorator in Needle

> Learn how to declare tools and generate JSON schemas with Needle's @needle.tool decorator. Effortlessly transform Python functions into LLM-compatible tools.

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

---

**The `@needle.tool` decorator automatically transforms ordinary Python functions into LLM-compatible tools by attaching OpenAI-style JSON schemas to the function object at import time.**

In the Needle framework, tools are the primary interface between large language models and external environments. Unlike complex registration systems, Needle uses a lightweight decoration approach where the `@needle.tool` decorator introspects function signatures and generates complete JSON schemas without manual configuration.

## How the @needle.tool Decorator Works

The decorator lives in **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** and operates by wrapping Python functions with metadata that language models can consume.

### Decorator Implementation

At lines 173-176 of [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), the decorator assigns a schema dictionary to a private attribute on the function object:

```python
def tool(fn: Callable) -> Callable:
    fn._needle_tool = build_schema(fn)   # attaches schema dict

    return fn

```

This single attribute, `_needle_tool`, stores the complete OpenAPI-compatible specification that LLM providers require for function calling.

### Schema Construction Pipeline

The heavy lifting occurs in `build_schema` (starting at line 20), which performs runtime introspection to construct the schema:

- **`inspect.signature`** extracts the function's parameter names and defaults
- **`typing.get_type_hints`** maps Python annotations to JSON types via internal helpers like `_json_type`
- **`_field_of`** processes `typing.Annotated` metadata for validation constraints
- **`_parse_doc`** parses the docstring for descriptions and per-parameter documentation

The resulting schema follows the OpenAI function-calling format:

```json
{
  "name": "my_tool",
  "description": "...",
  "parameters": {
    "type": "object",
    "properties": {
      "arg1": {"type": "string", "description": "..."},
      "arg2": {"type": "integer", "minimum": 0}
    },
    "required": ["arg1"]
  }
}

```

Required parameters are automatically inferred from the absence of default values and non-optional type annotations.

## Declaring Tools in Practice

Any Python function with type hints can become a tool by applying the decorator.

### Basic Tool Declaration

Environment modules, such as **[`needle/environments/wearable.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/wearable.py)** (line 17), demonstrate the pattern:

```python
import needle

@needle.tool
def turn_on_watch() -> str:
    """Turn the wearable device on."""
    return "Watch activated"

```

The decorator executes at import time, immediately populating `turn_on_watch._needle_tool` with the generated schema.

### Runtime Collection via TOOLS Lists

Environment modules expose their capabilities through module-level `TOOLS` lists. As shown in [`tests/test_environments.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_environments.py) (lines 17-18), these lists gather decorated functions:

```python
from .wearable import TOOLS as wearable_tools

# Each entry carries the ._needle_tool schema dict ready for LLM calls

for tool_func in wearable_tools:
    schema = tool_func._needle_tool

```

This pattern allows the Needle agent to discover all available capabilities within an environment at runtime.

## Complete Working Example

Here is a practical implementation showing type hints, default values, and docstring parsing:

```python
import needle
from typing import Annotated

@needle.tool
def set_thermostat(
    temperature: Annotated[int, "Target temperature in Fahrenheit"],
    room: str = "living_room"
) -> str:
    """Adjust the thermostat to the specified temperature.
    
    Args:
        temperature: The desired temperature between 50 and 90.
        room: Which room to adjust (defaults to living room).
    """
    return f"Set {room} to {temperature}°F"

# Access the generated schema

print(set_thermostat._needle_tool)

```

The resulting schema automatically marks `temperature` as required and `room` as optional with its default value, while extracting descriptions from both the docstring and `Annotated` metadata.

## Key Implementation Files

Understanding the `@needle.tool` decorator requires familiarity with these specific source locations:

- **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** – Contains the `tool` decorator (lines 173-176), the `build_schema` function (lines 20-51), and helper functions `_json_type`, `_field_of`, and `_parse_doc`
- **`needle/environments/*.py`** – Concrete implementations like [`wearable.py`](https://github.com/cactus-compute/needle/blob/main/wearable.py) and [`smart_home.py`](https://github.com/cactus-compute/needle/blob/main/smart_home.py) that demonstrate real-world tool definitions
- **[`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py)** – Unit tests validating schema generation, including assertions that `greet._needle_tool["name"]` matches the function name (line 161)
- **[`tests/test_environments.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_environments.py)** – Integration tests verifying that environment `TOOLS` lists correctly expose the `_needle_tool` attribute (lines 17-18)

## Summary

- The `@needle.tool` decorator attaches JSON schemas to functions via the `_needle_tool` attribute at import time.
- Schema generation relies on Python's `inspect` and `typing` modules to map function signatures to OpenAI-compatible specifications.
- Tools are declared by decorating functions with type hints in environment modules like [`needle/environments/wearable.py`](https://github.com/cactus-compute/needle/blob/main/needle/environments/wearable.py).
- Runtime discovery occurs through module-level `TOOLS` lists that aggregate decorated functions.
- The implementation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) supports advanced features like `Annotated` metadata and automatic docstring parsing.

## Frequently Asked Questions

### What Python types does the @needle.tool decorator support?

The decorator supports standard JSON-serializable types through the `_json_type` mapping function. This includes `str` (string), `int` (integer), `float` (number), `bool` (boolean), `list` (array), and `dict` (object). Complex types can be specified using `typing.Annotated` with Pydantic-style `Field` objects for additional validation constraints.

### How does Needle determine which parameters are required in the JSON schema?

Required parameters are inferred automatically by `build_schema` in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py). Any parameter without a default value in the function signature, or typed as non-optional without `None` in its annotation, is added to the `required` array in the generated JSON schema. Optional parameters with defaults are excluded from this list but include the default value in their property definition.

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

The current implementation in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) is designed for module-level functions. While the decorator itself will technically attach the `_needle_tool` attribute to any callable, the schema builder expects a standard function signature. For class methods, you would need to expose them as module-level wrappers or extend the `build_schema` logic to handle `self` and `cls` parameters.

### Where can I verify that my tool schema was generated correctly?

The test suite in [`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) demonstrates the validation pattern used in Needle. You can inspect the `_needle_tool` attribute directly on your decorated function to verify the generated schema, or follow the pattern in [`tests/test_environments.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_environments.py) where the test checks that each tool in an environment's `TOOLS` list contains the expected schema dictionary with the correct `name` and `parameters` structure.