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

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#L45-L56), where the function follows a four-step pipeline:

  1. Agent creation — A temporary Needle instance is constructed with tools=[schema].
  2. Prompt executionagent.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.

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.

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.

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

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:

The _is_pydantic_model() utility in 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#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 are designed to work with modern Pydantic versions. Check the repository's 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →