How to Declare Tools for Needle 2 Using Decorated Functions
Needle 2 discovers callable tools from Python functions wrapped with the @needle.tool decorator, automatically generating JSON schemas from type hints and docstrings to constrain the model's token-level grammar and guarantee syntactically correct tool calls.
The cactus-compute/needle framework enables reliable tool use on resource-constrained devices by validating model outputs against strict schemas. When you declare tools for Needle 2 using decorated functions, the library introspects your Python code to build validation contracts that drive the agent's constrained decoding process.
How the @needle.tool Decorator Works
The implementation in needle/agent/tools.py attaches a JSON schema description to your function as _needle_tool. When you instantiate needle.Needle(tools=[...]), the agent's tool-retrieval head scans supplied callables for this attribute, builds a contract schema, and constructs a byte-level grammar that permits only valid JSON tool calls during generation.
Inspecting Function Signatures
The decorator uses Python's inspect.signature and typing.get_type_hints to extract parameter names, type annotations, and default values from your function definition. This introspection occurs at decoration time, ensuring zero overhead during inference.
Mapping Python Types to JSON Schema
An internal _json_type function handles conversion of Python primitives, enum.Enum classes, typing.Literal definitions, container types (list, dict), Optional unions, and Pydantic models into compliant JSON Schema definitions. This mapping preserves nested structures and validation constraints across complex type hierarchies.
Extracting Documentation
The _parse_doc helper parses your function's docstring to populate the tool's top-level description. If your docstring includes an Args: block, the parser extracts per-parameter descriptions to enrich the schema metadata consumed by the model.
Enforcing Constraints with needle.Field
For parameters wrapped in typing.Annotated[..., needle.Field(...)], the decorator merges validation rules directly into the JSON schema. Supported constraints include numeric bounds (gt, le, ge, lt), string patterns (pattern), length limits (max_length, min_length), and enumerated values (enum).
Handling Required vs Optional Parameters
The decorator marks parameters as required in the schema only if they lack default values and are not typed as Optional[...] or Union[..., None]. This ensures the model understands which arguments must be present in every tool call.
Declaring a Simple Tool
The most basic tool requires only a function signature and docstring. The decorator extracts the name, parameters, and description automatically.
import needle
@needle.tool
def get_weather(city: str):
"""Get the current weather for a city."""
return {"city": city, "temp_c": 23, "sky": "clear"}
This generates a schema requiring a single string argument city and attaches it to get_weather._needle_tool.
Adding Validation Constraints with needle.Field
Use typing.Annotated combined with needle.Field to enforce business logic at the schema level, preventing invalid values from reaching your implementation.
import needle
from typing import Annotated
@needle.tool
def send_money(
amount: Annotated[float, needle.Field(
gt=0,
le=10_000,
description="USD amount, up to 10k"
)],
to: Annotated[str, needle.Field(
pattern=r"^@[a-z0-9_]+$",
description="Recipient handle"
)],
memo: Annotated[str, needle.Field(max_length=80)] = "",
):
"""Transfer money to a user handle."""
return {"sent": amount, "to": to, "memo": memo}
Here, amount must be between 0 and 10,000, to must match the regex pattern for handles, and memo is optional but limited to 80 characters when provided.
Using Literal Types for Fixed Choices
When an argument accepts only specific values, use typing.Literal to constrain the model to valid options without writing validation code.
from typing import Literal
import needle
@needle.tool
def set_thermostat(
temperature: int,
mode: Literal["heat", "cool", "auto"] = "auto"
):
"""Set the thermostat."""
return {"temperature": temperature, "mode": mode}
The generated schema restricts mode to the three enumerated strings, defaulting to "auto" when omitted.
Registering and Running Tools
Pass your decorated functions to the Needle constructor to activate them. The agent uses the stored schemas to build its constrained grammar before running inference.
# Build an agent with the declared tools
agent = needle.Needle(tools=[get_weather, send_money, set_thermostat])
# Run a query - the model emits JSON calls matching your schemas
result = agent.run("Send $42 to @alice and tell me the weather in Paris")
print(result["results"])
# Output:
# [{'sent': 42.0, 'to': '@alice', 'memo': ''},
# {'city': 'Paris', 'temp_c': 23, 'sky': 'clear'}]
The tool-retrieval head selects relevant tools from your list and embeds their schemas in the context, while the grammar constraint ensures emitted calls are syntactically valid against those schemas.
Summary
- The
@needle.tooldecorator inneedle/agent/tools.pygenerates JSON schemas and attaches them to functions via the_needle_toolattribute. - It uses
inspect.signatureandtyping.get_type_hintsfor introspection, plus_json_typefor type mapping and_parse_docfor documentation extraction. - Validation rules are defined using
typing.Annotatedwithneedle.Fieldparameters such asgt,le,pattern, andmax_length. - The agent consumes these schemas to build a byte-level grammar that constrains model output to valid tool calls.
- Tools are registered by passing the decorated function objects directly to
needle.Needle(tools=[...]).
Frequently Asked Questions
Where is the @needle.tool decorator implemented?
According to the cactus-compute/needle source code, the complete decorator logic resides in needle/agent/tools.py. The implementation is re-exported through needle/agent/__init__.py to provide the public needle.tool interface.
How does Needle 2 handle optional parameters?
Parameters with default values or typed as Optional[...] (or Union[..., None]) are marked as non-required in the JSON schema. The model may omit these arguments during tool calls, and the Python function will receive its default value.
Can I use Pydantic models as tool arguments?
Yes. The _json_type mapper in needle/agent/tools.py natively supports Pydantic models, recursively converting them into nested JSON Schema objects while preserving field-level constraints and descriptions defined in your model classes.
What happens if I forget to use the decorator?
Without @needle.tool, the function lacks the _needle_tool attribute. When you pass such a function to needle.Needle(tools=[...]), the agent will not recognize it as a valid tool, and it will be excluded from the tool-retrieval head and constrained grammar.
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 →