Phase 13 - Lesson 04
Saída Estruturada — JSON Schema, Pydantic, Zod, Decodificação Restrita
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
"Ask the model nicely to return JSON" fails 5 to 15 percent of the time, even on frontier models. Structured outputs close that gap with constrained decoding: the model is literally prevented from emitting a token that would violate the schema. OpenAI's strict mode, Anthropic's schema-typed tool use, Gemini's
responseSchema, Pydantic AI'soutput_type, and Zod's.parseare five surface forms of the same idea. This lesson builds the schema validator and the strict-mode contract learners will use for every production extraction pipeline.
Tipo: Build Idiomas: Python (stdlib, subconjunto de JSON Schema 2020-12) Pré-requisitos: Phase 13 · 02 (aprofundamento em chamada de função) Tempo: ~75 minutos
Objetivos de Aprendizado
- Escrever um JSON Schema 2020-12 para um alvo de extração usando as restrições corretas (enum, min/max, required, pattern).
- Explicar por que o strict mode e a decodificação restrita oferecem garantias diferentes de "validar após a geração".
- Distinguir os três modos de falha: erro de parse, violação de schema, recusa do modelo.
- Publicar um pipeline de extração com reparo tipado e tratamento de recusa tipado.
O Problema
Um agente que lê um e-mail de pedido de compra precisa transformar texto livre em {customer, line_items, total_usd}. Três abordagens.
Abordagem um: solicitar JSON via prompt. "Responda em JSON com os campos customer, line_items, total_usd." Funciona de 85 a 95 por cento das vezes em modelos de fronteira. Falha de seis maneiras: chave ausente, vírgula pendente, tipos incorretos, campos alucinados, truncamento no limite de tokens, prosa vazada como "Aqui está seu JSON:".
Abordagem dois: validar após a geração. Gerar livremente, analisar, validar contra o schema, tentar novamente em caso de falha. Confiável, mas caro — você paga por cada tentativa e bugs de truncamento custam um turno extra por ocorrência.
Abordagem três: decodificação restrita. O provedor impõe o schema no momento da decodificação. Tokens inválidos são mascarados da distribuição de amostragem. A saída tem garantia de ser analisada e validada. A falha colapsa em um único modo: recusa (o modelo decide que a entrada não se ajusta ao schema).
Cada provedor de fronteira em 2026 envia alguma forma de abordagem três.
- OpenAI.
response_format: {type: "json_schema", strict: true}maisrefusalna resposta se o modelo recusar. - Anthropic. Imposição de schema em entradas de
tool_use;stop_reason: "refusal"não existe, masend_turnsem chamada de ferramenta é o sinal. - Gemini.
responseSchemano nível da requisição; em 2026, o Gemini envia restrições gramaticais no nível de token para tipos selecionados. - Pydantic AI.
output_type=InvoiceModelemite umRunResultestruturado tipado paraInvoiceModel. - Zod (TypeScript). Parser de tempo de execução que valida a saída do provedor em relação a um schema Zod; combina com o
beta.chat.completions.parseda OpenAI.
O ponto em comum: declarar o schema uma vez, impô-lo de ponta a ponta.
O Conceito
JSON Schema 2020-12 — a língua franca
Cada provedor aceita o JSON Schema 2020-12. Os construtos que você mais usa:
type: um deobject,array,string,number,integer,boolean,null.properties: mapa do nome do campo para o subschema.required: lista de nomes de campos que devem aparecer.enum: conjunto fechado de valores permitidos.minimum/maximum(números),minLength/maxLength/pattern(strings).items: subschema aplicado a cada elemento do array.additionalProperties:falseproíbe campos extras (o padrão varia de acordo com o modo).
O strict mode da OpenAI adiciona três requisitos: cada propriedade deve ser listada em required, additionalProperties: false em todos os lugares e nenhuma $ref não resolvida. Se você violar isso, a API retornará 400 no momento da requisição.
Pydantic, a integração em Python
O Pydantic v2 gera o JSON Schema a partir de modelos em formato de dataclass via model_json_schema(). O Pydantic AI encapsula isso para que você escreva:
class Invoice(BaseModel):
customer: str
line_items: list[LineItem]
total_usd: Decimal
e o framework do agente traduz o schema para o strict mode da OpenAI, input_schema da Anthropic ou responseSchema do Gemini na ponta. A saída do modelo retorna como uma instância tipada de Invoice. Erros de validação lançam um ValidationError com caminhos de erro tipados.
Zod, a integração em TypeScript
Zod (z.object({customer: z.string(), ...})) é o equivalente em TS. O SDK de Node da OpenAI expõe zodResponseFormat(Invoice), que se traduz no payload JSON Schema da API.
Recusas (Refusals)
O strict mode não pode forçar o modelo a responder. Se a entrada não puder ser ajustada ao schema ("o e-mail era um poema, não uma fatura"), o modelo emite um campo refusal contendo o motivo. Seu código deve tratar isso como um resultado de primeira classe, não como uma falha. A recusa também é útil como um sinal de segurança: um modelo solicitado a extrair um número de cartão de crédito de um e-mail de conteúdo protegido retorna uma recusa com o motivo de segurança anexado.
Decodificação restrita no mundo open-source
Implementações de pesos abertos (open-weights) usam três técnicas.
- Decodificação baseada em gramática (
outlines,guidance,lm-format-enforcer): constrói um autômato finito determinístico a partir do schema; a cada passo, mascara os logits dos tokens que violariam a FSM. - Mascaramento de logits com um parser JSON: executa um parser JSON em fluxo contínuo em sincronia com o modelo; a cada passo, calcula o conjunto de próximos tokens válidos.
- Decodificação especulativa com um verificador: um modelo de rascunho barato propõe tokens, e o verificador impõe o schema.
Provedores comerciais escolhem uma dessas técnicas nos bastidores. O estado da arte em 2026 é mais rápido do que a geração de texto comum para saídas estruturadas curtas e tem aproximadamente a mesma velocidade para saídas longas.
Os três modos de falha
- Erro de parse. A saída não é um JSON válido. Não pode acontecer sob o strict mode. Ainda pode acontecer em provedores não estritos.
- Violação de schema. A saída é analisada com sucesso, mas viola o schema. Não pode acontecer sob o strict mode. Comum fora dele.
- Recusa. O modelo recusa a resposta. Deve ser tratada como um resultado tipado.
Estratégia de tentativa de recuperação (Retry strategy)
Quando você está fora do strict mode (uso de ferramentas da Anthropic, OpenAI não estrita, Gemini mais antigo), o padrão de recuperação é:
generate -> parse -> validate -> if fail, inject error and retry, max 3x
Uma nova tentativa geralmente é suficiente. Três tentativas capturam falhas de modelos mais fracos. Além de três é um sinal de um schema ruim: o modelo não consegue satisfazê-lo para algumas entradas, e o prompt ou o schema precisa ser corrigido.
Suporte a modelos pequenos
A decodificação restrita funciona em modelos pequenos. Um modelo aberto de 3B parâmetros com imposição de gramática supera um modelo de 70B parâmetros com prompting puro em tarefas estruturadas. Esta é a principal razão pela qual saídas estruturadas são importantes para produção: elas desacoplam a confiabilidade do tamanho do modelo.
Use Isso
code/main.py envia um validador JSON Schema 2020-12 mínimo em stdlib (types, required, enum, min/max, pattern, items, additionalProperties). Ele envolve um schema de Invoice e executa uma saída falsa de LLM através do validador, demonstrando caminhos de erro de parse, violação de schema e recusa. Substitua a saída falsa por qualquer resposta real do provedor em produção.
O que observar:
- O validador retorna uma lista
[ValidationError]tipada com o caminho e a mensagem. Essa é a estrutura que você deseja expor no prompt de nova tentativa. - O ramo de recusa NÃO realiza novas tentativas. Ele registra o erro e retorna uma recusa tipada. A Phase 14 · 09 usa recusas como um sinal de segurança.
- A verificação de
additionalProperties: falseé acionada na entrada de teste adversarial, mostrando por que o strict mode fecha as portas para campos alucinados.
Envie Isso
Esta lição produz outputs/skill-structured-output-designer.md. Dado um alvo de extração de texto livre (faturas, tickets de suporte, currículos, etc.), a skill produz um JSON Schema 2020-12 que é compatível com strict-mode e um modelo Pydantic correspondente, com tratamento de recusa e nova tentativa tipados esboçados.
Exercícios
Execute
code/main.py. Adicione um quarto caso de teste ondetotal_usdseja um número negativo. Confirme que o validador o rejeita com o caminho de restriçãominimum.Estenda o validador para suportar
oneOfcom um discriminador. O caso comum:line_itemé um produto ou um serviço, marcado porkind. O strict mode tem regras sutis aqui; consulte o guia de saídas estruturadas da OpenAI.Escreva o mesmo schema de fatura como um BaseModel do Pydantic e compare a saída do
model_json_schema()com o seu schema feito à mão. Identifique o único campo que o Pydantic define por padrão e que a versão feita à mão omite.Meça as taxas de recusa. Construa dez entradas que não deveriam ser extraíveis (uma letra de música, uma prova matemática, um e-mail em branco) e execute-as em um provedor real com strict mode. Conte as recusas versus as saídas alucinadas. Esta é a sua verdade básica (ground truth) para novas tentativas cientes de recusa.
Leia o guia de saídas estruturadas da OpenAI de ponta a ponta. Identifique o único construto que ele proíbe explicitamente no strict mode que o JSON Schema comum permite. Em seguida, projete um schema que use o construto proibido de forma não essencial e reatore-o para ser compatível com strict mode.
Key Terms
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| JSON Schema 2020-12 | "A especificação do schema" | Dialeto de rascunho da IETF de schema que todo provedor moderno fala |
| Strict mode | "Schema garantido" | Flag da OpenAI que impõe o schema via decodificação restrita |
| Constrained decoding | "Mascaramento de logit" | Imposição em tempo de decodificação que mascara os próximos tokens inválidos |
| Refusal | "Modelo recusa" | Resultado tipado quando a entrada não cabe no schema |
| Parse error | "JSON inválido" | A saída não foi analisada como JSON; impossível sob strict |
| Schema violation | "Formato incorreto" | Analisado com sucesso, mas violou tipos / obrigatórios / enums / intervalos |
additionalProperties: false |
"Sem extras permitidos" | Proíbe campos desconhecidos; obrigatório no strict da OpenAI |
| Pydantic BaseModel | "Saída tipada" | Classe Python que emite e valida JSON Schema |
| Zod schema | "Tipo de saída em TypeScript" | Schema de tempo de execução do TS para validação de saída do provedor |
| Grammar enforcement | "Decodificação restrita open-source" | Mascaramento de logit baseado em FSM, como em outlines / guidance |
Leituras Adicionais
- OpenAI — Structured outputs — strict mode, recusas e requisitos de schema
- OpenAI — Introducing structured outputs — post de lançamento de agosto de 2024 explicando a garantia de decodificação
- Pydantic AI — Output — ligações output_type tipadas que serializam para cada provedor
- JSON Schema — 2020-12 release notes — a especificação canônica
- Microsoft — Structured outputs in Azure OpenAI — notas de implantação empresarial e ressalvas do strict-mode