Understanding the Needle Tool‑Calling Contract: A Complete Technical Guide
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, the tool decorator calls build_schema to analyze your function:
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) performs three critical operations:
- Signature inspection: Extracts parameter names, types, and defaults using Python's
inspectmodule - Docstring parsing: Reads argument descriptions from Google‑style or NumPy‑style docstrings
- Schema construction: Outputs a JSON Schema object with
name,description, andparametersfields
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 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:
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:
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
_completewith 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:
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:
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:
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 |
@tool decorator and build_schema() implementation |
needle/__init__.py |
Needle class, _complete() method, and run loop |
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.tooldecorator inneedle/agent/tools.pyinspects Python signatures to build complete JSON Schema objects - The run loop in
needle/__init__.pyimplements a "text in, JSON out" cycle with configurablemax_stepsiteration - 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →