Ao vivo 18/11 · Turma 318 nov às 19:00 MSK — aula prática gratuita de agentes de IA · Turma 3 ao vivo começa 23/11Aula 18/11 →Turma 3 (23/11)
IndústriaAnálise · No radarMachine Learning Mastery→ original

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
Esquema validado, mas o valor da fatura está 10 vezes menor: como verificar o structured output de LLMs
Fonte: Machine Learning Mastery. Colagem: Hamidun News.
Ouvir artigo
Ficha técnica e números-chave
ParâmetroDados
Fonte originalMachine Learning Mastery ↗
Editor-chefe
Dado-chave #1o modo strict do Pydantic impede que a string "125000" vire número silenciosamente
Dado-chave #2Retries apenas para shape_error, no máximo três tentativas
Dado-chave #3Confira o número da resposta com a citação do documento de origem

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.

História

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.

ZK
Zhemal Khamidun — CPO da Alpina Digital, gerente de produto do AlpinaGPT. Cria produtos de IA desde 2019. O Hamidun News é um projeto pessoal que virou um agregador em produção com mais de 6.000 leitores.
✦ Unified AI Workspace · Nomad

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.

Try Nomad AI →Launch Nomad Agent
O que você acha?
Carregando comentários…