# How to Define Tools Using Raw JSON Schema in Needle

> Learn to define tools using raw JSON schema in Needle. Attach a dictionary to _needle_tool or use the @tool decorator for easy tool definition. Get started now.

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

---

**Attach a correctly structured dictionary to your function's `_needle_tool` attribute to supply a raw JSON schema, or use the `@tool` decorator to auto-generate one from type hints.**

Needle, an open-source LLM orchestration framework from cactus-compute/needle, enables developers to expose Python functions as callable tools for language models. While the library automatically generates JSON schemas from function signatures and docstrings, you can also define tools using raw JSON schema when you need precise control over parameter definitions or want to support complex validation rules that automatic introspection cannot capture.

## How Needle Discovers Tool Schemas

When initializing a `Needle` instance with a list of tools, the engine inspects each callable for a `_needle_tool` attribute. According to the source code in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (around line 210), the initialization logic first attempts to load `entry._needle_tool`. If the attribute exists, Needle uses the dictionary verbatim as the tool's JSON schema definition. If absent, the engine falls back to `build_schema(entry)`, which is implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) to automatically introspect the function.

This dual-path architecture allows you to choose between convenience (automatic generation) and precision (manual schema definition) on a per-tool basis.

## Approaches to Schema Definition

### Automatic Generation with @tool

The `@tool` decorator, defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), wraps Python functions and populates the `_needle_tool` attribute automatically. It inspects parameter types via `typing` hints, parses docstrings for descriptions, and constructs a complete JSON schema object. This approach is ideal when your functions use standard Python types and clear documentation.

### Raw JSON Schema Definition

To bypass automatic introspection entirely, manually assign a dictionary to the `_needle_tool` attribute before passing the function to the `Needle` class. This dictionary must conform to the JSON Schema specification, containing keys such as `name`, `description`, and `parameters` with proper type definitions and required field arrays.

## Implementation Examples

The following examples demonstrate both automatic and manual schema definition patterns.

### Example 1: Automatic Schema with @tool

This approach uses the decorator to generate the schema from type hints and docstrings.

```python
from needle.agent.tools import tool
from needle import Needle

@tool  # Automatically creates _needle_tool attribute

def greet(name: str, *, polite: bool = False) -> str:
    """Say hello to *name*.

    Parameters
    ----------
    name: The person to greet.
    polite: Whether to use a formal greeting.
    """
    return f"{'Good day' if polite else 'Hey'} {name}!"

# Pass the function to the engine

engine = Needle(tools=[greet])

```

### Example 2: Raw JSON Schema Definition

Here we manually construct the schema dictionary and attach it to the function.

```python
from needle import Needle

def add(a: int, b: int) -> int:
    """Return the sum of *a* and *b*."""
    return a + b

# Attach a raw schema that matches the function signature

add._needle_tool = {
    "name": "add",
    "description": "Add two integers and return the result.",
    "parameters": {
        "type": "object",
        "properties": {
            "a": {"type": "integer", "description": "First operand"},
            "b": {"type": "integer", "description": "Second operand"}
        },
        "required": ["a", "b"]
    }
}

# The engine will use this schema verbatim

engine = Needle(tools=[add])

```

### Example 3: Mixing Auto-Generated and Manual Schemas

You can combine both approaches in a single tool list.

```python
from needle.agent.tools import tool
from needle import Needle

def echo(message: str) -> str:
    """Return the received *message* unchanged."""
    return message

# Use automatic schema generation for echo

echo = tool(echo)

# Provide custom schema for multiply

def multiply(x: int, y: int) -> int:
    return x * y

multiply._needle_tool = {
    "name": "multiply",
    "description": "Multiply two integers.",
    "parameters": {
        "type": "object",
        "properties": {
            "x": {"type": "integer"},
            "y": {"type": "integer"}
        },
        "required": ["x", "y"]
    }
}

engine = Needle(tools=[echo, multiply])

```

## Summary

- **Dual registration paths**: Needle checks for `_needle_tool` in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) before falling back to `build_schema` from [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py).
- **Automatic generation**: The `@tool` decorator introspects functions and stores generated schemas on the `_needle_tool` attribute.
- **Manual control**: Assign a raw JSON schema dictionary directly to `_needle_tool` to override automatic generation.
- **Flexible mixing**: Both auto-generated and manually defined tools can coexist in the same `Needle` instance.
- **Schema structure**: Raw schemas require `name`, `description`, and `parameters` keys conforming to JSON Schema standards.

## Frequently Asked Questions

### What is the `_needle_tool` attribute in Needle?

The `_needle_tool` attribute is a dictionary attached to callable objects that contains the raw JSON schema definition. When present, Needle uses this schema directly instead of calling `build_schema` to introspect the function. According to the source code in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), this check occurs during engine initialization to determine whether to use the provided schema or generate one automatically.

### Can I mix auto-generated and manual schemas in the same Needle instance?

Yes. The `Needle` class accepts a list of tools where some functions use the `@tool` decorator for auto-generated schemas while others have manually assigned `_needle_tool` attributes. The engine handles both types seamlessly during initialization, making it possible to combine convenience and precision in the same application.

### Where does Needle check for custom tool schemas?

The check occurs in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) at approximately line 210, where the initialization code attempts to access `entry._needle_tool`. If the attribute exists, the engine uses it verbatim; otherwise, it invokes `build_schema(entry)` from [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) to generate the schema from the function's signature and type hints.

### What structure must a raw JSON schema follow in Needle?

The `_needle_tool` dictionary must include `name` (string identifier), `description` (string explanation), and `parameters` (object containing JSON Schema definitions). The parameters object should specify `type` (typically "object"), `properties` (mapping parameter names to their schemas), and `required` (array of mandatory parameter names) according to the JSON Schema specification.