Using Pydantic and OpenAI for Reliable Structured Outputs

Authors
  • avatar
    Name
    Nino
    Occupation
    Senior Tech Editor

For years, developers building applications on top of Large Language Models (LLMs) have faced a consistent, nagging headache: reliability. You ask an LLM for a JSON object, and it gives you a string that looks like JSON, but perhaps includes a trailing comma, a conversational preamble, or a key that doesn't exist in your schema. We tried regex, we tried json.loads() inside try-except blocks, and we tried complex prompt engineering.

With the introduction of native Structured Outputs by OpenAI, and the seamless integration with Pydantic, that era of fragility is over. By using n1n.ai, developers can access these robust features across various model versions with optimized latency and high-speed throughput. In this guide, we will dive deep into how to stop parsing JSON by hand and start trusting your model's output using Pydantic.

The Problem: The 'Stochastic' Nature of Text

Traditional LLM interactions are text-in, text-out. When you need to integrate an LLM into a software pipeline—say, to extract data from a medical report or generate a configuration file—you need structured data.

Before Structured Outputs, you might have used a prompt like: "Return a JSON object with keys 'name' and 'age'. Do not include any other text."

Even with this instruction, models like GPT-4o would occasionally fail, especially under high load or complex schemas. This forced developers to write 'defensive code' to clean the output. However, by using a specialized API aggregator like n1n.ai, you can ensure that you are hitting the most stable endpoints that support the latest response_format protocols.

Why Pydantic is the Solution

Pydantic is the most widely used data validation library for Python. It allows you to define the shape of your data using standard Python type hints. When paired with OpenAI's API, Pydantic acts as a bridge between the probabilistic world of LLMs and the deterministic world of software engineering.

Key benefits include:

  1. Type Safety: Ensure that a field marked as int is actually an integer.
  2. Validation: Use Pydantic's validators to check if a value falls within a specific range (e.g., age > 0).
  3. Auto-Documentation: The Pydantic model itself generates the JSON Schema that the LLM uses to constrain its output.

Implementation Guide: Step-by-Step

To get started, ensure you have the latest openai and pydantic libraries installed. We recommend using n1n.ai to manage your API keys and monitor usage across different models like GPT-4o or Claude 3.5 Sonnet.

1. Define Your Schema

First, we define a Pydantic model. Let's imagine we are building a tool to extract information from movie reviews.

from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum

class Sentiment(str, Enum):
    positive = "positive"
    negative = "negative"
    neutral = "neutral"

class MovieExtraction(BaseModel):
    title: str = Field(description="The name of the movie")
    rating: int = Field(description="Rating out of 10", ge=0, le=10)
    sentiment: Sentiment
    actors: List[str] = Field(default_factory=list)
    summary: Optional[str] = None

2. The API Call

OpenAI's SDK now includes a beta.chat.completions.parse method that handles the serialization of the Pydantic model into a JSON schema and the subsequent deserialization of the response back into a Pydantic object.

from openai import OpenAI

client = OpenAI(api_key="YOUR_N1N_API_KEY", base_url="https://api.n1n.ai/v1")

completion = client.beta.chat.completions.parse(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "system", "content": "Extract the movie details from the provided text."},
        {"role": "user", "content": "I watched Inception last night. It was amazing! 10/10. Leonardo DiCaprio was brilliant."},
    ],
    response_format=MovieExtraction,
)

movie_data = completion.choices[0].message.parsed
print(f"Title: {movie_data.title}")
print(f"Rating: {movie_data.rating}")

Advanced Pattern: Handling Nested Objects and Enums

One of the most powerful features of this approach is the ability to handle nested data structures. If you are building a RAG (Retrieval-Augmented Generation) system, you might want the model to return a list of citations alongside the answer.

class Citation(BaseModel):
    source_id: str
    quote: str

class AnswerWithCitations(BaseModel):
    answer: str
    citations: List[Citation]

By passing AnswerWithCitations to the response_format, the LLM is mathematically constrained by the underlying engine to only produce tokens that satisfy this specific JSON structure. This is not just a hint; it is a hard constraint applied during the sampling process.

Comparison: Manual vs. Structured

FeatureManual ParsingFunction CallingStructured Outputs (Pydantic)
ReliabilityLow (Regex/JSON.loads)MediumHigh (Schema Constrained)
ComplexityHigh (Custom logic)MediumLow (Native Pydantic)
Type SafetyNoneLimitedFull Python Type Hints
PerformanceVariableGoodExcellent

Pro Tips for Production

  1. Use Descriptions: The Field(description="...") parameter is not just for documentation. It is sent to the LLM as part of the schema, acting as a micro-prompt for that specific field. Use it to guide the model on nuances.
  2. Handle Refusals: Sometimes the model may refuse to answer (e.g., for safety reasons). Always check completion.choices[0].message.refusal before accessing the parsed data.
  3. Strict Mode: OpenAI's Structured Outputs require strict=True in the background. This means all fields must be present in the output, and additionalProperties are disallowed. This is why Pydantic is so useful—it enforces this strictness at the code level.
  4. Latency Considerations: While structured output ensures accuracy, it can slightly increase time-to-first-token because the model needs to process the schema. Using a high-performance provider like n1n.ai helps mitigate this by routing requests through the fastest available nodes.

Conclusion

The combination of Pydantic and OpenAI represents a massive leap forward in LLM developer experience. It moves the burden of validation from the application logic to the model's inference engine itself. This leads to cleaner code, fewer runtime errors, and a more predictable user experience.

Whether you are building complex agents or simple data extraction pipelines, the structured output pattern is the new gold standard. To start experimenting with these features using the most reliable LLM infrastructure, check out n1n.ai.

Get a free API key at n1n.ai