Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

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.

Rodolfo Mori5 min de leitura

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:

json
{
  "resposta": "Troca disponível em até 30 dias.",
  "fontes": [
    { "id": "politica-trocas", "titulo": "Política de trocas" }
  ],
  "precisaDeHumano": false
}
Contrato visualizado: resposta textual, lista de fontes e decisão booleana.

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:

bash
npm install openai@7.5.0 zod@4.4.3
added 2 packages, and audited 3 packages

Agora descreva o objeto com Zod:

ts
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>;
TypeScript: 0 erros; o tipo RespostaAssistente foi inferido do schema.

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:

ts
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);
{ resposta: 'Troca disponível em até 30 dias.', fontes: [ { id: 'politica-trocas', titulo: 'Política de trocas' } ], precisaDeHumano: false }

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:

ts
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);
Não executado contra a API: este caminho foi verificado pelo TypeScript e por um transporte HTTP simulado localmente.

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:

ts
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,
});
{ type: 'json_schema', name: 'resposta_suporte', strict: true, required: [ 'resposta', 'fontes', 'precisaDeHumano' ] }

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:

ts
const formalmenteValido = RespostaSchema.parse({
  resposta: "O pedido 9999 foi entregue hoje.",
  fontes: [],
  precisaDeHumano: false,
});

console.log(RespostaSchema.safeParse(formalmenteValido).success);
true

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:

ts
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,
    })));
  }
}
[ { caminho: 'precisaDeHumano', codigo: 'invalid_type' } ]

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:

ts
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);
}
RESPOSTA_ESTRUTURADA_AUSENTE

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:

ts
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 });
{ passou: true }

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.

  • structured outputs
  • json schema
  • zod
  • responses api
  • openai

Perguntas frequentes

Structured Outputs é a mesma coisa que pedir JSON no prompt?
Não. Pedir JSON é uma instrução em linguagem natural. Structured Outputs fornece um JSON Schema que a resposta deve respeitar quando o modelo e a operação oferecem suporte ao recurso.
Structured Outputs garante que os dados são verdadeiros?
Não. O schema garante a forma, como tipos e campos obrigatórios. Verdade, autorização e regras do negócio ainda precisam de fontes, ferramentas e validações no código.
Preciso usar Zod para ter saída estruturada?
Não. A API trabalha com JSON Schema. Zod é uma opção conveniente no TypeScript porque gera o formato aceito e também valida objetos localmente.
O que faço quando output_parsed não existe?
Trate como falha prevista. A resposta pode ter sido recusada ou ficar incompleta. Não transforme ausência em objeto vazio nem envie o dado para a próxima etapa sem inspeção.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. OpenAI Docs — Structured Outputs — developers.openai.com
  2. OpenAI API — Create a response — developers.openai.com

Continue por aqui