GuidesOneMux

Migración de aplicaciones Node.js con el SDK de OpenAI: Base URL, Streaming y Structured Outputs

Guía técnica para desarrolladores de Node.js sobre la migración de aplicaciones con el SDK de OpenAI: configuración de base URLs personalizadas, autenticación, streaming de structured outputs y diagnóstico de errores de API.

En esta página

Introducción

Migrar una aplicación Node.js existente que utiliza el SDK oficial openai no requiere reescribir tu lógica de negocio. Dado que muchos proveedores unificados de modelos se adhieren con precisión a las especificaciones REST de OpenAI, adaptar una aplicación existente para que funcione a través de una capa de enrutamiento compatible con OpenAI —como OneMux— solo exige actualizar los parámetros de inicialización del cliente.

Sin embargo, los pipelines en entornos de producción a menudo dependen de funciones avanzadas: cumplimiento estricto de esquemas para la extracción de datos, streaming token por token y recuperación robusta ante errores. Esta guía cubre cómo reconfigurar el SDK de OpenAI para Node.js, ejecutar extracciones estructuradas con esquemas JSON estrictos, manejar respuestas en streaming de forma fiable y diagnosticar errores de ejecución.


Paso 1: Configuración de Base URL y autenticación

El SDK oficial de OpenAI para Node.js (openai v4+) permite anular parámetros de configuración directamente en el constructor de la clase OpenAI. Para migrar tu capa de transporte a un endpoint compatible con OpenAI, configura la propiedad baseURL y define tu clave de API.

Inicialización del 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",
});

Requisitos previos para la migración

  1. Variables de entorno: Asegúrate de separar las claves de los proveedores en tu archivo de configuración .env (por ejemplo, ONEMUX_API_KEY frente a OPENAI_API_KEY).
  2. Sufijo de ruta: El SDK de OpenAI añade automáticamente rutas como /chat/completions a la baseURL. Comprueba siempre que tu baseURL incluya el sufijo /v1 (por ejemplo, https://api.onemux.net/v1), a menos que tu gateway gestione el enrutamiento desde la raíz.
  3. Versión del SDK: Verifica que estés ejecutando la versión openai ^4.0.0 o superior, ya que las versiones v3 anteriores utilizaban una estructura de configuración distinta (la clase Configuration).

Para consultar las reglas de enrutamiento admitidas y comenzar con tus credenciales de API, visita la guía de inicio rápido de OneMux.


Paso 2: Extracción estructurada con JSON Schema estricto

Los structured outputs obligan a los modelos a devolver respuestas que se ajusten con exactitud a un JSON Schema proporcionado. Según la documentación de OpenAI sobre structured outputs, establecer "strict": true exige que:

  • Todos los campos de los objetos se enumeren en el array required.
  • Se defina explícitamente additionalProperties: false en todos los esquemas de objetos.
  • Los campos opcionales se modelen como tipos de unión con null (por ejemplo, ["string", "null"]).

Implementación estándar 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);
}

La compatibilidad con gateways downstream depende de si el endpoint del proveedor subyacente admite el modo json_schema con strict: true. Para obtener más detalles sobre las capacidades de los modelos en distintas plataformas, consulta el catálogo de modelos de OneMux.


Paso 3: Manejo de streaming con Structured Outputs

Cuando se activa stream: true junto con response_format, el modelo transmite fragmentos parciales de texto correspondientes a la carga útil JSON. A diferencia del chat de texto estándar, los chunks individuales no se pueden parsear mediante JSON.parse(), ya que representan tokens JSON incompletos.

Acumulación de chunks JSON transmitidos por streaming

Para procesar structured outputs por streaming adecuadamente, acumula los fragmentos delta hasta que la transmisión finalice o canaliza los chunks entrantes a través de un parser incremental de streams 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}`);
  }
}

Si tu pipeline utiliza streaming exclusivamente para minimizar el tiempo hasta el primer byte (TTFB) en interfaces de usuario, comprueba que el endpoint downstream soporte Server-Sent Events (SSE) continuos a través de tu proxy de red.


Paso 4: Diagnóstico y resolución de errores

Al migrar llamadas del SDK a una baseURL personalizada, el diagnóstico de errores debe diferenciar entre fallos de proxies de red, desajustes de esquema, límites de tasa y denegaciones (refusals) del modelo.

Errores frecuentes y estrategias de resolución

Código de error / TipoCausa raízDiagnóstico y solución
401 UnauthorizedAPI key no válida o ausenteConfirma que tu apiKey contiene una credencial válida y reconocida por el gateway de destino (por ejemplo, revisa ONEMUX_API_KEY).
400 BadRequestErrorInfracción de esquema (strict: true)Inspecciona el mensaje de error en busca de palabras clave de JSON schema no admitidas (por ejemplo, la ausencia de additionalProperties: false o campos omitidos en required).
404 NotFoundRuta incorrecta o ID de modelo inexistenteAsegúrate de que la baseURL termine en /v1. Confirma que el modelo solicitado exista en la lista de modelos activos del proveedor.
429 RateLimitErrorSe ha alcanzado el límite de concurrencia o gastoImplementa retroceso exponencial (exponential backoff). Monitorea los límites de cuota a través del panel de control de tu proveedor.
Refusal (Cuerpo de Choice)Activación de barreras de seguridad del modeloEl modelo ha generado una denegación explícita. Comprueba choice.message.refusal en lugar de buscar en content.

Wrapper para el manejo estructurado de errores

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;
  }
}

Paso 5: Consolidación de proveedores con OneMux

Enrutar las peticiones del SDK de OpenAI a través de OneMux simplifica las operaciones multimodelo sin necesidad de incorporar librerías de terceros o adaptadores de protocolo personalizados.

  • Endpoint unificado: Mantén una única interfaz de cliente mientras enrutas tráfico entre modelos utilizando la semántica estándar del SDK de OpenAI.
  • Gestión y visibilidad del gasto: Centraliza la facturación, revisa el consumo por modelo y define reglas de presupuesto sin tener que gestionar facturas de múltiples proveedores.
  • Modalidad flexible Pay-as-You-Go: Recarga saldo en tu cuenta mediante recargas de crédito flexibles para evitar interrupciones de servicio inesperadas.

Para consultar las tarifas de uso actualizadas y comparar opciones de enrutamiento entre modelos, revisa la guía de precios de OneMux y lee la descripción técnica en la documentación de OneMux.


Fuentes

FAQ

¿Puedo utilizar Zod con el SDK de OpenAI cuando configuro una base URL personalizada?

Sí. Si tu gateway implementa por completo la especificación de structured outputs para `/chat/completions`, métodos auxiliares como `client.beta.chat.completions.parse()` y `zodResponseFormat()` funcionan de forma nativa traduciendo los esquemas de Zod a la carga útil estricta requerida de JSON Schema.

¿Por qué el modelo devuelve un error 400 cuando se usa 'strict: true'?

El motor de structured outputs de OpenAI exige que cada definición de objeto incluya explícitamente `additionalProperties: false` y enumere todas las propiedades declaradas dentro del array `required`. Si falta alguna clave en `required` o se utilizan palabras clave de JSON Schema no compatibles, la API rechaza la solicitud con un error de validación 400.

¿Afecta el cambio de baseURL a la telemetría del SDK o a los tiempos de espera (timeouts)?

Modificar `baseURL` redirige todo el transporte HTTP hacia la nueva URL. Las opciones de timeout y reintentos (como `maxRetries` o `timeout` en el constructor de OpenAI) continúan funcionando exactamente igual en el lado del cliente.

Artículos relacionados