Saída estruturada entre agentes: como exigir JSON com schema e parar de quebrar no parse

Saída estruturada é quando você para de pedir JSON no texto do prompt e passa a exigir um JSON Schema que vira gramática restringindo os tokens gerados na inferência. Na API da Claude ela está em disponibilidade geral desde 29 de janeiro de 2026, sem beta header, com o parâmetro em output_config.format. Para argumento de ferramenta existe o strict tool use, e no Claude Agent SDK a opção outputFormat devolve os dados validados no campo structured_output. Restam dois ramos que fogem do schema: recusa por segurança e resposta cortada por max_tokens. Trate os dois e metade do seu problema de orquestração vira erro explícito.
Fala aí, beleza? Se você já montou uma cadeia de agentes, conhece a cena: o agente 2 recebe um texto lindo do agente 1 e fica tentando adivinhar onde o JSON começa
Aí vem o regex, vem o try/catch, vem a crase de bloco de código na frente do objeto, e a orquestração inteira quebra num ponto que não tem NADA a ver com raciocínio do modelo
A boa notícia é que existe um caminho determinístico pra isso: o JSON Schema é compilado em uma gramática que restringe os tokens gerados durante a inferência (constrained decoding), em vez de pedir JSON no texto do prompt
E isso deixou de ser experimento: a saída estruturada da API da Claude está em disponibilidade geral (GA) desde 29 de janeiro de 2026, e não exige mais beta header 🙂
O que você precisa antes de exigir schema entre agentes
Checagem rápida antes de abrir o editor
- Um modelo com saída estruturada disponível na API da Claude: hoje a lista é Claude Mythos Preview, Opus 4.7, Opus 4.6, Sonnet 4.6, Sonnet 4.5, Opus 4.5 e Haiku 4.5
- Saber QUAL retorno você quer travar: a resposta final do agente e a chamada de ferramenta são caminhos diferentes, e é super comum travar um e deixar o outro solto
- O schema desenhado em JSON Schema: dá pra declarar com Zod no TypeScript ou Pydantic no Python, ninguém precisa escrever aquilo na unha
Roda na AWS? Também tem: o recurso está em GA no Amazon Bedrock pros modelos Claude 4.5, nas APIs Converse, ConverseStream, InvokeModel e InvokeModelWithResponseStream, em todas as regiões comerciais com Bedrock
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
Pedir JSON e exigir schema não são a mesma coisa:
Essa confusão é a raiz de metade da dor
O modo JSON garante que a resposta é um JSON válido, e só isso
Ele NÃO garante que o objeto tem os campos que a próxima etapa espera, com os tipos que ela espera
Quem garante aderência exata ao schema fornecido é a saída estruturada
E não é papo de marketing, é diferença de mecanismo: na avaliação de aderência a JSON Schema complexo da própria OpenAI, os números ficaram assim
| Configuração | Aderência a JSON Schema complexo (avaliação da OpenAI) |
|---|---|
| gpt-4-0613 | abaixo de 40% |
| Modelo treinado para schemas complexos | 93% |
| gpt-4o-2024-08-06 com Structured Outputs | 100% |
Se liga no detalhe mais importante dessa tabela: treinar o modelo pra entender schema levou a coisa até 93%
Os 100% só apareceram com a abordagem determinística de restringir a saída
Ou seja, prompt caprichado ajuda, mas quem fecha a conta é a gramática
Como exigir JSON com schema validado, passo a passo
- Escreva o schema só com os recursos suportados
No Claude Agent SDK são aceitos tipos básicos (object, array, string, number, boolean, null), enum, const, required, objetos aninhados e definições via $ref
Com Zod fica assim, e o schema vira o contrato do handoff
import { z } from "zod"
const Handoff = z.object({
status: z.enum(["ok", "parcial", "falhou"]),
arquivos: z.array(z.object({
caminho: z.string(),
linhas_alteradas: z.number()
})),
resumo: z.string()
})
O erro comum deste passo: enfiar um recurso exótico de JSON Schema que não está nessa lista e descobrir isso só na hora da chamada
- Ligue pelo parâmetro certo na API da Claude
Na virada pra GA o parâmetro mudou de lugar: output_format virou output_config.format
O header antigo anthropic-beta: structured-outputs-2025-11-13 junto do output_format continua funcionando por um período de transição, então nada explode hoje
O erro comum deste passo: copiar tutorial da época do beta, deixar rodando no caminho antigo e nunca migrar
- Se o dado crítico é o argumento da ferramenta, use strict tool use
Muita orquestração não quebra na resposta final, quebra no input que o agente passa pra ferramenta
Pra isso existe o strict tool use: "strict": true entra como propriedade de topo na definição da ferramenta, do lado de name, description e input_schema
{
"name": "registrar_resultado",
"description": "Grava o resultado da etapa anterior",
"input_schema": {
"type": "object",
"properties": {
"status": { "type": "string", "enum": ["ok", "parcial", "falhou"] },
"resumo": { "type": "string" }
},
"required": ["status", "resumo"]
},
"strict": true
}
Com isso o campo input do bloco tool_use passa a seguir o input_schema, e o name é sempre válido
É o mesmo pipeline de gramática, só que apontado pra chamada de ferramenta
O erro comum deste passo: travar só a resposta final e deixar a chamada de ferramenta solta, que é justamente onde o dado vira efeito colateral no seu sistema
- No Claude Agent SDK, leia o campo validado
Esse é o caso do agente que usa ferramentas no meio do caminho e MESMO ASSIM precisa devolver JSON validado no final
Tem a opção outputFormat no query() em TypeScript, e output_format em Python
Quando o agente termina, a result message traz o campo structured_output com os dados já validados
O erro comum deste passo: continuar fazendo regex no texto final do agente, com o campo validado ali do lado esperando ser lido 😛
- Mantenha o schema enxuto desde o primeiro dia
Campo opcional é o item que mais infla a gramática compilada: cada parâmetro opcional aproximadamente dobra uma parte do espaço de estados
E não é só ele: parâmetros opcionais, união de tipos, objetos aninhados e número de ferramentas interagem entre si
O erro comum deste passo: marcar tudo como opcional "por segurança", que é exatamente o caminho mais rápido pro schema estourar limite de complexidade
Quando o retorno não bate com o schema: o que aconteceu e o que fazer
Tem gente que liga o recurso e assume que agora TODA resposta é um objeto válido
Não é bem assim, e os casos são documentados
Caso 1: recusa por segurança
A recusa tem precedência sobre o schema
Ou seja, o modelo pode recusar e aquele retorno não vai ser o seu objeto
Conduta: a etapa que consome esse retorno precisa tratar esse ramo explicitamente, em vez de assumir objeto válido e seguir a vida
Prevenção: trate recusa como um estado normal da cadeia, com log e parada limpa, não como exceção genérica
Caso 2: resposta cortada no limite de tokens
Se a geração bate no max_tokens, ela para no meio
JSON incompleto é JSON quebrado, mesmo com a gramática restringindo os tokens
Conduta: dimensionar o limite pro tamanho REAL do objeto que você pediu
Tome cuidado com schema que devolve array de tamanho livre: é aí que o objeto cresce sem você perceber
Caso 3: o schema nem chega a rodar (erro 400)
Esse aqui nem é resposta fora do formato, é a chamada morrendo antes
A API responde com mensagens do tipo Schema is too complex ou Too many recursive definitions in schema
A razão é que existem limites de complexidade justamente pra evitar tempo excessivo de compilação da gramática
A solução documentada é bem direta
- simplificar a estrutura
- reduzir a profundidade de aninhamento
- quebrar em schemas menores
- dividir em múltiplas requisições
- marcar como
strictsó as ferramentas críticas
E olha, erro 400 explícito é BOA notícia
Bem melhor que JSON quebrado passando silencioso pro agente seguinte e explodindo três etapas depois, num lugar que não tem relação nenhuma com a causa
Orquestração com provedores diferentes: o mesmo contrato em cada API
Quem mistura fornecedores na mesma cadeia vive esse problema em dobro: cada etapa fala um dialeto
A sacada é parar de tratar o prompt como contrato
O contrato entre etapas é o SCHEMA, o prompt é só como você pede
Na OpenAI, você passa json_schema no parâmetro response_format com strict: true
No Gemini, define responseMimeType como application/json e o responseSchema dentro do generation_config
"generation_config": {
"responseMimeType": "application/json",
"responseSchema": { "...": "seu JSON Schema aqui" }
}
Os SDKs GenAI do Gemini aceitam o schema via Pydantic no Python e Zod no JavaScript, então o mesmo objeto que você já declarou serve
Resumo do mapa
| Onde | Como se ativa |
|---|---|
| API da Claude (resposta final) | output_config.format (o output_format com o header anthropic-beta: structured-outputs-2025-11-13 segue valendo por um período de transição) |
| API da Claude (chamada de ferramenta) | "strict": true na definição da ferramenta |
| Claude Agent SDK | outputFormat no query() em TypeScript, output_format em Python, resultado em structured_output |
| Amazon Bedrock (Claude 4.5) | GA em Converse, ConverseStream, InvokeModel e InvokeModelWithResponseStream |
| OpenAI | json_schema no response_format, com strict: true |
| Gemini | responseMimeType e responseSchema no generation_config |
Se as três pontas produzem e consomem o MESMO schema, o handoff deixa de depender do humor do modelo na hora de formatar texto
Conclusão
Enquanto o handoff entre agentes for texto livre, boa parte dos seus erros não vai ser de raciocínio, vai ser de parse
Não tenho número medido pra cravar a proporção, isso é argumento, não dado
Mas o mecanismo é o que interessa: gramática que restringe token é determinístico, regex em texto de modelo é torcida
Próximo passo, pequeno e concreto
- pegue UM ponto da sua orquestração onde uma etapa lê a saída da anterior
- escreva o schema mínimo desse handoff, só os campos que a próxima etapa realmente usa
- ligue pelo parâmetro do produto que você usa (
output_config.format,strictna ferramenta,outputFormatno Agent SDK,response_formatouresponseSchema) - trate os dois ramos que fogem do schema na resposta: recusa e corte por limite de tokens
Só depois disso funcionando você expande pro resto da cadeia
Começar travando tudo de uma vez é o caminho mais rápido pro Schema is too complex e pra frustração 😀
até o próximo post!
Perguntas frequentes
Quais modelos da Claude têm saída estruturada disponível na API hoje?
A lista atual é Claude Mythos Preview, Opus 4.7, Opus 4.6, Sonnet 4.6, Sonnet 4.5, Opus 4.5 e Haiku 4.5. Essa cobertura é maior que a do beta público, que saiu com menos modelos. Vale conferir se o modelo que você já usa na cadeia de agentes está nessa lista antes de desenhar o schema.
Saída estruturada e strict tool use resolvem o mesmo problema?
Não exatamente: são dois pontos diferentes da mesma cadeia. A saída estruturada trava o formato da resposta final do modelo, enquanto o strict tool use trava o campo input de uma chamada de ferramenta, com strict: true na definição da ferramenta ao lado de name, description e input_schema. Os dois usam o mesmo pipeline de gramática por trás, mas é comum travar só um e deixar o outro solto.
Preciso trocar output_format por output_config.format agora que saiu do beta?
Não com urgência: o parâmetro output_format e o header anthropic-beta: structured-outputs-2025-11-13 continuam funcionando por um período de transição. Mas o nome atual, já que o recurso está em GA desde 29 de janeiro de 2026, é output_config.format. Migrar o código que ainda usa o caminho do beta evita ficar preso a um parâmetro que um dia sai de circulação.
Saída estruturada elimina totalmente a chance de resposta fora do schema?
Não em 100% dos casos, e isso é documentado. São duas situações em que a RESPOSTA sai fora do schema mesmo com o recurso ligado: recusa por segurança, que tem precedência sobre o schema, e resposta cortada por atingir o limite de max_tokens. E tem um terceiro caso, que acontece antes disso: schema complexo demais é rejeitado com erro 400 (Schema is too complex, Too many recursive definitions in schema), aí a chamada nem chega a rodar. Fora esses casos, a decodificação restrita garante aderência exata ao schema fornecido.
Dá pra usar saída estruturada da Claude no Amazon Bedrock?
Dá sim, e também está em GA. Nos modelos Claude 4.5 funciona no Bedrock nas APIs Converse, ConverseStream, InvokeModel e InvokeModelWithResponseStream, em todas as regiões comerciais da AWS que têm Bedrock. Então quem orquestra agentes via Bedrock não fica de fora desse recurso.
E se eu orquestro agentes com Gemini junto de Claude, existe equivalente?
Existe. No Gemini o caminho é definir responseMimeType como application/json e passar responseSchema dentro do generation_config. Os SDKs GenAI aceitam o schema via Pydantic em Python e Zod em JavaScript, o que facilita manter o mesmo contrato de dados entre agentes de provedores diferentes.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
Agentes no Origin: o que eles conseguem fazer nas suas pull requests?
Veja o que os agentes no Origin fazem dentro da pull request: respondem dúvidas, editam código, atualizam a PR e dão push sem sair da página.
Verificação adversarial: vale usar um segundo agente para derrubar o achado do primeiro?
Verificação adversarial funciona quando errar é caro e há teste real. No estudo citado, 79% dos achados morreram antes do reporte. Saiba quando vale usar.
Como dividir uma tarefa grande entre agentes de IA sem que eles editem o mesmo arquivo?
Rodar agentes em paralelo é fácil; evitar que editem o mesmo arquivo, não. Veja como isolar por arquivo, usar worktree e fechar tudo com um merge só.
