用 Jev 做 RAG 重排:答案更准、成本更低

更新于 适用版本 Jev 1.13

TL;DR: 召回负责找到”看起来相关”的 chunk,重排(rerank)负责判断”哪个真的能回答问题”。用 Jev 的打分题(scoring)给每个候选 chunk 的相关性打 1-10 分,排序、按阈值截断,只让幸存者进入生成模型。用几次廉价的打分调用换来更少、更干净的上下文 token——答案更准,生成成本更低。文末附 50 行以内的最小实现。

这里的”重排”指什么

{"answer":8,"scale":[1,10],"confidence":0.86,"rationale":"直接定义了问题所问的重试策略。"}

answer 是相关性分,confidence 表示模型对自己这个分数有多大把握,rationale 在你回头排查检索质量时非常有用。

流程图

用户问题
   |
   v
第一阶段召回(top-50 候选)
   |
   v
逐 chunk 调 Jev scoring(相关性,1-10 分制)
   |
   v
按分数降序排序
   |
   +--> 分数 >= 7 且置信度可接受 --> 保留
   |
   +--> 其余 --> 丢弃
   |
   v
取前 k 个幸存 chunk 作为生成上下文

重活仍然在召回器,Jev 只负责判断”已经找到的东西够不够好”。

最小实现

import os, json, requests

URL = "https://openrouter.ai/api/v1/chat/completions"
# Confirm the exact model slug on the OpenRouter model page.
MODEL = "typesafe/jev-1.13"

def score_chunk(query: str, chunk: str) -> dict:
    resp = requests.post(
        URL,
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
        json={
            "model": MODEL,
            "messages": [
                {"role": "system", "content":
                 "Score how well this passage answers the query, 1-10. "
                 "1 = irrelevant, 10 = directly and completely answers it."},
                {"role": "user", "content": f"Query: {query}\n\nPassage: {chunk[:2000]}"},
            ],
            "temperature": 0,
        },
        timeout=30,
    )
    resp.raise_for_status()
    # 示例数据(example fixture):
    # {"answer":8,"scale":[1,10],"confidence":0.86,"rationale":"..."}
    return json.loads(resp.json()["choices"][0]["message"]["content"])

def rerank(query, chunks, min_score=7, top_k=5):
    scored = [(score_chunk(query, c), c) for c in chunks]
    scored.sort(key=lambda x: x[0]["answer"], reverse=True)
    return [c for s, c in scored if s["answer"] >= min_score][:top_k]

rerank() 返回清洗后的上下文列表,RAG 栈其余部分原样不动。官方 API 的端点与字段以 typesafe.ai 官方文档为准。

阈值建议

配置项建议理由
分制[1, 10]足以区分”沾边”和”能回答”
收录线分数 >= 7低于此线的 chunk 很少改变最终答案
top-k 上限5(在 3-8 之间调)即使大量 chunk 过线,生成 token 也有上限
打分低置信度丢弃,或触发更宽召回重试模型自己都没把握的分数,本身就是弱证据
批量说明串行或小并发打分批量重排案例讨论了吞吐与限流的平衡

本场景依赖 Jev 的三原语(three primitives)——scoring 是三种任务类型之一;成本与延迟(cost and latency)指南解释了为什么”廉价判断调用放在昂贵生成模型前面”通常在总花费上更划算。RAG chunk 批量重排的案例里给出了实测数据。

本文适用版本:Jev 1.13。

重排上线后的评估方法

离线回测

上线前先在历史问题集上构造一份离线基准:把”未重排”与”重排后”的上下文分别喂给生成模型,用同一批参考答案评估质量差异。常用的三个衡量维度:

指标衡量方式说明
召回命中率top-k 中含参考答案片段的比例用人工标注集或 LLM 裁判均可
上下文 token 用量喂给生成模型的总 token 数直接影响成本,详见成本与延迟指南
人工相关分1-5 分主观评估每次抽取 20-50 条做基准对比

示例数据(example fixture)——一次离线对比的结果片段,字段含义仅供参考,具体数字以自己项目实测为准:

{
  "without_rerank": {"avg_context_tokens": 3200, "hit_rate": 0.61},
  "with_rerank":    {"avg_context_tokens": 1100, "hit_rate": 0.74}
}

定期抽检

上线后每周从生产日志随机抽取若干问题,将重排前的候选集与最终留存 chunk 放在一起,人工判断截断是否合理。需要关注两类错误:

定期抽检的频率无需很高;每周抽 10-20 条并记录趋势,比偶发性大规模人工评估更可持续。

与向量检索、BM25 混合时的取舍

现代 RAG 常把向量检索(语义近邻)与 BM25(关键词词频)做混合来覆盖两类失配:语义相近但词面不同、以及词面一致但语义偏移。混合检索后接入重排时,以下取舍值得关注:

场景建议
混合检索候选集较大(> 30 条)重排收益更明显;可先用较低阈值(如 6 分)保留更多候选,再用 top-k 截断
候选集本身很小(< 10 条)阈值适当放宽,防止全部被截断导致上下文为空,参考常见坑第一条
实时问答、延迟敏感将候选集上限控制在 20 条以内,打分调用可小并发但注意限流
批量文档处理串行或小并发均可,关注 成本与延迟指南 的限流建议

混合检索提升了召回覆盖率,同时也把更多杂质候选带进来;重排负责”在更宽的召回结果里找真正有用的部分”。两者职责分明,接口层面互不干扰,可以独立迭代。

BM25 分支对精确短语匹配表现更好,向量分支对语义改写更鲁棒,但两路结果的分数量纲不同,融合时通常做归一化再相加。重排在融合之后统一对候选集打相关性分,不需要感知上游是哪路检索,这也是它在混合架构里接入代价低的原因之一。

常见坑

1. 候选集被全部截断:当所有 chunk 得分都低于 min_score 时,rerank() 返回空列表,下游生成模型会收到空上下文并可能产生幻觉。解决方式:返回空列表前回退到分数最高的 1 条,或者触发一次更宽的召回重试。具体降级策略可参考 置信度与回退指南。

2. 串行打分延迟叠加:50 个候选 × 每次打分约 300ms ≈ 15 秒端到端延迟。批量场景建议用线程池(4-8 并发),单条实时问答建议先把候选集压缩到 20 条以内再进重排。

3. confidence 字段未纳入决策:rationale 是可读解释,便于排查;但判断打分可信度应看 confidence。两个字段各司其职——只记录 answer 而忽略 confidence,相当于丢掉了模型对自身不确定性的表达。

4. 分制与 prompt 不一致:在调用里把 scale 设为 [0, 100] 却在 system prompt 里仍写”1-10 分制”,会让模型打分无参照,结果混乱。始终保持 scale 参数与 system prompt 中描述的分制完全一致。

5. 忽略召回质量退化:重排只能在已召回的候选里选优,无法弥补召回覆盖率本身的下降。如果发现即使放宽阈值命中率仍然持续下滑,应先排查向量索引或 BM25 分词是否有问题,而不是继续调整重排参数。

FAQ

常见问题

+ Jev 在 RAG 重排环节里扮演什么角色?

第一阶段召回拿到宽候选集后,用 Jev 的 scoring 调用给每个 chunk 的相关性打分,按分数排序、按阈值截断,只把留下的高质量 chunk 交给生成模型。

+ 截断 chunk 的分数阈值怎么定?

在 1-10 分制上,7 分且置信度可接受是比较合理的收录线;同时叠加 top-k 上限,保证生成模型不会拿到过多上下文。

+ 用 Jev 重排比只用向量检索更贵吗?

每个候选 chunk 多一次廉价的打分调用,但下游可以少喂、喂得更准。批量 chunk 重排案例详细测算了这笔成本账。

继续阅读