How to Declare a Tool Using the `@needle.tool` Decorator in Needle

Use the @needle.tool decorator on any Python function with type hints to register it as an LLM-exposed tool that includes automatic JSON-schema generation.

The Needle framework provides a lightweight decorator-based system for turning ordinary Python functions into tools that LLM agents can invoke. By applying @needle.tool to your function, you enable automatic schema generation, runtime discovery, and first-class integration with the agent's tool-calling API—all without manual boilerplate.

How the @needle.tool Decorator Works

The decorator performs three key operations at decoration time, as implemented in needle/agent/tools.py (line 169 and surrounding code):

  1. Wraps the function – creates a thin wrapper that forwards all arguments unchanged to the original callable.

  2. Generates a JSON schema – the internal build_schema(fn) function inspects Python type hints and the docstring to produce a structured schema describing parameters, return types, and descriptions.

  3. Stores metadata – attaches the schema to the function as _needle_tool, a hidden attribute that the runtime uses for discovery.

At agent startup, needle/__init__.py (line 109) scans for all callables with the _needle_tool attribute and registers them with the LLM's tool-calling interface.

Declaring Your First Tool

Basic Function Decoration

Any function with type-annotated parameters and a docstring can become a tool:

import needle

@needle.tool
def greet(name: str) -> str:
    """Return a friendly greeting."""
    return f"Hello, {name}!"

The decorator immediately attaches the generated schema:

>>> greet._needle_tool
{
  "name": "greet",
  "description": "Return a friendly greeting.",
  "parameters": {
    "type": "object",
    "properties": {
      "name": {"type": "string", "description": "Name of the person to greet"}
    },
    "required": ["name"]
  }
}

Tools with Optional Parameters

Default values are automatically captured in the schema:

@needle.tool
def get_weather(city: str, unit: str = "celsius") -> dict:
    """Fetch current weather for *city*."""
    # API implementation here

    return {"city": city, "temp": 22, "unit": unit}

The generated schema marks unit as non-required and includes its default, letting the LLM understand when parameters can be omitted.

Real-World Usage in Environments

Built-in Needle environments demonstrate @needle.tool in practice. In needle/environments/smart_home.py, tools expose device control capabilities:


# needle/environments/smart_home.py

@needle.tool
def turn_on_light(room: str) -> str:
    """Turn on the light in the specified room."""
    return f"Light in {room} turned on."

These environment tools are automatically collected into the TOOLS list at import time (verified in tests/test_environments.py, line 17), making them available to any agent running in that environment context.

Schema Generation Details

The build_schema function (in needle/agent/tools.py, line 169) handles:

  • Type mapping – converts Python types (str, int, float, bool, list, dict) to JSON Schema types.
  • Docstring parsing – extracts parameter descriptions from Google-style or plain docstrings.
  • Return type annotation – captures the return type for LLM context, though currently informational.

The decorator returns the original function object, so decorated tools remain fully callable in standard Python code:


# Direct Python call works normally

result = greet("Alice")  # "Hello, Alice!"

# LLM call uses the same function via tool-calling API

Key Source Files

File Purpose
needle/agent/tools.py Core @needle.tool decorator and build_schema implementation (line 169)
needle/__init__.py Runtime tool discovery via _needle_tool attribute scanning (line 109)
needle/environments/*.py Production examples: smart_home.py, wearable.py
tests/test_tools.py Unit tests validating schema generation (line 20)

Summary

  • Apply @needle.tool to any type-annotated function to register it as an LLM tool.
  • The decorator automatically generates JSON schemas from type hints and docstrings, storing them in _needle_tool.
  • Registered tools are discovered at runtime in needle/__init__.py and exposed through the agent's tool-calling API.
  • Decorated functions remain ordinary Python callables—no runtime overhead or calling convention changes.

Frequently Asked Questions

What happens if I forget type hints?

The build_schema function in needle/agent/tools.py relies on inspect.get_annotations to extract type information. Without hints, parameter types default to any in the JSON schema, reducing the LLM's ability to generate correct call arguments. Always include type hints for reliable tool behavior.

Can I use @needle.tool on class methods?

Currently, the decorator is designed for module-level functions. For class-based tools, define them as static methods or module functions that accept the instance state as an explicit parameter. The test suite in tests/test_tools.py focuses on function-based usage.

How do I inspect a tool's schema after decoration?

Access the _needle_tool attribute directly on the decorated function: my_function._needle_tool. This returns the complete JSON schema dictionary that the LLM receives during tool-calling negotiations.

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 →