ブログに戻る

APIとSDKガイド

OpenAI SDK を使って Node.js でチャット応答をストリーミングする

Node.js でチャット補完をトークン単位でストリーミング: for await ループ、必要に応じた停止、エラー処理、OpenAI SDK でのトークン使用量。

投稿者: CheapTokens Team公開日 更新日 読了時間 5 分

Node.js スクリプトからトークン単位でストリーミングされた回答を表示するターミナルウィンドウ
このページの内容
  1. 必要なもの
  2. ステップ 1. for await で回答をストリーミングする
  3. ステップ 2. 必要に応じてストリームを停止する
  4. ステップ 3. エラーを処理する
  5. ステップ 4. 使用量を読み取り、コストを見積もる
  6. 完全なスクリプト
  7. 次のステップ

ストリーミングを使うと、アプリは返答全体を待たずに、生成された分から順に表示できます。このガイドでは、CheapTokens API からチャット補完をトークン単位でストリーミングし、必要に応じて停止し、エラーを処理する小さな Node.js スクリプトを作成します。このトピックの Python 版は Python でのストリーミング応答 にあります。

必要なもの#

  • Node.js 22 以降(openai パッケージの README に記載されている最小バージョン、2026年10月22日時点で確認)。
  • CheapTokens API キー。まだお持ちでない場合は、CheapTokens での最初の API リクエスト に従ってください。
  • 公式 SDK: npm install openai。

CheapTokens は https://api.cheaptokens.dev/v1 で OpenAI 互換 API を公開しているため、公式の 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 は API から Server-Sent Events を読み取り、for await の各イテレーションで 1 つのチャンクを渡します。重要な点が 3 つあります:

  • テキストは chunk.choices[0].delta.content で届きます。テキストを含まないチャンクもあります(最初のチャンクはロールのみを持つことが多い)ので、delta が空でないことを確認してください。
  • process.stdout.write は改行なしで出力するため、単語が次々と表示されます。
  • stream_options: { include_usage: true } を指定すると、最後のチャンクにはプロンプトと補完のトークン数を含む usage オブジェクトが含まれ、その choices 配列は空になります。そのため、コードではオプショナルチェーン付きの chunk.choices[0]?. を使用しています。

モデル ID は CheapTokens のドキュメントから取得します。プロバイダーによって同じモデルの名前が異なるため、そこから ID をコピーしてください。

ステップ 2. 必要に応じてストリームを停止する#

ユーザーが停止を押したり、タブを閉じたり、チャットを切り替えたりします。誰も読まないトークンを待たないように、リクエストをキャンセルしてください。リクエストオプションで 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() もあり、break で for await ループを抜けると接続も閉じられます。コードに近い方を使ってください。キャンセルボタンが別の場所にある場合はシグナル、ループ内で判断する場合(例えばストップワードを見つけた場合)は break です。

ステップ 3. エラーを処理する#

ストリームは最初のトークンの前(キーが間違っている、残高が空)または途中(ネットワーク切断)で失敗する可能性があります。リクエストとループの両方を 1 つの 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 はリクエストが多すぎる、トークンに関するメッセージは残高のチャージが必要であることを意味します。その他の原因と修正方法は 一般的な接続エラーとその修正方法 と 「トークンが足りません」エラー にあります。
  • すでに出力されたテキストはエラー後も有効です。部分的な回答を表示したい場合は保持してください。

ステップ 4. 使用量を読み取り、コストを見積もる#

usage オブジェクトには prompt_tokens と completion_tokens が含まれます。1M トークンあたりのモデル価格と合わせると、リクエストの正確なコストがわかります。実行前の概算には、以下のライブテーブルを使用してください。これは、各リクエストが入力 2,000 トークン、出力 800 トークンの 1,000 リクエストのコストを示しています。

モデル リクエストあたり 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 出力トークン。100万トークンあたりの価格は現行のもので、当社の料金体系に合わせて変動します。OpenRouter 列は同じワークロードの参考価格を示しています。

ストリーミングチャット応答の場合、MiniMax-M2.7 は 1M 入力トークンあたり $0.05、1M 出力トークンあたり $0.15 で、コンテキストは 180K、回答あたり最大 8,192 出力トークンです。価格はモデルごとに時刻に関係なく固定されており、1 つの残高で全モデルをカバーするため、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 で利用できます。
  • キーを取得してスクリプトを実行します: CheapTokens API キーを作成 し、残高をチャージします。

すべてのモデルのエンドポイント、モデル ID、制限は CheapTokens ドキュメント にあります。