Zurück zum Blog

API & SDKAnleitung

Chat-Antworten in Node.js mit dem OpenAI SDK streamen

Streame eine Chat-Completion Token für Token in Node.js: for-await-Schleife, Stoppen auf Anfrage, Fehlerbehandlung und Token-Nutzung mit dem OpenAI SDK.

Von CheapTokens TeamVeröffentlicht am Aktualisiert am 4 Min. Lesezeit

Terminalfenster, das eine gestreamte Antwort Token für Token aus einem Node.js-Skript ausgibt
Auf dieser Seite
  1. Was du brauchst
  2. Schritt 1. Die Antwort mit for await streamen
  3. Schritt 2. Den Stream auf Anfrage stoppen
  4. Schritt 3. Fehler behandeln
  5. Schritt 4. Nutzung lesen und Kosten schätzen
  6. Vollständiges Skript
  7. Nächste Schritte

Streaming ermöglicht es deiner App, die Antwort anzuzeigen, während sie generiert wird, anstatt auf die vollständige Antwort zu warten. In dieser Anleitung erstellst du ein kleines Node.js-Skript, das eine Chat-Completion von der CheapTokens-API Token für Token streamt, sie auf Anfrage stoppt und Fehler behandelt. Die Python-Version dieses Themas findest du in Streaming responses in Python.

Was du brauchst#

  • Node.js 22 oder neuer (die im openai-Paket-README angegebene Mindestversion, geprüft am 22. Oktober 2026).
  • Einen CheapTokens-API-Schlüssel. Wenn du noch keinen hast, folge Your first API request with CheapTokens.
  • Das offizielle SDK: npm install openai.

CheapTokens stellt eine OpenAI-kompatible API unter https://api.cheaptokens.dev/v1 bereit, sodass das offizielle openai-Paket funktioniert, indem du nur die Basis-URL und den Schlüssel änderst. Bewahre den Schlüssel in einer Umgebungsvariable auf, nicht im Code:

bash
export CHEAPTOKENS_API_KEY="your-key"

Schritt 1. Die Antwort mit for await streamen#

Erstelle stream.mjs (die .mjs-Erweiterung ermöglicht dir die Verwendung von import und Top-Level-await):

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

Führe es mit node stream.mjs aus. Das SDK liest die Server-Sent Events von der API und übergibt dir pro Iteration von for await einen Chunk. Drei Details sind wichtig:

  • Text kommt in chunk.choices[0].delta.content an. Manche Chunks enthalten keinen Text (der erste enthält oft nur die Rolle), prüfe also, dass delta nicht leer ist.
  • process.stdout.write gibt ohne Zeilenumbruch aus, sodass die Wörter nacheinander erscheinen.
  • Mit stream_options: { include_usage: true } enthält der letzte Chunk ein usage-Objekt mit Prompt- und Completion-Token-Zählungen, und sein choices-Array ist leer. Deshalb verwendet der Code chunk.choices[0]?. mit optional chaining.

Die Modell-ID stammt aus der CheapTokens-Dokumentation: Anbieter benennen dasselbe Modell unterschiedlich, kopiere die ID also von dort.

Schritt 2. Den Stream auf Anfrage stoppen#

Benutzer drücken Stop, schließen einen Tab oder wechseln den Chat. Brich die Anfrage ab, damit du nicht auf Tokens wartest, die niemand lesen wird. Übergib ein AbortSignal in den Request-Optionen:

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

Das Stream-Objekt hat auch sein eigenes stream.controller.abort(), und das Verlassen der for await-Schleife mit break schließt die Verbindung ebenfalls. Verwende das, was näher an deinem Code liegt: ein Signal, wenn der Abbrechen-Button woanders liegt, break, wenn die Entscheidung innerhalb der Schleife getroffen wird (zum Beispiel, wenn du ein Stoppwort gefunden hast).

Schritt 3. Fehler behandeln#

Ein Stream kann vor dem ersten Token fehlschlagen (falscher Schlüssel, leeres Guthaben) oder mittendrin (Netzwerkabbruch). Umschließe sowohl die Anfrage als auch die Schleife mit einem 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 ist das, was du nach deinem eigenen abort() bekommst. Es ist kein Fehler, also zeige es nicht als solchen an.
  • APIError hat den HTTP-status: 401 bedeutet ein falscher oder fehlender Schlüssel, 429 bedeutet zu viele Anfragen, und eine Meldung über Tokens bedeutet, dass das Guthaben aufgeladen werden muss. Weitere Ursachen und Lösungen findest du in Common connection errors and how to fix them und "Not enough tokens" error.
  • Text, der bereits ausgegeben wurde, bleibt nach einem Fehler gültig. Behalte ihn, wenn du die Teilantwort anzeigen möchtest.

Schritt 4. Nutzung lesen und Kosten schätzen#

Das usage-Objekt enthält prompt_tokens und completion_tokens. Zusammen mit dem Modellpreis pro 1M Tokens ergibt sich daraus die exakten Kosten einer Anfrage. Für eine grobe Schätzung vor der Ausführung verwende die Live-Tabelle unten: Sie zeigt die Kosten für 1.000 Anfragen mit jeweils 2.000 Eingabe- und 800 Ausgabe-Tokens.

Modell Pro Anfrage 1.000 Anfragen OpenRouter, 1.000 Anfragen
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 Input- und 800 Output-Tokens pro Anfrage. Die Preise pro 1 Mio. Tokens sind aktuell und ändern sich zusammen mit unseren Tarifen; die OpenRouter-Spalte zeigt Referenzpreise für dieselbe Arbeitslast.

Für gestreamte Chat-Antworten kostet MiniMax-M2.7 $0.05 pro 1M Eingabe- und $0.15 pro 1M Ausgabe-Tokens, mit einem Kontext von 180K und bis zu 8.192 Ausgabe-Tokens pro Antwort. Der Preis ist pro Modell zu jeder Tageszeit fest, und ein Guthaben deckt alle Modelle ab, sodass das Ändern des model-Strings ausreicht, um sie mit deinen eigenen Prompts zu vergleichen.

Vollständiges Skript#

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

Nächste Schritte#

  • Sende die Chunks mit Server-Sent Events von deinem eigenen Endpunkt an einen Browser, damit der Schlüssel den Server nie verlässt.
  • Probiere andere Modelle aus, indem du nur den model-String änderst: MiniMax-M2.7 ist mit demselben Guthaben und derselben Basis-URL verfügbar.
  • Hole dir einen Schlüssel und führe das Skript aus: create a CheapTokens API key und lade das Guthaben auf.

Endpunkte, Modell-IDs und Limits für jedes Modell findest du in den CheapTokens docs.