GuidesOneMux

Migrer des applications Node.js avec le SDK OpenAI : Base URL, streaming et sorties structurées

Un guide technique pour les développeurs Node.js migrant leurs applications avec le SDK OpenAI : configuration d'une base URL personnalisée, gestion de l'authentification, streaming de sorties structurées et diagnostic des erreurs d'API.

Sur cette page

Introduction

Migrer une application Node.js existante qui utilise le SDK officiel openai ne nécessite pas de réécrire votre logique métier. Dans la mesure où de nombreux fournisseurs de modèles unifiés respectent scrupuleusement les spécifications REST d'OpenAI, adapter une application existante pour transiter par une couche de routage compatible OpenAI — telle que OneMux — requiert uniquement d'ajuster les paramètres d'initialisation de votre client.

Cependant, les pipelines en production reposent fréquemment sur des fonctionnalités avancées : respect strict des schémas pour l'extraction de données, streaming token par token et reprise robuste sur erreur. Ce guide explique comment reconfigurer le SDK OpenAI pour Node.js, exécuter une extraction structurée via JSON Schema, traiter les réponses en streaming de façon fiable et diagnostiquer les erreurs d'exécution.


Étape 1 : Configuration de la Base URL et de l'authentification

Le SDK officiel OpenAI pour Node.js (openai v4+) accepte des surcharges de configuration directement dans le constructeur de la classe OpenAI. Pour migrer votre couche de transport vers un point de terminaison compatible OpenAI, configurez la propriété baseURL et définissez votre clé d'API.

Initialisation du client 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érequis de migration

  1. Variables d'environnement : Veillez à bien séparer les clés de vos fournisseurs dans votre configuration .env (par exemple, ONEMUX_API_KEY contre OPENAI_API_KEY).
  2. Suffixe de chemin : Le SDK OpenAI ajoute automatiquement les chemins de route comme /chat/completions à la baseURL. Assurez-vous systématiquement que votre baseURL inclut le suffixe /v1 (ex. https://api.onemux.net/v1), à moins que votre passerelle ne prenne en charge le routage racine.
  3. Version du SDK : Vérifiez que vous utilisez openai en version ^4.0.0 ou supérieure, car les anciennes versions v3 s'appuyaient sur une structure de configuration différente (la classe Configuration).

Pour consulter les règles de routage prises en charge et démarrer avec vos identifiants d'API, rendez-vous sur le Guide de démarrage rapide OneMux.


Étape 2 : Extraction structurée avec JSON Schema strict

Les sorties structurées forcent les modèles à renvoyer un résultat strictement conforme au schéma JSON fourni. Selon la documentation d'OpenAI relative aux sorties structurées, définir "strict": true impose que :

  • Tous les champs de l'objet soient répertoriés dans le tableau required.
  • additionalProperties: false soit explicitement défini sur tous les schémas d'objets.
  • Les champs optionnels soient modélisés sous forme de types union avec null (ex. ["string", "null"]).

Implémentation d'une complétion structurée standard

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 compatibilité avec les passerelles sous-jacentes dépend de la prise en charge ou non du mode json_schema avec strict: true par le point de terminaison du fournisseur sélectionné. Pour plus de précisions sur les fonctionnalités des modèles selon les plateformes, consultez le Catalogue des modèles OneMux.


Étape 3 : Gestion du streaming avec des sorties structurées

Lorsque stream: true est activé en conjonction avec response_format, le modèle diffuse des fragments de chaînes partielles du payload JSON. À la différence d'un chat textuel standard, les blocs individuels ne peuvent pas être directement analysés avec JSON.parse(), car ils correspondent à des jetons JSON incomplets.

Accumulation des blocs JSON reçus en streaming

Pour diffuser correctement des sorties structurées, accumulez les fragments de deltas jusqu'à la fin du flux, ou redirigez les blocs entrants vers un parseur de flux JSON incrémental.

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 votre pipeline exploite le streaming uniquement pour réduire le délai d'affichage du premier octet (TTFB) dans les interfaces utilisateur, vérifiez que le point de terminaison sous-jacent prend bien en charge les événements Server-Sent Events (SSE) continus à travers votre proxy réseau.


Étape 4 : Diagnostic des erreurs et dépannage

Lors de la migration d'appels SDK vers une baseURL personnalisée, le diagnostic d'erreurs doit faire la distinction entre les défaillances de proxy réseau, les schémas mal configurés, les limites de débit et les refus du modèle.

Erreurs fréquentes et stratégies de résolution

Code / Type d'erreurCause racineDiagnostic et résolution
401 UnauthorizedClé d'API invalide ou manquanteVérifiez que votre apiKey contient un identifiant valide reconnu par la passerelle ciblée (par exemple, contrôlez ONEMUX_API_KEY).
400 BadRequestErrorViolation de schéma (strict: true)Inspectez le message d'erreur pour détecter les mots-clés de schéma JSON non supportés (ex. absence de additionalProperties: false ou champs manquants dans required).
404 NotFoundChemin ou identifiant de modèle incorrectAssurez-vous que la baseURL se termine par /v1. Vérifiez que le modèle demandé est bien présent dans la liste des modèles actifs du fournisseur.
429 RateLimitErrorPlafond de concurrence ou de budget atteintImplémentez un backoff exponentiel. Surveillez vos limites de quota depuis le tableau de bord de votre fournisseur.
Refusal (Corps du choice)Garde-fous de sécurité du modèle déclenchésLe modèle a émis un refus explicite. Inspectez choice.message.refusal plutôt que de lire content.

Enveloppeur structuré pour la gestion d'erreurs

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

Étape 5 : Consolider les fournisseurs via OneMux

Routier vos requêtes SDK OpenAI via OneMux simplifie les opérations multi-modèles sans imposer de bibliothèques tierces ni d'adaptateurs de protocole sur mesure.

  • Point de terminaison unifié : Conservez une interface client unique tout en routant vos requêtes vers divers modèles grâce à la sémantique standard du SDK OpenAI.
  • Visibilité et gestion des dépenses : Centralisez la facturation, suivez l'utilisation par modèle et définissez des règles de dépenses sans devoir gérer de multiples factures de prestataires.
  • Paiement à l'usage flexible : Approvisionnez votre compte via des recharges de crédits souples afin de prévenir toute interruption imprévue de service.

Pour connaître les tarifs en vigueur et comparer les options de routage entre modèles, consultez le Guide des tarifs OneMux ainsi que la présentation technique dans la Documentation OneMux.


Sources

FAQ

Puis-je utiliser Zod avec le SDK OpenAI lors de l'utilisation d'une base URL personnalisée ?

Oui. Dès lors que votre passerelle implémente intégralement la spécification des sorties structurées pour `/chat/completions`, les méthodes utilitaires telles que `client.beta.chat.completions.parse()` et `zodResponseFormat()` fonctionnent sans friction en traduisant les schémas Zod dans le format JSON Schema strict attendu.

Pourquoi le modèle renvoie-t-il une erreur 400 lors de l'utilisation de 'strict: true' ?

Le moteur de sorties structurées d'OpenAI exige que chaque définition d'objet comporte explicitement `additionalProperties: false` et référence l'ensemble des propriétés déclarées dans le tableau `required`. Si une clé manque dans `required`, ou si des mots-clés de schéma JSON non supportés sont présents, l'API rejette la requête avec une erreur de validation 400.

Le changement de la baseURL affecte-t-il la télémétrie ou les délais d'expiration (timeouts) du SDK ?

La surcharge de `baseURL` redirige l'intégralité du transport HTTP vers la nouvelle URL. Les options de délai d'attente et de nouvelle tentative (comme `maxRetries` ou `timeout` dans le constructeur OpenAI) continuent de fonctionner à l'identique côté client.

Articles liés