How to Define Tools Using the @needle.tool Decorator: A Complete Guide
The @needle.tool decorator exposes any Python function as an LLM-callable tool by automatically generating a JSON schema from the function's type hints, docstring, and Field metadata.
The decorator is implemented in the cactus-compute/needle repository and provides the bridge between Python code and agent-based systems. When applied, it attaches a _needle_tool attribute containing an OpenAI-compatible function schema that Needle's runtime can discover and invoke.
How the @needle.tool Decorator Works
The core implementation lives in needle/agent/tools.py. Understanding the internal mechanics helps you leverage the decorator effectively.
Schema Generation Pipeline
The build_schema(fn) function (lines 20-50) drives the conversion process:
- Signature inspection – extracts parameter names, type hints, and defaults
- Docstring parsing – pulls the function description and parameter documentation
- Field metadata processing – applies validation constraints from
Fieldobjects - Type mapping – converts Python types to JSON Schema via
_json_type(lines 57-86)
The decorator itself is minimal (lines 73-76): it simply calls build_schema(fn) and stores the result as fn._needle_tool.
Optional Parameter Handling
The _is_optional helper (lines 52-55) detects Optional[T] or T | None syntax. Optional parameters are excluded from the required array in the generated schema, matching OpenAI's function calling specification.
Basic Tool Definition
Import needle_tool from needle.agent.tools and apply it to any function:
from needle.agent.tools import needle_tool
@needle_tool
def echo(message: str) -> str:
"""Return the same text that was given."""
return message
The decorator populates echo._needle_tool with this schema:
{
"name": "echo",
"description": "Return the same text that was given.",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string"}
},
"required": ["message"]
}
}
The function name becomes the tool name, the docstring becomes the description, and type hints map to JSON Schema types automatically.
Adding Validation with Field
Use Field objects to specify constraints, defaults, and metadata:
from needle.agent.tools import needle_tool, Field
@needle_tool
def scale(value: float, factor: float = Field(default=1.0, ge=0.0, le=10.0)):
"""Multiply `value` by `factor`."""
return value * factor
The generated schema includes validation rules:
"factor": {
"type": "number",
"default": 1.0,
"minimum": 0.0,
"maximum": 10.0
}
Common Field parameters that translate to JSON Schema:
ge/le→minimum/maximumgt/lt→exclusiveMinimum/exclusiveMaximummin_length/max_length→minLength/maxLengthpattern→patternenum→enum
Using Enums and Literals
The decorator handles both Enum classes and Literal types for constrained choices:
from enum import Enum
from typing import Literal
from needle.agent.tools import needle_tool, Field
class Color(Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
@needle_tool
def paint(surface: str, colour: Color | Literal["yellow"] = Field(default=Color.RED)):
"""Paint a surface with the chosen colour."""
return f"{surface} painted {colour}"
The schema merges all allowed values: ["red", "green", "blue", "yellow"]. Enum members serialize to their values, so Color.RED becomes "red" in the default field.
Registering Multiple Tools from a Module
Organize tools in dedicated modules and register them collectively:
# math_tools.py
from needle.agent.tools import needle_tool
@needle_tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
@needle_tool
def subtract(a: int, b: int) -> int:
"""Subtract b from a."""
return a - b
# main.py
import importlib
from needle.agent import register_tools
module = importlib.import_module("math_tools")
tools = register_tools(module)
register_tools scans the module's globals, identifies all objects with _needle_tool attributes, and returns a list ready for agent initialization.
Accessing and Debugging Tool Schemas
Inspect the generated schema directly for debugging or external integration:
>>> print(add._needle_tool)
{'name': 'add',
'description': 'Add two integers.',
'parameters': {'type': 'object',
'properties': {'a': {'type': 'integer'},
'b': {'type': 'integer'}},
'required': ['a', 'b']}}
This attribute is available immediately after decoration—no runtime registration required.
Real-World Examples in the Repository
The cactus-compute/needle codebase demonstrates production usage:
needle/environments/smart_home.py(lines 19-74) – Multiple device control tools with complex parameter validationneedle/environments/wearable.py(lines 17-54) – Health and activity tracking tools using enums for categorical datatests/test_tools.py– Comprehensive verification of schema structure and edge cases
These files illustrate patterns for naming conventions, docstring formatting, and validation design that align with LLM interpretation.
Summary
@needle_toolattaches a JSON schema to any function, making it discoverable by Needle agents- Schema generation is automatic from type hints, docstrings, and
Fieldmetadata inneedle/agent/tools.py Fieldobjects add validation constraints and defaults that map directly to JSON Schema- Optional parameters (
T | NoneorOptional[T]) are excluded from therequiredarray register_toolscollects all decorated functions from a module for agent initialization_needle_toolattribute provides direct access to the schema for debugging and integration
Frequently Asked Questions
What Python versions support the @needle.tool decorator?
The decorator uses modern Python features including | union syntax and typing.Annotated. According to the source code, it requires Python 3.10 or newer. Earlier versions may work with adjustments to type hint syntax.
Can I use Pydantic models instead of individual parameters?
No—the current implementation in needle/agent/tools.py builds schemas from function signatures directly. For Pydantic-style validation, use Field objects within the function parameters rather than model classes.
How does the decorator handle complex return types?
Return type annotations are captured in the schema but not validated at runtime. The schema's parameters field describes inputs only; return type information is stored separately and may be used by agent orchestration layers for result formatting.
What happens if two tools have the same name?
The schema uses the Python function name as the tool name. Duplicate names in the same scope will overwrite earlier registrations when register_tools scans the module. Use distinct function names or organize tools in separate namespaces.
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 →