本节摘要:Jev 没有原生的 rank 类型,也不需要——标准做法是逐项打分再排序:对每个候选,用同一套逐字一致的 Score 或 Noul 问"它满足标准的程度",拿校准概率当分数,代码里排序。三条纪律保证可比性:每次评估独立请求(state 是"查询 + 该候选",候选不同 state 就不同,不能塞进一个请求);instructions 与 criteria 逐字一致(标尺变了,分数就不可比);候选用同一份上下文(查询原文一字不差)。Jev 排名对"让 LLM 给每个候选打个 1~10 分"式排序的核心优势:LLM 的分数没有稳定刻度(同一候选两次打分可差 3 分),Jev 的校准概率有。
阅读完本节,你应当能够:
场景:RAG 检索回 5 段候选摘要,要挑最相关的重排。
# 同一把尺子:所有候选用完全相同的 questions RELEVANCE = { "covers_query": { "type": "noul", "instructions": "该候选内容直接回答了查询的问题,而非仅话题相关", } } def rank(query: str, candidates: list[str]) -> list[tuple[str, float]]: scored = [] for cand in candidates: # state = 查询 + 该候选 —— 候选不同,state 不同,必须独立请求 state = {"query": query, "candidate": cand} p = jev_noul(state, RELEVANCE)["covers_query"] scored.append((cand, p)) return sorted(scored, key=lambda x: -x[1]) # 代码排序
判据的选型(第 3.4 节决策树在排名场景的落点):
| 判据形态 | 用 | 例 |
|---|---|---|
| 二值可判 | Noul(概率即分数) | "直接回答了问题吗" |
| 程度有阶梯 | Score(加权分即分数) | 相关性 0 完全无关~4 直接回答 |
| 多个维度 | 多问并行,代码加权(第 6.3 节) | 相关性 × 覆盖度 |
RELEVANCE),杜绝在循环里拼接措辞。⚠️ 为什么"让 LLM 打 1~10 分"不可比:LLM 的分数字面无锚点——同一候选两次打分可差 3 分,候选顺序还影响锚定。Jev 的概率是 RLCD 校准过的稳定量纲(0.85 与 0.72 的差距有统计含义),跨请求可比——这正是排名需要的性质。
候选多时逐项串行太慢,用并发(每个请求独立、无副作用,天然可并发):
from concurrent.futures import ThreadPoolExecutor def rank_concurrent(query: str, candidates: list[str]) -> list[tuple[str, float]]: def one(cand: str) -> tuple[str, float]: state = {"query": query, "candidate": cand} return cand, jev_noul(state, RELEVANCE)["covers_query"] with ThreadPoolExecutor(max_workers=5) as pool: # 并发度兼顾限速(第 8.3 节) scored = list(pool.map(one, candidates)) return sorted(scored, key=lambda x: -x[1])
实战参照:独立评测用 Jev 做"候选摘要挑最好"的排序,单次评估 ~100ms、近零成本——5 个候选的完整重排加起来仍远快于一次 LLM 调用。
💡 并列与悬殊:两个候选概率差 0.02 属于"统计上并列"——此时别硬排,要么都保留要么补充第二道判据(如"表述更清晰"Noul)再加权;差 0.4 以上才是真悬殊。
| 场景 | 打分判据 |
|---|---|
| RAG 重排 | "直接回答查询"(Noul)或相关性阶梯(Score) |
| 工单队列排序 | "最该先处理"复合分(第 6.3 节模式逐单打分) |
| 摘要/标题挑选 | "完整覆盖要点" + "无捏造" 双 Noul 加权 |
| 成对比较(A/B 谁好) | state 放 A+B,Choice 两项 + other——适合"同一位置的两种译法"这类成对输入 |
排名是把 Jev 用在输入侧(挑最好的进上下文);下一节把 Jev 用在输出侧——生成的东西,先过你自己写的闸门。