How to Declare Tools and Generate JSON Schemas Using the @needle.tool Decorator in Needle
The @needle.tool decorator automatically transforms ordinary Python functions into LLM-compatible tools by attaching OpenAI-style JSON schemas to the function object at import time.
In the Needle framework, tools are the primary interface between large language models and external environments. Unlike complex registration systems, Needle uses a lightweight decoration approach where the @needle.tool decorator introspects function signatures and generates complete JSON schemas without manual configuration.
How the @needle.tool Decorator Works
The decorator lives in needle/agent/tools.py and operates by wrapping Python functions with metadata that language models can consume.
Decorator Implementation
At lines 173-176 of needle/agent/tools.py, the decorator assigns a schema dictionary to a private attribute on the function object:
def tool(fn: Callable) -> Callable:
fn._needle_tool = build_schema(fn) # attaches schema dict
return fn
This single attribute, _needle_tool, stores the complete OpenAPI-compatible specification that LLM providers require for function calling.
Schema Construction Pipeline
The heavy lifting occurs in build_schema (starting at line 20), which performs runtime introspection to construct the schema:
inspect.signatureextracts the function's parameter names and defaultstyping.get_type_hintsmaps Python annotations to JSON types via internal helpers like_json_type_field_ofprocessestyping.Annotatedmetadata for validation constraints_parse_docparses the docstring for descriptions and per-parameter documentation
The resulting schema follows the OpenAI function-calling format:
{
"name": "my_tool",
"description": "...",
"parameters": {
"type": "object",
"properties": {
"arg1": {"type": "string", "description": "..."},
"arg2": {"type": "integer", "minimum": 0}
},
"required": ["arg1"]
}
}
Required parameters are automatically inferred from the absence of default values and non-optional type annotations.
Declaring Tools in Practice
Any Python function with type hints can become a tool by applying the decorator.
Basic Tool Declaration
Environment modules, such as needle/environments/wearable.py (line 17), demonstrate the pattern:
import needle
@needle.tool
def turn_on_watch() -> str:
"""Turn the wearable device on."""
return "Watch activated"
The decorator executes at import time, immediately populating turn_on_watch._needle_tool with the generated schema.
Runtime Collection via TOOLS Lists
Environment modules expose their capabilities through module-level TOOLS lists. As shown in tests/test_environments.py (lines 17-18), these lists gather decorated functions:
from .wearable import TOOLS as wearable_tools
# Each entry carries the ._needle_tool schema dict ready for LLM calls
for tool_func in wearable_tools:
schema = tool_func._needle_tool
This pattern allows the Needle agent to discover all available capabilities within an environment at runtime.
Complete Working Example
Here is a practical implementation showing type hints, default values, and docstring parsing:
import needle
from typing import Annotated
@needle.tool
def set_thermostat(
temperature: Annotated[int, "Target temperature in Fahrenheit"],
room: str = "living_room"
) -> str:
"""Adjust the thermostat to the specified temperature.
Args:
temperature: The desired temperature between 50 and 90.
room: Which room to adjust (defaults to living room).
"""
return f"Set {room} to {temperature}°F"
# Access the generated schema
print(set_thermostat._needle_tool)
The resulting schema automatically marks temperature as required and room as optional with its default value, while extracting descriptions from both the docstring and Annotated metadata.
Key Implementation Files
Understanding the @needle.tool decorator requires familiarity with these specific source locations:
needle/agent/tools.py– Contains thetooldecorator (lines 173-176), thebuild_schemafunction (lines 20-51), and helper functions_json_type,_field_of, and_parse_docneedle/environments/*.py– Concrete implementations likewearable.pyandsmart_home.pythat demonstrate real-world tool definitionstests/test_tools.py– Unit tests validating schema generation, including assertions thatgreet._needle_tool["name"]matches the function name (line 161)tests/test_environments.py– Integration tests verifying that environmentTOOLSlists correctly expose the_needle_toolattribute (lines 17-18)
Summary
- The
@needle.tooldecorator attaches JSON schemas to functions via the_needle_toolattribute at import time. - Schema generation relies on Python's
inspectandtypingmodules to map function signatures to OpenAI-compatible specifications. - Tools are declared by decorating functions with type hints in environment modules like
needle/environments/wearable.py. - Runtime discovery occurs through module-level
TOOLSlists that aggregate decorated functions. - The implementation in
needle/agent/tools.pysupports advanced features likeAnnotatedmetadata and automatic docstring parsing.
Frequently Asked Questions
What Python types does the @needle.tool decorator support?
The decorator supports standard JSON-serializable types through the _json_type mapping function. This includes str (string), int (integer), float (number), bool (boolean), list (array), and dict (object). Complex types can be specified using typing.Annotated with Pydantic-style Field objects for additional validation constraints.
How does Needle determine which parameters are required in the JSON schema?
Required parameters are inferred automatically by build_schema in needle/agent/tools.py. Any parameter without a default value in the function signature, or typed as non-optional without None in its annotation, is added to the required array in the generated JSON schema. Optional parameters with defaults are excluded from this list but include the default value in their property definition.
Can I use the @needle.tool decorator on class methods?
The current implementation in needle/agent/tools.py is designed for module-level functions. While the decorator itself will technically attach the _needle_tool attribute to any callable, the schema builder expects a standard function signature. For class methods, you would need to expose them as module-level wrappers or extend the build_schema logic to handle self and cls parameters.
Where can I verify that my tool schema was generated correctly?
The test suite in tests/test_tools.py demonstrates the validation pattern used in Needle. You can inspect the _needle_tool attribute directly on your decorated function to verify the generated schema, or follow the pattern in tests/test_environments.py where the test checks that each tool in an environment's TOOLS list contains the expected schema dictionary with the correct name and parameters structure.
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 →