Por que pedir com jeitinho falha
Sete cursos se apoiaram na mesma suposição: uma chamada de modelo devolve um valor que o seu código consegue usar. O encadeamento passa saída estruturada entre estágios. Roteadores devolvem uma rota. Orquestradores emitem planos. Avaliadores devolvem críticas tipadas. Todos eles quebram do mesmo jeito quando o JSON não é JSON.
A versão ingênua funciona na maior parte das vezes, e esse é o problema:
prompt = "Extraia o número da nota, o total e a data de vencimento. Responda apenas com JSON."
JSON.parse(response.text)
Isto é o que a produção devolve, do jeito que o Ruby vê:
response.text
# => "Claro! Aqui estão os dados extraídos:\n\n```json\n" \
# "{\"invoice_number\": \"NR-1029\", \"total\": \"R$ 1.240,00\", \"due\": \"sexta que vem\"}" \
# "\n```\n\nMe avise se precisar de mais alguma coisa!"
Quatro falhas diferentes numa resposta só. Um preâmbulo antes do JSON. Um bloco de código em volta dele. Um número escrito como string localizada. E uma data que não é uma data.
Os quatro modos de falha
Vale separá-los, porque cada um tem uma correção diferente e só dois deles são sobre parsing.
Envelope
Texto antes ou depois do JSON, ou um bloco de código Markdown em volta. A primeira correção de todo mundo é uma regex que acha a primeira { e a última }. Ela funciona até o texto antes ou depois do JSON conter uma { ou uma }.
Sintaxe
Vírgulas sobrando, aspas simples, chaves sem aspas, uma quebra de linha sem escape dentro de uma string. O JSON.parse levanta erro, e esse erro é o caso bom.
Formato
JSON válido, estrutura errada. Uma chave chamada due quando você esperava due_date, uma string onde você esperava um número, um campo ausente. O JSON.parse funciona, e o seu código falha mais adiante com NoMethodError: undefined method for nil.
Semântica
JSON válido, formato certo, conteúdo errado. Com um schema que declara os dois como string, "total": "R$ 1.240,00" é uma string válida que o seu código não consegue somar, e "due": "sexta que vem" é uma string válida que não é uma data que dê para guardar. Tudo faz parse, tudo valida, e o registro que você grava está errado.
flowchart TB
R["Resposta do modelo"] --> E{"Envelope?"}
E -->|"texto em volta do JSON"| F1["Problema de extração"]
E -->|"limpo"| S{"Faz parse?"}
S -->|"não"| F2["Problema de sintaxe"]
S -->|"sim"| H{"Formato certo?"}
H -->|"não"| F3["Problema de schema"]
H -->|"sim"| M{"Valores utilizáveis?"}
M -->|"não"| F4["Problema semântico"]
M -->|"sim"| OK["Valor utilizável"]
Saídas estruturadas eliminam os três primeiros em toda resposta que termina normalmente. O quarto continua sendo trabalho seu, e é o que corrompe dados em vez de levantar erro.
A resposta restringida
response = client.messages.create(
model: "claude-opus-5-5",
max_tokens: 4_096,
output_config: {
format: {
type: "json_schema",
schema: {
type: "object",
properties: {
invoice_number: { type: "string" },
total_cents: { type: "integer" },
due_date: { type: "string", format: "date" }
},
required: %w[invoice_number total_cents due_date],
additionalProperties: false
}
}
},
messages: [{ role: "user", content: document }]
)
JSON.parse(response.content.find { |block| block.type == :text }.text, symbolize_names: true)
"Responda apenas com JSON" é um pedido. Um formato de resposta é uma restrição: ele vale durante a geração, então, numa resposta que termina normalmente, as falhas de envelope, sintaxe e formato nem chegam a ser produzidas. A aula 6 cobre as duas exceções: recusas e truncamento por max_tokens. A resposta também pode começar com um bloco de thinking, e é por isso que o código lê o primeiro bloco de texto, e não o primeiro bloco.
O schema também faz trabalho semântico. total_cents: integer pede um valor diferente de total: string, e format: "date" diz qual dos muitos sentidos de "vencimento" você quer. Um schema é uma especificação, e os campos que você escolhe moldam a resposta. É por isso que a aula 3 trata de projetá-los em vez de copiar as colunas do seu banco.
Use output_config: { format: ... }. O parâmetro mais antigo, output_format, está descontinuado na API. Alguns helpers de SDK ainda o aceitam, mas output_config.format é a forma canônica e a que se escreve em código novo.
O que este curso cobre
- Por que pedir com jeitinho falha (você está aqui).
- Saídas estruturadas, e o que a garantia cobre.
- Como projetar um schema que o modelo consiga preencher corretamente.
- Ferramentas,
strict, e como escolher entre os dois mecanismos. - Validação depois da garantia: semântica, e Rails.
- Quando falha mesmo assim: recusas, truncamento e reparo.
- Juntando tudo: extração como pipeline.
A seguir: a garantia em si, o subconjunto de JSON Schema que ela suporta e o que acontece com as palavras-chave que ficam de fora.
Pontos-chave
- Todo padrão de vários passos desta série depende de obter um valor utilizável de uma chamada de modelo.
- As falhas se dividem em envelope, sintaxe, formato e semântica, e exigem correções diferentes.
- Saídas estruturadas eliminam envelope, sintaxe e formato restringindo a geração, em vez de pedir bom comportamento.
- Erros semânticos sobrevivem a qualquer garantia: JSON válido, no formato certo e com valores errados é a falha que corrompe dados em silêncio.
- Escreva
output_config: { format: ... }; o parâmetro mais antigooutput_formatestá descontinuado.
Continue com a aula 2
O que a garantia cobre
Lista o subconjunto de JSON Schema suportado, o que é garantido e o que recusas e truncamento contornam.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.