GuidesOneMux

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

  1. Variáveis de Ambiente: Mantenha as chaves dos provedores separadas em seu arquivo .env (por exemplo, ONEMUX_API_KEY em vez de OPENAI_API_KEY).
  2. Sufixo de Rota: O SDK da OpenAI anexa automaticamente caminhos de rota como /chat/completions à baseURL. Certifique-se sempre de que sua baseURL inclua o sufixo /v1 (ex.: https://api.onemux.net/v1), a menos que seu gateway faça o roteamento a partir da raiz.
  3. Versão do SDK: Verifique se você está utilizando a versão openai ^4.0.0 ou superior, pois versões legadas da v3 utilizavam uma estrutura de configuração diferente (classe Configuration).

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: false seja 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 / TipoCausa RaizDiagnóstico e Correção
401 UnauthorizedChave de API inválida ou ausenteConfirme se sua apiKey contém uma credencial válida reconhecida pelo gateway de destino (por exemplo, revise ONEMUX_API_KEY).
400 BadRequestErrorViolaçã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 NotFoundCaminho incorreto ou ID de modelo inexistenteGaranta que a baseURL termine em /v1. Confirme se o modelo solicitado está disponível na lista de modelos ativos do provedor.
429 RateLimitErrorLimite de concorrência ou teto de gastos atingidoImplemente 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 acionadasO 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