Jev API 教程:10 分钟发出第一次调用

更新于 适用版本 Jev 1.13

你有 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充值 creditsDashboard → Credits → Add Credits → 选金额 → 付款顶栏余额显示非零
3创建 API KeyDashboard → Keys → Create Key → 填名称 → 可设消费上限一串以 sk-or- 开头的 Key 复制到剪贴板
4把 Key 存入环境变量在 shell profile 里写 export OPENROUTER_API_KEY="sk-or-…"echo $OPENROUTER_API_KEY 打印出 Key,不是 None
5确认当前 model slugopenrouter.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\""
      }
    ]
  }'

每个部分的作用:

部分值作用
URLhttps://openrouter.ai/api/v1/chat/completionsOpenRouter 的 OpenAI 兼容端点,无需任何路径变体
Authorization 头Bearer $OPENROUTER_API_KEY从环境变量读 Key;不要把裸 Key 写进脚本
Content-Typeapplication/json告诉服务器 body 是 JSON;漏掉这个会触发 400
modeltypesafe/jev-1.13Jev 模型 id——确切 slug 以 OpenRouter 模型页为准
messages[0].roleuser单轮提问;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."
}

字段怎么读:

请求 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')}")

三点实现细节值得在复制前读完:

  1. resp.raise_for_status() 必须在 resp.json() 之前调用。否则失败请求的 body 可能解析成一个看起来合法的 JSON,把错误码悄悄吞掉。
  2. requests.post 的 json= 参数会自动序列化 body 并设置 Content-Type: application/json——不需要单独加 header,但示例里保留 header 是为了让代码意图对读者更清晰。
  3. json.loads(envelope["choices"][0]["message"]["content"]) 是每个人都会漏掉的那一行。message content 是包含 JSON 的字符串,不是已经解析好的 dict。跳过这步会得到 TypeError 或者一个字符串而不是字典,取决于你怎么访问它。

失败模式:哪里出错,为什么

四个 HTTP 状态码基本覆盖了所有首次调用的失败情况。下表给出成因、确认方法和修复方式:

状态码最常见原因如何确认修复方法
401 UnauthorizedKey 没在环境里、环境变量名拼错、或 Key 已被撤销print(os.environ.get("OPENROUTER_API_KEY")) — 如果是 None,export 在这个 shell 里没生效重新 export;逐字符核对变量名(大小写敏感)
403 ForbiddenKey 存在但 credits 为零,或 Key 的消费上限已达到登录 OpenRouter 控制台 → Credits,查看余额和单 Key 限额充值,或提高 Key 的消费上限
404 Not Foundmodel slug 写错、过期或有拼写错误把请求里的 model 值与 OpenRouter 模型页的确切字符串对比从模型页重新复制 slug;不要使用缓存值
429 Too Many Requests并发或每分钟请求数超过账户等级限额响应 body 通常含 Retry-After 头指数退避;降低 max_workers;查阅账户等级的文档限额
400 Bad RequestJSON 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

这五条检查不超过十五分钟,但能防止最常见的五类生产事故:

  1. Key 在环境变量里,不在源代码里。 提交任何代码前先扫描 sk-or- 前缀。泄露到公开仓库的 Key 可能在几分钟内就被爬虫耗尽额度。

  2. model slug 从配置读,不要硬编码。 把 slug 放进环境变量(JEV_MODEL_SLUG),版本升级时改一次配置就够,不用部署代码。上线前至少到 OpenRouter 模型页确认一次当前 slug。

  3. Key 有消费上限。 在 OpenRouter 控制台给每个 Key 设置单独的消费限额。重试循环 bug 或突发流量应该打爆 Key 的限额,而不是账户余额。

  4. raise_for_status() 在解析之前调用。 任何不先检查状态码就调用 resp.json() 的代码路径都会悄悄吞错误。错误只在后面访问答案时以莫名其妙的 KeyError 或 AttributeError 暴露出来。

  5. 对 message content 的 json.loads 用 try/except 包裹。 Jev 通常可靠地返回结构化 JSON,但网络重试、模型版本变更和边缘输入都可能产生非预期内容。解析失败时先记录原始字符串再重试或告警——你会需要它来排查。

这五条都确认没问题后,置信度与回退指南覆盖下一层:如何选置信度阈值、如何把低置信度结果路由给人工复核、如何衡量阈值的真实准确率和召回率。

接下来去哪儿

本指南覆盖单次调用的机制。后续指南构建在这个基础上:

指南内容
三原语判断题、选择题、打分题;各自适用场景;如何串联
API 渠道OpenRouter 与官方 TypeSafe AI 渠道对比;兼容两者的适配器;迁移 runbook
置信度与回退置信度阈值选取;低置信度结果路由给人工复核
成本与延迟单次调用经济成本测量;prompt 体积影响;批量与并发权衡
排错指南超出本文四个状态码之外的完整报错-修复表

垃圾评论识别 case 展示了本文这个调用被接入完整审核管线的端到端用法——机制跑通之后值得一读。

本文适用版本 Jev 1.13。

常见问题

+ 第一次调用 Jev 用哪个端点?

主演示渠道是 OpenRouter 的 OpenAI 兼容端点 https://openrouter.ai/api/v1/chat/completions。官方 TypeSafe AI 端点以 typesafe.ai 官方文档为准,不要凭空猜。

+ model id 传什么?

本教程用 typesafe/jev-1.13,但 slug 会随版本变化——每次写入代码前务必到 OpenRouter 模型页确认确切的 model slug。

+ 为什么第一次调用会报 JSON 错误?

通常是 message content 没有先做 JSON 解析就直接当对象用,或者模型 id 不对。content 是 JSON 字符串,不是 dict——先用 json.loads() 解析,再核对 slug。

+ 怎么测一次调用实际花了多少钱?

在请求 body 里加 usage: {include: true}。响应 envelope 里会有 usage 对象,包含 prompt_tokens、completion_tokens,OpenRouter 还会附带估算的 cost 字段。头几次调用都记录下来,上量前心里有底。

+ 每秒能发多少请求才不会被限速?

取决于你的 OpenRouter 信用等级,没有固定数字。从并发 5 开始,观察是否出现 429;没有则每次加 5 再测。换渠道后要重新测——限速是渠道级别的,不是通用参数。

继续阅读