Retour au blog

API & SDKGuide

Diffuser des réponses de chat en Node.js avec le SDK OpenAI

Diffusez une complétion de chat jeton par jeton en Node.js : boucle for await, arrêt à la demande, gestion des erreurs et usage des jetons avec le SDK OpenAI.

Par CheapTokens TeamPublié le Mis à jour le 4 min de lecture

Fenêtre de terminal affichant une réponse diffusée jeton par jeton depuis un script Node.js
Sur cette page
  1. Ce dont vous avez besoin
  2. Étape 1. Diffuser la réponse avec for await
  3. Étape 2. Arrêter le flux à la demande
  4. Étape 3. Gérer les erreurs
  5. Étape 4. Lire l'usage et estimer le coût
  6. Script complet
  7. Étapes suivantes

Le streaming permet à votre application d'afficher la réponse au fur et à mesure de sa génération au lieu d'attendre la réponse complète. Dans ce guide, vous allez créer un petit script Node.js qui diffuse une complétion de chat depuis l'API CheapTokens jeton par jeton, l'arrête à la demande et gère les erreurs. La version Python de ce sujet se trouve dans Streaming responses in Python.

Ce dont vous avez besoin#

  • Node.js 22 ou plus récent (la version minimale indiquée dans le README du paquet openai, vérifiée le 22 octobre 2026).
  • Une clé API CheapTokens. Si vous n'en avez pas encore, suivez Your first API request with CheapTokens.
  • Le SDK officiel : npm install openai.

CheapTokens expose une API compatible OpenAI à l'adresse https://api.cheaptokens.dev/v1, donc le paquet officiel openai fonctionne en changeant uniquement l'URL de base et la clé. Gardez la clé dans une variable d'environnement, pas dans le code :

bash
export CHEAPTOKENS_API_KEY="your-key"

Étape 1. Diffuser la réponse avec for await#

Créez stream.mjs (l'extension .mjs vous permet d'utiliser import et await au niveau supérieur) :

javascript
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.CHEAPTOKENS_API_KEY,
  baseURL: 'https://api.cheaptokens.dev/v1',
});

const stream = await client.chat.completions.create({
  model: 'deepseek-ai/DeepSeek-V4-Flash-0731', // take the id from the CheapTokens docs
  messages: [{ role: 'user', content: 'Explain streaming in two sentences.' }],
  stream: true,
  stream_options: { include_usage: true },
});

let usage = null;
for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
  if (chunk.usage) usage = chunk.usage;
}

console.log('\n\nusage:', usage);

Exécutez-le avec node stream.mjs. Le SDK lit les Server-Sent Events de l'API et vous fournit un chunk par itération de for await. Trois détails comptent :

  • Le texte arrive dans chunk.choices[0].delta.content. Certains chunks ne contiennent pas de texte (le premier ne contient souvent que le rôle), donc vérifiez que delta n'est pas vide.
  • process.stdout.write s'affiche sans saut de ligne, donc les mots apparaissent les uns après les autres.
  • Avec stream_options: { include_usage: true }, le dernier chunk contient un objet usage avec les nombres de jetons de prompt et de complétion, et son tableau choices est vide. C'est pourquoi le code utilise chunk.choices[0]?. avec le chaînage optionnel.

L'identifiant du modèle provient de la documentation CheapTokens : les fournisseurs nomment différemment le même modèle, donc copiez l'identifiant depuis là.

Étape 2. Arrêter le flux à la demande#

Les utilisateurs cliquent sur Stop, ferment un onglet ou changent de chat. Annulez la requête pour ne pas attendre des jetons que personne ne lira. Passez un AbortSignal dans les options de la requête :

javascript
const controller = new AbortController();

process.on('SIGINT', () => controller.abort()); // Ctrl+C stops the stream

const stream = await client.chat.completions.create(
  {
    model: 'deepseek-ai/DeepSeek-V4-Flash-0731',
    messages: [{ role: 'user', content: 'Write a long story about a lighthouse.' }],
    stream: true,
  },
  { signal: controller.signal },
);

L'objet stream possède aussi son propre stream.controller.abort(), et sortir de la boucle for await avec break ferme également la connexion. Utilisez ce qui est le plus proche de votre code : un signal quand le bouton d'annulation se trouve ailleurs, break quand la décision est prise à l'intérieur de la boucle (par exemple, vous avez trouvé un mot d'arrêt).

Étape 3. Gérer les erreurs#

Un flux peut échouer avant le premier jeton (mauvaise clé, solde vide) ou en cours de route (coupure réseau). Enveloppez à la fois la requête et la boucle dans un seul try :

javascript
try {
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
  }
} catch (err) {
  if (err instanceof OpenAI.APIUserAbortError) {
    console.log('\n[stopped by user]');
  } else if (err instanceof OpenAI.APIError) {
    console.error(`\nAPI error ${err.status}: ${err.message}`);
  } else {
    throw err;
  }
}
  • APIUserAbortError est ce que vous obtenez après votre propre abort(). Ce n'est pas un échec, donc ne l'affichez pas comme tel.
  • APIError contient le status HTTP : 401 signifie une clé erronée ou manquante, 429 signifie trop de requêtes, et un message sur les jetons signifie que le solde doit être rechargé. D'autres causes et solutions se trouvent dans Common connection errors and how to fix them et "Not enough tokens" error.
  • Le texte déjà affiché reste valide après une erreur. Conservez-le si vous voulez afficher la réponse partielle.

Étape 4. Lire l'usage et estimer le coût#

L'objet usage contient prompt_tokens et completion_tokens. Avec le prix du modèle par 1M de jetons, il vous donne le coût exact d'une requête. Pour une estimation approximative avant d'exécuter quoi que ce soit, utilisez le tableau en direct ci-dessous : il montre le coût de 1 000 requêtes avec 2 000 jetons d'entrée et 800 jetons de sortie chacune.

Modèle Par requête 1 000 requêtes OpenRouter, 1 000 requêtes
GLM-5.3-Flash $0.000072 $0.072 $0.143
DeepSeek-V4-Flash $0.00011 $0.11 $0.22
MiniMax-M2.7 $0.00022 $0.22 $1.56

2 000 tokens d'entrée et 800 tokens de sortie par requête. Les prix par million de tokens sont actuels et évoluent avec nos tarifs ; la colonne OpenRouter indique des prix de référence pour la même charge de travail.

Pour les réponses de chat diffusées, MiniMax-M2.7 est facturé à $0.05 par 1M de jetons d'entrée et $0.15 par 1M de jetons de sortie, avec un contexte de 180K et jusqu'à 8 192 jetons de sortie par réponse. Le prix est fixe par modèle à tout moment de la journée, et un seul solde couvre tous les modèles, donc changer la chaîne model suffit pour les comparer sur vos propres prompts.

Script complet#

javascript
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.CHEAPTOKENS_API_KEY,
  baseURL: 'https://api.cheaptokens.dev/v1',
});

const controller = new AbortController();
process.on('SIGINT', () => controller.abort());

try {
  const stream = await client.chat.completions.create(
    {
      model: 'deepseek-ai/DeepSeek-V4-Flash-0731',
      messages: [{ role: 'user', content: 'Explain streaming in two sentences.' }],
      stream: true,
      stream_options: { include_usage: true },
    },
    { signal: controller.signal },
  );

  let usage = null;
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
    if (chunk.usage) usage = chunk.usage;
  }
  console.log('\n\nusage:', usage);
} catch (err) {
  if (err instanceof OpenAI.APIUserAbortError) {
    console.log('\n[stopped by user]');
  } else if (err instanceof OpenAI.APIError) {
    console.error(`\nAPI error ${err.status}: ${err.message}`);
  } else {
    throw err;
  }
}

Étapes suivantes#

  • Envoyez les chunks à un navigateur avec Server-Sent Events depuis votre propre endpoint, afin que la clé ne quitte jamais le serveur.
  • Essayez d'autres modèles en changeant uniquement la chaîne model : MiniMax-M2.7 est disponible sur le même solde et la même URL de base.
  • Obtenez une clé et exécutez le script : create a CheapTokens API key et rechargez le solde.

Les endpoints, identifiants de modèles et limites de chaque modèle se trouvent dans la CheapTokens docs.