# How to Create a Needle Agent with Custom Tools: A Complete 3-Step Guide

> Learn to create a Needle agent with custom tools using Python decorators. Follow this 3-step guide to build powerful, personalized AI agents with the cactus-compute/needle repository.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: how-to-guide
- Published: 2026-08-31

---

**You can create a Needle agent with custom tools by decorating Python functions with `@tool` from [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), then passing them to `NeedleAgent`.**

The **Needle** framework (cactus-compute/needle) provides a lightweight way to turn ordinary Python functions into LLM-executable tools. This guide walks through the complete workflow using actual source code from the repository.

---

## What the @tool Decorator Does

The `@tool` decorator in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) automates three critical tasks:

- **Extracts type hints** including `typing.Annotated` metadata from function signatures
- **Generates JSON-Schema** that conforms to OpenAI's function-calling format (`name`, `description`, `parameters`)
- **Attaches schema metadata** to the function object via `fn._needle_tool`

This eliminates manual schema writing and registration boilerplate. A well-typed, well-documented Python function becomes a fully specified tool automatically.

---

## Step 1: Define Your Custom Tool

Create a standard Python function with type hints and a docstring, then apply the `@tool` decorator.

```python

# file: my_tools.py

from needle.agent.tools import tool

@tool
def get_weather(city: str) -> str:
    """
    Retrieve the current weather for a city.

    Parameters
    ----------
    city : str
        Name of the city (e.g., "Paris").

    Returns
    -------
    str
        Human-readable weather description.
    """
    # In production, call a weather API here

    return f"The weather in {city} is sunny with a temperature of 23 °C."

```

The decorator inspects this definition and stores the generated schema at `get_weather._needle_tool`.

---

## Step 2: Instantiate NeedleAgent with Your Tools

Pass the decorated function (or list of functions) to the agent constructor.

```python

# file: run_agent.py

from needle.agent import NeedleAgent
from my_tools import get_weather

# Build agent with custom tool registration

agent = NeedleAgent(tools=[get_weather])

# The LLM decides when to invoke the tool

response = agent.run("What's the weather like in Tokyo today?")
print(response)

```

The `NeedleAgent` class in [`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py) scans the `tools` list for `_needle_tool` attributes and builds the complete tool-calling prompt automatically.

---

## Step 3: Combine Multiple Custom Tools

Define additional tools using the same pattern:

```python

# file: more_tools.py

from needle.agent.tools import tool

@tool
def calculate_sum(a: int, b: int) -> int:
    """Return the sum of two integers."""
    return a + b

@tool
def echo(message: str) -> str:
    """Return the exact message that was given."""
    return message

```

Then pass all tools to the agent:

```python

# file: demo_multi.py

from needle.agent import NeedleAgent
from more_tools import calculate_sum, echo

agent = NeedleAgent(tools=[calculate_sum, echo])

# Model may use either tool, or sequence multiple calls

print(agent.run("Add 12 and 7, then repeat the result back to me."))

```

The core inference logic in [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) handles `function_call` routing—matching the LLM's tool selection to your Python implementations and feeding results back to the model.

---

## How Tool Execution Works Under the Hood

When `agent.run()` processes a prompt:

1. **Prompt construction**: `NeedleAgent` builds a system prompt including all tool schemas from `_needle_tool` metadata
2. **LLM inference**: The model receives context with available tools and may output `function_call` requests
3. **Routing**: [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) parses the `function_call`, looks up the matching Python function, and executes it with provided arguments
4. **Response synthesis**: Tool outputs are formatted and sent back to the LLM for final answer generation

This closed-loop execution ensures type-safe, sandboxed code execution without manual intervention.

---

## Key Source Files Reference

| File | Purpose | Location |
|------|---------|----------|
| [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) | `@tool` decorator and `build_schema()` implementation | [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) |
| [`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py) | `NeedleAgent` class and tool registration logic | [`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py) |
| [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) | Core inference and `function_call` handling | [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) |
| [`needle/cli.py`](https://github.com/cactus-compute/needle/blob/main/needle/cli.py) | CLI entry point that creates `NeedleAgent` internally | [`needle/cli.py`](https://github.com/cactus-compute/needle/blob/main/needle/cli.py) |
| [`needle/model/export.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/export.py) | Agent export utilities including tool serialization | [`needle/model/export.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/export.py) |

---

## Summary

- **Decorate** any Python function with `@tool` from [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) to generate JSON-Schema automatically
- **Pass** decorated functions to `NeedleAgent(tools=[...])` for seamless LLM integration
- **Execute** with `agent.run(prompt)`—the framework handles tool selection, calling, and response synthesis
- **Extend** by adding more `@tool` functions; the architecture scales to arbitrary tool counts

---

## Frequently Asked Questions

### What Python types does the @tool decorator support?

The `@tool` decorator handles standard types (`str`, `int`, `float`, `bool`, `list`, `dict`) and their `typing` module equivalents. It also extracts metadata from `typing.Annotated` for enhanced schema descriptions. Complex nested types are converted to JSON-Schema objects according to OpenAI's function-calling specification.

### Can I use async functions as Needle tools?

The source code analysis focuses on synchronous functions. Check [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) for current async support—if the `build_schema` implementation handles coroutines, async tools may be supported directly. Otherwise, wrap async logic in `asyncio.run()` within a synchronous `@tool` function.

### How do I debug when the LLM doesn't call my tool?

Verify three things: (1) your function has complete type hints and docstring, (2) the `@tool` decorator successfully attaches `_needle_tool` metadata, and (3) the tool name in your prompt matches what the LLM expects. Enable verbose logging in [`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py) to trace prompt construction and `function_call` detection.

### Can I export an agent with its tools for deployment?

Yes—[`needle/model/export.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/export.py) provides utilities to serialize a fully configured `NeedleAgent` including all tool schemas to a JSON package. This enables reproducible agent deployment without recreating tool definitions at runtime.