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.signature extracts the function's parameter names and defaults
  • typing.get_type_hints maps Python annotations to JSON types via internal helpers like _json_type
  • _field_of processes typing.Annotated metadata for validation constraints
  • _parse_doc parses 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 the tool decorator (lines 173-176), the build_schema function (lines 20-51), and helper functions _json_type, _field_of, and _parse_doc
  • needle/environments/*.py – Concrete implementations like wearable.py and smart_home.py that demonstrate real-world tool definitions
  • tests/test_tools.py – Unit tests validating schema generation, including assertions that greet._needle_tool["name"] matches the function name (line 161)
  • tests/test_environments.py – Integration tests verifying that environment TOOLS lists correctly expose the _needle_tool attribute (lines 17-18)

Summary

  • The @needle.tool decorator attaches JSON schemas to functions via the _needle_tool attribute at import time.
  • Schema generation relies on Python's inspect and typing modules 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 TOOLS lists that aggregate decorated functions.
  • The implementation in needle/agent/tools.py supports advanced features like Annotated metadata 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →