Technical branch for developers Assemble manually

Structured outputs for business processes

Structured outputs aren’t just “JSON instead of text.” They’re the foundation for building resilient AI agents and integrations. In this article: how to do it right—with examples, pitfalls, and a ready-to-use checklist.

What are structured outputs and why does a business need them?

Structured outputs — This is an LLM response format where the result is presented in a strictly defined structure (usually JSON) that matches a pre-defined schema. Unlike free-form text, such responses can be used directly in code: stored in a database, passed to an API, validated, logged, and automated.

For business, this means:

  • Eliminating “human” parsing (“need to extract the amount from the text”)
  • Guaranteed data structure—even if the model makes a mistake, it returns JSON, not “broken” text.
  • Direct integration with systems: CRM, ERP, backend, workflow engines
  • Automatic validation and retry capability

How it works: pipeline from prompt to JSON

flowchart TD  
    A[User Request] --> B[Prompt with Schema]  
    B --> C[LLM API Call]  
    C --> D{Is Response Valid?}  
    D -->|Yes| E[JSON Object]  
    D -->|No| F[Retry / Fallback]  
    E --> G[Schema Validation]  
    G --> H[Use in Business Logic]

Key point: the schema is defined to API call. This can be a JSON Schema, Pydantic model, or built-in type (e.g., in OpenAI—`response_format`).

Example: from prompt to JSON in Python

Basic example with OpenAI API (via Pydantic):

from pydantic import BaseModel  
from openai import OpenAI  

class InvoiceData(BaseModel):  
    amount: float  
    currency: str = "RUB"  
    invoice_id: str  
    status: "paid" | "unpaid" | "overdue"  

client = OpenAI()  
completion = client.beta.chat.completions.parse(  
    model="gpt-4o-mini",  
    messages=[  
        {"role": "system", "content": "Extract data from the invoice. Use only the information provided in the text."},  
        {"role": "user", "content": "Invoice №INV-2024-089 dated 05/12/2024: amount 45 000 RUB, status — paid."}  
    ],  
    response_format=InvoiceData  
)  

invoice = completion.choices[0].message.parsed  
print(invoice.amount, invoice.status)  # 45000.0, 'paid'

Important: parse() instead of create(), and the model itself guarantees that the response will conform to the schema (or return an error).

Business Case: Automating Incoming Email Processing

Scenario: A client sends an email requesting a change to the payment deadline. You need to:

  1. Extract order ID, new date, reason
  2. Verify that the order exists and is not overdue.
  3. Create a task in Jira / CRM

Without structured outputs: 3 steps—LLM → text → regex → data. With it: 1 step—LLM → JSON → data.

Example schema for the query:

class RequestChange(BaseModel):
    order_id: str
    new_due_date: str  # ISO 8601
    reason: str
    priority: "low" | "medium" | "high"

After that—just call the API, validate, and send the data to the CRM.

Common mistakes and how to avoid them

  • Too complex a scheme → Model “hallucinates” (returns JSON but with false data). Solution: break into subtasks, use step-by-step reasoning.
  • No fallback logic → On error, the pipeline crashes. Solution: Always handle errors. ValidationError and return fallback values (null, default, human review).
  • Type Ignoring"45 000" instead of 45000.0Solution: Use strict validation (Pydantic v2, strict=True).
  • No logging → Impossible to debug why the model “incorrectly” parsed. Solution: Log input, output, and errors (including raw response).

Structured Outputs Implementation Checklist

  • [ ] Schema described as JSON Schema / Pydantic and versioned
  • [ ] Specified in the API call response_format (or equivalent)
  • [ ] There is a handler ValidationError and fallback logic
  • [ ] Logs raw response + parsed object + error (if any)
  • [ ] Conducted test validation on 10–20 real-world examples
  • [ ] SLA defined: time allocated for parsing and retries

Next step: from outputs to agent

Structured outputs — not an endpoint, but the foundation for agents that:

  • Parse input → Make decision → Call tool → Return structured result
  • Can handle errors and retry calls
  • Integrated into queues (e.g., RabbitMQ) for scaling.

Next — in the Telegram channel and in the following articles: how to build an agent with tool-calling, how to test its behavior, and how to make a production-ready workflow.