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:
- Agent creation — A temporary
Needleinstance is constructed withtools=[schema]. - Prompt execution —
agent.complete()sends your text to the underlying LLM. - Function-call extraction — The response is scanned for
function_calls. - Result conversion — The first call's
argumentsare transformed into output:- If
_is_pydantic_model(schema)returnsTrue, the model is instantiated viaschema(**arguments). - Otherwise, the raw argument dictionary is returned.
- If
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:
- [
needle/__init__.py](https://github.com/cactus-compute/needle/blob/main/needle/__init__.py#L45-L56) — Coreextract()implementation and public API. - [
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) —Needleclass definition used by the extraction pipeline.
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 temporaryNeedleagent with your schema as the sole available tool. - Both module-level
extract()andNeedle.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →