o problema
Pedir json a um modelo sempre funcionou até a hora que não funciona. O prompt descreve o formato, o modelo obedece na maioria das vezes, e a minoria vira erro de JSON.parse() em produção: uma vírgula sobrando, um campo obrigatório ausente, um número que veio como string, uma chave inventada que o schema não previa. Nada disso é raro o bastante para ignorar nem frequente o bastante para ser fácil de reproduzir.
A resposta padrão era defensiva: parse com try/catch, validação contra o schema, retry quando falha, talvez um segundo modelo para consertar a saída do primeiro. Isso é código que existe só porque a fronteira entre o modelo e o resto do sistema é texto livre. Custa latência, custa token e continua sendo probabilístico — o retry pode falhar de novo. Para um pipeline que roda milhões de extrações, “quase sempre válido” é uma taxa de erro, não uma garantia.
a ideia
Em vez de pedir o formato, restringir o formato. O schema que você manda deixa de ser descrição e vira regra de decodificação: em cada passo de geração, os tokens que quebrariam o schema são eliminados antes da amostragem. Se a gramática permite apenas true ou false naquela posição, não existe caminho para o modelo escrever outra coisa.
A feature tem duas metades independentes, que podem ser usadas juntas no mesmo request. output_config.format controla o que Claude fala: a resposta sai como json conforme o seu schema, no bloco de texto. strict: true numa ferramenta controla como Claude chama suas funções: nome e input validados contra o input_schema. A primeira resolve extração e formatação de resposta; a segunda resolve o loop agêntico, onde um argumento malformado quebra a chamada seguinte.
como funciona
O schema é compilado num artefato de gramática. Compilação custa tempo, então o resultado fica em cache por 24 horas a partir do último uso: a primeira chamada com um schema inédito é mais lenta, as seguintes não. O cache é invalidado se a estrutura do schema muda ou se o conjunto de ferramentas do request muda; mexer só em name ou description não invalida.
Junto com o grammar, a api injeta um system prompt adicional explicando o formato esperado. Isso aparece na sua conta de input tokens, e trocar output_config.format invalida o prompt cache daquela thread. O grammar vale só para a saída direta do modelo — chamadas de ferramenta, resultados de ferramenta e blocos de thinking ficam de fora, e o estado da gramática reseta entre as seções, então o modelo pensa livre e só a resposta final sai amarrada.
Do lado do cliente, os sdks fazem uma tradução que vale entender antes de confiar nela. Python (Pydantic), TypeScript (Zod), Ruby e PHP transformam seu schema: removem restrições não suportadas, empurram a informação dessas restrições para a description do campo, forçam additionalProperties: false em todo objeto, filtram formatos de string e validam a resposta contra o schema original. Ou seja: um minimum: 100 do Pydantic não chega ao modelo como restrição de gramática — chega como a frase “Must be at least 100” e como uma checagem local depois. C# e Go fazem o mesmo quando o schema é derivado de um tipo nativo.
o que isso custou
A gramática precisa ser finita e compilável, e é aí que a lista de exclusões nasce. Schemas recursivos não passam. Restrições numéricas (minimum, maximum, multipleOf) e de string (minLength, maxLength) não passam. $ref externo não passa. Array só aceita minItems com valor 0 ou 1. Regex funciona para o caso simples, mas backreference, lookahead e limite de palavra não. Uso indevido devolve 400.
Há teto de complexidade explícito: 20 ferramentas strict por request, 24 parâmetros opcionais somados entre todos os schemas, 16 parâmetros com tipo em união. Além disso existem limites internos sobre o tamanho da gramática compilada, mais um timeout de compilação de 180 segundos — dá para respeitar cada limite da tabela e ainda assim receber “Schema is too complex for compilation”. A recomendação da própria doc é reduzir opcionais, achatar aninhamento e marcar como strict só as ferramentas onde a violação dói.
E a garantia tem exceções declaradas. Se o modelo recusa, stop_reason vem como refusal, o status é 200, você paga os tokens e a saída pode não bater com o schema. Se bate no max_tokens, o json sai truncado. Capitalização de enum e const não é garantida — compare sem diferenciar caixa. Propriedades required aparecem antes das optional, então a ordem do seu schema não é a ordem do output. Citations e prefilling são incompatíveis com json outputs. E PHI não pode entrar no schema: ele é cacheado separado das mensagens e não recebe as mesmas proteções.
onde isso aparece hoje
O par natural é Implementing tool use: strict tool use é a mesma máquina aplicada ao input_schema, e usar as duas metades juntas é o desenho recomendado para workflow agêntico — ferramenta chamada com parâmetro válido, resposta final em json parseável. Combina com Streaming Messages, com contagem de tokens sem compilar, e com Batch processing, onde a extração estruturada em escala ainda pega os 50% de desconto. Duas fricções para lembrar: Citations devolve 400 se você ligar as duas coisas, porque intercalar bloco de citação com texto briga com a gramática, e mudar o formato derruba o Prompt caching da conversa.