Pular para o conteúdo
mnzes

Como fazer um LLM devolver dados estruturados?

PorDiógenes MenezesAprendendo IA em público

12 min de leitura

Você tem três formas de fazer um LLM devolver dados estruturados, em ordem crescente de garantia: pedir o formato no prompt, validar a resposta contra um schema e repetir quando falha, ou restringir a decodificação para que token inválido nunca seja amostrado. Só a terceira garante JSON sintaticamente válido. As duas primeiras são mais baratas e resolvem a maioria dos casos.

A escolha entre os três degraus é quase sempre uma escolha de custo. Forçar JSON válido descreve o caminho mais curto quando você só precisa que a resposta abra no parser; JSON Schema e Zod cobre a parte de validação; e parsing tolerante a falha trata do que fazer quando a resposta chega quebrada apesar de tudo.

Antes de escolher, vale entender por que o formato quebra. A causa está no jeito como o modelo gera texto, não em prompt mal escrito — e isso explica por que instrução mais enfática rende tão pouco aqui, ao contrário do que costuma valer em engenharia de prompts.

Por que o formato quebra

O modelo escolhe um token por vez, amostrando de uma distribuição sobre o vocabulário inteiro. Nada nesse processo carrega a informação de que existe uma chave aberta esperando fechamento, ou de que o campo total foi declarado como número. Validade de JSON é uma propriedade da sequência inteira; a amostragem é local. O modelo acerta o formato porque viu muito JSON no treino, não porque alguma coisa o impede de errar.

Na prática a quebra aparece em quatro formas, e vale distinguir porque a correção de cada uma é diferente.

Cerca de markdown em volta. O modelo devolve o JSON certo, embrulhado em ```json. É a falha mais comum e a mais barata de consertar, porque o dado está inteiro.

Vírgula final. Um , antes do } ou do ]. Legível para qualquer humano, recusado por JSON.parse e por json.loads.

Aspas. Duas variantes distintas: aspas tipográficas (“ ”) no lugar das retas, quando o texto do campo veio de conteúdo formatado; e aspas retas não escapadas dentro de uma string, que fecham o valor no meio.

Truncamento. A mais traiçoeira. O JSON está impecável até o ponto em que os tokens acabaram. O sintoma é falhar só nos casos longos, o que parece aleatoriedade até você olhar o motivo de parada da resposta (stop_reason ou finish_reason, conforme o provedor) e encontrar max_tokens.

Uma resposta com os quatro ao mesmo tempo, que é o caso realista:

Claro! Aqui está o JSON:

```json
{
  "cliente": "Padaria do Zé",
  "itens": [
    {"sku": "PF-001", "qtd": 12, "obs": "farinha "tipo 1""},
    {"sku": "PF-002", "qtd": 3,},
  ],
  "total": 148.

Cerca de markdown com prosa antes, aspas não escapadas em obs, vírgula final em dois lugares e truncamento no meio de total. Cada um pede um tratamento diferente, e nenhum deles é “peça com mais firmeza”.

mais garantia, mais custopedir no promptvalidar depoisrestringira decodificaçãoquebra sozinhocusta outra chamadanão gera inválido
Figura 1Cada degrau custa mais e garante mais. Pedir no prompt falha sozinho; validar recupera com uma chamada extra; restringir a decodificação torna o inválido inatingível.

Degrau 1: pedir no prompt

O mais barato, e o suficiente para prototipar. Três coisas ajudam de verdade:

  1. Mostrar a saída exata, não descrevê-la. Um exemplo resolvido comunica nome de campo, tipo e formato de data de uma vez.
  2. Declarar o que não deve aparecer. “Responda apenas com o objeto JSON, sem cerca de código e sem texto antes ou depois” corta a maior parte das cercas.
  3. Deixar espaço. Metade das falhas de truncamento é max_tokens apertado demais para o pior caso, não para o caso médio.

Um truque clássico morreu recentemente e vale registrar: preencher o começo da resposta do assistente com { para forçar o modelo a continuar de dentro do objeto. Em julho de 2026, os modelos atuais da Anthropic, da geração 4.6 em diante, recusam a requisição com erro 400 quando a última mensagem é do assistente. Onde o prefill ainda existe, ele funciona; onde não existe, o substituto é a saída estruturada nativa.

Este degrau falha de um jeito específico: ele funciona nos seus dez casos de teste e quebra na primeira semana em produção, com uma entrada que tinha aspas no nome do cliente.

Degrau 2: validar depois

Aqui você aceita que a saída pode vir errada e trata isso como um caso normal, e não como exceção. O padrão tem três partes: extrair, validar, repetir.

Extrair é recortar o objeto de dentro do que veio. Um corte da primeira { até a última } resolve cerca de markdown e prosa em volta sem nenhuma inteligência.

Validar é conferir contra um schema, não só contra o parser. JSON.parse aceita {"qtd": "doze"}; o seu schema não deveria.

import { z } from 'zod';

const Pedido = z.object({
  cliente: z.string().min(1),
  itens: z.array(z.object({ sku: z.string(), qtd: z.number().int().positive() })),
  total: z.number(),
});

function extrair(bruto: string) {
  const i = bruto.indexOf('{');
  const j = bruto.lastIndexOf('}');
  if (i < 0 || j < i) throw new Error('nenhum objeto na resposta');
  return JSON.parse(bruto.slice(i, j + 1));
}

Repetir é a parte que quase todo mundo faz mal. Reenviar o mesmo prompt e torcer para o modelo acertar desta vez desperdiça a informação mais útil que você tem: a mensagem de erro. Devolva o erro do validador ao modelo, junto com a resposta anterior, e a taxa de acerto na segunda tentativa sobe muito.

const conserto = [
  { role: 'assistant', content: bruto },
  { role: 'user', content: `Isso falhou na validação: ${erro.message}\n` +
      `Devolva apenas o objeto corrigido.` },
];

Uma passada de conserto assim é barata e mensurável. No estudo de Tam e colegas, pedir a um modelo pequeno que reformatasse saídas com erro de parse melhorou o resultado final em JSON e em YAML1.

Degrau 3: restringir a decodificação

O terceiro degrau muda a natureza do problema. Em vez de julgar o texto depois de gerado, você intervém no momento da amostragem: a cada passo, os tokens que levariam a uma sequência inválida recebem probabilidade zero. O modelo não é convencido a produzir JSON válido — ele fica incapaz de produzir outra coisa.

O mecanismo é uma máscara sobre os logits. O schema, ou a gramática, é compilado em um autômato; o estado do autômato depois dos tokens já emitidos determina quais tokens do vocabulário são aceitáveis agora, e o resto é zerado antes da amostragem. Depois de {"qtd": só passam dígitos e sinal; depois de {"cliente": só passa aspas.

A objeção óbvia é custo. Testar o vocabulário inteiro a cada token é caro: mesmo um vocabulário pequeno como o do GPT-2 tem 50.257 entradas, e implementações ingênuas percorrem isso a cada passo. Willard e Louf mostraram como construir um índice do vocabulário por estado do autômato antes da geração, o que reduz o trabalho por token a custo constante em média2. É por isso que a técnica saiu do papel e virou recurso de API.

A ideia se aplica além de JSON. Gramáticas livres de contexto descrevem o espaço de saída de uma classe grande de tarefas (SQL, chamadas de função, sequências de ação de agente), e o mesmo mecanismo de restrição serve para todas3.

Em julho de 2026, os provedores expõem isso de duas maneiras. Na API da Anthropic, output_config.format com um json_schema restringe a resposta, e strict: true na definição de uma ferramenta restringe os parâmetros dela; o schema precisa declarar additionalProperties: false e listar os campos obrigatórios. Um schema novo paga uma compilação na primeira chamada e fica em cache por 24 horas. Duas consequências práticas: a primeira requisição de um schema é mais lenta que as seguintes, e montar o schema de novo a cada chamada joga o cache fora.

O formato é só a primeira frente

saída estruturadaformatoJSON mode, schema, gramáticavalidaçãoJSON Schema, Zod, Pydanticerroparsing tolerante, retryescalalote, rate limit, streaming
Figura 2O formato é só a primeira frente. Validação, tratamento de erro e escala aparecem depois, e é onde a maior parte do trabalho de integração acontece.

Resolver o formato é a parte visível. Os três problemas seguintes aparecem em ordem, e cada um custa mais que o anterior: garantir que o conteúdo faz sentido, decidir o que fazer quando não faz, e sustentar isso em volume — lote, limite de requisições, streaming, idempotência de retry.

Onde isso falha

JSON válido não é resposta correta. A decodificação restrita garante que {"categoria": "cobranca"} vai abrir no parser e que cobranca está no enum. Ela não garante que aquele ticket era de cobrança. É a confusão mais cara do assunto, porque o sistema para de falhar de forma barulhenta e passa a falhar em silêncio.

A ordem dos campos no schema muda o resultado. Tam e colegas encontraram o caso mais claro disso: em 100% das respostas do GPT-3.5 Turbo em modo JSON, a chave answer apareceu antes da chave reason, o que transformou uma tarefa de chain-of-thought em resposta direta e derrubou o desempenho1. O modelo escreve na ordem em que os campos aparecem; se o resultado vem antes do raciocínio, não há raciocínio.

A perda não vem do parse. No mesmo trabalho, o LLaMA 3 8B teve 0,148% de erro de parse na tarefa Last Letter em JSON, e mesmo assim 38,15% de diferença de desempenho em relação à saída em texto livre1. Vale como aviso contra a explicação fácil: se estruturar piorou o seu resultado, o parser provavelmente não é o culpado.

Restrição mal implementada machuca de verdade. Beurer-Kellner e colegas mediram uma versão de GSM8K codificada em JSON com Mistral 7B e cinco exemplos: 41,5% de acerto sem restrição, 30,8% com restrição ingênua, 41,8% com restrição minimamente invasiva4. A causa é desalinhamento de subpalavra — a máscara força uma fragmentação de tokens que o modelo nunca viu no treino, e a distribuição seguinte fica fora de distribuição. A conclusão prática é que a qualidade da biblioteca importa, não só o fato de haver restrição.

A cobertura de JSON Schema é parcial. Um benchmark de 2025 avaliou seis frameworks de decodificação restrita sobre 10 mil schemas reais e encontrou diferenças relevantes de cobertura entre eles5. Na prática: restrições numéricas (minimum, multipleOf), restrições de tamanho de string (minLength) e schemas recursivos costumam ficar de fora. O schema é aceito, a saída “obedece” e a sua regra de negócio não foi verificada por ninguém. Mantenha o validador do degrau 2 mesmo usando o degrau 3.

Nem toda tarefa perde. Em classificação, restringir o espaço de respostas às vezes ajuda: no mesmo estudo, um dos modelos avaliados melhorou em um conjunto de diagnóstico com o modo JSON ligado1. Faz sentido: em classificação, o formato carrega a resposta.

texto livresaída estruturadaresposta em prosaregex e heurísticaJSON contra schemaobjeto tipadofalha em silênciofalha na fronteira
Figura 3A diferença não está na chamada, está no código que consome. Com texto livre a falha vira dado errado adiante; com schema ela para na fronteira.

Por onde começar

  1. Meça antes de decidir. Rode 100 entradas reais com o prompt mais simples e conte quantas abriram no parser. Se forem 98, o degrau 2 basta; se forem 60, olhe o que quebrou antes de subir de degrau.
  2. Separe as falhas por tipo. Cerca de markdown, vírgula, aspas e truncamento têm consertos diferentes, e três deles são de uma linha.
  3. Escreva o schema com o raciocínio primeiro. Se a tarefa exige pensar, o campo de raciocínio precisa vir antes do campo de resultado.
  4. Suba para decodificação restrita quando o custo da falha for alto, não quando a taxa de falha for alta. Ingestão em lote sem supervisão justifica; uma tela com um humano olhando geralmente não.
  5. Mantenha o validador. Ele deixa de ser o mecanismo principal e vira a rede de segurança contra truncamento e contra o pedaço do schema que a restrição não cobre.

Custo

Três estimativas grosseiras, para dimensionar a decisão.

Validar e repetir custa uma chamada extra na fração de casos que falha. Com 2% de falha e uma passada de conserto, o acréscimo fica em torno de 2% do custo total de saída — irrelevante. Com 30% de falha, você está pagando um terço a mais e deveria consertar o prompt antes de automatizar o conserto.

Restringir a decodificação custa pouco em tempo de geração quando a implementação pré-computa o índice: Willard e Louf reportam sobrecarga pequena sobre a geração livre2, e a abordagem minimamente invasiva de Beurer-Kellner e colegas chega a superar a geração sem restrição em throughput graças a decodificação especulativa4. O custo real está na primeira chamada de cada schema novo e no trabalho de manter os schemas estáveis o suficiente para o cache servir.

Não estruturar custa o parser que você vai escrever, os casos de borda que vai descobrir em produção e os dados errados que entraram no banco antes de alguém perceber. Esse é o único dos três que não aparece na fatura.

Footnotes

  1. Tam et al. (2024) comparam decodificação restrita, instrução de formato e conversão em duas etapas, em tarefas de raciocínio e de classificação. 2 3 4

  2. Willard e Louf (2023) reformulam a geração guiada como transições entre estados de um autômato finito e constroem um índice sobre o vocabulário do modelo, o que torna o custo por token constante em média. 2

  3. Geng et al. (2023) mostram que gramáticas formais descrevem o espaço de saída de uma faixa ampla de tarefas de PLN, e propõem gramáticas dependentes da entrada para estruturas que variam com o caso.

  4. Beurer-Kellner et al. (2024) medem a perda de acurácia causada por desalinhamento entre subpalavras e restrição, e apresentam um algoritmo alinhado a subpalavras que a elimina. 2

  5. Geng et al. (2025) avaliam seis frameworks de decodificação restrita sobre 10 mil schemas reais de JSON Schema, em eficiência, cobertura e qualidade.

Perguntas frequentes

Como faço um LLM devolver JSON?
Descreva o formato no prompt, dê um exemplo da saída exata e extraia o primeiro objeto da resposta antes de chamar o parser. Isso resolve a maior parte dos casos. Se precisar de garantia dura, use o modo de saída estruturada do provedor, que restringe a decodificação e impede a geração de JSON inválido.
Por que o JSON vem dentro de uma cerca de markdown?
Porque o modelo viu milhões de blocos de código cercados durante o treino, e responder assim é o comportamento mais provável. Instrução no prompt reduz a frequência, mas não zera. O conserto barato é recortar o trecho entre a primeira chave e a última antes de fazer o parse.
Saída estruturada garante que a resposta está correta?
Não. Ela garante sintaxe e, em parte, o schema. O modelo continua livre para preencher os campos com valores errados, inventar um identificador que não existe ou escolher a categoria errada do enum. Validação de formato e validação de conteúdo são problemas separados, e a decodificação restrita resolve só o primeiro.
Restringir a decodificação piora a qualidade da resposta?
Pode piorar, dependendo de como o schema é escrito e de como a restrição é implementada. Um estudo de 2024 mediu queda grande em tarefas de raciocínio quando o campo da resposta vinha antes do campo do raciocínio no schema. Colocar o raciocínio primeiro recupera boa parte da diferença.
Ainda preciso de retry se já uso decodificação restrita?
Sim. A decodificação restrita não impede truncamento por limite de tokens, não cobre toda a especificação de JSON Schema e não protege contra recusa do modelo. Mantenha o validador e o caminho de retry; a diferença é que eles passam a disparar raramente, em vez de a cada dez chamadas.
Qual a diferença entre JSON mode e schema estrito?
JSON mode garante apenas que a saída é um JSON sintaticamente válido, com qualquer estrutura. Schema estrito compila o seu schema em uma restrição sobre a decodificação e garante também os campos, os tipos e os valores de enum. O segundo costuma custar uma compilação na primeira chamada.

Referências

  1. Willard, B. T. e Louf, R.. Efficient Guided Generation for Large Language Models (2023)arXiv:2307.09702
  2. Geng, S. et al.. Grammar-Constrained Decoding for Structured NLP Tasks without Finetuning (2023)arXiv:2305.13971
  3. Beurer-Kellner, L. et al.. Guiding LLMs The Right Way: Fast, Non-Invasive Constrained Generation (2024)arXiv:2403.06988
  4. Tam, Z. R. et al.. Let Me Speak Freely? A Study on the Impact of Format Restrictions on Performance of Large Language Models (2024)arXiv:2408.02442
  5. Geng, S. et al.. JSONSchemaBench: A Rigorous Benchmark of Structured Outputs for Language Models (2025)arXiv:2501.10868