使用 OpenAI SDK 在 Node.js 中流式传输聊天响应
在 Node.js 中逐 token 流式获取聊天补全:for await 循环、按需停止、错误处理以及使用 OpenAI SDK 的 token 用量。

流式传输让你的应用在答案生成时就能展示,而不必等待完整回复。在本指南中,你将构建一个小型 Node.js 脚本,从 CheapTokens API 逐 token 流式获取聊天补全,按需停止并处理错误。本主题的 Python 版本见 Python 中的流式响应。
你需要准备#
- Node.js 22 或更新版本(
openai包 README 中声明的最低版本,检查于 2026 年 10 月 22 日)。 - 一个 CheapTokens API 密钥。如果还没有,请按照 使用 CheapTokens 发出你的第一个 API 请求 操作。
- 官方 SDK:
npm install openai。
CheapTokens 在 https:/ 提供兼容 OpenAI 的 API,因此官方 openai 包只需更改 base 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 的每次迭代中交给你一个数据块。有三个细节很重要:
- 文本位于
chunk.中。有些数据块不携带文本(第一个通常只包含 role),因此要检查choices[0]. delta. content delta是否为空。 process.打印时不换行,因此单词会一个接一个出现。stdout. write - 使用
stream_时,最后一个数据块携带一个options: { include_ usage: true } usage对象,包含提示和补全的 token 计数,其choices数组为空。这就是代码使用带可选链的chunk.的原因。choices[0]?.
模型 id 来自 CheapTokens 文档:不同提供商对同一模型的命名不同,因此请从那里复制 id。
第 2 步。按需停止流#
用户会按下停止、关闭标签页或切换聊天。取消请求,这样你就不必等待没人会读取的 token。在请求选项中传入一个 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 循环也会关闭连接。选择离你代码更近的方式:当取消按钮位于别处时使用 signal,当决定在循环内部做出时(例如你发现了停止词)使用 break。
第 3 步。处理错误#
流可能在第一个 token 之前失败(密钥错误、余额为空),也可能在中间失败(网络中断)。将请求和循环都包在一个 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 表示请求过多,关于 token 的消息表示余额需要充值。更多原因和修复方法见 常见连接错误及如何修复 和 "Not enough tokens" 错误。- 已经打印的文本在出错后仍然有效。如果你想展示部分答案,可以保留它。
第 4 步。读取用量并估算成本#
usage 对象包含 prompt_tokens 和 completion_。结合每 1M token 的模型价格,你就能得到一次请求的精确成本。若要在运行任何内容之前进行粗略估算,请使用下面的实时表格:它显示 1,000 次请求的成本,每次请求 2,000 个输入 token 和 800 个输出 token。
| 模型 | 输入 $/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 个输入 token 和 800 个输出 token。每 100 万 token 的价格为当前价格,会随我们的资费一同变动;OpenRouter 列显示相同工作负载的参考价格。
对于流式聊天回复,MiniMax-M2.7 的价格为每 1M 输入 token $0.05、每 1M 输出 token $0.15,上下文为 180K,每个答案最多 8,192 个输出 token。价格在任何时段都按模型固定,一个余额覆盖所有模型,因此只需切换 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 可在同一余额和同一 base URL 上使用。 - 获取密钥并运行脚本:创建 CheapTokens API 密钥 并充值余额。
每个模型的端点、模型 id 和限制都在 CheapTokens 文档 中。



