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

このページの内容
ストリーミングを使うと、アプリは返答全体を待たずに、生成された分から順に表示できます。このガイドでは、CheapTokens API からチャット補完をトークン単位でストリーミングし、必要に応じて停止し、エラーを処理する小さな Node.js スクリプトを作成します。このトピックの Python 版は Python でのストリーミング応答 にあります。
必要なもの#
- Node.js 22 以降(
openaiパッケージの README に記載されている最小バージョン、2026年10月22日時点で確認)。 - CheapTokens API キー。まだお持ちでない場合は、CheapTokens での最初の API リクエスト に従ってください。
- 公式 SDK:
npm install openai。
CheapTokens は https:/ で OpenAI 互換 API を公開しているため、公式の openai パッケージはベース URL とキーを変更するだけで動作します。キーはコード内ではなく環境変数に保持してください:
export CHEAPTOKENS_API_KEY="your-key"ステップ 1. for await で回答をストリーミングする#
stream.mjs を作成します(.mjs 拡張子を使うと import とトップレベルの await が使えます):
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 を渡します:
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. もあり、break で for await ループを抜けると接続も閉じられます。コードに近い方を使ってください。キャンセルボタンが別の場所にある場合はシグナル、ループ内で判断する場合(例えばストップワードを見つけた場合)は break です。
ステップ 3. エラーを処理する#
ストリームは最初のトークンの前(キーが間違っている、残高が空)または途中(ネットワーク切断)で失敗する可能性があります。リクエストとループの両方を 1 つの try で囲みます:
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には HTTPstatusが含まれます。401 はキーが間違っているか欠落している、429 はリクエストが多すぎる、トークンに関するメッセージは残高のチャージが必要であることを意味します。その他の原因と修正方法は 一般的な接続エラーとその修正方法 と 「トークンが足りません」エラー にあります。- すでに出力されたテキストはエラー後も有効です。部分的な回答を表示したい場合は保持してください。
ステップ 4. 使用量を読み取り、コストを見積もる#
usage オブジェクトには prompt_tokens と completion_ が含まれます。1M トークンあたりのモデル価格と合わせると、リクエストの正確なコストがわかります。実行前の概算には、以下のライブテーブルを使用してください。これは、各リクエストが入力 2,000 トークン、出力 800 トークンの 1,000 リクエストのコストを示しています。
| モデル | 入力 $/1M | 出力 $/1M | リクエストあたり | 1,000 リクエスト | OpenRouter、1,000 リクエスト |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.000072 | $0.072 | $0.143 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.00011 | $0.11 | $0.22 |
| MiniMax-M2.7 | $0.05 | $0.15 | $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 文字列を切り替えるだけで、自分のプロンプトで比較できます。
完全なスクリプト#
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 ドキュメント にあります。



