Structured Outputs: receba JSON confiável da IA
Aprenda a definir um JSON Schema com Zod, usar Structured Outputs na Responses API e validar a resposta antes de entregá-la ao restante do código.
Structured Outputs faz a resposta do modelo chegar no formato que o programa espera: campos conhecidos, tipos definidos e itens obrigatórios. Nesta lição, você vai criar esse contrato com Zod, gerar o JSON Schema usado pela Responses API e bloquear um dado inválido antes que ele avance pelo sistema.
O nome técnico significa saídas estruturadas. Em vez de receber um parágrafo
e tentar descobrir onde está cada informação, a aplicação recebe um objeto. O
benefício prático aparece quando outra função precisa ler fontes ou decidir se
precisaDeHumano é verdadeiro sem interpretar texto solto.
Os resultados desta página foram produzidos no Node com objetos e respostas de
rede simuladas localmente. O SDK atual foi compilado e o fluxo foi executado
contra um fetch de teste. Nenhuma saída abaixo é apresentada como resposta
real de um modelo ou da API OpenAI.
A ficha de expedição não deixa cada caixa inventar seus campos
Imagine o setor de envio da Club Store. Toda caixa precisa de uma ficha com destinatário, endereço e aviso de fragilidade. Se cada funcionário escrever uma carta diferente, a transportadora terá de adivinhar onde está cada dado. Um formulário com campos definidos resolve a forma da entrega.
No paralelo técnico, o formulário é o JSON Schema; cada campo é uma
propriedade; o tipo diz que valor cabe ali; required lista o que não pode
faltar; e additionalProperties: false fecha campos inesperados. O limite da
analogia é importante: uma ficha preenchida corretamente ainda pode conter um
endereço falso. Structured Outputs controla estrutura, não verdade.
Para o suporte da loja, queremos este objeto:
{
"resposta": "Troca disponível em até 30 dias.",
"fontes": [
{ "id": "politica-trocas", "titulo": "Política de trocas" }
],
"precisaDeHumano": false
}Ter um exemplo no prompt ajuda o modelo, mas não cria um contrato para o TypeScript. Vamos transformar essa intenção em validação executável.
O mesmo schema serve na fronteira da API e dentro do programa
Instale as bibliotecas usadas nesta lição:
npm install openai@7.5.0 zod@4.4.3Agora descreva o objeto com Zod:
import { z } from "zod";
const FonteSchema = z.object({
id: z.string().min(1),
titulo: z.string().min(1),
});
export const RespostaSchema = z.object({
resposta: z.string().min(1),
fontes: z.array(FonteSchema),
precisaDeHumano: z.boolean(),
});
export type RespostaAssistente = z.infer<typeof RespostaSchema>;Zod tem dois papéis aqui. Ele descreve o formato para a integração e valida
objetos que entram no restante da aplicação. Isso evita manter um type e um
schema manual que podem se afastar com o tempo.
Teste primeiro um objeto local conhecido:
const resultado = RespostaSchema.parse({
resposta: "Troca disponível em até 30 dias.",
fontes: [{ id: "politica-trocas", titulo: "Política de trocas" }],
precisaDeHumano: false,
});
console.log(resultado);O objeto passou porque todos os campos e tipos correspondem ao contrato. Esse teste local não mede a qualidade de um modelo; ele comprova que a fronteira do seu código está funcionando.
Como usar Structured Outputs na Responses API atual
No SDK oficial para JavaScript, zodTextFormat converte o schema para o formato
estruturado. A chamada atual fica em responses.parse, e o resultado validado
aparece em output_parsed:
import OpenAI from "openai";
import { zodTextFormat } from "openai/helpers/zod";
import { RespostaSchema } from "./contracts.ts";
const client = new OpenAI();
const response = await client.responses.parse({
model: "gpt-5.6-luna",
input: "Um livro sem uso pode ser trocado em até 30 dias.",
text: {
format: zodTextFormat(RespostaSchema, "resposta_suporte"),
},
});
console.log(response.output_parsed);Por que registrar isso? Sem uma chave e uma chamada real, não existe resposta do modelo para publicar. O trecho comprova a assinatura do SDK 7.5.0, mas não prova latência, custo nem qualidade do modelo. O tutorial do assistente com Responses API monta o transporte simulado e separa claramente os dois níveis de teste.
Podemos inspecionar o formato gerado por Zod sem rede:
import { zodTextFormat } from "openai/helpers/zod";
const formato = zodTextFormat(RespostaSchema, "resposta_suporte");
console.log({
type: formato.type,
name: formato.name,
strict: formato.strict,
required: formato.schema.required,
});name identifica o schema; strict pede aderência; e required confirma que
os três campos fazem parte de toda resposta válida. Quando mudar o contrato,
esse teste mostra exatamente o que também mudou na requisição.
JSON válido pode continuar sendo uma resposta ruim
Compare três camadas que parecem iguais no início:
| camada | o que garante | o que não garante |
|---|---|---|
| pedir JSON no prompt | apenas uma instrução textual | sintaxe e campos |
| JSON bem formado | texto pode ser interpretado como JSON | contrato do negócio |
| Structured Outputs | aderência ao schema suportado | verdade e autorização |
Este objeto obedece ao schema e ainda seria perigoso:
const formalmenteValido = RespostaSchema.parse({
resposta: "O pedido 9999 foi entregue hoje.",
fontes: [],
precisaDeHumano: false,
});
console.log(RespostaSchema.safeParse(formalmenteValido).success);O validador não sabe se o pedido existe. Status atual deve vir de uma função autorizada, assunto da lição de function calling. Conhecimento textual pode vir de RAG. A saída estruturada organiza o resultado dessas peças; não assume o trabalho delas.
O erro de tipo mostra por que validar de novo é útil
Mesmo com garantia oferecida pela API, valide novamente na fronteira que entra no domínio. Mocks, dados antigos, migrações ou outra origem podem burlar a integração principal. Veja o que acontece quando um texto ocupa o lugar do booleano:
import { z } from "zod";
try {
RespostaSchema.parse({
resposta: "ok",
fontes: [],
precisaDeHumano: "não",
});
} catch (erro) {
if (erro instanceof z.ZodError) {
console.log(erro.issues.map((item) => ({
caminho: item.path.join("."),
codigo: item.code,
})));
}
}A mensagem diz que precisaDeHumano existe, mas tem tipo incompatível. Não
converta automaticamente a string "não" em false: uma conversão permissiva
pode esconder problemas de integração. Registre a falha, tente novamente apenas
quando a operação for segura e encaminhe quando o impacto pedir revisão.
Também trate ausência de parse como um estado explícito:
function exigirResultado<T>(valor: T | null | undefined): T {
if (valor == null) throw new Error("RESPOSTA_ESTRUTURADA_AUSENTE");
return valor;
}
try {
exigirResultado(undefined);
} catch (erro) {
console.log((erro as Error).message);
}Essa ausência pode acompanhar recusa ou resposta incompleta; preserve os dados originais para diagnóstico. Não invente um objeto padrão que pareça uma resposta real do modelo.
Missão: acrescente prioridade sem quebrar o contrato anterior
Adicione uma prioridade restrita a três valores:
const RespostaComPrioridadeSchema = RespostaSchema.extend({
prioridade: z.enum(["baixa", "normal", "alta"]),
});
const teste = RespostaComPrioridadeSchema.safeParse({
resposta: "Pagamento precisa de revisão.",
fontes: [],
precisaDeHumano: true,
prioridade: "alta",
});
console.log({ passou: teste.success });Seu critério de sucesso é verificável: o novo objeto passa; um objeto com
prioridade: "urgente" falha; e os três campos anteriores continuam
obrigatórios no formato gerado. Depois, crie um caso em que o schema está
correto, mas a fonte está vazia. Essa segunda prova lembra a divisão principal
da lição: estrutura confiável é necessária, mas conteúdo confiável exige outras
barreiras do sistema.
Perguntas frequentes
Structured Outputs é a mesma coisa que pedir JSON no prompt?
Structured Outputs garante que os dados são verdadeiros?
Preciso usar Zod para ter saída estruturada?
O que faço quando output_parsed não existe?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 26.3.0, TypeScript 7.0.2, OpenAI SDK 7.5.0 e Zod 4.4.3; formato testado com fixture local, sem chamada à API, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- OpenAI Docs — Structured Outputs — developers.openai.com
- OpenAI API — Create a response — developers.openai.com


