# How to Perform Structured Extraction Using Needle: A Complete Guide

> Learn structured extraction with Needle. Use the extract function and Pydantic models for one-shot data retrieval from text. Get typed objects easily.

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

---

**Use the `extract` function from [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) to perform one-shot structured extraction by passing your text and a Pydantic model or dict schema; the function creates a temporary agent, forces the LLM to emit a matching function call, and returns a typed object.**

Needle provides a lightweight, purpose-built API for **structured extraction** that eliminates complex prompt engineering. This guide covers the core `extract` function, its implementation in the `needle` codebase, and practical patterns for extracting typed data from unstructured text.

## The `extract` Function: Core API

The primary entry point for structured extraction is `extract`, defined at **lines 79‑92 of [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)**.

```python
def extract(text: str, schema: type | dict, system: str | None = None,
            max_new_tokens: int = 256, weights: str | None = None) -> object:
    """One‑shot structured extraction:
    • `schema` is registered as the sole tool.
    • The shared engine is (re‑)initialised with this schema.
    • Returns a Pydantic model instance when `schema` is a model,
      otherwise a plain dict."""
    _track("extract", {"n_tools": 1, "tuned": bool(weights or _active_weights)})
    agent = Needle(tools=[schema], system=system, weights=weights or _active_weights)
    response = agent._complete(text, max_new_tokens)
    calls = response.get("function_calls") or []
    if not calls:
        return None
    arguments = calls[0].get("arguments") or {}
    return schema(**arguments) if _is_pydantic_model(schema) else arguments

```

The function signature reveals three key behaviors:

- **`schema`** – Accepts either a Pydantic `BaseModel` subclass or a dict mapping field names to types
- **`system`** – Optional system prompt to guide the LLM's extraction behavior
- **`weights`** – Path to custom fine-tuned weights (defaults to bundled weights)

## How Structured Extraction Works Internally

The `extract` function implements a four-step pipeline that guarantees deterministic, schema-compliant output.

### Step 1: Single-Tool Agent Creation

`extract` instantiates a temporary `Needle` agent with **only your schema** as a registered tool. This is implemented in **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)**, where the `Needle` class stores tools and builds runtime descriptors that the LLM can invoke.

```python
agent = Needle(tools=[schema], system=system, weights=weights or _active_weights)

```

By registering the schema as the sole tool, the LLM has no alternative but to emit a function call matching that structure.

### Step 2: Prompt Dispatch via `_complete`

The agent's `_complete` method sends your text to the compiled native runtime. As shown in **[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)**, this calls the underlying `_lib().needle_complete` function:

```python
response = agent._complete(text, max_new_tokens)

```

### Step 3: Function-Call Extraction

The LLM returns a JSON-encoded response containing function calls. The `extract` function pulls the first call from `response.get("function_calls")` at **lines 88‑90**:

```python
calls = response.get("function_calls") or []
if not calls:
    return None
arguments = calls[0].get("arguments") or {}

```

### Step 4: Type-Safe Deserialization

Finally, arguments are mapped onto your schema. At **lines 91‑92**, the code checks whether the schema is a Pydantic model:

```python
return schema(**arguments) if _is_pydantic_model(schema) else arguments

```

- **Pydantic models** → Instantiated and validated automatically
- **Dict schemas** → Raw dict returned for flexibility

## Practical Examples

### Simple Dict-Based Extraction

For quick prototyping, pass a dict describing expected fields and types:

```python
from needle import extract

schema = {"city": str, "country": str}

result = extract("The capital of France is Paris, France.", schema)
print(result)

# → {'city': 'Paris', 'country': 'France'}

```

### Pydantic Model Extraction (Recommended)

For production code, use Pydantic models to get **validation**, **type checking**, and **IDE support**:

```python
from pydantic import BaseModel
from needle import extract

class WeatherReport(BaseModel):
    city: str
    temperature_c: float
    condition: str

text = "Weather in Berlin: 23.5°C, partly cloudy."
report = extract(text, WeatherReport)

print(report)

# → WeatherReport(city='Berlin', temperature_c=23.5, condition='partly cloudy')

print(report.dict())

# → {'city': 'Berlin', 'temperature_c': 23.5, 'condition': 'partly cloudy'}

```

### Multi-Tool Workflows with Full Agent

When you need extraction alongside other capabilities, use the `Needle` class directly. This pattern from **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** lets you combine your schema with custom tools:

```python
from needle import Needle, tool
from pydantic import BaseModel
from typing import Annotated

@tool
def log(message: Annotated[str, "Log message to output"]):
    print("[LOG]", message)

class Contact(BaseModel):
    name: str
    email: str

agent = Needle(tools=[Contact, log])

# The LLM can both extract structured data AND invoke your custom tools

response = agent.run("Send an email to John Doe <john@doe.com>")

```

The `agent.run` method returns a response containing both the extracted `Contact` model and any `log` calls triggered during processing.

## Configuration Options

| Parameter | Default | Purpose |
|-----------|---------|---------|
| `system` | `None` | System prompt guiding extraction style |
| `max_new_tokens` | `256` | Maximum tokens for LLM response |
| `weights` | `None` | Path to custom fine-tuned weights |

Pass `weights` to use a domain-specific fine-tuned model:

```python
from needle import extract

class MedicalTerm(BaseModel):
    term: str
    icd10_code: str

result = extract(
    "Patient diagnosed with Type 2 diabetes mellitus",
    MedicalTerm,
    weights="/path/to/medical-tuned-weights.safetensors"
)

```

## Key Source Files

Understanding these files deepens your ability to debug and extend Needle's extraction capabilities:

- **[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)** – Public API surface including `extract` and telemetry tracking via `_track`
- **[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** – Core `Needle` class with tool registration and runtime bindings
- **[`needle/model/run.py`](https://github.com/cactus-compute/needle/blob/main/needle/model/run.py)** – Low-level wrappers for LLM prompt execution
- **[`tests/test_inference.py`](https://github.com/cactus-compute/needle/blob/main/tests/test_inference.py)** – Reference implementations demonstrating `Contact` model extraction

## Summary

- **`extract`** in [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py) provides the fastest path to structured extraction—one function call with your schema and text
- **Single-tool agent architecture** guarantees schema compliance by eliminating alternative function choices
- **Pydantic models** give you automatic validation, IDE autocomplete, and clean `.dict()` serialization
- **`Needle` class** unlocks multi-tool workflows when you need extraction plus custom actions
- **Custom weights** support domain-specific fine-tuning via the `weights` parameter

## Frequently Asked Questions

### What happens if the LLM fails to return a function call?

The `extract` function returns `None` when `response.get("function_calls")` is empty. You should handle this case in production code:

```python
result = extract(text, MySchema)
if result is None:
    # Fall back to retry with different prompt or raise error

    pass

```

This behavior is implemented at **line 89 of [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)**.

### Can I use nested Pydantic models for complex schemas?

Yes. The `_is_pydantic_model` check and `schema(**arguments)` instantiation support arbitrarily nested Pydantic models. The LLM receives a JSON Schema representation of your full model hierarchy, and nested structures deserialize automatically.

### How does `extract` differ from using `Needle` directly?

`extract` creates a **temporary, single-purpose agent** with your schema as the only tool, optimized for one-shot extraction. The `Needle` class supports **persistent agents with multiple tools**, conversation history, and repeated interactions. Use `extract` for simple data extraction; use `Needle` for interactive agents or multi-step workflows.

### Does structured extraction work with custom fine-tuned models?

Yes. Pass the path to your weights via the `weights` parameter in either `extract` or `Needle`. The library reinitializes the engine with your weights and tracks fine-tuned usage via `_track("extract", {"tuned": True})` as shown at **line 84 of [`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)**.