# How to Pass a Raw JSON Schema for Tool Definition in Needle 2

> Learn how to pass a raw JSON schema for tool definition in Needle 2. Explore methods like attaching to _needle_tool or using the tools argument for seamless integration.

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

---

**You can pass a raw JSON schema to Needle 2 either by attaching it to a function’s `_needle_tool` attribute or by providing a dictionary directly to the `tools=` argument in `Agent` or `needle.run()`.**

Needle 2 automatically constructs tool definitions from Python type hints and docstrings via the `build_schema` utility. When you need granular control over parameter validation, descriptions, or complex nested types, you can pass a raw JSON schema for tool definition in Needle 2 to override this automatic generation.

## Method 1: Attach a Raw Schema to a Function

The most common approach is to manually set the `_needle_tool` attribute on your function object. This attribute expects a dictionary that follows the OpenAI function-calling specification.

According to the source code in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), the standard `@tool` decorator normally executes `fn._needle_tool = build_schema(fn)` at line 169 to store the generated schema on the function object. By setting this attribute yourself before the decorator runs—or by overriding it afterward—you force Needle to use your custom schema verbatim instead of building one from type hints.

When Needle discovers tools via `env.TOOLS` or the `run` API, it checks for this attribute first. If `_needle_tool` exists, Needle transmits that schema directly to the LLM without calling `build_schema`.

```python

# Attach a raw schema to a function

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

# Manually override the automatically generated schema

greet._needle_tool = {
    "name": "greet",
    "description": "Greet a user by name.",
    "parameters": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "description": "The user's name"
            }
        },
        "required": ["name"]
    }
}

# The function can now be used like any other Needle tool

from needle import Agent
agent = Agent(tools=[greet])
response = agent.run("Say hi to Alice.")
print(response)          # → "Hello, Alice!"

```

## Method 2: Pass a Raw Schema Directly to the Runtime

Alternatively, you can skip the function-attachment step entirely and pass raw schema dictionaries directly to the runtime via the `tools=` parameter. Both `needle.run()` and the `Agent` class accept a list that may contain a mix of callable functions and plain dictionaries.

When Needle encounters a dictionary in the `tools` list, it bypasses the `build_schema` step and forwards the JSON-Schema straight to the LLM. This logic is handled in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), where the entry point checks for the `_needle_tool` attribute (line 109) and falls back to `build_schema` only if the attribute is missing. Since raw dictionaries have no such attribute, they flow through unchanged.

```python

# Supply a raw schema dict directly to the runtime

raw_schema = {
    "name": "add_numbers",
    "description": "Add two integers and return the sum.",
    "parameters": {
        "type": "object",
        "properties": {
            "a": {"type": "integer", "description": "First addend"},
            "b": {"type": "integer", "description": "Second addend"}
        },
        "required": ["a", "b"]
    }
}

def add_numbers(a: int, b: int) -> int:
    return a + b

from needle import Agent
agent = Agent(tools=[raw_schema])   # no need for a Python function here

response = agent.run("What is 3 plus 7?")
print(response)          # → "10"

```

## How Needle Resolves Tool Definitions

Understanding the resolution order helps you decide which method to use:

1. **Dictionary check**: If an item in the `tools` list is a `dict`, Needle uses it verbatim.
2. **Attribute check**: If a callable has the `_needle_tool` attribute (as set in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) at line 169), Needle uses that value.
3. **Fallback generation**: Otherwise, Needle calls `build_schema(fn)`, defined in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (lines 15–46), to infer the schema from type hints and docstrings.

The `build_schema` function relies on `_json_type` to map Python types to JSON Schema types. By supplying a raw schema, you circumvent this entire inference chain.

## Summary

- **Attach to function**: Set `func._needle_tool = {...}` to override auto-generation for that specific callable. Ideal when you want to keep the Python function signature but customize the LLM-facing schema.
- **Pass directly**: Include raw dictionaries in the `tools=` list when you don’t need a corresponding Python function or when defining tools dynamically.
- **Source locations**: The schema storage logic resides in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) (line 169), while fallback behavior is handled in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (line 109).
- **Validation**: Needle does not validate raw schemas; ensure your dictionary conforms to the OpenAI function-calling specification before runtime.

## Frequently Asked Questions

### What format must the raw JSON schema follow?

The dictionary must conform to the OpenAI function-calling schema specification. It requires a `name` (string), `description` (string), and a `parameters` object containing `type`, `properties`, and `required` arrays. Needle passes this structure directly to the LLM without modification.

### Can I mix raw schemas and Python functions in the same agent?

Yes. The `tools=` argument accepts a heterogeneous list. You can include functions with auto-generated schemas, functions with manually attached `_needle_tool` attributes, and raw dictionaries side by side. Needle processes each item according to the resolution order documented above.

### Does Needle validate custom schemas before sending them to the LLM?

No. When you provide a raw schema, Needle forwards it verbatim to the underlying LLM provider. Validation against the JSON Schema specification or the provider’s requirements is your responsibility. The test suite in [`tests/test_tools.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_tools.py) (line 20) demonstrates how the `_needle_tool` attribute is accessed, but does not enforce schema compliance.

### Where is the automatic schema generation implemented?

The `build_schema` function and the `_json_type` helper are implemented in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) between lines 15 and 46. This module also contains the `@tool` decorator logic that normally populates the `_needle_tool` attribute at line 169.