Powrót do bloga

API i SDKPrzewodnik

Strumieniuj odpowiedzi czatu w Node.js z OpenAI SDK

Strumieniuj uzupełnienie czatu token po tokenie w Node.js: pętla for await, zatrzymywanie na żądanie, obsługa błędów i użycie tokenów z OpenAI SDK.

Autor: CheapTokens TeamOpublikowano Zaktualizowano 4 min czytania

Okno terminala drukujące strumieniowaną odpowiedź token po tokenie ze skryptu Node.js
Na tej stronie
  1. Czego potrzebujesz
  2. Krok 1. Strumieniuj odpowiedź za pomocą for await
  3. Krok 2. Zatrzymaj strumień na żądanie
  4. Krok 3. Obsługa błędów
  5. Krok 4. Odczytaj użycie i oszacuj koszt
  6. Pełny skrypt
  7. Następne kroki

Strumieniowanie pozwala Twojej aplikacji pokazywać odpowiedź w miarę jej generowania, zamiast czekać na całą odpowiedź. W tym przewodniku zbudujesz mały skrypt Node.js, który strumieniuje uzupełnienie czatu z API CheapTokens token po tokenie, zatrzymuje je na żądanie i obsługuje błędy. Wersja w Pythonie tego tematu znajduje się w Strumieniowanie odpowiedzi w Pythonie.

Czego potrzebujesz#

  • Node.js 22 lub nowszy (minimalna wersja podana w README pakietu openai, sprawdzona 22 października 2026).
  • Klucz API CheapTokens. Jeśli jeszcze go nie masz, postępuj zgodnie z Twoje pierwsze żądanie API z CheapTokens.
  • Oficjalne SDK: npm install openai.

CheapTokens udostępnia API kompatybilne z OpenAI pod https://api.cheaptokens.dev/v1, więc oficjalny pakiet openai działa po zmianie tylko bazowego URL i klucza. Trzymaj klucz w zmiennej środowiskowej, a nie w kodzie:

bash
export CHEAPTOKENS_API_KEY="your-key"

Krok 1. Strumieniuj odpowiedź za pomocą for await#

Utwórz stream.mjs (rozszerzenie .mjs pozwala używać import i najwyższego poziomu 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);

Uruchom go za pomocą node stream.mjs. SDK odczytuje Server-Sent Events z API i przekazuje Ci jeden fragment na każdą iterację for await. Trzy szczegóły mają znaczenie:

  • Tekst przychodzi w chunk.choices[0].delta.content. Niektóre fragmenty nie zawierają tekstu (pierwszy często zawiera tylko rolę), więc sprawdź, czy delta nie jest puste.
  • process.stdout.write drukuje bez znaku nowej linii, więc słowa pojawiają się jedno po drugim.
  • W stream_options: { include_usage: true } ostatni fragment zawiera obiekt usage z liczbą tokenów promptu i uzupełnienia, a jego tablica choices jest pusta. Dlatego kod używa chunk.choices[0]?. z opcjonalnym łańcuchowaniem.

Identyfikator modelu pochodzi z dokumentacji CheapTokens: dostawcy nazywają ten sam model różnie, więc skopiuj identyfikator stamtąd.

Krok 2. Zatrzymaj strumień na żądanie#

Użytkownicy naciskają Stop, zamykają kartę lub przełączają czaty. Anuluj żądanie, aby nie czekać na tokeny, których nikt nie przeczyta. Przekaż AbortSignal w opcjach żądania:

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

Obiekt strumienia ma również własne stream.controller.abort(), a wyjście z pętli for await za pomocą break również zamyka połączenie. Użyj tego, co jest bliżej Twojego kodu: sygnału, gdy przycisk anulowania znajduje się gdzie indziej, break, gdy decyzja jest podejmowana wewnątrz pętli (na przykład znalazłeś słowo stop).

Krok 3. Obsługa błędów#

Strumień może zawieść przed pierwszym tokenem (zły klucz, puste saldo) lub w środku (utrata sieci). Otocz zarówno żądanie, jak i pętlę jednym 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 to, co otrzymujesz po własnym abort(). To nie jest błąd, więc nie pokazuj go jako błędu.
  • APIError ma HTTP status: 401 oznacza zły lub brakujący klucz, 429 oznacza zbyt wiele żądań, a komunikat o tokenach oznacza, że saldo wymaga doładowania. Więcej przyczyn i rozwiązań znajduje się w Typowe błędy połączenia i jak je naprawić i Błąd "Not enough tokens".
  • Tekst, który został już wydrukowany, pozostaje ważny po błędzie. Zachowaj go, jeśli chcesz pokazać częściową odpowiedź.

Krok 4. Odczytaj użycie i oszacuj koszt#

Obiekt usage zawiera prompt_tokens i completion_tokens. Razem z ceną modelu za 1M tokenów daje to dokładny koszt żądania. Aby uzyskać przybliżony szacunek przed uruchomieniem czegokolwiek, użyj poniższej tabeli na żywo: pokazuje koszt 1 000 żądań z 2 000 tokenów wejściowych i 800 tokenów wyjściowych każde.

Model Za żądanie 1000 żądań OpenRouter, 1000 żądań
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

2000 tokenów wejściowych i 800 tokenów wyjściowych na żądanie. Ceny za 1 mln tokenów są aktualne i zmieniają się wraz z naszymi taryfami; kolumna OpenRouter pokazuje ceny referencyjne dla tego samego obciążenia.

Dla strumieniowanych odpowiedzi czatu MiniMax-M2.7 jest wyceniany na $0.05 za 1M tokenów wejściowych i $0.15 za 1M tokenów wyjściowych, z kontekstem 180K i do 8 192 tokenów wyjściowych na odpowiedź. Cena jest stała dla modelu o każdej porze dnia, a jedno saldo obejmuje wszystkie modele, więc zmiana ciągu model wystarczy, aby porównać je na własnych promptach.

Pełny skrypt#

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

Następne kroki#

  • Wysyłaj fragmenty do przeglądarki za pomocą Server-Sent Events z własnego punktu końcowego, aby klucz nigdy nie opuszczał serwera.
  • Wypróbuj inne modele, zmieniając tylko ciąg model: MiniMax-M2.7 jest dostępny na tym samym saldzie i pod tym samym bazowym URL.
  • Zdobądź klucz i uruchom skrypt: utwórz klucz API CheapTokens i doładuj saldo.

Punkty końcowe, identyfikatory modeli i limity dla każdego modelu znajdują się w dokumentacji CheapTokens.