How to Use the `@needle.tool` Decorator to Define Tools in Needle

The @needle.tool decorator converts any Python function into a tool schema that Needle agents can invoke, automatically generating JSON schema from type hints and docstrings.

The @needle.tool decorator is the primary mechanism for equipping Needle agents with callable capabilities. By applying this decorator to a function, you enable large language models to discover, understand, and execute that function during an agentic session. This article explains how the decorator works under the hood, how to structure your tool functions, and how Needle resolves tool calls at runtime.


What the @needle.tool Decorator Actually Does

When you apply @needle.tool to a function, Needle invokes build_schema to generate a JSON Schema representation of that function's signature. This schema is attached to the function object as the _needle_tool attribute in needle/agent/tools.py【/needle/agent/tools.py#L62-L65】.

The schema captures:

  • Parameter names and types from Python type hints
  • Default values for optional parameters
  • Descriptions from the function's docstring and Args section

This metadata is what the language model sees when deciding which tool to invoke. The decorator itself does not modify the function's behavior—it simply annotates it with machine-readable metadata.


Defining a Tool with Type Hints and Docstrings

Needle follows standard Python conventions. The more precise your type hints and docstrings, the better the model's tool selection becomes.

import needle

@needle.tool
def set_thermostat(temperature: int, mode: str = "auto"):
    """Set the thermostat to a specific temperature and mode.

    Args:
        temperature: Desired temperature in Celsius. Must be between 15 and 30.
        mode: Operating mode — one of "heat", "cool", or "auto".
    """
    return {"temperature": temperature, "mode": mode}

Key requirements for effective tool definitions:

  • Use concrete types (int, str, float, bool) rather than Any
  • Provide detailed docstrings with an Args section explaining each parameter
  • Add constraints via needle.Field for ranges, enums, or patterns when needed (documented in doc/apis.md)

Registering Tools with a Needle Agent

Once decorated, tools are passed to the Needle constructor as a list. The agent extracts schemas from the _needle_tool attribute or builds them on the fly for plain functions.


# Register multiple tools

agent = needle.Needle(tools=[set_thermostat, control_lights, check_weather])

During initialization in needle/__init__.py, the agent walks the tools list and constructs an internal mapping from tool names to callables【/needle/init.py#L84-L94】. This mapping is stored in agent._functions for later lookup.


How Tool Execution Works at Runtime

When agent.run() is called, Needle enters an inference loop:

  1. The model receives all tool schemas in its context
  2. The model either returns a direct answer or emits a function_calls JSON object
  3. If tools are called, Needle looks up each tool name in agent._functions
  4. The callable is executed with the model-provided arguments
  5. Results are fed back to the model for potential follow-up calls
  6. Final aggregated results are returned【/needle/init.py#L107-L126】
result = agent.run("Make it 21°C and set cooling mode")
print(result["results"])

# → [{'temperature': 21, 'mode': 'cool'}]

The loop continues until the model produces a final response without additional tool calls.


Manual Tool Execution with complete()

For finer control, use agent.complete() directly. This exposes the raw response type ("call" vs "text") and lets you handle tool execution manually:

response = agent.complete("Dim the living room lights to 30%")

if response["type"] == "call":
    call = response["function_calls"][0]
    tool_fn = agent._functions[call["name"]]
    result = tool_fn(**call["arguments"])
    
    # Continue the conversation with tool results

    follow_up = agent.complete(f'<tool_response>{result}</tool_response>')

Manual execution is useful when you need to:

  • Intercept or validate arguments before execution
  • Handle tool failures with custom retry logic
  • Log or audit tool calls separately from the main loop

Alternative: Raw JSON Schemas Without the Decorator

The @needle.tool decorator is a convenience, not a requirement. You can supply raw JSON schemas directly to Needle(tools=...). However, the decorator provides type safety, automatic documentation extraction, and maintainability that raw schemas lack.


Summary

  • @needle.tool generates JSON schema from Python functions and attaches it as _needle_tool【/needle/agent/tools.py#L62-L65】
  • Tool registration happens via Needle(tools=[...]), which builds an internal callable mapping【/needle/init.py#L84-L94】
  • Runtime execution involves model-driven tool selection, name-based lookup, and result feedback【/needle/init.py#L107-L126】
  • Manual control is available through complete() for custom execution flows

Frequently Asked Questions

What Python types does @needle.tool support for schema generation?

@needle.tool relies on build_schema in needle/agent/tools.py, which handles standard JSON-encodable types: str, int, float, bool, list, dict, and Optional variants. Complex custom objects require manual schema construction using needle.Field or raw JSON schemas.

Can I use the same decorated function with multiple Needle agents?

Yes. The @needle.tool decorator attaches immutable schema metadata to the function object. You can pass the same function to any number of Needle instances without side effects or re-decoration.

How does Needle handle tool name collisions?

Tool names are derived from the function's __name__. If you pass multiple tools with identical names to Needle(), the later one overwrites the earlier in agent._functions. Use distinct function names or wrap functions with functools.wraps renaming to avoid collisions.

What happens if the model provides invalid arguments to a tool?

Needle does not automatically validate arguments against the schema before calling the function. Invalid types will raise standard Python exceptions (e.g., TypeError). For production systems, add validation inside your tool functions or use needle.Field constraints to guide the model toward valid outputs.

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 →