# How to Use `agent.complete()` for One-Shot Tool Calls in Needle

> Learn how to use agent.complete() for one-shot tool calls in Needle. Get structured JSON responses from your native engine with a single inference request.

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

---

**`agent.complete()` is the primary entry point for single inference requests in Needle, allowing the native engine to optionally invoke registered tools once and return a structured JSON response with no automatic follow-up loops.**

Needle is a lightweight Python SDK from **cactus-compute/needle** that wraps a native inference engine. The `complete()` method handles basic text generation, audio processing, and—crucially—**one-shot tool calls** where the model selects and describes a tool invocation in a single request. This article explains how tool registration, native engine binding, and response parsing work together to enable synchronous function calling.

---

## Tool Registration with `@tool` and `Needle(tools=...)`

Before `complete()` can invoke any function, you must register tools during agent initialization.

**The `@tool` decorator** (in [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py), lines 73–76) attaches a `_needle_tool` attribute containing a JSON schema generated by `build_schema`. This metadata is later serialized by `_resolve()` (lines 96–107 in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)) into the format expected by the native engine.

Tools can be:

- **Decorator-wrapped functions** (shown below)
- **Pydantic models** with callable semantics
- **Plain callables** that pass schema introspection

```python
from needle import Needle, tool

@tool
def add(a: int, b: int) -> int:
    """Return the sum of *a* and *b*."""
    return a + b

# Register at initialization

agent = Needle(tools=[add])

```

The `tools` argument accepts a list; multiple tools are supported, though the engine selects **at most one** per `complete()` call in one-shot mode.

---

## Understanding the `agent.complete()` Method Signature

The public `complete()` method (lines 15–22 of [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)) sanitizes inputs and delegates to `_complete()`:

```python
def complete(self, text: str = "", max_new_tokens: int = 256,
             audio=None, audio_format="wav", sample_rate=0,
             channels=1) -> dict:
    _track("complete", self._track_props())
    payload = _prepare_audio(audio, audio_format, sample_rate, channels)
    return self._complete(text, max_new_tokens, payload)

```

| Parameter | Purpose |
|-----------|---------|
| `text` | Primary prompt string; can be empty if `audio` is provided |
| `max_new_tokens` | Hard limit on output token count |
| `audio` | File path, bytes, or buffer for audio inputs (Needle 3 models) |
| `audio_format` | Container format: `"wav"`, `"mp3"`, etc. |
| `sample_rate`, `channels` | Audio metadata for proper decoding |

Return value is always a **Python dictionary** parsed from the native engine's JSON response.

---

## Native Engine Invocation and One-Shot Semantics

Inside `_complete()` (lines 24–40), the method binds the shared library via `ctypes` if not already cached, then invokes `needle_complete` through one of two signatures:

- **Generation 2 models**: Direct call to `lib.needle_complete`
- **Generation 3 models**: Audio-aware extended signature

The native function writes a JSON string into a pre-allocated buffer. On any non-zero status code, `RuntimeError` is raised with the engine's error message.

**Critical distinction from `run()`**: `complete()` performs **exactly one inference round**. There is no implicit loop that:
- Executes the selected tool
- Feeds results back to the model
- Requests a follow-up completion

This "one-shot" behavior makes `complete()` ideal for **fire-and-forget commands** where your application handles tool execution separately.

---

## Parsing Tool Call Responses

When the engine selects a registered tool, the JSON envelope contains a `function_calls` array:

```python
response = agent.complete("What is 7 plus 5?")
print(response)

```

Example output:

```json
{
  "type": "call",
  "function_calls": [
    {"name": "add", "arguments": {"a": 7, "b": 5}}
  ]
}

```

Your application extracts `name` and `arguments`, executes the corresponding Python function, and decides whether to:
- Return results to the user directly
- Issue a second `complete()` call with tool results appended

For **pure text generation** without tools, the envelope contains a `text` field instead.

---

## Complete Code Examples for One-Shot Tool Calls

### Example 1: Arithmetic Tool Invocation

```python
from needle import Needle, tool

@tool
def add(a: int, b: int) -> int:
    """Return the sum of *a* and *b*."""
    return a + b

agent = Needle(tools=[add])
response = agent.complete("What is 7 plus 5?")

# Extract and execute manually

if response.get("type") == "call":
    call = response["function_calls"][0]
    if call["name"] == "add":
        result = add(**call["arguments"])
        print(f"Result: {result}")  # => 12

```

### Example 2: Audio Transcription with Tool Selection

```python
from needle import Needle, tool

@tool
def transcribe(audio: str) -> str:
    """Return textual transcription of the provided audio file."""
    return "placeholder"  # Engine overrides in practice

agent = Needle(tools=[transcribe])

response = agent.complete(
    "Transcribe this recording",
    audio="meeting.wav",
    audio_format="wav",
    max_new_tokens=256,
)

print(response["function_calls"][0]["name"])  # => "transcribe"

```

### Example 3: Plain Text Generation (No Tools)

```python
agent = Needle()
response = agent.complete("Explain quantum computing in one sentence.")
print(response["text"])

```

---

## Key Source Files Reference

| File | Purpose | Lines of Interest |
|------|---------|-----------------|
| [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) | `Needle` class, `complete()`, `_complete()`, `_resolve()` | 15–22 (public API), 24–40 (native binding), 96–107 (schema resolution) |
| [`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py) | `@tool` decorator, `build_schema`, Pydantic support | 73–76 (decorator implementation) |
| [`needle/_worker.py`](https://github.com/cactus-compute/needle/blob/main/needle/_worker.py) | Thin wrapper for native `needle_complete` in fine-tuned deployments | Entire module |
| [`tests/test_worker.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_worker.py) | Validation suite for one-shot `complete` behavior and tool invocation | Test cases demonstrating expected JSON envelopes |

---

## Summary

- `agent.complete()` in **cactus-compute/needle** executes **single-round inference** with optional tool selection, returning a JSON envelope the caller must interpret.
- Tools are registered via `Needle(tools=[...])` after decoration with `@tool`, which attaches JSON schemas via `_needle_tool`.
- The native engine is invoked through `_complete()` in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), with generation-aware signatures for text-only or audio-capable models.
- One-shot semantics mean **no automatic tool execution or re-prompting**—your application receives the tool call description and handles fulfillment.
- For multi-turn conversations with automatic tool looping, use `agent.run()` instead.

---

## Frequently Asked Questions

### What is the difference between `complete()` and `run()` in Needle?

`complete()` performs a **single inference request** and returns immediately, making it suitable for simple queries or when your application manages tool execution externally. `run()` implements an **agentic loop** that automatically executes selected tools, appends results to context, and re-prompts the model until completion—useful for complex multi-step tasks.

### Can `complete()` execute the selected tool automatically?

No. As implemented in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py), `complete()` delegates to the native `needle_complete` function and returns the raw JSON envelope. Your application must parse `function_calls[0]["name"]` and `function_calls[0]["arguments"]`, then invoke the corresponding Python callable. This design keeps the core SDK lightweight and deterministic.

### How do I pass multiple tools to `agent.complete()`?

Initialize the agent with a list: `Needle(tools=[add, subtract, multiply])`. The engine will select **zero or one** tool per call based on prompt relevance. Multiple simultaneous tool calls in one request are not supported in one-shot mode; use sequential `complete()` calls or switch to `run()` for dependent operations.

### What audio formats does `complete()` support?

According to the `_prepare_audio` helper used in `complete()`, the SDK accepts `audio_format="wav"` natively, with other formats (MP3, FLAC, OGG) potentially supported depending on your Needle engine version and shared library compilation. Always provide `sample_rate` and `channels` explicitly for non-WAV inputs to ensure proper buffer preparation.