翻译文档到底该接一家模型,还是走一个聚合网关?一开始我只接了 OpenAI,但很快发现两个问题:翻译中文文档时,不同模型对不同领域的术语表现差异很大;而且只绑定一家,价格和可用性都被卡死。OpenRouter 用一套 OpenAI 兼容接口把几十家模型聚到一个网关后面,我切换到它之后,模型切换从”改代码”变成”改配置”。下面把接入路径、模型分级、降级限流和成本留痕完整拆开讲。
一、为什么选 OpenRouter
选网关而不是逐家直连,本质是”把厂商差异挡在业务层外”。直连的痛点很具体:OpenAI、Anthropic、通义各自有自己的 SDK、鉴权方式和限流语义,业务代码里每接一家就要写一个适配层,而且测试环境想换模型还得改代码重新部署。
| 价值 | 说明 |
|---|---|
| 一套接口 | OpenAI 兼容,按 model 字段切换厂商 |
| 多模型 | 主流闭源与开源模型一站接入 |
| 按量计费 | 集中账单,便于成本分析 |
| 路由能力 | 按 region 或价格策略自动选模型 |
对一个翻译工具来说,最值钱的是第 3 条。翻译任务是 token 消耗大户,客户要按项目对账,如果每个模型分开开票,财务核算会变成灾难。OpenRouter 把账单集中到一张表,按模型、按项目都能拆出来。
接入本身不复杂,我走完这三步就跑通了第一条翻译任务:
- 注册 OpenRouter 账号并创建 API Key;
- 用 OpenAI SDK 把 baseURL 指向网关节点的 /api/v1 地址;
- 在配置里填上模型档位,发起一次真实翻译验证。
三步跑通,剩下的就是把业务逻辑一层层挂上去。
二、模型分级
翻译工具对模型的诉求是”长上下文 + 稳定格式 + 适中价格”,因此按场景分级。一开始我所有内容都用同一个强模型,一个月账单下来成本高得吓人;后来按”文档长短 × 质量敏感度”分档,短文档走轻量模型,成本直接降下来一半:
| 场景 | 模型档位 | 示例 |
|---|---|---|
| README 短文档 | 轻量快 | openai/gpt-4o-mini |
| 中等文档(≤ 5k token) | 主模型 | openai/gpt-4o |
| 长文档 / API 文档 | 长上下文 | anthropic/claude-3.5-sonnet |
| 术语校对 | 强模型 | anthropic/claude-3.5-sonnet |
| 自定义私有 | 私有部署 | self-hosted/llama-3 |
分级不是简单的”便宜省事”,而是”把贵模型用在刀刃上”。术语校对这步看起来只是复查,但术语错一个,整篇译文都要重译,所以必须用当前档位里更强的模型;而 README 这种几十行的短文,用轻量模型质量差别可以接受。
三、统一 Client 抽象
所有模型调用都收敛到一个 client 上,业务层不知道背后是哪个厂商。这层抽象是后续切换模型不痛的关键:
import OpenAI from "openai";
export const openrouter = new OpenAI({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api/v1",
defaultHeaders: {
"HTTP-Referer": "https://example.com",
"X-Title": "GitHub Doc Translator",
},
});
HTTP-Referer 和 X-Title 两个头是给 OpenRouter 后台统计用的,填上之后可以在它的后台看到每个项目分别烧了多少 token。调用使用 client.chat.completions.create,model 字段传 OpenRouter 路由名——也就是说,业务代码从头到尾只认识 OpenAI 这一个 SDK,厂商差异被网关挡死了。
四、配置化模型选择
把模型与场景映射到配置文件,改档位不用动代码。我在 CI 里也接了这一层:测试环境用便宜档,压测环境用主档,避免测试跑完烧掉生产预算。
models:
short: { name: "openai/gpt-4o-mini", max_tokens: 2048 }
main: { name: "openai/gpt-4o", max_tokens: 4096 }
long: { name: "anthropic/claude-3.5-sonnet", max_tokens: 8192 }
review: { name: "anthropic/claude-3.5-sonnet", max_tokens: 4096 }
运行时根据 segments.length * avg_tokens 自动选档。这里有个细节:翻译任务的片段长度是可预估的,因为切分时已经按 token 估算过了,所以选档不用等模型返回,任务入队时就能定下来,省掉一次无谓的模型试探。
五、提示词设计
提示词拆成三段:系统提示、用户上下文、待翻译片段。系统提示里说明术语、Markdown 保留规则、代码块不翻译。
你是专业的技术文档翻译,请按以下规则翻译:
- 保留 Markdown 结构、链接地址、代码块原样;
- 专有名词参考下方术语表;
- 输出仅包含译文,不附加解释。
术语表来自任务配置,由用户或管理员维护。实际踩过的坑是:如果把术语表塞进用户上下文,每次翻译都重复传,token 翻倍;把术语表放进系统提示并做 provider 缓存,短文档翻译的成本能再降 30%。
六、降级与限流
6.1 失败重试
- 仅对 5xx、429、超时重试,最多 3 次;
- 指数退避 + 抖动;
- 重试仍失败则切下一档模型。
重试最容易踩的坑是”把参数错误也重试”。OpenRouter 返回 400 说明 prompt 或参数有问题,重试一万次也不会成功,还会把日志刷满;所以重试只对 5xx、429 这类瞬时错误开放,400 直接落库等人工。
6.2 限流
- 用 Redis 维护每用户每分钟调用次数;
- 接近阈值时排队或拒绝;
- 跨任务共享令牌池,避免单用户打满。
多用户同时翻译时,如果没有令牌池,某个用户一次性提交 200 个片段,会把整批配额烧光,其他用户全部排队。把配额做成跨任务的共享池,单用户超额先入队,整体吞吐反而更稳。
七、流式输出
OpenRouter 支持 stream。翻译工具用流式让前端逐步渲染:
const stream = await openrouter.chat.completions.create({
model,
stream: true,
messages,
});
for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta?.content ?? "";
yield delta;
}
流式对翻译场景的价值不只是”看起来快”。长文档翻译要几十秒,如果等全部完成才返回,用户会以为卡死而刷新页面,任务就断了。边收边渲染,进度条是真实推进的,用户愿意等。
八、日志与成本
每次调用记录:
- taskid、segmentid、model、inputtokens、outputtokens、cost、latency;
- 入库后用于成本分析与质量回溯;
- 提供 admin 看板,按模型/用户/任务类型统计。
成本看板是我后来补的,起因是有位客户月底对账时问”这个月翻译费为什么比上个月高 40%”。有了分模型、分任务的成本表,五分钟就定位到是新增了一批 5k token 以上的大文档走了主模型档——于是把大文档切到长上下文档,价格曲线立刻回落。
九、隐私与合规
- 默认不把用户内容发到非必要模型;
- 企业版允许私有部署模型,按 region 路由;
- ToS 中说明数据流向与保留期。
这里要提醒:OpenRouter 是多厂商网关,内容会落到你选中的第三方模型厂商。如果客户文档有保密要求,必须在 ToS 里写清楚,或者把数据交给私有部署模型——这也是我保留 self-hosted/llama-3 档位的原因。
十、回归与监控
- 故障注入:把 OpenRouter 临时指向 mock,验证降级;
- 慢调用:超过阈值的请求拉出 prompt 排查;
- 成本告警:单日成本同比上涨 > 30% 触发提醒。
回归这步不能省。我踩过一次:某次升级把 baseURL 配错成直连 OpenAI,线上任务全部走了直连,既没走分级也没走成本统计,跑了一整天才发现。故障注入 + 成本告警双保险,能把这个窗口缩到分钟级。
常见问题(FAQ)
Q1:OpenRouter 一定比直连便宜吗?
不一定,取决于模型与流量。建议按月对比账单。
Q2:能不能在 OpenRouter 之外接自托管模型?
可以,加一个 Model Provider 接口,与 OpenRouter 并列。
Q3:多模型如何保证译文风格一致?
术语表 + 统一系统提示 + 同一任务内锁定主模型。