Esquema validado, mas o valor da fatura está 10 vezes menor: como verificar o structured output de LLMs
O modelo devolveu um JSON perfeitamente válido: todas as chaves presentes, tipos corretos, citação real da fatura. Só que o valor é 12 500 centavos em vez de 125 000, ou seja, 125 euros em vez de 1 250. Nem o parser nem o JSON Schema detectam isso. O autor analisa três camadas de proteção: um contrato estrito em Pydantic com conferência contra o texto de origem, retries limitados e constrained decoding.
Processado por IA de Machine Learning Mastery; editado por Hamidun News
Eis uma fatura de quatro linhas: número 2026-0417, pagador Northwind Traders, «Total due: EUR 1,250.00», pagamento em até 30 dias. Você pede ao modelo que extraia o número, a moeda, o valor em centavos e a linha de onde ele tirou o valor. A resposta chega assim: valor 12 500, moeda EUR, citação «Total due: EUR 1,250.00».
O JSON é válido, todas as chaves estão presentes, os tipos estão certos, EUR consta da lista permitida e a citação realmente existe na fatura. Mas 12 500 centavos são 125 euros, e a fatura diz 1 250. Um erro de dez vezes que nenhum parser percebe: 12500 é um inteiro perfeitamente normal. Só é possível detectá-lo comparando com o texto de origem, e essa comparação você mesmo terá de escrever.
A Machine Learning Mastery publicou uma análise que mostra como construir a proteção em três camadas. O structured output já se tornou um recurso padrão: a Responses API da OpenAI aceita um esquema JSON ou um modelo Pydantic, e o vLLM faz o mesmo em servidor local por meio do xgrammar. A maioria dos tutoriais termina no momento em que o «JSON foi parseado». O autor considera que é justamente aí que tudo começa.
Contrato e conferência com a fonte
A primeira camada é um contrato que pertence à aplicação, e não ao prompt. No exemplo, trata-se do modelo Invoice com quatro campos. Dois parâmetros de configuração fazem o trabalho principal. O modo strict=True desativa as conversões implícitas do Pydantic: a string "125000" num campo inteiro gera o erro int_type, em vez de virar número silenciosamente. O mesmo vale para o booleano True, que de outro modo viraria 1. O parâmetro extra="forbid" transforma uma chave extra em erro, e não em lixo ignorado.
Outro detalhe: três campos aceitam null, mas não têm valor padrão. O modelo pode dizer honestamente «isto não consta da fatura», mas não pode simplesmente omitir o campo nem inventar um número só para não escrever null. O dinheiro é armazenado em centavos como int, para que 1 250,00 não se transforme em 1249,9999.
Em seguida, a função accept() executa quatro passos. Primeiro, o parsing e a verificação da forma; é o único passo que pode devolver shape_error. Depois, a verificação de que a citação consta literalmente da fonte. Então, a conferência do número da fatura e da moeda com o texto. Por fim, o valor é comparado com os dígitos da citação: «1,250.00» sem separadores dá 125000, o modelo enviou 12500, e o objeto é rejeitado. Há quatro resultados possíveis: accepted, needs_review, rejected e shape_error. Se o valor não estiver indicado na fatura, o resultado é needs_review, ou seja, quem decide é uma pessoa, e não um zero inventado.
Quando repetir a requisição e quando não
A segunda camada são os retries com uma política rígida. Só vale repetir em caso de shape_error: é possível pedir ao modelo que corrija a forma, passando a lista de erros. Se a resposta for rejected, repetir é inútil, porque o conteúdo da fatura não vai mudar. Uma recusa do provedor ou um erro de configuração também interrompem o ciclo de imediato. O máximo é de três tentativas: a primeira e não mais que duas regenerações.
O feedback é limitado a cinco motivos, para que o prompt não vire uma transcrição de todas as falhas. O esquema e o texto de origem não mudam entre as tentativas. Segundo as teses do artigo, é preciso ainda controlar à parte os retries de transporte ocultos do próprio SDK; caso contrário, o número real de requisições será maior do que o previsto no orçamento.
Constrained decoding: útil, mas não é panaceia
A terceira camada é o constrained decoding. O gerador só pode produzir tokens que não levem a resposta para fora do esquema, de modo que toda uma classe de erros de forma nem chega à validação. Mas o esquema não expressa a regra «os centavos devem coincidir com a citação». Um decodificador que respeita o esquema à perfeição ainda assim produzirá os mesmos 12 500. Um objeto válido é um passe para a próxima verificação, não o resultado final.
Há também limitações. O exemplo é didático: duas moedas, uma verificação simples de dígitos. Para formatos reais, é preciso código que entenda os seus separadores decimais. Se as faturas chegam como digitalizações, verifique primeiro o texto reconhecido: um dígito errado na entrada passará por todas as verificações enquanto o objeto for internamente consistente.
A conclusão prática é simples. Não confie na validação «verde» do esquema: guarde na resposta uma citação-prova e verifique o número contra ela. Use retries apenas onde a repetição pode ajudar e defina um limite rígido. Ative o constrained decoding como economia em erros de forma, mas não no lugar das suas próprias verificações. Vale cobrir cada caminho de falha com testes usando um cliente mock, como faz o autor.
- 12 de nov. de 2026Jensen Huang deixa o cargo de CEO da Nvidia: empresa perde US$ 1,4 trilhão em duas semanas
- 12 de nov. de 2026Sindicato de Oakland garante requalificação em vez de demissões por causa da IA: 94,1% votaram a favor da greve
- 12 de nov. de 2026Oito países, OpenAI e Anthropic prometem um «botão de desligar» para modelos de IA, mas sem prazos nem multas
- 12 de nov. de 2026OpenAI e ModRetro lançam console de US$ 200 para jogos criados no Codex
Perguntas frequentes sobre a notícia
O que se sabe sobre Contrato e conferência com a fonte?
A primeira camada é um contrato que pertence à aplicação, e não ao prompt. No exemplo, trata-se do modelo Invoice com quatro campos. Dois parâmetros de configuração fazem o trabalho principal. O modo strict=True desativa as conversões implícitas do Pydantic: a string "125000" num campo inteiro gera o erro int_type, em vez de virar número silenciosamente. O mesmo vale para o booleano True, que de outro modo viraria 1. O parâmetro extra="forbid" transforma uma chave extra em erro, e não em lixo ignorado.
Quando repetir a requisição e quando não?
A segunda camada são os retries com uma política rígida. Só vale repetir em caso de shape_error: é possível pedir ao modelo que corrija a forma, passando a lista de erros. Se a resposta for rejected, repetir é inútil, porque o conteúdo da fatura não vai mudar. Uma recusa do provedor ou um erro de configuração também interrompem o ciclo de imediato. O máximo é de três tentativas: a primeira e não mais que duas regenerações.
O que se sabe sobre Constrained decoding: útil, mas não é panaceia?
A terceira camada é o constrained decoding. O gerador só pode produzir tokens que não levem a resposta para fora do esquema, de modo que toda uma classe de erros de forma nem chega à validação. Mas o esquema não expressa a regra «os centavos devem coincidir com a citação». Um decodificador que respeita o esquema à perfeição ainda assim produzirá os mesmos 12 500. Um objeto válido é um passe para a próxima verificação, não o resultado final. Há também limitações.
Test Frontier LLMs & Autonomous Agents in One Workspace
Run Claude Opus 5.5, GPT-6, Gemini 3.1, and DeepSeek side-by-side in Nomad AI (nomadnet.ai) — unified model routing, deep research, and multimodal workflows under one subscription.
O essencial da IA — uma vez por semana
Sete histórias que realmente importaram, escolhidas a dedo. Sem ruído nem releases.
Pronto! Verifique seu e-mail para a confirmação.