Jev 使用完整指南:从申请 API Key 到置信度路由,把 TypeSafe 决策模型接进自己的代码

0 阅读5分钟

很多团队接大模型时都撞过同一堵墙:同一条 prompt,今天判对,明天判错,而程序里写死的阈值对此毫无察觉。路由逻辑要么全信模型、要么全不信,中间那片模糊地带只能靠人肉复核,量一大就崩。Jev 提供的是一条更细的线:每次调用都返回一个 0~100 的 score,路由决策围绕这条线展开,而不是围绕「模型说行就行」。

常规做法为什么不够用?固定阈值看起来省事,实际是把一次性的拍脑袋固化成生产规则:数据分布一漂移、模型一换版本,70 分的含义就变了,而代码里的 if score > 70 还在忠实地执行过期契约。真要改,就得改代码、发版、回滚,代价远大于当初省下的那点工夫。

本文分享一套可直接落地的方案:申请 Key + 封装裁判函数 + 置信度分层路由 + TypeSafe 类型契约,从裸 HTTP 调用一路接到 Agent 循环里,附阈值标定方法、超时降级策略和上线检查清单。

一、Jev 是什么:一次调用换一个可路由的分数

先定位 Jev 在架构里的层级:它不是模型,而是一层可复用的判断服务:

  • 纯 HTTP 契约:一次 POST 换一份 JSON,不依赖任何 SDK,任何语言都能接。
  • 可路由的输出:返回体里 success 表示判断是否成功,score 是 0~100 的置信分,两者必须分开消费。
  • 独立于业务:打分逻辑与你的业务代码解耦,阈值调整不用改主流程。
  • TypeSafe 落点:分数最终被翻译成有限的几个类型(通过 / 修正 / 复核),这是路由的真正依据。

问题:阈值散落在十几个 if-else 里,改一次要全仓搜索。 治理:分数入口只有一处,阈值收敛到一个配置对象,改一行全生效。

核心结论:Jev 卖的不是「对错」,而是一个可以写进路由的数,先拿到这个数,后面所有分层策略才有地基。

image_20260922_162302_899121_1.png

二、申请 API Key:从注册到跑通第一次调用

拿到凭据分四步走,全程在控制台完成:

  1. 注册登录:打开 Jev 官网入口,完成账号注册并登录控制台。
  2. 生成 Key:进入 user/generateapikey 页面点击生成,Key 只在生成时完整显示一次。
  3. 立即存档:把 Key 写进环境变量或密钥管理服务,不要提交进 Git。
  4. 冒烟测试:用最短的 curl 验证 Key 有效,再开始写业务代码。

下面这段在 macOS/Linux 的终端里执行,用一行 curl 打一次最小请求验证凭据:

export JEV_API_KEY="你的Key"

curl -sS -X POST "https://api.jev.example/chat/api/judgment/get" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $JEV_API_KEY" \
  -d '{"prompt":"请总结这段话的核心观点","judgment":"总结准确"}'

强约束凭据 → 最小请求 → 拿到 score 再谈业务

常见坑是把 Key 直接硬编码进前端或开源仓库,一旦推送就只能作废重发,所以第一原则是 Key 只活在服务端环境变量里。

**核心结论:**冒烟能通就说明 Key、域名、请求头三件套都对了,后面所有问题都只剩业务层。

三、请求格式:三个字段决定成败

先把请求与响应的字段约定钉死,后面封装才有依据:

字段位置类型说明
prompt请求体string待判断的原始内容,Jev 真正打分的对象
judgment请求体string你期望的判断标准,决定分数的语义
Authorization请求头stringBearer <Key>,缺失直接 401
success响应体boolean判断是否成功执行,不等于判断结论为真
score响应体number0~100 置信分,路由的唯一输入

下面这段 Python 用 requests 打一次调用并把关键字段拆出来,适配 Python 3.9+ 环境:

import os, requests

JUDGE_URL = "https://api.jev.example/chat/api/judgment/get"

def judge(prompt: str, judgment: str, timeout: float = 5.0) -> dict:
    resp = requests.post(
        JUDGE_URL,
        headers={"Authorization": f"Bearer {os.environ['JEV_API_KEY']}"},
        json={"prompt": prompt, "judgment": judgment},
        timeout=timeout,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("success"):
        raise RuntimeError(f"judgment failed: {data}")
    return {"score": int(data["score"]), "raw": data}

if __name__ == "__main__":
    print(judge("天空为什么是蓝色的", "解释瑞利散射且结论正确"))

success 管调用、score 管路由,两者混用会把服务故障当成低分

注意 judgment 写得越具体,分数越可比:写「准确」和写「包含关键数据且无编造」得到的 80 分完全不是一回事,阈值标定的前提是判断标准先固定下来。

**核心结论:**字段少不代表可以随手写,判断标准文本就是分数的标尺,标尺一换,历史阈值全部作废。

image_20260922_162400_849940_2.png

四、封装裁判函数:把 HTTP 细节收敛成一次调用

业务代码不该关心超时、状态码和 JSON 解析,所以要收口:

  • 单一入口:全项目只保留一个 judge(),将来换域名、加请求头只改这一处。
  • 显式超时timeout 必须传,默认 5 秒,绝不让请求无限挂着。
  • 失败即异常successfalse 时抛异常,而不是返回一个假的 0 分污染统计。
  • 结果归一:返回 int(score),避免上游拿到 float 后比较逻辑出现精度意外。

下面这段在上一节基础上补齐重试与结构化日志,适配生产环境直接复用:

import time, logging
from tenacity import retry, stop_after_attempt, wait_exponential

log = logging.getLogger("jev")

@retry(stop=stop_after_attempt(3), wait=wait_exponential(min=0.2, max=2))
def judge(prompt: str, judgment: str, timeout: float = 5.0) -> dict:
    resp = requests.post(
        JUDGE_URL,
        headers={"Authorization": f"Bearer {os.environ['JEV_API_KEY']}"},
        json={"prompt": prompt, "judgment": judgment},
        timeout=timeout,
    )
    resp.raise_for_status()
    data = resp.json()
    if not data.get("success"):
        raise RuntimeError(f"judgment failed: {data}")
    score = int(data["score"])
    log.info("judge score=%s judgment=%s", score, judgment[:40])
    return {"score": score, "raw": data}

收口 + 指数退避重试 + 结构化日志,三件事一次做齐

日志里记 judgment 前 40 字很关键,日后做阈值复盘时,你能凭日志还原当时用的是哪把标尺。

核心结论:裁判函数是整个链路的单一事实来源,路由写得再花哨,也要从这一个出口拿分数。

五、阈值标定:用真实分布定三条线

阈值不是拍出来的,是量出来的,先看三档路由的含义:

分数区间路由目标典型动作成本
90 ~ 100TypeSafe 模型直接结构化输出,进入下游
70 ~ 89LLM 修正追加一轮改写后再判断
0 ~ 69人工复核挂起并推给值班同学

下面这段 Python 拿标注集跑一遍 Jev,统计分数分布并自动求出候选阈值,适配离线标定任务:

import numpy as np

def calibrate(samples: list[dict], target_pass: float = 0.9):
    scores = np.array([judge(s["prompt"], s["judgment"])["score"] for s in samples])
    labels = np.array([s["label"] for s in samples])  # 1=正确, 0=错误
    # 选出让「通过组里真正确」的比例 ≥ target_pass 的最低阈值
    best = None
    for t in range(50, 101):
        passed = labels[scores >= t]
        if len(passed) == 0:
            continue
        precision = passed.mean()
        if precision >= target_pass and (best is None or scores[scores >= t].mean() > best[1]):
            best = (t, scores[scores >= t].mean())
    return best  # (阈值, 该组平均分)

print(calibrate(SAMPLES, target_pass=0.95))

问题:阈值 70 沿用了三年,没人知道它从哪来。 治理:每次模型换版都用 200 条标注样本重跑标定,阈值带版本号入库。

先保证 90 分 以上组的准确率达标,再反推阈值,而不是先定阈值再祈祷准确率——顺序反了,标定就没有意义。

核心结论:阈值必须可复算、可追溯、可版本化,否则它只是写进代码里的迷信。

六、两层兜底:修正与人工复核怎么接

低分不等于失败,它只是告诉你这条路不能直走:

  • 第一层 LLM 修正:70~89 分段追加一轮改写,把模糊结论补成可验证陈述。
  • 第二层人工复核:70 分以下直接挂起,宁可慢也不让低置信内容进生产。
  • 修正后重判:改写完成必须重新调一次 Jev,用新分数决定去留,不许自我通过。
  • 兜底上限:修正最多一次,避免无限循环烧钱。

下面这段 JS 用原生 fetch 实现分层路由,适配 Node 18+ 与浏览器同构环境:

export async function route(prompt, judgment) {
  const first = await judge(prompt, judgment);
  if (first.score >= 90) return { path: 'typesafe', payload: first };

  if (first.score >= 70) {
    const fixed = await rewrite(prompt, first);          // 追加一轮改写
    const second = await judge(fixed, judgment);         // 改写后必须重判
    if (second.score >= 90) return { path: 'typesafe', payload: second };
    return { path: 'manual', payload: second, reason: 'correction_failed' };
  }

  return { path: 'manual', payload: first, reason: 'low_score' };
}

低分 → 修正 → 重判 → 仍低则挂起,四步构成完整兜底

修正失败的分支必须显式带 reason,否则一周后你分不清是分数太低还是改写模型超时,排障成本翻倍。

核心结论:兜底的价值不在多聪明,而在每条路径都有终点,绝不允许内容卡在中间状态。

image_20260922_162436_871267_3.png

七、速度与失败:超时、重试与降级

判断服务也是服务,必须按故障件对待,先看延迟预算怎么排:

环节预算超时动作降级策略
Jev 打分500ms ~ 5s指数退避重试 3 次降级为保守人工路径
LLM 修正3 ~ 10s单次重试直接挂起待复核
TypeSafe 输出1 ~ 3s快速失败缓存上一次成功结果
端到端≤ 15s中断并告警返回降级响应体

下面这段 bash 用 curl 探测 Jev 的实际延迟分布,适配上线前的压测脚本:

#!/usr/bin/env bash
for i in $(seq 1 20); do
  t=$(curl -o /dev/null -sS -w '%{time_total}' -X POST \
    "https://api.jev.example/chat/api/judgment/get" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $JEV_API_KEY" \
    -d '{"prompt":"latency probe","judgment":"有效回答"}')
  echo "run $i total=${t}s"
  sleep 0.2
done

测出 p95 延迟,才配给 timeout 赋值

降级顺序要提前写死:打分服务不可用时,路由宁可全走人工复核,也不能默认高分放行——失败方向必须朝安全侧偏。

**核心结论:**超时值来自实测分位数,拍脑袋的 30 秒超时只会把故障掩盖成慢查询

八、TypeSafe 模型:把分数翻译成有限类型

分数是连续的,路由需要离散的类型,这一步就是 TypeSafe 的用武之地:

  • 枚举封顶:只有 PASS / CORRECT / REVIEW 三种取值,新增路径必须改类型定义。
  • 构造函数守门:类型只能由 score 构造,外部无法直接 new 出一个越界结果。
  • 穷尽检查switch 必须 never 兜底,漏写分支时编译期就报错。
  • 序列化固定:入库字段与枚举一一对应,历史数据永远可读。

下面这段 TypeScript 定义分数到类型的唯一映射,适配任意 TS 工程:

type Verdict =
  | { kind: 'PASS';    score: 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 }
  | { kind: 'CORRECT'; score: 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79
                      | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 }
  | { kind: 'REVIEW';  score: 0 };

export function toVerdict(raw: number): Verdict {
  const score = Math.max(0, Math.min(100, Math.round(raw)));
  if (score >= 90) return { kind: 'PASS', score: score as Verdict extends { kind: 'PASS' } ? never : never } as Verdict;
  if (score >= 70) return { kind: 'CORRECT', score } as unknown as Verdict;
  return { kind: 'REVIEW', score: 0 };
}

export function assertNever(x: never): never { throw new Error(`unhandled: ${x}`); }

分数裁剪 → 区间映射 → 类型收窄,越界值在入口就被吞掉

真正的价值在下游:拿到 verdict.kind === 'PASS' 时,你不需要再判断分数,编译器已经替你排除了其他两种可能。

核心结论:TypeSafe 的本质是把运行期的分数不确定性,在类型层一次性关掉

image_20260922_162518_272816_4.png

九、接入 Agent 循环:把判断嵌进真实工作流

Agent 里每次工具调用后都该有一次判断,否则错误会逐轮放大:

  • 判断时机:工具返回结果之后、下一步规划之前,插入一次 Jev 调用。
  • 按类型分流PASS 直接进记忆,CORRECT 触发改写,REVIEW 中断循环并请求人工。
  • 轮次上限:单个任务最多修正 2 次,超限直接转人工,防止自嗨循环。
  • 全程留痕:每轮记录 scoreverdict,回放时能精确定位从哪一步开始跑偏。

下面这段 Python 把判断嵌进最小 Agent 主循环,适配自研 Agent 或现有框架:

def run_agent(task: str, max_rounds: int = 5) -> str:
    memory = []
    for round_no in range(max_rounds):
        result = call_model(task, memory)
        verdict = to_verdict(judge(result, "结论可验证且无编造")["score"])
        log_round(round_no, verdict)

        if verdict["kind"] == "PASS":
            memory.append(result)
            continue
        if verdict["kind"] == "CORRECT":
            result = rewrite(result)              # 改写后重新入池
            memory.append(result)
            continue
        return escalate(task, memory)             # REVIEW:交人
    return escalate(task, memory)                 # 轮次耗尽同样交人

打分 → 分型 → 分流,三步插在每轮规划之前

escalate 一定要连同 memory 一起交出去,否则值班同学接手时得从零复现整个上下文,兜底就变成了负担。

核心结论:判断不进循环,Agent 的错误只会逐轮复利,而每轮一次打分是成本最低的刹车。

十、上线检查清单:从测试到监控

最后一公里靠清单兜住,逐项打勾再放量:

检查项验收标准常见翻车点
Key 存储仅服务端环境变量,仓库有扫描提交进前端代码
冒烟用例生产同款域名跑通 20 次用测试域名验收
阈值标定有标注集与版本号直接抄默认 70/90
超时重试p95 实测后再赋值沿用 30 秒默认值
降级路径打分挂掉时走人工复核失败默认高分放行
监控埋点分数分布、路由占比、失败率只监控 HTTP 状态码
留痕回放每轮 score 与 verdict 可查只存最终结果
# 每日阈值漂移巡检:分数分布偏移超过 5 分即告警
curl -sS "$PROMETHEUS/api/v1/query" \
  --data-urlencode 'query=histogram_quantile(0.95, sum(rate(jev_score_bucket[1h])) by (le))' \
  | jq '.data.result[0].value[1]'

盯分布而不是盯单点,漂移早在用户投诉之前就能看见

放量节奏建议三步:先 5% 流量观察一天路由占比,再 30% 观察修正触发率是否异常,最后全量并保留一键回滚到人工复核的开关。

**核心结论:**上线不是终点,分数分布的日常巡检才是这套路由能长期活着的原因。

image_20260922_162610_218234_5.png

结语

这篇指南从申请 Key 起步,把 Jev 的裸 HTTP 契约逐步封装成可复用的裁判函数,再用标定出来的三档阈值驱动 TypeSafe 类型路由,最后嵌进 Agent 主循环并配上完整的上线清单。整条链路没有一处依赖「模型说行就行」,每一次分支都有分数依据、每一次降级都有明确终点,出了问题也能凭留痕回放还原现场。

真正值得带走的方法论是:别让程序猜模型的意图,让分数说话,让类型封口。