How to Define Tools Using the @needle.tool Decorator in Needle
The @needle.tool decorator converts any Python function into a JSON-schema tool definition that the Needle agent can automatically invoke during conversations.
The cactus-compute/needle library provides a lightweight way to equip LLM agents with capabilities through typed Python functions. By applying the @needle.tool decorator, you transform regular functions into structured tools using native type hints and docstrings, eliminating the need to write raw JSON schemas manually.
How the @needle.tool Decorator Works
Internally, the decorator leverages schema generation to make functions discoverable by the model. When you decorate a function, Needle calls build_schema to parse the signature and docstring, then attaches the resulting JSON schema to the function object as the _needle_tool attribute.
According to the source code in /needle/agent/tools.py (lines 62-65), this metadata attachment happens immediately at decoration time. The schema captures parameter types, default values, and descriptions extracted from your docstring's Args section, creating a complete specification that compatible LLMs can interpret.
Basic Usage: Creating Your First Tool
Define a tool by decorating a function with standard Python type hints and a Google-style docstring. The decorator introspects these annotations to generate the required schema automatically.
import needle
@needle.tool
def set_thermostat(temperature: int, mode: str = "auto"):
"""Set the thermostat.
Args:
temperature: Desired temperature in Celsius.
mode: Heating mode – one of "heat", "cool", or "auto".
"""
# In a real app this would issue a command to a device.
return {"temperature": temperature, "mode": mode}
The function above becomes a valid tool entry that describes its parameters to the model. For additional constraints, you can use needle.Field (documented in doc/apis.md) to specify requirements like value ranges or enums directly in the function signature.
Registering Tools with the Needle Agent
To make tools available during an agent session, pass them as a list when instantiating the Needle class. The constructor examines each item, extracts the attached _needle_tool schema (or builds one on the fly if provided as a raw dictionary), and stores an internal mapping from tool name to callable.
As implemented in /needle/__init__.py (lines 84-94), this initialization step creates the _functions registry that maps string names to the actual Python callables. This registry enables the runtime to look up and execute the correct function when the model requests a tool call.
# Create an agent bound to the tool
agent = needle.Needle(tools=[set_thermostat])
# Run a query; the model decides whether to call the tool
result = agent.run("Make it 21°C and set cooling mode")
print(result["results"])
# → [{'temperature': 21, 'mode': 'cool'}]
Runtime Execution Flow
During a conversation, the Needle agent presents the registered tool schemas to the model within the system context. When the model determines it needs external data or action, it returns a JSON response containing a function_calls array with the target tool name and populated arguments.
The agent then resolves the call by looking up the name in its _functions mapping and executing the corresponding Python function with the provided arguments. As shown in /needle/__init__.py (lines 107-126), this execution loop handles the tool invocation, feeds the result back into the conversation context, and aggregates final outputs for the user.
Manual Tool Calling with complete()
For lower-level control, you can use the complete() method to handle tool execution manually. This approach exposes the raw function_calls structure, allowing you to inspect the model's intent before invoking the function or to implement custom error handling.
response = agent.complete("Dim the living room lights to 30%")
if response["type"] == "call":
# The model chose a tool; extract the call details
call = response["function_calls"][0]
tool_fn = agent._functions[call["name"]]
tool_result = tool_fn(**call["arguments"])
# Feed the result back to continue the dialogue
import json
next_resp = agent.complete(json.dumps([tool_result]))
This pattern accesses the internal _functions dictionary (populated during initialization) to retrieve the callable by name, then dispatches it with the keyword arguments supplied by the model.
Summary
- The
@needle.tooldecorator attaches a JSON schema (generated bybuild_schema) to functions as the_needle_toolattribute in/needle/agent/tools.py. - Tool registration occurs in
Needle.__init__at lines 84-94 of/needle/__init__.py, which builds a mapping from tool names to callables. - At runtime, the agent presents these schemas to the model and executes requested calls by looking up functions in the
_functionsregistry (lines 107-126). - You can rely on Python type hints and docstrings for automatic schema generation, or manually handle tool calls via the
complete()method for advanced workflows.
Frequently Asked Questions
What is the _needle_tool attribute used for?
The _needle_tool attribute stores the generated JSON schema representation of your function. Needle uses this metadata internally to register the tool when you pass the function to the Needle constructor, ensuring the model receives a complete description of the function's parameters and return type.
Can I use the @needle.tool decorator without type hints?
While the decorator may still function, omitting type hints results in an incomplete JSON schema that limits the model's ability to provide correct arguments. The build_schema utility relies on Python annotations to generate accurate type information, so adding type hints is strongly recommended for reliable tool calling performance.
How does Needle handle multiple tool calls in a single response?
According to the execution loop in /needle/__init__.py (lines 107-126), Needle processes the function_calls array sequentially. It iterates through each call object, resolves the corresponding function from the _functions mapping, executes it with the parsed arguments, and aggregates the results before returning the final output to the user or continuing the conversation.
Is it possible to define tools without using the decorator?
Yes. You can supply raw JSON schema dictionaries directly to the tools parameter when creating a Needle instance. However, using the @needle.tool decorator provides a type-safe, maintainable approach that keeps your Python function definitions and their LLM-facing schemas synchronized automatically.
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 →