Saída estruturada: JSON confiável e validação
Como obter saídas em formato fixo que o resto do sistema consegue consumir com segurança.
5 min de leitura
Escolha como aprender
— combine como quiserDica: o vídeo é um resumo visual rápido — o áudio e o texto trazem a aula completa.
Você integrou o modelo, a resposta chegou — e agora precisa fazer o parse. Se o modelo devolveu texto livre, você está no território do improviso: regex frágil, split por linha, esperança. Saída estruturada existe para eliminar esse improviso e fazer o modelo se comportar como uma API que retorna JSON previsível, validável e consumível pelo resto do sistema.
Por que texto livre quebra sistemas
Quando o modelo responde em linguagem natural, ele está otimizando para legibilidade humana — não para parsers. A mesma informação pode aparecer como "R$ 42,00", "42 reais" ou "quarenta e dois reais" dependendo do humor do modelo, da temperatura, da versão. Para um humano lendo, tudo bem. Para um sistema downstream tentando extrair um número, é um pesadelo.
O problema escala quando você encadeia chamadas. Na aula 07 vimos tool calling: o modelo precisa decidir qual ferramenta chamar e com quais argumentos. Se os argumentos chegam como texto livre, a ferramenta não sabe o que fazer com eles. Schema é o contrato entre o modelo e o código que o consome.
A regra prática é simples: se outra parte do sistema vai consumir a saída programaticamente, exija estrutura. Se a saída é para exibição direta ao usuário final, texto livre pode ser aceitável. Em sistemas de agentes, pipelines de extração, classificação, roteamento — você quase sempre está no primeiro caso.
Pipeline de saída estruturada: do prompt à aplicação
Fluxo de como um schema entra no request, o modelo produz JSON constrangido e a aplicação valida antes de usar.
- Aplicação · código do sistema
- JSON Schema · (Pydantic / Zod / dict)
- Validação · parse + assert
- Fallback · retry / default
- API do modelo · (Bedrock / OpenAI / etc.)
- Decoding constrangido · ou instrução de schema
- Resposta JSON · (pode ter erros)
- Ferramenta / DB · / próxima etapa
Como pedir saída estruturada — e por que validar mesmo assim
Existem três abordagens principais, em ordem crescente de confiabilidade:
1. Instrução no prompt. Você escreve "Responda APENAS com JSON válido seguindo este schema: {...}". Funciona razoavelmente bem com modelos grandes e temperatura baixa, mas é frágil — o modelo pode adicionar texto antes do JSON, usar aspas erradas ou omitir campos opcionais.
2. Modo JSON / response_format. Provedores como OpenAI e Amazon Bedrock (via Converse API) permitem passar um parâmetro que força o modelo a emitir JSON válido. Isso elimina o texto extra, mas não garante que o JSON segue seu schema específico — apenas que é JSON parseável.
3. Structured outputs com schema explícito. A abordagem mais robusta: você passa o JSON Schema completo no request e o provedor usa decoding constrangido (ou equivalente) para garantir que a saída respeita a estrutura campo a campo. OpenAI response_format: {type: "json_schema"}, Anthropic tool-use com schema, Bedrock tool use — todos seguem esse padrão.
Mesmo com a opção 3, sempre valide no seu código. O modelo pode preencher um campo com o tipo errado, retornar null onde você esperava string, ou o provedor pode ter um bug. Use Pydantic (Python), Zod (TypeScript) ou equivalente. Se a validação falhar, você tem duas saídas: retry com a mensagem de erro incluída no contexto, ou degradar para um valor padrão seguro. Nunca deixe um objeto não validado entrar no sistema downstream.
Exemplo prático: extraindo dados de uma nota fiscal
- 1
Defina o schema com Pydantic
``
python from pydantic import BaseModel from typing import List class ItemNota(BaseModel): descricao: str quantidade: int valor_unitario: float class NotaFiscal(BaseModel): numero: str data_emissao: str # ISO 8601 fornecedor: str itens: List[ItemNota] total: float`` - 2
Passe o schema no request e valide a resposta
```python import json from pydantic import ValidationError def extrair_nota(texto: str, client, model_id: str) -> NotaFiscal | None: schema = NotaFiscal.model_json_schema() response = client.converse( modelId=model_id, messages=[{"role": "user", "content": [{"text": texto}]}], toolConfig={ "tools": [{ "toolSpec": { "name": "extrair_nota_fiscal", "description": "Extrai campos estruturados de uma nota fiscal.", "inputSchema": {"json": schema} } }], "toolChoice": {"tool": {"name": "extrair_nota_fiscal"}} } ) raw = response["output"]["message"]["content"][0]["toolUse"]["input"] try: return
Na prática, eu uso tool calling como mecanismo principal de saída estruturada — mesmo quando não existe ferramenta real a ser chamada. Você define uma 'ferramenta' cujo único propósito é receber os campos que quer extrair, força o modelo a chamá-la com toolChoice, e recebe os argumentos já parseados. É o padrão mais portável entre provedores (Bedrock, Anthropic, OpenAI todos suportam), elimina o texto extra e te dá o schema no mesmo lugar onde você define a lógica. O custo é um pouco mais de boilerplate — vale cada linha.
A ligação com tool calling e o princípio geral
Na aula 07 você viu que tool calling usa JSON Schema para descrever os argumentos de cada ferramenta. Saída estruturada e tool calling são o mesmo mecanismo visto de ângulos diferentes: em ambos os casos, você está passando um schema para o modelo e esperando que ele produza JSON que respeite esse contrato.
A diferença é semântica: tool calling implica que o modelo está decidindo chamar uma ação externa; saída estruturada implica que você quer extrair informação em formato fixo. Na implementação, muitos provedores usam exatamente o mesmo endpoint e os mesmos parâmetros para os dois casos — por isso o exemplo acima usa toolConfig no Bedrock mesmo para extração pura.
O princípio que unifica tudo: schema é o contrato, validação é a garantia. O modelo é um colaborador probabilístico — ele tenta seguir o contrato, mas pode errar. Seu código é o guardião que decide o que entra no sistema. Nunca transfira essa responsabilidade para o modelo.
Um detalhe de design importante: mantenha seus schemas simples. Schemas com muitos campos opcionais, anyOf aninhados ou estruturas recursivas aumentam a chance de o modelo errar. Se precisar de algo complexo, quebre em múltiplas chamadas com schemas menores — é mais confiável e mais fácil de debugar.
Pontos-chave desta aula
Abordagens para saída estruturada
| Abordagem | Confiabilidade | Portabilidade | Quando usar | |
|---|---|---|---|---|
| Instrução no prompt | Baixa | Alta (qualquer modelo) | Prototipagem rápida, modelos sem suporte a schema | — |
| Modo JSON (`response_format`) | Média (JSON válido, schema não garantido) | Média (OpenAI, alguns outros) | Quando você só precisa de JSON parseável | — |
| Schema explícito / Tool use | Alta (decoding constrangido ou equivalente) | Alta (Bedrock, OpenAI, Anthropic) | Produção, pipelines, agentes — padrão recomendado | — |
Dúvidas frequentes
O modelo pode recusar a gerar JSON se o conteúdo for sensível?
Sim. Guardrails e filtros de conteúdo atuam antes do decoding constrangido — se o modelo recusar a resposta, você receberá um erro ou resposta vazia, não JSON malformado. Trate isso como um caso separado na sua lógica de fallback. Veremos guardrails em detalhe na aula 10.
Devo incluir exemplos de JSON no prompt mesmo usando schema explícito?
Às vezes ajuda, especialmente para campos com semântica não óbvia (ex: formato de data esperado, unidades de medida). Mas não substitui o schema — exemplos guiam o conteúdo, schema garante a estrutura. Use os dois quando o domínio for ambíguo.
Quantas vezes devo tentar o retry antes de desistir?
Na maioria dos casos, 1-2 retries com o erro de validação no contexto são suficientes. Se depois de 2 tentativas o modelo ainda errar o schema, o problema provavelmente é o schema em si (muito complexo) ou o modelo escolhido (fraco para a tarefa). Não faça retry infinito — defina um limite e degrade com segurança.
Checagem rápida
1. Por que validar a saída estruturada do modelo?