How to Create a Needle Agent with Custom Tools: A Complete 3-Step Guide
You can create a Needle agent with custom tools by decorating Python functions with @tool from needle/agent/tools.py, then passing them to NeedleAgent.
The Needle framework (cactus-compute/needle) provides a lightweight way to turn ordinary Python functions into LLM-executable tools. This guide walks through the complete workflow using actual source code from the repository.
What the @tool Decorator Does
The @tool decorator in needle/agent/tools.py automates three critical tasks:
- Extracts type hints including
typing.Annotatedmetadata from function signatures - Generates JSON-Schema that conforms to OpenAI's function-calling format (
name,description,parameters) - Attaches schema metadata to the function object via
fn._needle_tool
This eliminates manual schema writing and registration boilerplate. A well-typed, well-documented Python function becomes a fully specified tool automatically.
Step 1: Define Your Custom Tool
Create a standard Python function with type hints and a docstring, then apply the @tool decorator.
# file: my_tools.py
from needle.agent.tools import tool
@tool
def get_weather(city: str) -> str:
"""
Retrieve the current weather for a city.
Parameters
----------
city : str
Name of the city (e.g., "Paris").
Returns
-------
str
Human-readable weather description.
"""
# In production, call a weather API here
return f"The weather in {city} is sunny with a temperature of 23 °C."
The decorator inspects this definition and stores the generated schema at get_weather._needle_tool.
Step 2: Instantiate NeedleAgent with Your Tools
Pass the decorated function (or list of functions) to the agent constructor.
# file: run_agent.py
from needle.agent import NeedleAgent
from my_tools import get_weather
# Build agent with custom tool registration
agent = NeedleAgent(tools=[get_weather])
# The LLM decides when to invoke the tool
response = agent.run("What's the weather like in Tokyo today?")
print(response)
The NeedleAgent class in needle/agent/__init__.py scans the tools list for _needle_tool attributes and builds the complete tool-calling prompt automatically.
Step 3: Combine Multiple Custom Tools
Define additional tools using the same pattern:
# file: more_tools.py
from needle.agent.tools import tool
@tool
def calculate_sum(a: int, b: int) -> int:
"""Return the sum of two integers."""
return a + b
@tool
def echo(message: str) -> str:
"""Return the exact message that was given."""
return message
Then pass all tools to the agent:
# file: demo_multi.py
from needle.agent import NeedleAgent
from more_tools import calculate_sum, echo
agent = NeedleAgent(tools=[calculate_sum, echo])
# Model may use either tool, or sequence multiple calls
print(agent.run("Add 12 and 7, then repeat the result back to me."))
The core inference logic in needle/model/run.py handles function_call routing—matching the LLM's tool selection to your Python implementations and feeding results back to the model.
How Tool Execution Works Under the Hood
When agent.run() processes a prompt:
- Prompt construction:
NeedleAgentbuilds a system prompt including all tool schemas from_needle_toolmetadata - LLM inference: The model receives context with available tools and may output
function_callrequests - Routing:
needle/model/run.pyparses thefunction_call, looks up the matching Python function, and executes it with provided arguments - Response synthesis: Tool outputs are formatted and sent back to the LLM for final answer generation
This closed-loop execution ensures type-safe, sandboxed code execution without manual intervention.
Key Source Files Reference
| File | Purpose | Location |
|---|---|---|
needle/agent/tools.py |
@tool decorator and build_schema() implementation |
needle/agent/tools.py |
needle/agent/__init__.py |
NeedleAgent class and tool registration logic |
needle/agent/__init__.py |
needle/model/run.py |
Core inference and function_call handling |
needle/model/run.py |
needle/cli.py |
CLI entry point that creates NeedleAgent internally |
needle/cli.py |
needle/model/export.py |
Agent export utilities including tool serialization | needle/model/export.py |
Summary
- Decorate any Python function with
@toolfromneedle/agent/tools.pyto generate JSON-Schema automatically - Pass decorated functions to
NeedleAgent(tools=[...])for seamless LLM integration - Execute with
agent.run(prompt)—the framework handles tool selection, calling, and response synthesis - Extend by adding more
@toolfunctions; the architecture scales to arbitrary tool counts
Frequently Asked Questions
What Python types does the @tool decorator support?
The @tool decorator handles standard types (str, int, float, bool, list, dict) and their typing module equivalents. It also extracts metadata from typing.Annotated for enhanced schema descriptions. Complex nested types are converted to JSON-Schema objects according to OpenAI's function-calling specification.
Can I use async functions as Needle tools?
The source code analysis focuses on synchronous functions. Check needle/agent/tools.py for current async support—if the build_schema implementation handles coroutines, async tools may be supported directly. Otherwise, wrap async logic in asyncio.run() within a synchronous @tool function.
How do I debug when the LLM doesn't call my tool?
Verify three things: (1) your function has complete type hints and docstring, (2) the @tool decorator successfully attaches _needle_tool metadata, and (3) the tool name in your prompt matches what the LLM expects. Enable verbose logging in needle/model/run.py to trace prompt construction and function_call detection.
Can I export an agent with its tools for deployment?
Yes—needle/model/export.py provides utilities to serialize a fully configured NeedleAgent including all tool schemas to a JSON package. This enables reproducible agent deployment without recreating tool definitions at runtime.
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 →