# Understanding the Needle Tool‑Calling Contract: A Complete Technical Guide

> Explore Needle's tool calling contract, a typed JSON schema protocol enabling secure Python function invocation for language models via text in JSON out.

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

---

**Needle's tool‑calling contract is a typed, JSON‑schema‑based protocol that allows language models to safely invoke user‑defined Python functions through a structured "text in, JSON out" cycle.**

The open‑source Needle framework implements a precise contract between language models and executable code. This contract ensures type safety, automatic schema generation, and deterministic execution loops—making it ideal for building reliable AI agents. In this guide, you'll learn exactly how Needle's tool‑calling contract works under the hood, with references to the actual source implementation.

## What Is the Needle Tool‑Calling Contract?

The **Needle tool‑calling contract** defines how a language model communicates its intent to execute code and how the framework validates, runs, and returns results. The contract follows a strict "text → JSON → function → JSON → text" pipeline:

- **Text input**: Natural language queries from users
- **JSON schema**: Structured descriptions of available tools
- **Function execution**: Validated Python function calls
- **JSON output**: Structured results returned to the model
- **Final response**: Synthesized answer after tool execution

This design ensures that models can only emit calls that conform to declared schemas, and the Python runtime safely executes those calls without arbitrary code injection.

## How Tool Schemas Are Generated

The foundation of the contract lies in automatic JSON schema generation. When you apply the `@needle.tool` decorator to a function, Needle inspects its signature and generates a complete JSON schema.

### The `@tool` Decorator Implementation

In [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), the `tool` decorator calls `build_schema` to analyze your function:

```python
import needle

@needle.tool
def get_weather(city: str, units: str = "celsius") -> dict:
    """Get current weather conditions for a specified city.
    
    Args:
        city: The name of the city to query
        units: Temperature units, either "celsius" or "fahrenheit"
    
    Returns:
        Dictionary containing temperature and conditions
    """
    # Implementation would call a weather API

    return {
        "city": city,
        "temperature": 22 if units == "celsius" else 72,
        "conditions": "partly cloudy"
    }

```

The `build_schema` function (lines 15–46 in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)) performs three critical operations:

1. **Signature inspection**: Extracts parameter names, types, and defaults using Python's `inspect` module
2. **Docstring parsing**: Reads argument descriptions from Google‑style or NumPy‑style docstrings
3. **Schema construction**: Outputs a JSON Schema object with `name`, `description`, and `parameters` fields

The resulting schema is stored in the function's `__needle_schema__` attribute for runtime access.

## The Needle Agent Run Loop

Once tools are defined, the `Needle` class orchestrates the execution cycle. The run loop is implemented in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) and handles the bidirectional communication between model and code.

### Step‑by‑Step Execution Flow

**Step 1: Schema registration**

When you instantiate an agent, tool schemas populate the internal `_functions` map:

```python
agent = needle.Needle(tools=[get_weather])

```

**Step 2: Initial model call**

Your query is sent via `_complete` to the underlying inference engine (lines 39–46). The model receives both your question and the available tool schemas.

**Step 3: Function call detection**

The model returns a structured response. If tool execution is needed, the response includes a `"type": "function_call"` field and a `"function_calls"` list containing the tool name and JSON‑encoded arguments.

**Step 4: Execution and iteration**

The `Needle.run` method (lines 47–60) implements the core loop:

```python
response = agent.run(
    "What's the weather in Tokyo in fahrenheit?",
    max_steps=5  # Prevent infinite loops

)

```

For each iteration:
- Parse the model's JSON response
- Look up the corresponding Python function in `_functions`
- Execute with `json.loads()`‑validated arguments
- Append results to the conversation context
- Re‑invoke `_complete` with updated context

**Step 5: Result aggregation**

After completion, the response object includes a `"results"` field (lines 58–60) containing an ordered list of all tool execution outcomes. This preserves the full execution trace for debugging and auditing.

## Response Format and Type Safety

The contract enforces strict output formatting through two mechanisms.

### Grammar‑Constrained Generation

As documented in the README (lines 9–11), Needle uses **byte‑level grammar enforcement** to constrain model outputs. The inference engine validates token sequences against the declared JSON schemas at generation time, preventing malformed tool calls before they occur.

### Structured Response Objects

Every `agent.run()` call returns a dictionary with predictable structure:

```python
response = agent.run("Compare weather in Paris and Rome")

print(response.keys())

# dict_keys(['text', 'results', 'usage', 'finish_reason'])

# Text contains the model's final synthesized answer

print(response["text"])

# "Paris is 18°C and sunny, while Rome is 24°C and clear..."

# Results contains ordered tool execution outputs

print(response["results"])

# [{'city': 'Paris', 'temperature': 18, 'conditions': 'sunny'},

#  {'city': 'Rome', 'temperature': 24, 'conditions': 'clear'}]

```

## Practical Example: Building a Calculator Agent

Here's a complete implementation demonstrating the full contract:

```python
import needle
from typing import Literal

# Define tools with precise type annotations

@needle.tool
def calculate(
    operation: Literal["add", "subtract", "multiply", "divide"],
    a: float,
    b: float
) -> dict:
    """Perform a basic arithmetic operation.
    
    Args:
        operation: The arithmetic operation to perform
        a: First operand
        b: Second operand
    
    Returns:
        Dictionary with result and operation metadata
    """
    ops = {
        "add": lambda x, y: x + y,
        "subtract": lambda x, y: x - y,
        "multiply": lambda x, y: x * y,
        "divide": lambda x, y: x / y if y != 0 else float("inf")
    }
    
    result = ops[operation](a, b)
    return {
        "operation": operation,
        "operands": [a, b],
        "result": result,
        "is_exact": result == int(result)
    }

@needle.tool
def format_currency(amount: float, currency: str = "USD") -> str:
    """Format a number as currency.
    
    Args:
        amount: The numeric amount to format
        currency: ISO 4217 currency code
    
    Returns:
        Formatted currency string
    """
    symbols = {"USD": "$", "EUR": "€", "GBP": "£"}
    symbol = symbols.get(currency, currency + " ")
    return f"{symbol}{amount:,.2f}"

# Instantiate agent with both tools

agent = needle.Needle(tools=[calculate, format_currency])

# Complex query requiring multiple tool calls

response = agent.run(
    "If I have $1,500 and invest it at 8% annual return for 5 years, "
    "what's the final amount in euros? First calculate 1500 * 1.08^5, "
    "then convert the result using a rate of 0.92 USD/EUR."
)

print(f"Final answer: {response['text']}")
print(f"Tool calls executed: {len(response['results'])}")

```

## Structured Data Extraction Mode

Needle extends the same contract for **extraction-only** workflows via `needle.extract()`. This specialized mode treats a Pydantic model as a "virtual tool" with single‑shot execution:

```python
from pydantic import BaseModel
import needle

class ResearchPaper(BaseModel):
    title: str
    authors: list[str]
    publication_year: int
    key_contribution: str
    methodology: Literal["experimental", "theoretical", "simulation"]

# The schema is generated internally and used for one-time extraction

paper = needle.extract(
    """
    In their 2023 Nature paper "Quantum Error Correction Below the Surface Code 
    Threshold", Google Quantum AI researchers demonstrated experimental 
    suppression of logical error rates using distance‑5 superconducting qubits.
    """,
    ResearchPaper
)

assert paper.publication_year == 2023
assert paper.methodology == "experimental"

```

This demonstrates the contract's flexibility: the same "JSON schema → validation → execution" pattern applies whether you're building interactive agents or one‑shot extractors.

## Key Source Files

| Path | Purpose |
|------|---------|
| [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) | `@tool` decorator and `build_schema()` implementation |
| [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) | `Needle` class, `_complete()` method, and run loop |
| [`README.md`](https://github.com/cactus-compute/needle/blob/main/README.md) | High‑level contract documentation and grammar constraints |
| `needle/environments/*.py` | Production‑ready tool collections |

## Summary

- **Needle's tool‑calling contract** guarantees type‑safe, schema‑validated execution through automatic JSON schema generation
- The `@needle.tool` decorator in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) inspects Python signatures to build complete JSON Schema objects
- The run loop in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) implements a "text in, JSON out" cycle with configurable `max_steps` iteration
- Grammar‑level constraints prevent invalid tool calls at the token generation stage
- Results are returned in a structured response object with full execution traceability via the `"results"` field

## Frequently Asked Questions

### How does Needle prevent the model from calling non‑existent tools?

Needle's grammar constraints and schema registration work together. Only tools passed to `needle.Needle(tools=[...])` have their schemas included in the system prompt. The byte‑level grammar enforcer restricts the model's output to valid JSON matching only those registered schemas, making structurally invalid or unknown tool calls impossible to generate.

### Can I use complex Pydantic models as tool parameters?

Yes. The `build_schema` function recursively processes type hints, including nested Pydantic models, lists, unions, and Literal types. Complex validation constraints from Pydantic `Field()` specifications are preserved in the generated JSON schema, though runtime validation currently relies on standard JSON decoding.

### What happens if a tool raises an exception during execution?

The run loop catches exceptions and surfaces them to the model as structured error messages within the conversation context. The model can then decide whether to retry with modified parameters, call alternative tools, or report failure to the user. This error‑as‑context pattern enables robust recovery from expected failure modes.