Jev API 教程:10 分钟发出第一次调用
你有 API Key,有十分钟——发第一次 Jev 调用真的只需要这些。因为模型的主演示渠道是 OpenRouter 的 OpenAI 兼容端点,请求结构和发给聊天模型的完全一样;变的是返回的东西。本教程给出从注册到第一次成功调用的完整步骤,外加 curl 和 Python 两个可直接复制的完整调用、逐字段解释、所有人第一次都会踩的坑,以及上生产前必须做的五条检查。
TL;DR: 向
https://openrouter.ai/api/v1/chat/completions发 POST,带Authorization: Bearer <key>、Jev 模型 id,以及一个按判断题/选择题/打分题措辞的问题。答案以结构化 JSON——answer、confidence、rationale——出现在标准 OpenAI 兼容响应壳里。把 message content 按 JSON 解析后读字段即可。
从零到第一次成功调用:7 步 Runbook
这份 runbook 假设你什么都没有——没账号、没 Key、没代码。按顺序走,每步都有具体的预期结果,知道什么时候该继续。
| 步骤 | 操作 | UI 路径 / 字段 | 预期结果 |
|---|---|---|---|
| 1 | 注册 OpenRouter 账号 | openrouter.ai → Sign Up | 收到确认邮件,账号激活 |
| 2 | 充值 credits | Dashboard → Credits → Add Credits → 选金额 → 付款 | 顶栏余额显示非零 |
| 3 | 创建 API Key | Dashboard → Keys → Create Key → 填名称 → 可设消费上限 | 一串以 sk-or- 开头的 Key 复制到剪贴板 |
| 4 | 把 Key 存入环境变量 | 在 shell profile 里写 export OPENROUTER_API_KEY="sk-or-…" | echo $OPENROUTER_API_KEY 打印出 Key,不是 None |
| 5 | 确认当前 model slug | openrouter.ai → Models → 搜索 “jev” → 打开模型页 | 看到确切的 slug 字符串,例如 typesafe/jev-1.13 |
| 6 | 发测试调用 | 复制下一节的 curl 命令,粘贴执行 | HTTP 200,终端里打印出 JSON 响应体 |
| 7 | 解析答案 | 取 choices[0].message.content,对它执行 json.loads | 得到含 answer、confidence、rationale 的字典 |
第 6 步几乎所有失败都源于两件事:Key 在剪贴板里但没 export 到当前 shell,以及 model slug 与教程里展示的不一样。排查其他问题前先确认这两点。
请求逐字段解读
第一次调用用判断题——三种原语里最简单的一种。最小可用的 curl:
# 执行前请到 OpenRouter 模型页确认确切的 model slug
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": "Judgment question: does this comment contain spam? Comment: \"Great post, check my site for cheap meds\""
}
]
}'
每个部分的作用:
| 部分 | 值 | 作用 |
|---|---|---|
| URL | https://openrouter.ai/api/v1/chat/completions | OpenRouter 的 OpenAI 兼容端点,无需任何路径变体 |
Authorization 头 | Bearer $OPENROUTER_API_KEY | 从环境变量读 Key;不要把裸 Key 写进脚本 |
Content-Type | application/json | 告诉服务器 body 是 JSON;漏掉这个会触发 400 |
model | typesafe/jev-1.13 | Jev 模型 id——确切 slug 以 OpenRouter 模型页为准 |
messages[0].role | user | 单轮提问;Jev 不保留对话状态 |
messages[0].content | 你的问题 | 按判断/选择/打分措辞,把待判内容直接嵌入 |
注意不需要什么:没有特殊的”判断模式”参数,没有专用端点,没有 session token。题型由 content 的措辞承载,仅此而已。官方 TypeSafe AI 渠道的实际端点和字段,请到 typesafe.ai 官方文档确认。
响应解读
HTTP 响应是标准 OpenAI 兼容壳。外层对象包含 choices 数组;第一个元素的 message.content 是结构化答案——以 JSON 字符串形式存放。字符串内部——示例数据(example fixture),正式字段名以官方文档为准:
{
"answer": "yes",
"confidence": 0.97,
"rationale": "The comment promotes an unrelated pharmaceutical site, a classic spam pattern."
}
字段怎么读:
answer——判断题取yes、no或unclear之一;选择题取你列出的选项之一;打分题是数字并附带来源scale。confidence——0 到 1 的分数,可以拿来做分支。低于阈值的走人工复核,不要直接接受。rationale——简短解释。务必记日志;排查问题措辞时,rationale 直接告诉你模型是怎么理解输入的。
请求 body 里加了 "usage": {"include": true} 的话,响应 envelope 里还会有 usage 对象,包含 prompt_tokens、completion_tokens 和估算 cost。详见下文成本与延迟实测一节。
提取路径用伪代码表示:
envelope = JSON 解析 HTTP body
content = envelope["choices"][0]["message"]["content"]
result = JSON 解析 content ← 最容易漏的一行
answer = result["answer"]
Python 版本
import json, os, requests
resp = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
"Content-Type": "application/json",
},
json={
# 执行前到 OpenRouter 模型页确认确切的 model slug
"model": "typesafe/jev-1.13",
"messages": [{
"role": "user",
"content": (
"Judgment question: does this comment contain spam? "
'Comment: "Great post, check my site for cheap meds"'
),
}],
"usage": {"include": True}, # 返回 token 数量和估算 cost
},
timeout=30,
)
resp.raise_for_status() # 先检查状态码,再解析
envelope = resp.json()
result = json.loads(envelope["choices"][0]["message"]["content"])
print(result["answer"], result["confidence"])
# 可选:记录前几次调用的成本,用于校准预期
usage = envelope.get("usage", {})
print(f"tokens: {usage.get('prompt_tokens')}+{usage.get('completion_tokens')}")
三点实现细节值得在复制前读完:
resp.raise_for_status()必须在resp.json()之前调用。否则失败请求的 body 可能解析成一个看起来合法的 JSON,把错误码悄悄吞掉。requests.post的json=参数会自动序列化 body 并设置Content-Type: application/json——不需要单独加 header,但示例里保留 header 是为了让代码意图对读者更清晰。json.loads(envelope["choices"][0]["message"]["content"])是每个人都会漏掉的那一行。message content 是包含 JSON 的字符串,不是已经解析好的 dict。跳过这步会得到TypeError或者一个字符串而不是字典,取决于你怎么访问它。
失败模式:哪里出错,为什么
四个 HTTP 状态码基本覆盖了所有首次调用的失败情况。下表给出成因、确认方法和修复方式:
| 状态码 | 最常见原因 | 如何确认 | 修复方法 |
|---|---|---|---|
401 Unauthorized | Key 没在环境里、环境变量名拼错、或 Key 已被撤销 | print(os.environ.get("OPENROUTER_API_KEY")) — 如果是 None,export 在这个 shell 里没生效 | 重新 export;逐字符核对变量名(大小写敏感) |
403 Forbidden | Key 存在但 credits 为零,或 Key 的消费上限已达到 | 登录 OpenRouter 控制台 → Credits,查看余额和单 Key 限额 | 充值,或提高 Key 的消费上限 |
404 Not Found | model slug 写错、过期或有拼写错误 | 把请求里的 model 值与 OpenRouter 模型页的确切字符串对比 | 从模型页重新复制 slug;不要使用缓存值 |
429 Too Many Requests | 并发或每分钟请求数超过账户等级限额 | 响应 body 通常含 Retry-After 头 | 指数退避;降低 max_workers;查阅账户等级的文档限额 |
400 Bad Request | JSON body 格式错误;缺 Content-Type 头;model 字段完全没写 | 读响应 body——通常会指出缺少哪个字段 | 发送前校验 JSON body;确认所有必填字段都在 |
客户端 TypeError / JSONDecodeError | 跳过了对 message content 的 json.loads,或该次调用的 content 不是合法 JSON | 在解析前打印原始 message.content 字符串 | 用 try/except 包裹 json.loads;解析失败时记录原始内容,重试一次再告警 |
一个容易忽视的失败:Key 已经正确 export,但环境变量名字有拼写错误。例如 OPENROUTER_APIKEY 而不是 OPENROUTER_API_KEY。Python 的 os.environ['OPENROUTER_API_KEY'] 会立即抛 KeyError;os.environ.get('OPENROUTER_API_KEY') 则静默返回 None,等到构建请求头时才出错。如果一个”应该在”的 Key 反复返回 None,就逐字符核对变量名。
成本与延迟首次实测
不要等到第一张账单才知道一次 Jev 调用花了多少钱。第一次运行时就在请求 body 里加 "usage": {"include": True},解析答案前先读 envelope:
envelope = resp.json()
usage = envelope.get("usage", {})
prompt_tokens = usage.get("prompt_tokens", 0)
completion_tokens = usage.get("completion_tokens", 0)
# OpenRouter 含估算 cost 字段——确切字段名以模型页为准
estimated_cost_usd = usage.get("total_cost", 0)
print(f"prompt={prompt_tokens} completion={completion_tokens} cost≈${estimated_cost_usd:.6f}")
延迟测量用 time.perf_counter() 包裹请求,同一个 prompt 连跑三次:
import time
times = []
for i in range(3):
t0 = time.perf_counter()
resp = requests.post(URL, headers=HEADERS, json=BODY, timeout=30)
resp.raise_for_status()
times.append(time.perf_counter() - t0)
print(f"延迟: {min(times):.2f}s / {sum(times)/3:.2f}s / {max(times):.2f}s (最小/均值/最大)")
三次数据算不上基准测试,但能告诉你每次调用花的是分之几美分还是整美分,延迟是亚秒级还是多秒级——这两点直接决定 pipeline 的设计方式。上量之后的完整测量方法见成本与延迟指南。
两件事从第一天就值得记日志,即使你现在还不关心成本优化:每次调用的 prompt_tokens(告诉你随输入变化 prompt 体积是否在漂移)和每次调用的墙钟延迟(面向用户的 pipeline 关注 p95)。进入生产后再补观测性永远比从一开始就记更难。
问题措辞常见错误
让请求返回 HTTP 200 是第一步。得到有用的答案是第二步——大部分调试时间都花在问题措辞上。三类模式造成了绝大多数措辞错误:
忘记把输入嵌入问题里。 问题必须包含被判断的内容。"Judgment question: is this spam?" 没有文本可判断。完整形式是 "Judgment question: is this spam? Text: \"…\""。
把判断题写成了聊天指令。 "告诉我这是不是垃圾信息" 是给聊天助手的指令。Jev 不是聊天助手——它回答特定格式的问题。用明确的问题形式:"Judgment question: does this text contain spam? Text: \"…\""。
一条消息里混了多种题型。 "这是垃圾信息吗?如果是,严重程度 1-10 分" 把判断题和打分题混在了一个 content 字段里。分开发两次调用,每次一种题型,在你自己的代码里合并结果。三原语指南详细介绍了每种题型以及如何串联它们。
一个实用的调试循环:当调用返回的 confidence 低于 0.7 时,读 rationale 字段。它通常直接告诉你模型对什么不确定——缺少上下文、措辞模糊、还是输入本身确实横跨两个类别。调整措辞后重跑;大多数低置信度结果一两次迭代就能解决。
上生产前:5 条 Checklist
这五条检查不超过十五分钟,但能防止最常见的五类生产事故:
-
Key 在环境变量里,不在源代码里。 提交任何代码前先扫描
sk-or-前缀。泄露到公开仓库的 Key 可能在几分钟内就被爬虫耗尽额度。 -
model slug 从配置读,不要硬编码。 把 slug 放进环境变量(
JEV_MODEL_SLUG),版本升级时改一次配置就够,不用部署代码。上线前至少到 OpenRouter 模型页确认一次当前 slug。 -
Key 有消费上限。 在 OpenRouter 控制台给每个 Key 设置单独的消费限额。重试循环 bug 或突发流量应该打爆 Key 的限额,而不是账户余额。
-
raise_for_status()在解析之前调用。 任何不先检查状态码就调用resp.json()的代码路径都会悄悄吞错误。错误只在后面访问答案时以莫名其妙的KeyError或AttributeError暴露出来。 -
对 message content 的
json.loads用 try/except 包裹。 Jev 通常可靠地返回结构化 JSON,但网络重试、模型版本变更和边缘输入都可能产生非预期内容。解析失败时先记录原始字符串再重试或告警——你会需要它来排查。
这五条都确认没问题后,置信度与回退指南覆盖下一层:如何选置信度阈值、如何把低置信度结果路由给人工复核、如何衡量阈值的真实准确率和召回率。
接下来去哪儿
本指南覆盖单次调用的机制。后续指南构建在这个基础上:
| 指南 | 内容 |
|---|---|
| 三原语 | 判断题、选择题、打分题;各自适用场景;如何串联 |
| API 渠道 | OpenRouter 与官方 TypeSafe AI 渠道对比;兼容两者的适配器;迁移 runbook |
| 置信度与回退 | 置信度阈值选取;低置信度结果路由给人工复核 |
| 成本与延迟 | 单次调用经济成本测量;prompt 体积影响;批量与并发权衡 |
| 排错指南 | 超出本文四个状态码之外的完整报错-修复表 |
垃圾评论识别 case 展示了本文这个调用被接入完整审核管线的端到端用法——机制跑通之后值得一读。
本文适用版本 Jev 1.13。