Назад до блогу

API та SDKПосібник

Стрімте відповіді чату в Node.js за допомогою OpenAI SDK

Стрімте завершення чату токен за токеном у Node.js: цикл for await, зупинка на вимогу, обробка помилок і використання токенів за допомогою OpenAI SDK.

Автор: CheapTokens TeamОпубліковано Оновлено 4 хв читання

Вікно терміналу, що друкує стрімовану відповідь токен за токеном зі скрипта Node.js
На цій сторінці
  1. Що вам потрібно
  2. Крок 1. Стрімте відповідь за допомогою for await
  3. Крок 2. Зупиніть стрім на вимогу
  4. Крок 3. Обробляйте помилки
  5. Крок 4. Читайте використання та оцінюйте вартість
  6. Повний скрипт
  7. Наступні кроки

Стрімінг дозволяє вашому застосунку показувати відповідь у міру її генерації, а не чекати на всю відповідь. У цьому посібнику ви створите невеликий скрипт Node.js, який стрімить завершення чату з API CheapTokens токен за токеном, зупиняє його на вимогу та обробляє помилки. Версія цієї теми для Python — у Стрімінгові відповіді в Python.

Що вам потрібно#

  • Node.js 22 або новіший (мінімальна версія, зазначена в README пакета openai, перевірено 22 жовтня 2026 року).
  • Ключ API CheapTokens. Якщо у вас його ще немає, дотримуйтеся Вашого першого запиту до API з CheapTokens.
  • Офіційний SDK: npm install openai.

CheapTokens надає API, сумісний з OpenAI, за адресою https://api.cheaptokens.dev/v1, тому офіційний пакет openai працює, якщо змінити лише базову URL-адресу та ключ. Тримайте ключ у змінній середовища, а не в коді:

bash
export CHEAPTOKENS_API_KEY="your-key"

Крок 1. Стрімте відповідь за допомогою for await#

Створіть stream.mjs (розширення .mjs дозволяє використовувати import та 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);

Запустіть його за допомогою node stream.mjs. SDK читає Server-Sent Events з API і передає вам один фрагмент за кожну ітерацію for await. Важливі три деталі:

  • Текст надходить у chunk.choices[0].delta.content. Деякі фрагменти не містять тексту (перший часто містить лише роль), тому перевіряйте, що delta не порожній.
  • process.stdout.write друкує без перенесення рядка, тому слова з'являються одне за одним.
  • З stream_options: { include_usage: true } останній фрагмент містить об'єкт usage з кількістю токенів запиту та завершення, а його масив choices порожній. Саме тому код використовує chunk.choices[0]?. з опціональним ланцюжком.

Ідентифікатор моделі береться з документації CheapTokens: провайдери називають ту саму модель по-різному, тому копіюйте ідентифікатор звідти.

Крок 2. Зупиніть стрім на вимогу#

Користувачі натискають Stop, закривають вкладку або перемикають чати. Скасуйте запит, щоб не чекати на токени, які ніхто не прочитає. Передайте AbortSignal в опціях запиту:

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

Об'єкт стріму також має власний stream.controller.abort(), а вихід із циклу for await за допомогою break також закриває з'єднання. Використовуйте те, що ближче до вашого коду: сигнал, коли кнопка скасування розташована в іншому місці, break, коли рішення ухвалюється всередині циклу (наприклад, ви знайшли стоп-слово).

Крок 3. Обробляйте помилки#

Стрім може зазнати невдачі до першого токена (неправильний ключ, порожній баланс) або посередині (обрив мережі). Обгорніть і запит, і цикл в один 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 — це те, що ви отримуєте після власного abort(). Це не збій, тому не показуйте його як збій.
  • APIError має HTTP status: 401 означає неправильний або відсутній ключ, 429 означає забагато запитів, а повідомлення про токени означає, що баланс потрібно поповнити. Більше причин і виправлень — у Поширені помилки з'єднання та як їх виправити та Помилка "Not enough tokens".
  • Текст, який уже надруковано, залишається дійсним після помилки. Збережіть його, якщо хочете показати часткову відповідь.

Крок 4. Читайте використання та оцінюйте вартість#

Об'єкт usage містить prompt_tokens та completion_tokens. Разом із ціною моделі за 1M токенів він дає вам точну вартість запиту. Для приблизної оцінки перед запуском чогось використовуйте живу таблицю нижче: вона показує вартість 1 000 запитів із 2 000 вхідних і 800 вихідних токенів кожен.

Модель За запит 1 000 запитів OpenRouter, 1 000 запитів
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 вхідних і 800 вихідних токенів на запит. Ціни за 1 млн токенів актуальні та змінюються разом із нашими тарифами; стовпець OpenRouter показує довідкові ціни для того самого навантаження.

Для стрімових відповідей чату MiniMax-M2.7 коштує $0.05 за 1M вхідних і $0.15 за 1M вихідних токенів, із контекстом 180K і до 8 192 вихідних токенів на відповідь. Ціна фіксована для моделі в будь-який час доби, і один баланс покриває всі моделі, тому достатньо змінити рядок model, щоб порівняти їх на власних запитах.

Повний скрипт#

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

Наступні кроки#

  • Надсилайте фрагменти до браузера за допомогою Server-Sent Events зі своєї кінцевої точки, щоб ключ ніколи не залишав сервер.
  • Спробуйте інші моделі, змінивши лише рядок model: MiniMax-M2.7 доступна на тому самому балансі та тій самій базовій URL-адресі.
  • Отримайте ключ і запустіть скрипт: створіть ключ API CheapTokens і поповніть баланс.

Кінцеві точки, ідентифікатори моделей і ліміти для кожної моделі — у документації CheapTokens.