Jev API 渠道怎么选:官方 API vs OpenRouter
Jev 到你手上有两扇门:官方 TypeSafe AI 渠道和 OpenRouter——模型的主演示渠道。两者都说 OpenAI 兼容的请求/响应”方言”,所以选哪个都不会被锁死;但在接入速度、计费方式和看到新版本的早晚上有实质差异。这篇先做对比,再往下讲真正耗时间的部分:“OpenAI 兼容”到底保证了什么、哪些请求字段只属于市场侧、怎么用一个适配层同时覆盖两条路,以及一份照着做就不会重写的迁移清单。
TL;DR: 先用 OpenRouter:路径最短,按 token 计费(第三方列表约 $0.0462 / 1M input tokens——以模型页为准)。当你需要企业条款、直连支持或版本保障时再考虑官方渠道;其端点以 typesafe.ai 文档为准。把所有 Jev 调用收敛到一个 client 函数后面,日后切换只是一个下午的事,不是一次重写。
两个渠道对比
| 维度 | OpenRouter(演示渠道) | 官方 TypeSafe AI API |
|---|---|---|
| 接入速度 | 几分钟:注册、充值、拿 Key | 较慢:按官方文档走开通流程 |
| 请求格式 | OpenAI 兼容 chat completions | 以 typesafe.ai 文档为准 |
| 定价 | 按 token 计费;列表价约 $0.0462 / 1M input tokens(第三方数据,以模型页为准) | 按官方条款;查 typesafe.ai |
| 计费方式 | OpenRouter 预充值,可按 Key 设消费上限 | 以官方计费安排为准 |
| 版本可用性 | 模型页列出;slug 可能领先或滞后官方文档 | 第一手;以官方发布说明为准 |
| 上游路由 | 同一 slug 背后多家上游,可按价格、延迟或吞吐排序 | 由官方渠道决定 |
| 请求侧扩展 | 模型兜底数组、单次调用成本回传、数据采集过滤 | 不保证;逐字段以官方文档为准 |
| 花费可观测性 | Key 级用量与剩余额度查询 | 以官方工具为准 |
| 支持 | 市场级别 | 直连,按官方渠道 |
| 适合 | 原型、中小规模生产、快速实验 | 企业负载、合规需求、量级承诺 |
两个诚实的提醒。第一,模型 id 在两个渠道可能不同——typesafe/jev-1.13 是写作本文时 OpenRouter 的列表值,slug 会随版本变化,以模型页为准。第二,本文刻意不编造官方端点 URL;官方渠道提供什么,typesafe.ai 的文档是唯一事实来源,介绍 System One 模型与 Jev 的发布公告也是。
“OpenAI 兼容”到底保证了什么
兼容是个光谱。搞清楚你的代码依赖光谱的哪一半,就知道换渠道会疼到什么程度。
| 层次 | 跨渠道可迁移? | 为什么重要 |
|---|---|---|
鉴权头形式(Authorization: Bearer …) | 通常可以 | HTTP 客户端和密钥注入方式原样保留 |
请求体结构(model、messages[]) | 可以 | 你已经写好的请求依然合法 |
响应信封(choices[0].message.content) | 可以 | 解析入口不变 |
| 模型 slug | 不可以 | 换渠道后 404 的头号原因 |
| 额外请求字段 | 不可以 | 市场侧能力不属于这套方言 |
| 错误响应体结构 | 不保证 | 错误处理要针对状态码写,别匹配文案 |
| 用量/token 统计字段 | 不保证 | 自己记 token,别赌字段名 |
| 限流响应头与语义 | 不保证 | 退避策略得按渠道重新调 |
实用规则:只依赖上表标”可以”的三层,其余全部当成渠道专属配置。读解析后 answer、confidence 的代码可以平移;拿正则去 HTTP 原始响应里找 “billing” 的代码不行。
值得知道的 OpenRouter 请求字段
这些是让演示渠道不止是”透传”的市场侧能力,也正是你切到不声明支持这些字段的渠道时必须剥掉的部分——未知字段要么被 400 拒掉,要么更糟:被静默忽略。而静默忽略,就是”我们设了数据采集过滤”这句话最后被证明是假的原因。
模型兜底数组。 传 models: ["typesafe/jev-1.13", "<备用 slug>"],主模型出错时路由会试第二个。版本过渡期有用,但用在判断任务上要小心:备用模型有自己的标定,它返回的 0.94 和你阈值假设的那个 0.94 不是一回事。用了兜底就必须记录”这次到底是谁回答的”。
上游路由。 provider 对象可以收窄或排序同一 slug 背后的上游:order 指定优先上游,allow_fallbacks: false 拒绝替补,sort 按 "price"、"latency" 或 "throughput" 排序,data_collection: "deny" 排除会留存或拿输入做训练的上游。对延迟敏感的内容审核流水线,按延迟排序值得实测;对处理个人信息、要写进合规文档的流水线,数据采集过滤值得记录——同时记住它是市场侧控制项,不是合同。
单次调用成本回传。 加 usage: {"include": true},响应里会带上 token 数与成本,可以按流水线直接归因花费,不用再去对账单导出。这是 OpenRouter 专属能力,下面的适配层会保证它不会发给别的渠道。
归因头。 HTTP-Referer 和 X-Title 让你的应用在 marketplace 面板里可辨识。设置成本极低,而且是多个项目共用一个账号时唯一的分账依据。
同时用上其中三项的请求:
# Confirm the exact model slug on the OpenRouter model page
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "HTTP-Referer: https://your-app.example" \
-H "X-Title: inbox-router" \
-d '{
"model": "typesafe/jev-1.13",
"provider": {"sort": "latency", "data_collection": "deny"},
"usage": {"include": true},
"messages": [{"role": "user", "content": "Choice question: route this email to sales, billing or support? Email: \"...\""}]
}'
OpenRouter 实际用起来什么样
一次调用、一个 Key、你熟悉的端点:
# Confirm the exact model slug on the OpenRouter model page
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-1.13",
"messages": [{"role": "user", "content": "Choice question: route this email to sales, billing or support? Email: \"...\""}]
}'
标准 chat completions 请求——分流语义由问题的措辞承载。
解析后的答案——示例数据(example fixture),正式字段名以官方文档为准:
{
"answer": "billing",
"confidence": 0.94,
"rationale": "The email disputes a subscription charge, which maps to the billing queue."
}
这正是推荐先用 OpenRouter 的原因:你现有的 OpenAI 客户端库、重试逻辑和观测手段全都原样可用。
一个适配层,两份配置
整个”迁移成本低”的论证,其实就落在这一个文件上。把所有渠道差异放进数据,而不是散落在代码各处的分支里:
import json, os, requests
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Channel:
name: str
base_url: str
api_key_env: str
model: str
extra_headers: dict = field(default_factory=dict)
extra_body: dict = field(default_factory=dict)
CHANNELS = {
"openrouter": Channel(
name="openrouter",
base_url="https://openrouter.ai/api/v1/chat/completions",
api_key_env="OPENROUTER_API_KEY",
model=os.environ.get("JEV_MODEL_SLUG", "typesafe/jev-1.13"),
extra_headers={
"HTTP-Referer": "https://your-app.example",
"X-Title": "inbox-router",
},
extra_body={"usage": {"include": True}},
),
# 官方 TypeSafe AI 渠道:这两个值按官方文档填写。
"official": Channel(
name="official",
base_url=os.environ.get("TYPESAFE_BASE_URL", ""),
api_key_env="TYPESAFE_API_KEY",
model=os.environ.get("TYPESAFE_MODEL", ""),
),
}
def ask(question: str, channel: str = "openrouter", timeout: int = 30) -> dict:
ch = CHANNELS[channel]
if not ch.base_url:
raise RuntimeError(f"{ch.name}: base URL not configured")
resp = requests.post(
ch.base_url,
headers={
"Authorization": f"Bearer {os.environ[ch.api_key_env]}",
"Content-Type": "application/json",
**ch.extra_headers,
},
json={
"model": ch.model,
"messages": [{"role": "user", "content": question}],
**ch.extra_body,
},
timeout=timeout,
)
resp.raise_for_status()
envelope = resp.json()
return json.loads(envelope["choices"][0]["message"]["content"])
三个性质值得注意。渠道是运行时参数,所以灰度分流 5% 的流量不需要发版。市场侧字段只活在 extra_headers 和 extra_body 里,对没声明支持的渠道会自动消失。解析结果就是上面那个示例数据形态——把它当成待核对的 example fixture,而不是规格说明。
迁移成本:架构对了就很低
切换成本大多是自找的,也就大多可以避免:
- 一个 client 函数。 所有 Jev 调用走同一个封装:进一个问题,出解析后的答案。
- base URL、Key、模型 id 全部走配置。 用环境变量,不写字面量。
- 容错解析。 防御式解析 message content;多出来的未知字段当可忽略,不要当致命错误。
有了这个结构,从 OpenRouter 迁到官方渠道就是改三个配置值、再对照官方文档核对一遍字段名——上面那个响应形态是示例数据(example fixture),官方渠道的确切字段名以其文档为准。
迁移清单
光改配置并不能回答”新渠道的行为和老渠道一样吗”。按顺序走这六步,每步都有放行条件,永远知道自己该不该继续。
| 步骤 | 动作 | 放行条件 |
|---|---|---|
| 1. 冻结 golden set | 导出 50–200 条真实输入,附人工标注 | 每条都有期望答案 |
| 2. 各渠道固定 slug | 模型 id 进配置,不进代码 | 两个值都能解析;slug 写错在启动时就报错 |
| 3. 离线回放 | golden set 同时灌两条渠道,不接线上流量 | 有标注样本上的答案一致率 ≥ 95% |
| 4. 比分布 | 比 confidence 与延迟,不只看答案 | confidence 中位差 ≤ 0.05;p95 延迟增幅 ≤ 30% |
| 5. 灰度 | 5% 线上流量走新渠道,两边都记日志 | 一周内回退率没有越过阈值 |
| 6. 切换并观察 | 改默认值,老渠道保持可用 | 回滚只需改一个环境变量,实测 5 分钟内完成 |
第 4 步是最常被跳过、也最能捞出真问题的一步。两条渠道可以在 96% 的答案上一致,却在”什么时候该不确定”上不一致——如果新渠道系统性给出更高的 confidence,你的回退阈值会悄悄不再触发,错分开始流到客户面前。这个阈值本身怎么定,《置信度与回退》有完整讲法。
回放脚本短到写一次就能长期复用:
GOLDEN = [
("Judgment question: does the string 'abc' contain the letter 'z'?", "no"),
("Choice question: which word is a colour? Options: red, seven, run. Word: 'seven'", "seven"),
("Scoring question (1-10): rate the similarity of 'cat' and 'cat'.", 10),
]
def compare(channel_a: str, channel_b: str) -> dict:
agree, deltas = 0, []
for question, expected in GOLDEN:
a = ask(question, channel=channel_a)
b = ask(question, channel=channel_b)
agree += int(str(a["answer"]) == str(b["answer"]) == str(expected))
deltas.append(abs(a["confidence"] - b["confidence"]))
return {"agreement": agree / len(GOLDEN), "median_conf_delta": sorted(deltas)[len(deltas) // 2]}
两个结果里的答案都是示例数据形态;接进 CI 之前,先把官方渠道的字段名核对清楚。
分渠道的可观测性
比不了的渠道就选不了。两条渠道都会给你状态码和延迟,OpenRouter 另外提供了一组端点,正好回答你运维时真正会问的问题。
| 问题 | OpenRouter 的能力面 | 用途 |
|---|---|---|
| 这个 Key 花了多少、还剩多少? | Key 信息查询(用量、上限、剩余) | 按项目做每日预算护栏 |
| 账号余额要见底了吗? | 额度查询(总额度、总用量) | 批量任务前先报警 |
| 这一次调用花了多少? | 按请求 id 查单次生成 | 把成本和自己的日志关联 |
| 配置的 slug 还在列表里吗? | 模型列表(含定价) | 每日 slug 漂移检查 |
最后一条值得第一天就自动化。slug 会随版本变化,而一旦 slug 消失,所有调用会在同一秒变成 404。一个每天拉一次模型列表、断言配置 slug 仍在、不在就报警的定时任务,把一次生产事故降级成一张工单。真漏过去了怎么办,故障排查那篇有完整的”报错—原因—修法”对照表。
限流不同,并发要分渠道调
限流是最像”可移植”、其实最不可移植的一项。市场侧通常按 Key 计量,天花板跟你的充值档位挂钩;官方渠道的配额写在它自己的协议里。于是同一份 max_workers=10 的线程池,在一条渠道上很舒服,在另一条上就是 429 制造机。
对迁移清单有两个推论。第一,把并发数当成和 base URL 同级的渠道配置,切换后重新实测,不要把旧值直接搬过去。先按 10 并发起,盯十分钟重试率,429 超过请求量的约 1% 就往下调。第二,记住灰度流量不是公平的压测:5% 的线上流量打在一个全新 Key、全新配额上,只能验证正确性,不能告诉你这条渠道在你真实量级下的表现。把每条渠道的重试率单独当指标看,上涨在变成积压之前就能被发现。
按阶段给建议
| 阶段 | 渠道 | 理由 |
|---|---|---|
| 评估 / 原型 | OpenRouter | 最快跑通 |
| 早期生产 | OpenRouter | OpenAI 兼容生态,可按 Key 限消费 |
| 规模化 / 企业 | 官方 API(评估后) | 支持、条款、版本保障——去 typesafe.ai 核实 |
| 混合 | 任一 | 保留适配层,按负载路由 |
三团队邮件分流的 case 展示了 OpenRouter 路径撑起生产负载的样子。钱的层面怎么算,《Jev 成本与延迟》把 token 经济学摆给你看。
会推翻偏好的四个硬约束
有四类需求会直接替你决定渠道,和上面的建议表无关。
- 采购流程。 财务要的是采购单和发票,不是一张预付卡,那市场侧就不是对的门。在动手之前先确认官方渠道的签约路径。
- 数据处理。 市场侧的数据采集过滤只是收窄了能看到你输入的上游范围;企业协议是另一种性质的凭据。如果监管或客户在问这件事,你需要的是后者,而且要白纸黑字。
- 版本固定。 如果你的验收测试是针对某一个模型版本写的,你需要一条能按你自己节奏保留该版本的渠道。市场侧的上下架有自己的节奏。
- 多模型路由。 如果流水线里 Jev 只是几个模型之一,一个端点、一个 Key、一张账单能省下真实的工程时间——这是市场侧的核心能力。
会白花一周的反模式
- base URL 硬编码在五个地方。 切换变成一场会漏掉调用点的全局替换。
- 所有项目共用一个 Key。 花费无法归因,轮换变成全公司事故。
- 对原始响应做字符串匹配。
if "billing" in body会命中 rationale——一次分给 sales 的调用,在解释理由时同样可能提到 billing。老老实实解析字段。 - 假设两个渠道的 slug 一样。 经常不一样,而失败模式是生产环境的
404。 - 把市场侧字段当可移植。 上游路由和兜底数组不会跟着走,要按渠道剥掉。
- 没有分渠道指标。 延迟和回退率不分开统计,就看不出是哪条渠道在退化,灰度也就成了猜。
把每条渠道的花费关进笼子
预充值把一种失败模式变得极好防:一个重试循环或一次失控的批量任务,把余额在月中抽干。两个习惯就够了。给每个 Key 设消费上限,泄露的凭据就掏不空账号;任何批量任务启动前先查剩余额度,预计花费超过余额就中止。预计值要用日志里实测的 prompt token 算,不要估——prompt 长度几乎就是全部成本模型,《Jev 成本与延迟》把它算给你看过。
混合架构下,两条渠道的 Key 分开放进不同的 secrets,各设各的上限。这样一条渠道的定价意外或故障只影响它自己,而你的备用路径仍然是付得起钱的。
本文适用版本 Jev 1.13。