home
diamond Go Premium
Data Engineering Path  ·  Data & AI

Structured Outputs and JSON Enforcement

In automated data pipelines, an LLM completion is almost never consumed directly by a human. It is consumed by downstream code: Airflow task operators, PySpark DataFrames, or database insertion cursors.

If an LLM returns:

Here is the JSON you requested:
```json
{"status": "SUCCESS"}

I hope this helps!

Your downstream JSON parser (`json.loads()`) will throw a `JSONDecodeError` and crash the pipeline.

**Structured Outputs** is the engineering discipline of forcing LLMs to return 100% syntactically valid JSON conforming strictly to a pre-defined schema.

---

## 1. The 3 Approaches to JSON Generation

```mermaid
flowchart TD
    subgraph Approaches["Approaches to Structured Data"]
        PromptOnly["1. Prompt-Only ('Please output JSON')<br/>❌ 5-15% failure rate (chatty headers, trailing commas)"]
        JSONMode["2. Native JSON Mode (OpenAI / Gemini)<br/>⚠️ Guarantees valid JSON syntax, but may miss specific schema keys"]
        Constrained["3. Strict Schemas / Constrained Logit Masking (Pydantic / Outlines)<br/>✅ 100% Guaranteed JSON matching exact types & required keys"]

        PromptOnly --> JSONMode --> Constrained
    end

    style PromptOnly fill:#991b1b,color:#fff
    style JSONMode fill:#d97706,color:#fff
    style Constrained fill:#047857,color:#fff

2. Constrained Decoding at the Logits Level

How do frontier model APIs guarantee valid JSON without post-processing?

The breakthrough is Logits Masking via Context-Free Grammars (CFG / GBNF):

sequenceDiagram
    participant LLM as Model Logits Core
    participant Masker as Grammar Masker (Outlines/GBNF)
    participant Output as Generated Stream

    LLM->>Masker: Raw logits over 128k vocabulary
    Masker->>Masker: Schema rule: Expected string quote '"'
    Masker->>LLM: Mask all tokens EXCEPT quotes to -infinity (-inf)
    LLM->>Output: Emits '"' with 100% certainty

    Masker->>Masker: Schema rule: Expected key name 'user_id'
    Masker->>LLM: Mask all tokens EXCEPT 'user_id'
    LLM->>Output: Emits 'user_id'

At each token generation step, the grammar engine evaluates the current JSON parse state. Any vocabulary token that would produce invalid JSON is assigned a probability of zero ($-\infty$ logit). It is physically impossible for the model to emit a syntax error!


3. Defining Schemas with Pydantic

In Python data engineering, Pydantic is the gold standard for defining structured models:

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

class SeverityLevel(str, Enum):
    LOW = "LOW"
    MEDIUM = "MEDIUM"
    HIGH = "HIGH"
    CRITICAL = "CRITICAL"

class DataPipelineError(BaseModel):
    task_id: str = Field(description="Name of the failed Airflow task or Spark job")
    root_cause_summary: str = Field(description="One-sentence technical summary of the failure")
    severity: SeverityLevel = Field(description="Impact level on downstream SLAs")
    affected_tables: List[str] = Field(description="List of target tables that were not updated")
    suggested_fix_sql: Optional[str] = Field(description="Remediation SQL query if applicable", default=None)
    retry_recommended: bool = Field(description="Whether transient failure makes retry viable")

4. Production Robust Extraction Pattern with Self-Healing

Even with structured modes, production ETL pipelines should implement validation and automatic error reflection:

import json
from pydantic import BaseModel, ValidationError

class ExtractedInvoice(BaseModel):
    vendor_name: str
    invoice_number: str
    total_amount: float
    currency: str
    line_items_count: int

def parse_with_reflection(llm_output: str, max_retries: int = 2) -> ExtractedInvoice:
    """
    Attempts to parse LLM output against Pydantic schema.
    If parsing fails, returns error context for self-healing.
    """
    # Strip accidental markdown code fences
    cleaned = llm_output.strip()
    if cleaned.startswith("```json"):
        cleaned = cleaned[7:]
    if cleaned.startswith("```"):
        cleaned = cleaned[3:]
    if cleaned.endswith("```"):
        cleaned = cleaned[:-3]
    cleaned = cleaned.strip()

    try:
        data = json.loads(cleaned)
        return ExtractedInvoice(**data)
    except (json.JSONDecodeError, ValidationError) as err:
        print(f"[Schema Failure]: {err}")
        # In production: pass 'err' back to LLM in turn 2 to self-correct
        raise err

if __name__ == "__main__":
    sample_llm_response = """
    ```json
    {
        "vendor_name": "Databricks Inc.",
        "invoice_number": "INV-2026-9921",
        "total_amount": 14250.75,
        "currency": "USD",
        "line_items_count": 4
    }
    ```
    """

    invoice = parse_with_reflection(sample_llm_response)
    print("Parsed Pydantic Object Successfully:")
    print(f"Vendor : {invoice.vendor_name}")
    print(f"Amount : {invoice.currency} {invoice.total_amount:,.2f}")

Key Takeaways

  • Never rely on pure prompt instructions for pipeline-critical JSON.
  • Use native Structured Outputs (OpenAI / Gemini response_schema) or constrained logit masking.
  • Define schemas with Pydantic: Provides type-checking, serialization, and clean integration into data engineering codebases.
Find this content helpful? ☕ Buy me a coffee

Entity Details

Create New Item

help

Submit Technical Query

Have a question or run into an issue? Describe it below, upload an optional screenshot, and our engineering team will answer it!

image Attach image (optional)

Submit Feedback

build Free Developer Utility Free Tool
gavel

Privacy & Legal Disclaimer

1. Client-Side Browser Processing

All utility tools on DeepEngineerHub (including Image to PDF, Text Formatters, JSON Converters, and Encryptors) execute 100% locally within your client browser using WebAssembly and JavaScript. No uploaded images, text, or documents are transmitted, collected, or stored on remote servers.

2. Limitation of Liability ("As-Is" Provision)

Tools and services are provided free of charge for convenience and educational purposes "as-is" without warranties of any kind. DeepEngineerHub shall not be held liable for any data loss, formatting inconsistencies, or indirect damages resulting from tool usage.

3. Open Source & Third-Party Software

Certain utilities utilize open-source client libraries (such as jsPDF, Mermaid.js, Pyodide) licensed under MIT, Apache, or BSD open licenses. All intellectual property remains with their respective copyright holders.