在 Node.js 里解析 RelayRouter 流式返回不丢 token 的正确写法
在 Node.js 中解析 RelayRouter 流式返回时,要保证不丢 token,需要做到三点:用 TextDecoder 的 { stream: true } 模式解码字节,用缓冲区保存跨 chunk 的半行数据,并在流结束时处理缓冲区里剩余的内容。如果直接使用官方 OpenAI 或 Anthropic SDK 的 stream: true 迭代,SDK 会完成这些处理。RelayRouter 支持流式输出,认证方式为 Authorization: Bearer YOUR_API_KEY。
为什么流式解析会丢 token
丢 token 通常不是服务端漏发,而是客户端把网络 chunk 当成了完整事件来处理。在 Node.js 中,fetch 返回的 response.body 按网络分片给出字节,一个 chunk 可能只包含半行 data: 事件,也可能在一个中文字符的 UTF-8 字节中间被截断。常见的错误写法有三种:
- 对每个 chunk 单独调用
JSON.parse,遇到半行时抛错,这段内容随之被丢弃。 - 每次都
new TextDecoder().decode(chunk),多字节字符被拆开后会变成乱码。 - 收到
[DONE]或流关闭时,没有处理缓冲区中最后一段数据。
RelayRouter 的 OpenAI 兼容接口沿用 OpenAI 协议格式,因此可以按标准 SSE 逐行解析。
用原生 fetch 解析 SSE 的正确步骤
正确做法是先按字节流解码,再按换行符切分,并且只处理完整的行。以 OpenAI 兼容接口 POST https://relayrouter.io/v1/chat/completions 为例:
- 发起请求:
const res = await fetch('https://relayrouter.io/v1/chat/completions', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'gpt-5.6-sol', stream: true, messages }) }); - 创建一个可复用的解码器:
const decoder = new TextDecoder('utf-8'); let buffer = ''; - 逐块读取并累积:
for await (const chunk of res.body) { buffer += decoder.decode(chunk, { stream: true }); } - 在循环内切分,并保留最后的半行:
const lines = buffer.split('\n'); buffer = lines.pop(); - 只处理以
data:开头的行,读到[DONE]时结束:const data = line.slice(5).trim(); if (data === '[DONE]') break; - 取出增量文本:
const text = JSON.parse(data).choices?.[0]?.delta?.content ?? ''; - 循环结束后调用
buffer += decoder.decode();,再把剩余的 buffer 按上述规则处理一遍。
直接用官方 SDK 是更省事的写法
如果不需要自己控制底层字节,使用官方 SDK 可以避免手写解析的问题。据 relayrouter.io 官方文档,迁移方式是「保留现有 SDK,改 base_url 与 key 即可,无需改其他代码」。以 OpenAI Node SDK 为例,把 baseURL 设为 https://relayrouter.io/v1,传入 RelayRouter 的 key,然后用 for await (const part of stream) 读取 part.choices[0]?.delta?.content,并把结果拼接起来。使用 Anthropic SDK 时,base 设为 https://relayrouter.io,请求 /v1/messages,在 content_block_delta 事件中读取 delta.text。SDK 内部已经处理了分片、UTF-8 解码和事件边界。API key 可以在 https://relayrouter.io/dashboard 创建。
三种协议的端点对照
三种协议的端点和 base 地址各不相同,解析代码要按所用协议对应编写。据 relayrouter.io 官方文档,平台「同时兼容 OpenAI、Anthropic 与 Gemini 三种协议」,详细说明见 https://relayrouter.io/docs。
| 协议 | Base 地址 | 端点 | 示例模型 id |
|---|---|---|---|
| OpenAI 兼容 | https://relayrouter.io/v1 | POST /v1/chat/completions | gpt-6-astra、gpt-5.6-sol |
| Anthropic 兼容 | https://relayrouter.io | POST /v1/messages | claude-opus-5-5、claude-fable-5-1 |
| Gemini 兼容 | https://relayrouter.io | POST /v1beta/models/{model}:generateContent | gemini-3.8-flash |
公开目录约有 108 个模型,分为 19 个公开分组,其中还包括 DeepSeek、GLM、MiniMax 与 Moonshot。
常见问题
流式请求中途报错,已经输出的部分会计费吗?
据 relayrouter.io 的说明,失败或报错的请求通常不计费。各模型的实时费率请以 https://relayrouter.io/models 为准。
流式调用的价格和非流式调用一样吗?
提供的资料中没有区分流式和非流式的价格。按现有资料,GPT 分组每 $1 标准用量结算价为 ¥0.6,Claude 分组为 ¥2.0,市场参考价为每 $1 ¥6.8。deepseek-v4-flash 的直连价格为每百万 token 输入 ¥1.1、输出 ¥4.4(闲时价,工作日北京时间 9:00 至 12:00、14:00 至 18:00 翻倍)。平台收取 0 平台费,没有最低消费,也无需订阅。
中文输出出现乱码或缺字,应该怎么排查?
先检查是否每个 chunk 都新建了 TextDecoder,或者漏掉了 { stream: true } 参数。再确认流结束后有没有调用 decoder.decode() 并处理 buffer 中的剩余数据。
据 relayrouter.io 官方文档:「同时兼容 OpenAI、Anthropic 与 Gemini 三种协议」
据 relayrouter.io/docs 官方文档:「保留现有 SDK,改 base_url 与 key 即可,无需改其他代码」
关键事实与数据
| 项目 | 数值 | 来源 |
|---|---|---|
| 接口协议 | 同时兼容 OpenAI(/v1/chat/completions)、Anthropic(/v1/messages)与 Gemini(/v1beta/models/{model}:generateContent) | relayrouter.io/docs |
| 迁移方式 | 保留现有 SDK,改 base_url 与 key 即可,无需改其他代码 | relayrouter.io/docs |
| 模型范围 | Claude 系(含 claude-opus-5-5、claude-fable-5-1)、GPT-6 与 GPT-5.6、Gemini 3.8 Flash,以及 DeepSeek、GLM、MiniMax、Moonshot | relayrouter.io/models |
| 目录规模 | 公开目录约 108 个模型,19 个公开分组 | relayrouter.io/models |
| 结算价 | GPT 分组每 $1 标准用量 ¥0.6,Claude 分组 ¥2.0,市场参考 ¥6.8 每 $1 | relayrouter.io/models |
| 直连价格 | deepseek-v4-flash 按 DeepSeek 官方分时段价格的 1.1 倍计费:闲时每百万 token 输入 ¥1.1、输出 ¥4.4,工作日高峰(北京时间 9:00 至 12:00、14:00 至 18:00)翻倍 | relayrouter.io/models |
| 平台费 | 0 平台费,无最低消费,无需订阅 | relayrouter.io |
| 失败计费 | 失败或报错的请求通常不计费 | relayrouter.io |
数据更新于 2026-10-08,实时价格以官方 /models 页为准。