APIOpenRouter教程TypeScript错误处理
Jev API 教程:从 OpenRouter 起步(密钥、请求、错误、重试)
·约 3 分钟
Jev API 教程:从 OpenRouter 起步
这份 Jev API 教程带你五分钟从零拿到类型化响应。Jev 通过 OpenRouter 提供服务,一把密钥可同时调用其他模型。模型 ID:typesafe/jev-1.13。
一句话总结:
POST /api/v1/chat/completions,content = { state, questions }。读answers[].selected、confidence、distribution。429 要重试;发送前先校验 questions。
1. 获取 OpenRouter API Key
- 在 OpenRouter 注册账号。
- 生成 API 密钥(
sk-or-...)。 - 放进环境变量——不要写进前端或 git 仓库。
export OPENROUTER_API_KEY="sk-or-..."
这条路不需要单独注册 TypeSafe。
2. 发出第一个请求
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": {
"state": "同一笔订单 #4821 扣了我两次钱,麻烦尽快处理。",
"questions": [{
"type": "choice",
"text": "这条工单该由哪个团队处理?",
"options": ["billing", "tech_support", "sales"]
}]
}
}]
}'
请求结构
| 字段 | 类型 | 说明 |
|---|---|---|
state |
string | 非结构化文本(邮件、工单、DOM 快照…) |
questions[].type |
choice | score | noul |
问题类型 |
questions[].text |
string | 要分支的那句判断 |
questions[].options |
string[] | choice / score 需要 |
一次调用可带多个问题(例如意图 + 紧急度)。
3. 读取类型化结果
{
"answers": [{
"type": "choice",
"text": "这条工单该由哪个团队处理?",
"selected": "billing",
"confidence": 0.94,
"distribution": {
"billing": 0.94,
"tech_support": 0.04,
"sales": 0.02
}
}]
}
selected— 最高选项(score 则为加权结果)。confidence— 最高选项的概率。distribution— 完整概率质量。
type JevAnswer = {
type: 'choice' | 'score' | 'noul';
text: string;
selected?: string;
confidence: number;
distribution?: Record<string, number>;
};
TypeScript 客户端示意
export async function runJev(state: string, questions: unknown[]) {
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'typesafe/jev-1.13',
messages: [{ role: 'user', content: { state, questions } }],
}),
});
if (!res.ok) throw new Error(`Jev HTTP ${res.status}`);
const json = await res.json();
return json.choices?.[0]?.message?.content?.answers ?? json.answers ?? json;
}
热路径上发送前先本地校验 questions——缺 options 是常见 400。
错误处理与重试
| 状态码 | 含义 | 动作 |
|---|---|---|
| 401 | 密钥错误/缺失 | 快速失败;查环境变量 |
| 400 | 问题负载非法 | 改请求;不要盲目重试 |
| 429 | 限流 | 指数退避 + 抖动 |
| 5xx | 上游异常 | 有限重试;走兜底路径 |
| 超时 | 网络 / 过慢 | 预算约 2s;转人工或队列 |
async function withRetry<T>(fn: () => Promise<T>, tries = 3): Promise<T> {
let lastErr: unknown;
for (let i = 0; i < tries; i++) {
try { return await fn(); } catch (e) {
lastErr = e;
await new Promise((r) => setTimeout(r, 2 ** i * 200 + Math.random() * 100));
}
}
throw lastErr;
}
Jev API 的安全注意
- 密钥保密时,在服务端 / Worker 调用,不要直接打浏览器。
- 必须走前端时,加带鉴权与限流的代理。
- 政策要求时对
state做 PII 脱敏。 - 设总超时(约 2s),产品交互不能挂死。
日志与可观测性
每次调用记录:模型 ID、延迟、selected、confidence、distribution 摘要、工单/消息 ID。
对错误率与 p95 延迟告警;对平均 confidence 与熵做漂移看板。
下一步
FAQ
有官方 SDK 吗?
社区有 TypeScript / Python 封装;HTTP 契约与 OpenRouter 兼容。
能流式输出吗?
分类返回小类型化载荷,流式几乎无用。
如何锁定模型版本?
使用完整模型 ID typesafe/jev-1.13,升级前看发布说明。