# How to Use needle.extract() with Pydantic Models for Structured LLM Output

> Learn to use needle.extract() to convert unstructured text into validated Pydantic objects. Treat your model as the LLM's only tool for structured output.

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

---

**`needle.extract()` provides a one-shot API that converts unstructured text into validated Pydantic objects by treating your model as the only tool available to the LLM.**

The `extract()` function in the Needle library lets you parse raw text into structured, type-safe data. When you pass a Pydantic model as the schema, the function automatically validates the LLM's output against your model's field definitions—running custom validators, respecting type constraints, and returning a fully instantiated Python object rather than raw JSON.

## How needle.extract() Works Under the Hood

The implementation resides in [[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py#L45-L56), where the function follows a four-step pipeline:

1. **Agent creation** — A temporary `Needle` instance is constructed with `tools=[schema]`.
2. **Prompt execution** — `agent.complete()` sends your text to the underlying LLM.
3. **Function-call extraction** — The response is scanned for `function_calls`.
4. **Result conversion** — The first call's `arguments` are transformed into output:
   - If `_is_pydantic_model(schema)` returns `True`, the model is instantiated via `schema(**arguments)`.
   - Otherwise, the raw argument dictionary is returned.

This design ensures that Pydantic validation rules—type coercion, range constraints, and custom validators—are applied automatically without additional boilerplate.

## Defining Your Extraction Schema

The Pydantic model you define becomes the **contract** between your application and the LLM. Field descriptions help guide extraction, while validators enforce data integrity.

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


class ProductInfo(BaseModel):
    """Schema for extracting product details from unstructured text."""
    name: str = Field(..., description="Name of the product")
    price: float = Field(..., gt=0, description="Price in USD")
    in_stock: bool = Field(..., description="Availability flag")


class CustomerReview(BaseModel):
    """Schema for parsing sentiment and metadata from reviews."""
    rating: int = Field(..., ge=1, le=5, description="Star rating from 1-5")
    reviewer_name: str | None = Field(None, description="Name if mentioned")
    pros: list[str] = Field(default_factory=list, description="Positive aspects")
    cons: list[str] = Field(default_factory=list, description="Negative aspects")

```

## Extracting Data with needle.extract()

Pass your source text and Pydantic model to `extract()`. The function returns a validated instance ready for use.

```python
text = """
The new SmartWidget costs $199.99 and is currently available.
"""

product = extract(text, ProductInfo)

# Access validated fields directly

print(product.name)        # → 'SmartWidget'

print(product.price)       # → 199.99

print(product.in_stock)    # → True

# Convert to dictionary for serialization

print(product.dict())

# → {'name': 'SmartWidget', 'price': 199.99, 'in_stock': True}

```

The `gt=0` constraint on `price` automatically rejects negative values. If the LLM returns invalid data, Pydantic raises a validation error with descriptive context.

## Alternative: Using the Needle Instance Method

You can also invoke `extract()` through a `Needle` client instance. This approach is useful when you need to share configuration across multiple extractions.

```python
from needle import Needle

needle = Needle(api_key="your-key")  # Configure once

product = needle.extract(text, ProductInfo)
review = needle.extract(review_text, CustomerReview)

```

Both the module-level `extract()` function and the `Needle.extract()` method share the same implementation. The module-level helper simply creates a default `Needle` instance internally.

## Dictionary Schemas vs. Pydantic Models

Needle supports two schema types, but Pydantic models provide significant advantages:

| Approach | Validation | Type Safety | IDE Support | Recommended For |
|----------|-----------|-------------|-------------|---------------|
| **Pydantic model** | Full (constraints, custom validators) | Strong | Excellent | Production applications |
| **Plain dictionary** | None (raw JSON returned) | Weak | Limited | Prototyping, dynamic schemas |

### Plain Dictionary Example

```python
schema = {
    "name": {"type": "string"},
    "price": {"type": "number"},
    "in_stock": {"type": "boolean"}
}

result = extract(text, schema)
print(result)  # → {'name': 'SmartWidget', 'price': 199.99, 'in_stock': True}

```

The dictionary schema skips validation entirely. Use Pydantic models when data integrity matters.

## Key Source Files and Utilities

The Pydantic integration relies on utilities defined across several modules:

- **[[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py#L45-L56)** — Core `extract()` implementation and public API.
- **[[`needle/agent/tools.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)](https://github.com/cactus-compute/needle/blob/main/needle/agent/tools.py)** — `_is_pydantic_model()` detection and schema conversion utilities.
- **[[`needle/agent/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py)](https://github.com/cactus-compute/needle/blob/main/needle/agent/__init__.py)** — `Needle` class definition used by the extraction pipeline.

The `_is_pydantic_model()` utility in [`tools.py`](https://github.com/cactus-compute/needle/blob/main/tools.py) checks whether a schema inherits from Pydantic's `BaseModel`, triggering the instantiation path when true.

## Summary

- **`needle.extract()`** converts unstructured text to structured data using your Pydantic model as the extraction schema.
- Pass a **Pydantic model** for automatic validation, type coercion, and IDE-friendly return types.
- Pass a **plain dictionary** for unvalidated raw output when prototyping or working with dynamic structures.
- The implementation in [[`needle/__init__.py`](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py)](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py#L45-L56) uses a temporary `Needle` agent with your schema as the sole available tool.
- Both module-level `extract()` and `Needle.extract()` provide identical functionality.

## Frequently Asked Questions

### What happens if the LLM returns data that fails Pydantic validation?

Pydantic raises a `ValidationError` with detailed context about which fields failed and why. This protects your downstream code from processing malformed data. You can catch this exception and implement retry logic or fallback handling as needed.

### Can I use nested Pydantic models with needle.extract()?

Yes. Needle converts your entire model hierarchy into a tool schema that the LLM can populate. Define nested models normally—Pydantic's recursive instantiation handles the rest. Field descriptions on nested models are preserved and visible to the LLM.

### Does extract() support Pydantic V2?

The Needle library's `_is_pydantic_model()` utility and schema generation in [`tools.py`](https://github.com/cactus-compute/needle/blob/main/tools.py) are designed to work with modern Pydantic versions. Check the repository's [`pyproject.toml`](https://github.com/cactus-compute/needle/blob/main/pyproject.toml) or requirements for the specific Pydantic version pinned in your installation.

### How does needle.extract() compare to manual prompting with JSON output?

**Manual JSON prompting** requires crafting specific instructions, parsing raw strings, and validating manually. **`needle.extract()`** leverages function calling APIs for more reliable structured output, applies validation automatically, and returns native Python objects—reducing boilerplate and error rates significantly.