Structured outputs: output_config, tool_use e strict
Objetivos de aprendizagem
- Usar
output_config={"format": ...}eclient.messages.parse()como caminho atual para JSON garantido (task statement 4.3). - Usar tool use com JSON schema como padrão clássico de extração estruturada.
- Explicar o que
strict: truegarante (sintaxe/schema) e o que NÃO garante (semântica). - Diferenciar
tool_choice:auto,anye forçado — e a restrição em modelos mais novos. - Projetar schemas com campos opcionais/nullable e enums com
"other"+ detail e"unclear"para evitar fabricação. - Saber por que o prefill de assistente é um padrão obsoleto que retorna erro 400 nos modelos atuais.
O caminho atual: output_config.format
Nos modelos atuais, a forma direta de obter JSON sintaticamente válido e aderente a um schema é o parâmetro output_config com um format do tipo json_schema. A API garante que o bloco de texto da resposta contém JSON válido conforme o schema — sem cercas de markdown, sem prosa em volta, sem vírgula sobrando.
import json
import anthropic
client = anthropic.Anthropic()
INVOICE_SCHEMA = {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"supplier": {"type": "string"},
"total": {"type": ["number", "null"]},
"due_date": {"type": ["string", "null"]},
"category": {
"type": "string",
"enum": ["servicos", "produtos", "impostos", "unclear", "other"],
},
"category_detail": {"type": ["string", "null"]},
},
"required": ["supplier", "total", "due_date", "category", "category_detail"],
"additionalProperties": False,
},
}
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
output_config={"format": INVOICE_SCHEMA},
messages=[{
"role": "user",
"content": "Extraia: 'NF 5501 - Consultoria Delta - R$ 8.900,00 - venc. 15/12/2026'",
}],
)
text = next(b.text for b in response.content if b.type == "text")
data = json.loads(text) # JSON garantidamente válido e aderente ao schema
print(data["supplier"], data["total"])
No SDK Python, o helper client.messages.parse() faz o mesmo com um modelo Pydantic e já devolve a instância validada em response.parsed_output:
from typing import Optional, List
from pydantic import BaseModel
import anthropic
class Contact(BaseModel):
name: str
email: Optional[str]
interests: List[str]
demo_requested: bool
client = anthropic.Anthropic()
response = client.messages.parse(
model="claude-opus-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Extraia: Jane Doe (jane@co.com) quer plano Enterprise e uma demo.",
}],
output_format=Contact, # kwarg do helper parse(): recebe o modelo Pydantic
)
contact = response.parsed_output # instância Contact validada
print(contact.name, contact.demo_requested)
Não confunda: output_format=Contact acima é o argumento do helper parse() do SDK (recebe uma classe Pydantic). Na chamada bruta messages.create(), o parâmetro atual da API é output_config={"format": ...} — um antigo parâmetro top-level output_format da API foi substituído por output_config e aparece em provas apenas como distrator.
O padrão clássico: tool use com JSON schema
Antes dos structured outputs nativos, o padrão consolidado de extração era definir uma ferramenta cujo input_schema é o schema da extração e ler os dados do bloco tool_use da resposta. A "ferramenta" não executa nada — é só um recipiente tipado. Esse padrão continua válido, aparece bastante no exame, e é o único caminho quando você também precisa de ferramentas reais na mesma chamada.
import anthropic
client = anthropic.Anthropic()
extraction_tool = {
"name": "record_invoice",
"description": "Registra os dados extraídos de uma nota fiscal.",
"strict": True, # garante aderência exata ao input_schema
"input_schema": {
"type": "object",
"properties": {
"supplier": {"type": "string"},
"total": {"type": ["number", "null"]},
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {"type": "string"},
"amount": {"type": "number"},
},
"required": ["description", "amount"],
"additionalProperties": False,
},
},
},
"required": ["supplier", "total", "line_items"],
"additionalProperties": False,
},
}
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=[extraction_tool],
tool_choice={"type": "any"}, # obriga a chamar uma ferramenta (ver restrição abaixo)
messages=[{"role": "user", "content": "Extraia: 'ACME - item A R$ 100 - item B R$ 50 - total R$ 150'"}],
)
for block in response.content:
if block.type == "tool_use":
print(block.input) # dict já estruturado conforme o schema
tool_choice: auto, any e forçado
| Valor | Comportamento | Uso típico em extração |
|---|---|---|
{"type": "auto"} | O modelo decide se chama ferramenta ou responde em texto. | Inadequado quando a saída estruturada é obrigatória — o modelo pode devolver prosa. |
{"type": "any"} | O modelo é obrigado a chamar alguma ferramenta, mas escolhe qual. | Vários schemas de extração e tipo de documento desconhecido: garante saída estruturada e deixa o modelo classificar. |
{"type": "tool", "name": "..."} | O modelo é obrigado a chamar aquela ferramenta específica. | Garantir que uma extração específica rode (ex.: extract_metadata antes das etapas de enriquecimento). |
{"type": "none"} | Proíbe chamadas de ferramenta. | Forçar resposta final em texto mesmo com tools definidas. |
Restrição em modelos mais novos: nas gerações topo de linha mais recentes (ex.: Claude Opus 5.5 e Fable 5.1), tool_choice any e forçado ({"type": "tool"}) retornam erro 400 — nesses modelos, prefira output_config.format para saída estruturada garantida. Em claude-opus-5 ambos ainda funcionam normalmente. Existe também disable_parallel_tool_use para limitar a uma chamada por vez.
strict: true — garantia sintática, não semântica
strict: true na definição da ferramenta (e o output_config.format nativo) garantem que a saída adere exatamente ao schema: tipos corretos, campos obrigatórios presentes, nenhuma propriedade extra, JSON sempre parseável. Eliminam a classe inteira de erros de sintaxe.
O que eles não garantem é a semântica dos valores:
- itens de linha que não somam o total declarado (
line_items= 100 + 50,total= 180); - valor colocado no campo errado (data de emissão no campo de vencimento);
- valor inventado para satisfazer um campo obrigatório.
Erros semânticos exigem validação pós-extração e retry com feedback (Lição 5) e schemas bem projetados (abaixo).
Design de schema que evita fabricação
- Campos opcionais/nullable: se o documento pode não conter a informação, o campo deve aceitar
null(ou ser opcional). Campo obrigatório + informação ausente = convite à fabricação: o modelo precisa preencher algo para satisfazer o schema. - Enum com
"unclear": para classificações, incluir um valor de saída para "ambíguo/ilegível" dá ao modelo uma rota honesta em vez de forçar um chute entre categorias. - Enum com
"other"+ campo detail: categorias do mundo real são abertas;"other"acompanhado de um campo texto (category_detail) captura casos fora da lista sem quebrar o schema — e os detail acumulados indicam quais categorias novas criar. - Regras de normalização no prompt: o schema define a forma; regras como "datas em ISO 8601, valores como número decimal sem símbolo de moeda" entram no prompt, ao lado do schema.
Prefill de assistente: padrão obsoleto (erro 400)
Um padrão antigo de forçar JSON era o prefill: terminar messages com uma mensagem assistant parcial (ex.: {"role": "assistant", "content": "{"}) para o modelo "continuar" de dentro do JSON. Nos modelos atuais esse padrão retorna erro 400 — a API rejeita a mensagem assistant parcial ao final. Ele foi substituído por output_config.format e por tool use com strict, que dão garantias reais de schema (o prefill nunca garantiu validade — só induzia o começo). Em questões de prova, prefill aparece como distrator de "solução legada".
Pegadinhas da prova
- Distrator típico: "use prefill de assistente para forçar JSON" — obsoleto, retorna 400 nos modelos atuais e nunca garantiu schema.
- Distrator típico: "com
strict: trueos totais sempre baterão" — strict garante sintaxe/aderência ao schema, não aritmética nem campo certo (validação semântica continua necessária). - Distrator típico:
tool_choice: "auto"quando a saída estruturada é obrigatória — auto permite resposta em texto; a garantia vem deany/forçado (emclaude-opus-5) ou deoutput_config.format. - Distrator típico: tornar todos os campos
required"para garantir extração completa" — isso causa fabricação quando o documento não tem a informação; o correto é nullable/opcional. - Distrator típico: parâmetro top-level
output_formatna API — o parâmetro atual éoutput_config={"format": ...}(não confundir com o kwargoutput_formatdo helperparse()do SDK).
Resumo em 5 linhas
output_config={"format": {"type": "json_schema", ...}}(oumessages.parse()com Pydantic) é o caminho atual para JSON garantido.- Tool use com
input_schemaé o padrão clássico de extração: os dados saem no blocotool_use. strict: trueelimina erros de sintaxe/schema, mas não erros semânticos (somas erradas, campo trocado).tool_choice:autopode devolver texto;anyobriga alguma ferramenta; forçado obriga uma específica —any/forçado retornam 400 nos modelos topo de linha mais novos.- Campos nullable, enum com
"unclear"e"other"+detail evitam fabricação; prefill de assistente é obsoleto (400).
Documentação oficial
Questões de fixação
1. Um pipeline precisa de JSON 100% parseável de cada resposta, sem prosa em volta, usando claude-opus-5 sem ferramentas reais. Qual é a abordagem atual recomendada?
Gabarito: C. output_config.format garante JSON válido aderente ao schema no bloco de texto. A é o prefill obsoleto — retorna 400 nos modelos atuais. B é enforcement por prompt: falha esporadicamente (cercas de markdown, prosa introdutória). D corta a resposta arbitrariamente e não garante validade nenhuma.
2. Com strict: true numa ferramenta de extração de faturas, a equipe ainda encontra registros em que os line_items não somam o total. Por quê?
Gabarito: B. Schemas estritos eliminam erros de sintaxe e de tipo, mas não sabem que 100+50 deveria dar o total — coerência semântica exige validação pós-extração (ex.: comparar calculated_total vs stated_total, Lição 5). A é falsa — strict independe de tool_choice. C não tem relação: required controla presença, não aritmética. D é invenção.
3. Um sistema tem 3 ferramentas de extração (nota fiscal, recibo, contrato) e recebe documentos de tipo desconhecido em claude-opus-5. É obrigatório que toda resposta seja uma chamada de ferramenta, mas o modelo deve classificar o tipo. Qual configuração atende?
Gabarito: A. any obriga o modelo a chamar alguma ferramenta (garantia estrutural) e o deixa escolher qual (classificação do documento). B permite resposta em texto — quebra a obrigatoriedade. C força sempre a ferramenta de nota fiscal, errando para recibos e contratos. D proíbe ferramentas, o oposto do requisito. (Lembre: em modelos topo de linha mais novos, any/forçado retornam 400 — lá, use output_config.format.)
4. (escolha duas) Documentos de origem nem sempre contêm CNPJ e às vezes a categoria é ilegível. Quais decisões de schema evitam que o modelo fabrique valores?
Gabarito: B e D. Nullable dá rota honesta para informação ausente; "unclear" dá rota honesta para ambiguidade. A é exatamente o que causa fabricação (o modelo precisa inventar algo para satisfazer o campo). C pede ativamente que o modelo invente dado não presente na fonte. E destrói a padronização que o enum garante — o problema era dar saídas honestas, não abrir o campo.
5. Por que enums de categoria em schemas de extração costumam incluir "other" acompanhado de um campo detail em texto?
Gabarito: D. Categorias do mundo real são abertas: "other"+detail captura o caso imprevisto de forma estruturada e os detail acumulados mostram quais categorias novas criar. A e C são regras inventadas de JSON Schema/strict. B é falso — o campo detail até adiciona tokens; o objetivo é extensibilidade, não economia.