# `run()`, `complete()`, and `extract()` in Needle: A Complete API Comparison

> Compare Needle's run(), complete(), and extract() APIs. Understand which function to use for single-shot generation, iterative tool-calling, and structured data extraction to optimize your LLM interactions.

- Repository: [Cactus Compute, Inc./needle](https://github.com/cactus-compute/needle)
- Tags: api-reference
- Published: 2026-08-14

---

**TL;DR:** Needle provides three distinct APIs—`complete()` for single-shot generation, `run()` for iterative tool-calling loops, and `extract()` for structured data extraction—each designed for different interaction patterns with language models.

The **Needle** library (`cactus-compute/needle`) offers multiple ways to interact with language models depending on whether you need raw completions, multi-step reasoning with tools, or structured data extraction. Understanding the differences between `run()`, `complete()`, and `extract()` helps you choose the right abstraction for your use case.

## `complete()`: Direct Single-Shot Completion

The **`complete()`** method is the thinnest wrapper around Needle's native engine. It performs exactly one model call and returns the raw response envelope.

### How It Works

In [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 103–118), `complete()` serializes your prompt, calls the underlying C library function `needle_complete`, and deserializes the JSON response:

```python
from needle import Needle

agent = Needle()
response = agent.complete("What is the capital of France?")
print(response["text"])  # "Paris is the capital of France."

```

### Return Value

`complete()` returns a **JSON object (dict)** containing:
- The model's generated text
- Optional `function_calls` if your prompt includes tool schemas
- Metadata from the native engine

This method does **not** automatically execute any tool calls—it merely reports them in the response.

## `run()`: Iterative Tool-Calling with Automatic Execution

The **`run()`** method builds on `complete()` to enable **multi-step conversations** where the model can call tools, observe results, and continue reasoning.

### The Execution Loop

As implemented in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 19–40), `run()`:
1. Calls `complete()` once
2. Checks for `function_calls` in the response
3. Executes each call using the Python functions stored in `self._functions`
4. Feeds results back to the model via another `complete()` call
5. Repeats until no calls remain or `max_steps` is reached

```python
from needle import Needle

def get_weather(city: str) -> str:
    return f"Sunny and 75°F in {city}"

def convert_temp(fahrenheit: float) -> float:
    return (fahrenheit - 32) * 5 / 9

agent = Needle(tools=[get_weather, convert_temp])

result = agent.run(
    "What's the weather in Miami in Celsius?",
    max_steps=3
)

print(result["results"])  # [{'city': 'Miami', 'return': 'Sunny and 75°F in Miami'}, {'fahrenheit': 75.0, 'return': 23.888...}]

print(result["text"])     # Final answer from the model

```

### Key Difference from `complete()`

`run()` enriches the return value with a **`"results"` field** containing the complete history of tool executions. This lets you audit what happened during the multi-step process.

## `extract()`: One-Shot Structured Data Extraction

The **`extract()`** method is a **convenience API** for the common pattern of extracting structured data from unstructured text using a Pydantic schema.

### Temporary Agent Pattern

Unlike `run()` and `complete()`, `extract()` does not use a persistent `Needle` instance. According to [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 57–68 and 141–142), it:

1. Creates a **temporary agent** with your schema as the sole available tool
2. Runs a single `complete()` call
3. Instantiates the Pydantic model from the first `function_call`
4. Returns the parsed object directly (or `None` if extraction fails)

```python
from needle import Needle
from pydantic import BaseModel, EmailStr
from datetime import date

class Invoice(BaseModel):
    amount: float
    due_date: date
    vendor_email: EmailStr

text = """
Please pay $1,250.00 to acme-supplies@example.com 
by September 15, 2024 for order #4921.
"""

invoice = Needle().extract(text, Invoice)
print(invoice)

# Invoice(amount=1250.0, due_date=date(2024, 9, 15), vendor_email='acme-supplies@example.com')

```

### Return Type

`extract()` returns either:
- An **instance of your Pydantic model** (successful extraction)
- **`None`** (no matching function call generated)

This eliminates boilerplate compared to manually setting up an agent and parsing `function_calls`.

## API Comparison Summary

| API | Steps | Tool Scope | Return Value | Best For |
|-----|-------|-----------|--------------|----------|
| `complete()` | 1 | Instance-bound tools | Raw JSON envelope | Simple completions, manual tool handling |
| `run()` | 1 to `max_steps` | Instance-bound tools | JSON envelope + `"results"` list | Multi-step reasoning with automatic tool execution |
| `extract()` | 1 | Single temporary schema | Pydantic instance or `None` | Structured data extraction from text |

## Choosing the Right API

Use **`complete()`** when you need maximum control over the interaction flow or when implementing your own tool-handling logic.

Use **`run()`** when you want the model to autonomously solve problems requiring multiple tool calls, with Needle managing the conversation loop.

Use **`extract()`** when your only goal is converting unstructured text into a typed data structure—it's the most concise option for this specific task.

## Summary

- **`complete()`** in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 103–118) provides direct access to the native engine with single-shot execution
- **`run()`** in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 19–40) implements an iterative loop that automatically executes tools and feeds results back to the model
- **`extract()`** in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) (lines 57–68, 141–142) creates a temporary single-tool agent for streamlined structured extraction
- The `run()` vs `complete()` vs `extract()` distinction centers on **iteration depth**, **return type enrichment**, and **tool set flexibility**

## Frequently Asked Questions

### Can I use `extract()` with multiple schemas at once?

No. The `extract()` API is designed for exactly one schema. If you need the model to choose between multiple extraction schemas, create a persistent `Needle` agent with `tools=[schema1, schema2, ...]` and use `run()` or `complete()` to handle the selection logic yourself.

### Does `run()` block until all steps complete?

Yes. The `run()` method is synchronous and blocks until either no more tool calls are generated or `max_steps` is reached. There is no built-in async variant in the current Needle implementation—use `complete()` directly with your own async orchestration if you need non-blocking execution.

### What happens if a tool raises an exception during `run()`?

The exception is caught, recorded in the `"results"` list with an error indication, and fed back to the model as context. The loop continues if steps remain, allowing the model to potentially recover or report the failure. Check `result["results"]` after `run()` completes to inspect any errors.

### Is `extract()` available as a standalone function or only as a method?

Both. You can call `Needle().extract(text, schema)` as an instance method, or import `extract` directly from the `needle` module as a standalone function that internally creates the temporary agent.