Phase 05 - Lesson 20
Saídas Estruturadas e Decodificação Restrita
Peça JSON a um LLM. Receba JSON na maioria das vezes. Em produção, "maioria" é o problema. A decodificação restrita transforma "maioria" em "sempre" ao editar os logits antes da amostragem.
Tipo: Build Linguagens: Python Pré-requisitos: Fase 5 · 17 (Chatbots), Fase 5 · 19 (Tokenização de Subpalavras) Tempo: ~60 minutos
O Problema
Um classificador solicita a um LLM: "Retorne um de {positive, negative, neutral}." O modelo retorna "O sentimento é positivo — esta avaliação é extremamente favorável porque o cliente afirma explicitamente que ...". Seu parser quebra. O F1 do seu classificador é 0.0.
Geração livre não é um contrato. É uma sugestão. Um sistema de produção precisa de um contrato.
Existem três camadas em 2026.
- Prompting. Peça com educação. "Retorne apenas o objeto JSON." Funciona ~80% em modelos de fronteira, menos em modelos menores.
- APIs nativas de saída estruturada.
response_formatda OpenAI, uso de ferramentas da Anthropic, modo JSON do Gemini. Confiável em schemas suportados. Preso ao fornecedor. - Decodificação restrita. Modifica os logits a cada passo de geração de modo que o modelo não possa emitir tokens inválidos. 100% válido por construção. Funciona em qualquer modelo local.
Esta lição constrói intuição para as três e indica quando recorrer a cada uma.
O Conceito
Como a decodificação restrita funciona. A cada passo de geração, o LLM produz um vetor de logits sobre todo o vocabulário (~100k tokens). Um processador de logits fica entre o modelo e o amostrador. Ele calcula quais tokens são válidos dada a posição atual na gramática alvo — JSON Schema, regex, gramática livre de contexto — e define os logits de todos os tokens inválidos como infinito negativo. O softmax sobre os logits restantes coloca massa de probabilidade apenas em continuações válidas.
Implementações em 2026:
- Outlines. Compila JSON Schema ou regex em uma máquina de estados finitos. Cada token recebe uma busca O(1) do próximo token válido. Baseado em FSM, então schemas recursivos precisam ser achatados.
- XGrammar / llguidance. Motores de gramática livre de contexto. Lidam com JSON Schema recursivo. Overhead de decodificação quase nulo. A OpenAI creditou o llguidance em sua implementação de saída estruturada de 2025.
- vLLM guided decoding.
guided_json,guided_regex,guided_choice,guided_grammarintegrados via backends Outlines, XGrammar ou lm-format-enforcer. - Instructor. Wrapper baseado em Pydantic sobre qualquer LLM. Tenta novamente em caso de falha de validação. Multi-fornecedor, mas não modifica logits — ele depende de retentativas + prompts cientes de saída estruturada.
O resultado contraintuitivo
A decodificação restrita é frequentemente mais rápida do que a geração irrestrita. Duas razões. Primeira, ela encolhe o espaço de busca do próximo token. Segunda, implementações inteligentes pulam totalmente a geração de tokens forçados (estrutura fixa como {"name": " — cada byte é determinado).
A armadilha que te custa caro
A ordem dos campos importa. Coloque answer antes de reasoning, e o modelo se compromete com uma resposta antes de pensar. O JSON é válido. A resposta está errada. Nenhuma validação detecta isso.
// BAD
{"answer": "yes", "reasoning": "because ..."}
// GOOD
{"reasoning": "... therefore ...", "answer": "yes"}
A ordem dos campos do schema é lógica, não formatação.
Construa
Passo 1: geração restrita por regex do zero
Veja code/main.py para uma implementação autônoma de FSM. A ideia central em 30 linhas:
def mask_logits(logits, valid_token_ids):
mask = [float("-inf")] * len(logits)
for tid in valid_token_ids:
mask[tid] = logits[tid]
return mask
def generate_constrained(model, tokenizer, prompt, fsm):
ids = tokenizer.encode(prompt)
state = fsm.initial_state
while not fsm.is_accept(state):
logits = model.next_token_logits(ids)
valid = fsm.valid_tokens(state, tokenizer)
logits = mask_logits(logits, valid)
tok = sample(logits)
ids.append(tok)
state = fsm.transition(state, tok)
return tokenizer.decode(ids)
A FSM rastreia quais partes da gramática satisfizemos até agora. valid_tokens(state, tokenizer) calcula quais tokens do vocabulário podem avançar a FSM sem sair de um caminho de aceitação.
Passo 2: Outlines para JSON Schema
from pydantic import BaseModel
from typing import Literal
import outlines
class Review(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
confidence: float
evidence_span: str
model = outlines.models.transformers("meta-llama/Llama-3.2-3B-Instruct")
generator = outlines.generate.json(model, Review)
result = generator("Classify: 'The wait staff was attentive and the food arrived hot.'")
print(result)
# Review(sentiment='positive', confidence=0.93, evidence_span='attentive ... hot')
Zero erros de validação. Nunca. A FSM torna a saída inválida inalcançável.
Passo 3: Instructor para Pydantic agnóstico de fornecedor
import instructor
from anthropic import Anthropic
from pydantic import BaseModel, Field
class Invoice(BaseModel):
vendor: str
total_usd: float = Field(ge=0)
line_items: list[str]
client = instructor.from_anthropic(Anthropic())
invoice = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
response_model=Invoice,
messages=[{"role": "user", "content": "Extract from: 'Acme Corp $420. Widget, Gizmo.'"}],
)
Mecanismo diferente. O Instructor não toca nos logits. Ele formata o schema dentro do prompt, faz o parsing da saída e tenta novamente em caso de falha de validação (3 vezes por padrão). Funciona com qualquer fornecedor. Retentativas adicionam latência e custo. A portabilidade entre fornecedores é o argumento de venda.
Passo 4: APIs nativas de fornecedores
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-5",
input=[{"role": "user", "content": "Classify: 'The food was cold.'"}],
text={"format": {"type": "json_schema", "name": "sentiment",
"schema": {"type": "object", "required": ["sentiment"],
"properties": {"sentiment": {"type": "string",
"enum": ["positive", "negative", "neutral"]}}}}},
)
print(response.output_parsed)
Decodificação restrita no lado do servidor. Paridade de confiabilidade com o Outlines para schemas suportados. Sem gerenciamento de modelo local. Prende você ao fornecedor.
Armadilhas
- Schemas recursivos. O Outlines achata a recursão até uma profundidade fixa. Saídas em árvore (comentários aninhados, AST) precisam de XGrammar ou llguidance (baseados em CFG).
- Enums gigantes. Um enum de 10.000 opções compila lentamente ou estoura o tempo limite. Mude para um retriever: primeiro preveja os top-k candidatos, depois restrinja a eles.
- Gramática estrita demais. Force a regex
date: "YYYY-MM-DD"e o modelo não conseguirá produzir"unknown"para datas ausentes. O modelo compensa inventando uma data. Permitanullou um sentinela. - Comprometimento prematuro. Veja a armadilha de ordem dos campos acima. Sempre coloque o raciocínio primeiro.
- Modo JSON do fornecedor sem schema. O modo JSON puro só garante JSON válido, não válido para o seu caso de uso. Sempre forneça um schema completo.
Use
A stack de 2026:
| Situação | Escolha |
|---|---|
| Modelo OpenAI/Anthropic/Google, schema simples | Saída estruturada nativa do fornecedor |
| Qualquer fornecedor, fluxo com Pydantic, tolera retentativas | Instructor |
| Modelo local, precisa de 100% de validade, schema plano | Outlines (FSM) |
| Modelo local, schema recursivo | XGrammar ou llguidance |
| Servidor de inferência auto-hospedado | vLLM guided decoding |
| Processamento em lote com retentativas aceitáveis | Instructor + modelo mais barato |
Entregue
Salve como outputs/skill-structured-output-picker.md:
---
name: structured-output-picker
description: Choose a structured output approach, schema design, and validation plan.
version: 1.0.0
phase: 5
lesson: 20
tags: [nlp, llm, structured-output]
---
Given a use case (provider, latency budget, schema complexity, failure tolerance), output:
1. Mechanism. Native vendor structured output, Instructor retries, Outlines FSM, or XGrammar CFG. One-sentence reason.
2. Schema design. Field order (reasoning first, answer last), nullable fields for "unknown", enum vs regex, required fields.
3. Failure strategy. Max retries, fallback model, graceful `null` handling, out-of-distribution refusal.
4. Validation plan. Schema compliance rate (target 100%), semantic validity (LLM-judge), field-coverage rate, latency p50/p99.
Refuse any design that puts `answer` or `decision` before reasoning fields. Refuse to use bare JSON mode without a schema. Flag recursive schemas behind an FSM-only library.
Exercícios
- Fácil. Faça um prompt a um pequeno modelo de pesos abertos (ex.: Llama-3.2-3B) sem decodificação restrita para
Review(sentiment, confidence, evidence_span). Meça a fração que faz parsing como JSON válido em 100 avaliações. - Médio. Mesmo corpus com o modo JSON do Outlines. Compare taxa de conformidade, latência e acurácia semântica.
- Difícil. Implemente um decodificador restrito por regex do zero para números de telefone (
\d{3}-\d{3}-\d{4}). Verifique 0 saídas inválidas em 1000 amostras.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Decodificação restrita | Força saída válida | Mascara os logits de tokens inválidos a cada passo de geração. |
| Processador de logits | A coisa que restringe | Função: (logits, state) -> masked_logits. |
| FSM | Máquina de estados finitos | Representação compilada da gramática; busca O(1) do próximo token válido. |
| CFG | Gramática livre de contexto | Gramática que lida com recursão; mais lenta, porém mais expressiva que a FSM. |
| Ordem dos campos do schema | Isso importa? | Sim — o primeiro campo se compromete; sempre coloque o raciocínio antes da resposta. |
| Guided decoding | Nome dado pelo vLLM | Mesmo conceito, integrado ao servidor de inferência. |
| Modo JSON | Versão inicial da OpenAI | Garante a sintaxe JSON; NÃO garante correspondência ao schema. |
Leitura Adicional
- Willard, Louf (2023). Efficient Guided Generation for LLMs — o artigo do Outlines.
- Artigo do XGrammar (2024) — decodificação restrita rápida baseada em CFG.
- vLLM — Structured Outputs — integração com o servidor de inferência.
- OpenAI — Structured Outputs guide — referência da API + pegadinhas.
- Biblioteca Instructor — Pydantic + retentativas entre fornecedores.
- JSONSchemaBench (2025) — benchmark de 6 frameworks de decodificação restrita.