Migração de Aplicações Node.js com OpenAI SDK: Base URL, Streaming e Structured Outputs
Guia técnico para desenvolvedores Node.js migrarem aplicações com o OpenAI SDK: configuração de base URLs personalizadas, autenticação, streaming de saídas estruturadas e diagnóstico de erros de API.
Nesta página
Introdução
Migrar uma aplicação Node.js existente que utiliza o SDK oficial da openai não exige reescrever suas regras de negócio. Como diversos provedores de modelos unificados seguem rigorosamente as especificações REST da OpenAI, adaptar seu projeto para operar por meio de uma camada de roteamento compatível — como o OneMux — requer apenas a atualização dos parâmetros de inicialização do cliente.
Contudo, pipelines em produção frequentemente dependem de recursos avançados: conformidade estrita de schema para extração de dados, streaming token a token e recuperação robusta de falhas. Este guia demonstra como reconfigurar o OpenAI SDK em Node.js, executar extrações com JSON Schema estrito, processar respostas em streaming com estabilidade e diagnosticar erros de execução.
Passo 1: Configuração de Base URL e Autenticação
O SDK oficial da OpenAI para Node.js (openai v4+) aceita a sobreposição de configurações diretamente no construtor da classe OpenAI. Para migrar a camada de transporte para um endpoint compatível com a OpenAI, configure a propriedade baseURL e defina sua chave de API.
Inicialização do Cliente SDK
import OpenAI from "openai";
// Initialize the client pointing to an OpenAI-compatible gateway
const client = new OpenAI({
apiKey: process.env.ONEMUX_API_KEY,
baseURL: "https://api.onemux.net/v1",
});
Pré-requisitos da Migração
- Variáveis de Ambiente: Mantenha as chaves dos provedores separadas em seu arquivo
.env(por exemplo,ONEMUX_API_KEYem vez deOPENAI_API_KEY). - Sufixo de Rota: O SDK da OpenAI anexa automaticamente caminhos de rota como
/chat/completionsàbaseURL. Certifique-se sempre de que suabaseURLinclua o sufixo/v1(ex.:https://api.onemux.net/v1), a menos que seu gateway faça o roteamento a partir da raiz. - Versão do SDK: Verifique se você está utilizando a versão
openai^4.0.0ou superior, pois versões legadas da v3 utilizavam uma estrutura de configuração diferente (classeConfiguration).
Para revisar as regras de roteamento suportadas e obter credenciais de API, consulte o OneMux Quickstart.
Passo 2: Extração Estruturada com JSON Schema Estrito
Os structured outputs obrigam os modelos a retornar dados estritamente alinhados ao JSON Schema fornecido. Conforme a documentação da OpenAI sobre saídas estruturadas, definir "strict": true exige que:
- Todos os campos do objeto estejam listados explicitamente no array
required. additionalProperties: falseseja configurado obrigatoriamente em todos os schemas de objeto.- Campos opcionais sejam definidos como uniões de tipos contendo
null(por exemplo,["string", "null"]).
Implementação Padrão de Structured Completion
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.ONEMUX_API_KEY,
baseURL: "https://api.onemux.net/v1",
});
// Define strict JSON Schema for invoice extraction
const invoiceExtractionSchema = {
type: "object",
properties: {
vendorName: { type: "string" },
invoiceId: { type: "string" },
totalAmount: { type: "number" },
currency: { type: "string" },
items: {
type: "array",
items: {
type: "object",
properties: {
description: { type: "string" },
quantity: { type: "number" },
unitPrice: { type: "number" },
},
required: ["description", "quantity", "unitPrice"],
additionalProperties: false,
},
},
},
required: ["vendorName", "invoiceId", "totalAmount", "currency", "items"],
additionalProperties: false,
} as const;
async function extractInvoice(rawDocumentText: string) {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "system",
content: "Extract normalized invoice records from the provided unstructured input.",
},
{
role: "user",
content: rawDocumentText,
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "invoice_extraction",
strict: true,
schema: invoiceExtractionSchema,
},
},
});
const choice = response.choices[0];
// Check for safety or policy refusals
if (choice.message.refusal) {
throw new Error(`Model refused extraction: ${choice.message.refusal}`);
}
const rawJson = choice.message.content;
if (!rawJson) {
throw new Error("Received empty response payload");
}
return JSON.parse(rawJson);
}
A compatibilidade com gateways intermediários depende do suporte do provedor subjacente ao modo json_schema com strict: true. Para detalhes sobre as capacidades de cada modelo entre diferentes provedores, consulte o Catálogo de Modelos do OneMux.
Passo 3: Gerenciando Streaming com Structured Outputs
Quando stream: true é habilitado juntamente com response_format, o modelo transmite fragmentos de string parciais da carga JSON. Diferente do chat de texto convencional, chunks individuais não podem ser processados diretamente com JSON.parse(), pois representam tokens incompletos de uma estrutura JSON.
Acumulando Chunks de JSON em Streaming
Para processar o streaming de saídas estruturadas corretamente, acumule os fragmentos de delta até o encerramento do fluxo ou encaminhe os chunks recebidos por meio de um parser incremental de stream JSON.
async function streamInvoiceExtraction(rawDocumentText: string) {
const stream = await client.chat.completions.create({
model: "gpt-4o",
stream: true,
messages: [
{
role: "system",
content: "Extract normalized invoice records from the provided text.",
},
{
role: "user",
content: rawDocumentText,
},
],
response_format: {
type: "json_schema",
json_schema: {
name: "invoice_extraction",
strict: true,
schema: invoiceExtractionSchema,
},
},
});
let accumulatedJson = "";
let refusalReason: string | null = null;
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.refusal) {
refusalReason = (refusalReason || "") + delta.refusal;
}
if (delta?.content) {
accumulatedJson += delta.content;
// Emit partial events or log progress if building a reactive UI
}
}
if (refusalReason) {
throw new Error(`Extraction was refused during stream: ${refusalReason}`);
}
try {
return JSON.parse(accumulatedJson);
} catch (err) {
throw new Error(`Failed to parse streamed JSON payload: ${accumulatedJson}`);
}
}
Se o seu pipeline utiliza streaming primordialmente para reduzir o tempo até o primeiro byte (TTFB) em interfaces de usuário, certifique-se de que o endpoint de destino suporte Server-Sent Events (SSE) contínuos através de eventuais proxies na sua rede.
Passo 4: Diagnóstico e Resolução de Erros
Ao migrar chamadas do SDK para uma baseURL personalizada, o diagnóstico de erros deve distinguir falhas de proxy de rede, erros de configuração de schema, limites de requisições (rate limits) e recusas do modelo.
Erros Comuns e Estratégias de Resolução
| Código de Erro / Tipo | Causa Raiz | Diagnóstico e Correção |
|---|---|---|
401 Unauthorized | Chave de API inválida ou ausente | Confirme se sua apiKey contém uma credencial válida reconhecida pelo gateway de destino (por exemplo, revise ONEMUX_API_KEY). |
400 BadRequestError | Violação de schema (strict: true) | Inspecione a mensagem de erro em busca de palavras-chave de JSON Schema não suportadas (como ausência de additionalProperties: false ou campos fora de required). |
404 NotFound | Caminho incorreto ou ID de modelo inexistente | Garanta que a baseURL termine em /v1. Confirme se o modelo solicitado está disponível na lista de modelos ativos do provedor. |
429 RateLimitError | Limite de concorrência ou teto de gastos atingido | Implemente backoff exponencial. Acompanhe os limites de cota pelo painel do seu provedor. |
Refusal (Corpo da Choice) | Políticas de moderação/segurança do modelo acionadas | O modelo gerou uma recusa explícita. Verifique choice.message.refusal em vez de inspecionar a propriedade content. |
Wrapper Estruturado para Tratamento de Erros
import OpenAI from "openai";
async function safeExtractWrapper(fn: () => Promise<any>) {
try {
return await fn();
} catch (error: any) {
if (error instanceof OpenAI.APIError) {
console.error(`OpenAI API Error: status=${error.status}, type=${error.type}`);
console.error(`Message: ${error.message}`);
if (error.status === 400) {
console.error("Verify that your schema complies strictly with JSON Schema constraints.");
} else if (error.status === 401) {
console.error("Authentication failed: Check your bearer token and proxy baseURL.");
} else if (error.status === 429) {
console.warn("Rate limit reached. Queue request for delayed retry.");
}
} else {
console.error("Unexpected application error:", error);
}
throw error;
}
}
Passo 5: Consolidando Provedores com o OneMux
Rotear requisições do OpenAI SDK via OneMux simplifica operações com múltiplos modelos sem exigir bibliotecas de terceiros ou adaptadores de protocolo customizados.
- Endpoint Unificado: Mantenha uma única interface de cliente enquanto alterna o roteamento entre modelos utilizando a semântica padrão do SDK da OpenAI.
- Gestão e Visibilidade de Gastos: Centralize o faturamento, acompanhe o consumo detalhado por modelo e defina limites orçamentários sem precisar administrar faturas de vários provedores.
- Modelo Flexível Pay-as-You-Go: Recarregue sua conta por meio de créditos flexíveis, prevenindo interrupções inesperadas de serviço.
Para conferir os valores vigentes e comparar as opções de roteamento entre os modelos, consulte a Tabela de Preços do OneMux e leia a visão geral técnica na Documentação do OneMux.
Fontes
Perguntas frequentes
Posso usar o Zod com o OpenAI SDK ao utilizar uma base URL customizada?
Sim. Se o seu gateway implementar integralmente a especificação de structured outputs do endpoint `/chat/completions`, métodos auxiliares como `client.beta.chat.completions.parse()` e `zodResponseFormat()` funcionam normalmente, convertendo os schemas do Zod no payload de JSON Schema estrito exigido.
Por que o modelo retorna erro 400 ao usar 'strict: true'?
O mecanismo de structured outputs da OpenAI exige que todas as definições de objeto incluam explicitamente `additionalProperties: false` e listem todas as propriedades declaradas dentro do array `required`. Se alguma chave for omitida de `required` ou se forem utilizadas palavras-chave não suportadas do JSON Schema, a API rejeitará a chamada com erro de validação 400.
Alterar a baseURL afeta a telemetria ou os timeouts do SDK?
Sobrescrever a `baseURL` apenas redireciona o transporte HTTP para o novo endereço. Opções de tempo limite (timeout) e tentativas de repetição (como `maxRetries` ou `timeout` no construtor da OpenAI) continuam operando da mesma maneira no lado do cliente.
Artigos relacionados
Guides
Migração de Aplicação Node.js com OpenAI SDK: Base URL, Streaming e Triagem com Human-in-the-Loop
Aprenda a migrar uma aplicação Node.js com o SDK da OpenAI para um gateway compatível como o OneMux, gerenciar streaming, diagnosticar erros e estruturar um fluxo de triagem de suporte com aprovação humana.
Guides
Redução de Preço do GPT-5.6 Terra: Por Que Inputs Mais Baratos Mudam Sua Estratégia de Tokens
A OpenAI reduziu os preços de input do GPT-5.6 Terra e Luna. Veja como tokens mais baratos impactam compressão de prompts, custos de API e roteamento — e como a OneMux simplifica o acesso.
Guides
OpenAI flexibiliza os limites do GPT-5.6 Sol: o que indie hackers precisam saber sobre acesso à API de IA
A OpenAI flexibilizou recentemente os limites de taxa do GPT-5.6 Sol. Mas não chame isso de ilimitado. Saiba como indie hackers podem usar o GPT-5.6 Luna via OneMux para obter acesso flexível e econômico à API de IA.
Guides
Suporte Day 0 do OneMux para GPT-5.6 Luna: gestão inteligente de gastos com API usando proxy de IA
Como o OneMux oferece acesso imediato ao GPT-5.6 Luna da OpenAI com roteamento unificado, visibilidade de gastos e preços conforme o uso. Saiba como gerenciar os custos de LLM com eficiência.