Files
SuperBizAgent-java/docs/RAG排序-多路召回与RRF.md
T
zhuyongxin 7ae9707a3b feat(harness,rag): dual LLM audit fields, run conclusion, and hybrid quality
Persist provider reasoning and assistant text separately on agent_reasoning_audit
(DeepSeekAssistantMessage path), extract diagnosis_run.conclusion, enrich RAG
tool audit (step_id/query/qualityScore), gate empty mysql tools, drop devtools,
and align MVP docs after live E2E verification.
2026-07-28 19:43:13 +08:00

889 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 诊断 Agent 场景下的 RAG 排序:从 K=3 规则加分,到多路召回与 RRF
**日期**:2026-07-27
**范围**:知识检索排序、多路召回、分数融合、Rerank 选型
**读者**:需要在 Agent 系统里落地 RAG,而不是只做 Demo 问答的工程同学
**关联实现**:`Lookup_knowledge` 模块化链路(L0 hint → L1 向量 → 规则后处理 → 投影)
---
## 1. 引言:RAG 排序常被高估,也常被低估
在诊断 Agent 里,知识库检索很少是“搜一下、答一句”那么简单。一次 `lookup_knowledge` 往往要在有限工具预算里,尽快给出可引用的证据片段。这时排序质量会直接影响:
- Agent 是否看得到正确 runbook / 排查步骤
- 是否被错误 domain 的文档带偏
- 是否在本就很少的 topK 里,把唯一有用的 chunk 挤掉
讨论排序时,团队里很容易出现两种极端:
1. **高估 Rerank**
一提质量问题就上 Cross-Encoder、商业 Rerank、LLM listwise。
但候选池只有 3 条时,精排只能在这 3 条里换座位,召不回的内容永远排不上来。
2. **低估融合**
觉得“都是相似度,加一加就行”。
但向量 L2、BM25、关键词加分根本不在同一尺度上,硬加权会把系统调成玄学。
本文基于一次针对真实诊断 Agent RAG 链路的讨论,整理一套可落地的判断框架:
```text
1. 候选太少时,复杂 rerank 价值有限
2. 多路召回解决“漏”,精排解决“噪”
3. 跨路不要硬加原始分,优先 RRF 用名次投票
4. L0 / 关键词适合做提示,不适合当最终裁判
5. 权重 w_i 是“路权”,不是文档原始分
```
目标不是证明某一种模型永远最优,而是回答工程上更常见的问题:
> 现在的 K、现有的 L0/L1、现有的规则加分,下一步到底该扩召回、该融合,还是该上精排?
```mermaid
flowchart TB
P[排序/质量问题] --> A{候选池 K 多大?}
A -->|K 很小 3~5| B[先扩召回 / 修去重 / 轻规则]
A -->|K 中等 15~30| C[多路 + RRF + 可选轻精排]
A -->|K 很大 50+| D[强 rerank 才划算]
P --> E{跨路分数?}
E -->|原始分硬加| F[尺度不同 · 易玄学]
E -->|RRF 名次投票| G[推荐]
```
---
## 2. 现状解剖:有“重排”,不等于有“强 Rerank”
### 2.1 一条典型主链路
以模块化 `lookup_knowledge` 为例,主路径大致是:
```text
Agent query
-> KnowledgeQueryTransformer # L0:domain / keyword hint,可选 category filter
-> KnowledgeDocumentRetriever # L1:向量 topK
-> KnowledgeEvidencePostProcessor # 归一化 + 规则 boost + 排序 + 去重
-> KnowledgeContextPacker # 字符预算打包
-> LookupResultAssembler
-> RagResultProjector # 投影成 Agent 可见契约
```
```mermaid
flowchart TB
AQ[Agent query] --> L0[KnowledgeQueryTransformer<br/>L0 hint / category filter]
L0 --> L1[KnowledgeDocumentRetriever<br/>L1 向量 topK]
L1 --> POST[KnowledgeEvidencePostProcessor<br/>归一化 + 规则 boost + 去重]
POST --> PACK[KnowledgeContextPacker]
PACK --> ASM[LookupResultAssembler]
ASM --> PROJ[RagResultProjector]
PROJ --> AGENT[Agent 可见结果]
```
其中“重排”发生在后处理阶段,名字也常叫 rerank,但实现通常是:
```text
baseScore = 向量距离归一化后的相似度
finalScore = baseScore
+ domain_match
+ entity_match
+ keyword_match
+ source_type_prior
再按 finalScore 降序
```
```mermaid
flowchart LR
BASE[baseScore<br/>向量相似度] --> SUM[finalScore]
D[+ domain]
E[+ entity]
K[+ keyword]
S[+ source_type]
D --> SUM
E --> SUM
K --> SUM
S --> SUM
SUM --> SORT[按 finalScore 降序]
```
同时会留下 `rerankTrace`(base/final score、boost reasons),便于内部审计。
### 2.2 这套做法解决了什么
它不是毫无意义:
- 在小候选池里,能把更像目标域、更像 runbook 的结果往前推
- 有可解释的 boost 原因,方便 trace
- 实现成本低,不引入额外模型服务
如果只是 Demo,或语料很小、query 很规范,这种轻规则重排往往够用。
### 2.3 它没解决什么
真正的问题通常不在“3 条里谁排第一”,而在:
1. **K 太小**
`topK=3` 时,重排空间极小。复杂精排模型也只能在这 3 条里微调。
2. **规则加分不稳定**
依赖 L0 词表和字符串 `contains`。短词、泛词、别名缺失都会让 boost 误触发或漏触发。
3. **L0 filter 可能误杀**
唯一 domain 时加 category filter,能降噪;一旦 L0 判错域,正确文档可能根本进不了候选。
4. **去重粒度若偏文档级**
同文档多个相关 chunk 可能被压成 1 条,召回了也会在后处理/投影阶段丢掉。
5. **Agent 侧看不到排序细节**
投影层常只保留 excerpt 与少量元数据,score / hitReasons / retrievalTrace 被裁掉。这对安全边界合理,但对模型“判断有多相关”不友好。
一句话概括现状:
> 不是没有排序,而是在太小的候选池里,用不够稳的启发式做微调。
---
## 3. 关键判断:候选池大小决定策略上限
排序策略必须和 K 匹配。可以先用下面这张表做决策:
```mermaid
flowchart TB
K{retrieve-k / 候选规模}
K -->|3~5| S1[修去重 · 轻规则<br/>双路径 dense 融合]
K -->|15~30| S2[多路召回 + RRF<br/>可选轻精排]
K -->|50+| S3[强 Cross-Encoder / 托管 Rerank]
S1 -.->|先别上| X1[商业 Rerank]
S2 -.->|收益有限| X2[只继续调 keyword boost]
S3 -.->|避免| X3[无评测堆模型]
```
| 召回规模 K | 更适合做什么 | 不太值得先做什么 |
|---|---|---|
| 3 ~ 5 | 修去重、轻规则、双路径 dense 融合 | Cross-Encoder / 商业 Rerank |
| 15 ~ 30 | 多路召回 + RRF + 轻精排 | 只继续调 keyword boost |
| 50+ | 强 rerank 才划算 | 无评测地堆模型 |
背后的原因很简单:
```text
Rerank 的价值来自:
候选很多、噪声大 → 精排把好的顶到前面
如果好文档根本不在候选里:
再贵的 rerank 也救不回来
```
因此工程上应把两个参数拆开:
```text
retrieve-k # 召回阶段多捞一些,例如 20
return-n # 最终给 Agent 的条数,例如 3~5
```
而不是始终:
```text
topK = 3,召回、排序、返回都是 3
```
**先让该进来的进来,再谈谁排前面。**
---
## 4. 多路召回:先分清“同模态双路径”和“跨维度多路”
“多路召回”不是只有 BM25 + 向量这一种。可以分层理解。
### 4.1 同模态双路径:filtered + unfiltered
很多系统已经有类似逻辑:
```text
若 L0 给出唯一 category:
先 filtered 向量检索
若结果差,再 unfiltered 重试
```
```mermaid
flowchart TB
Q[query + L0 category?] --> F[filtered dense]
F --> BAD{结果差/空?}
BAD -->|是| U[unfiltered retry]
BAD -->|否| USE[采用 filtered]
U --> REP[常见:整锅替换 filtered]
```
这能工作,但常见实现是 **串行整锅替换**:
- retry 成功后,直接丢掉第一次 filtered 的全部结果
- 没有“两边好结果都保留,再统一排序”
更稳的做法是把它升级为并行/双路融合:
```text
路 A:dense + category filter # 求准
路 B:dense + 无 filter # 求全
去重合并后一起排序
```
```mermaid
flowchart LR
Q[query] --> A[路A filtered dense]
Q --> B[路B unfiltered dense]
A --> M[去重合并]
B --> M
M --> RRF[RRF / 统一排序]
RRF --> OUT[topN]
```
#### 为什么值得做?
因为两路解决的是不同失败模式:
| 路径 | 优点 | 风险 |
|---|---|---|
| filtered | 更贴当前域,噪声少 | filter 错了会漏召回 |
| unfiltered | 召回面宽,容错强 | 容易掺进其他域文档 |
典型场景:
- L0 误判成 `mysql`,真正文档在 `redis`
→ 只走 filtered 会空或很差
→ unfiltered 能救回
- L0 判对了 `mysql`
→ filtered 更干净
→ unfiltered 可能带噪声
所以:
> filtered 求准,unfiltered 求全;融合比二选一更稳。
#### 它算多路吗?
算,但要说清楚边界:
```text
这是同一 dense 检索器、不同过滤条件的双路径
属于多路召回的子集
还不是完整的跨维度多路
```
它的主要价值是:
1. 降低 L0 category 误杀
2. 保留“有 filter 时更准”的收益
3. 避免 retry 整锅替换导致好结果被误删
即便暂时不上 BM25,只做这一步,也常常比继续调 keyword boost 更有效。
### 4.2 跨维度多路:Dense + BM25
更完整的多路,通常来自不同相关性维度:
| 路 | 擅长 | 不擅长 |
|---|---|---|
| Dense(向量) | 语义相近、换说法、同义表达 | 罕见专有名词可能漂 |
| BM25 / 关键词 | 错误码、类名、配置键、告警名、精确术语 | 换一种说法就容易漏 |
例子:
```text
用户说:连接池打满了
文档写:HikariCP pending threads high
→ dense 更容易搭上
用户说:SQLSTATE 08001
文档标题就含 08001
→ BM25 / 字面匹配往往更稳
```
因此:
```text
candidates = dense ∪ bm25
```
不是“BM25 替代向量”,而是“补向量漏掉的字面命中”。
### 4.3 L0 能不能当一路召回?
可以,但要降权、控边界。
L0(文档 frontmatter 关键词 / domain hint)适合:
- 决定要不要启用 filtered 路
- 提供精排时的弱特征
- 写出可解释 trace
不适合:
- 关键词命中就直接当高置信事实证据
- 用 L0 粗分主导最终排序
一句话:
> L0 是导航,不是裁判长。
---
## 5. 融合为什么难:不是不会加权,是分数不可比
多路召回之后,第一反应常常是:
```text
final = 0.7 * dense_score + 0.3 * bm25_score
```
这在课堂上好讲,在工程上很脆。
### 5.1 原始分为什么不能直接加
不同路的分数:
- Dense:L2 距离或 cosine similarity,分布随 embedding 模型和语料变化
- BM25:另一套量纲,数值大小和 dense 完全不可比
- 规则 boost:+0.15 / +0.20 这种启发式加分,更像人工偏好,不是校准概率
把它们直接线性相加,等于默认“0.1 的向量分提升”和“2.0 的 BM25 提升”可以交换。这个默认通常不成立。
### 5.2 规则 keyword boost 为什么不稳定
若最终分主要靠:
```text
query.contains(keyword) || keyword.contains(query)
```
再叠加固定加分,会出现:
- 短词误命中
- 泛词普遍加分,区分度下降
- 词表一改,线上排序整体漂移
- 同义词没写进 frontmatter 就完全没帮助
所以:
> 现在的“权重”如果本质是关键词启发式加分,稳定性天然有限。
这不代表规则无用,而是应把它放对层:路内微调或精排特征,而不是跨路主融合器。
---
## 6. RRF:跨路融合时,优先用名次投票
### 6.1 核心思想
RRF(Reciprocal Rank Fusion)的关键思想是:
> 不管各路原始分是什么尺度,只看每条候选在各路里的名次,再投票。
基础公式:
```text
RRF(d) = Σ 1 / (k + rank_i(d))
```
其中:
| 符号 | 含义 |
|---|---|
| `d` | 某个候选文档或 chunk |
| `i` | 第 i 路召回 |
| `rank_i(d)` | d 在第 i 路中的名次(从 1 开始);未出现则该路贡献为 0 |
| `k` | 常数,常用 60,用来缓和头部名次过强 |
直觉:
- 某路第 1 名:贡献约 `1/61`
- 某路第 2 名:贡献约 `1/62`
- 多路都靠前的候选,融合分自然更高
- 只在一路偶然靠前的候选,不会单靠绝对分尺度“爆掉”
```mermaid
flowchart TB
subgraph paths["各路有序结果"]
P1[dense ranks]
P2[BM25 ranks]
P3[filtered ranks · 可选]
end
P1 --> RRF["RRF(d) = Σ 1/(k + rank_i)"]
P2 --> RRF
P3 --> RRF
RRF --> OUT[融合序 · 不依赖原始分尺度]
L2[L2 原分] -.->|不直接相加| X[避免]
BM[BM25 原分] -.-> X
```
### 6.2 为什么适合 RAG 多路融合
RRF 特别适合下面这种现实约束:
```text
dense 用 L2
bm25 用 BM25
filtered / unfiltered 虽同度量,但候选集合不同
暂时没有可靠的分数校准器
```
它把问题从:
```text
如何把不可比的分数对齐?
```
简化成:
```text
各路是否都认为它靠前?
```
### 6.3 加权 RRF:w_i 是路权,不是文档分
加权形式:
```text
RRF_w(d) = Σ w_i / (k + rank_i(d))
```
这里的 `w_i` 非常容易被误解。
#### 正确理解
```text
w_i = 第 i 整路的话语权
```
例如:
```text
w_dense = 1.0
w_bm25 = 1.5 # 想让 BM25 更重要,就提高这一路的 w
w_filtered_dense = 1.0
```
含义是:
- BM25 这一路投出的“名次票”更值钱
- 并不是把某个文档的 BM25 原始分 12.7 直接拿来和向量分相加
#### 错误理解
```text
先算 keyword boost 得到一个大杂烩分
再把这个分塞进 RRF
```
或:
```text
RRF = f(L2原始分, BM25原始分, keyword加分)
```
这都不是加权 RRF。
### 6.4 分层心智模型
建议始终按三层理解分数角色:
```text
第 1 层:路内排序
dense 路用向量分排序
bm25 路用 BM25 分排序
各用各的分,互不直接相加
第 2 层:跨路融合
只吃各路 rank
普通 RRF 或加权 RRF(w_i 为路权)
第 3 层:精排(可选)
对融合后的 topM 再打分
这里才适合规则特征或 Cross-Encoder
```
一句话记住:
> 原始分只负责路内排名;RRF 只负责跨路投票;w_i 只调节哪一路更值钱;精排才做最终挑剔。
### 6.5 手算例子
设:
```text
k = 60
w_dense = 1.0
w_bm25 = 1.5
```
候选 A:
- dense 第 1 名
- bm25 第 5 名
```text
RRF(A)
= 1.0/(60+1) + 1.5/(60+5)
= 1/61 + 1.5/65
≈ 0.01639 + 0.02308
≈ 0.03947
```
候选 B:
- dense 第 4 名
- bm25 第 1 名
```text
RRF(B)
= 1.0/(60+4) + 1.5/(60+1)
= 1/64 + 1.5/61
≈ 0.01563 + 0.02459
≈ 0.04022
```
在这个设定下 B 更高,因为你主动提高了 BM25 路权,BM25 头名更吃香。
如果把 `w_bm25` 降到 `0.5`,同样名次下 dense 会重新占主导。
这就是路权的意义:调的是整路话语权,不是某个文档的原始分公式。
### 6.6 w_i 怎么设
实操建议:
1. **起步全设 1.0**
先看纯 RRF,不要一上来调花活。
2. **再按评测微调**
- 专有名词/报错码总靠 BM25 才找得到,却总被 dense 压下去 → 提高 `w_bm25`(如 1.2~1.5)
- BM25 噪声大、泛词乱入 → 降低 `w_bm25`(如 0.6~0.8)
- filtered 很准但有时过窄 → 可与 unfiltered 同权,或略高一点
3. **经验范围**
`w_i` 常见落在 `0.5 ~ 2.0`。
若一路是 10、另一路是 0.1,基本等于放弃多路。
4. **没有回归集就不要谈“最优权重”**
路权应来自离线评测,而不是长期拍脑袋。
---
## 7. 精排放在哪里:有了候选池,才配谈 Rerank
### 7.1 轻规则精排
在 RRF 融合出 top 10~15 后,可以用更稳的字段级规则做二次排序:
```text
title 命中 > breadcrumb 命中 > body 覆盖
精确术语 > 泛词
runbook / case 来源轻微加分
与已选证据过相似则降权(MMR 思路)
```
注意:这里的规则特征,最好作用于 **融合后的候选精排**,而不是重新发明一套跨路原始分加法。
### 7.2 模型精排
当 `retrieve-k` 到 15~30,且评测证明融合后噪声仍高时,再考虑:
```text
RRF topM
-> Cross-Encoder / 托管 Rerank API
-> 取 topN 给 Agent
```
可选路线:
- 开源 reranker(如 bge-reranker 一类)
- 云厂商 / Cohere 等托管 rerank
- LLM listwise(贵且不稳,一般不当首选)
### 7.3 什么时候不要上模型 rerank
- 仍在 `K=3` 主路径上
- 还没修 chunk 级去重
- 还没有固定 query 回归集
- 延迟和工具预算已经很紧
否则花的是精排成本,换不到召回质量。
---
## 8. 工程落地:比“换模型”更重要的顺序
### 8.1 建议的目标形态
```text
Query
├─ dense unfiltered top 20
├─ dense filtered top 10 # 有唯一 domain 时
└─ bm25 / keyword top 10 # 第二阶段
│
▼
chunk 级去重(docId#chunkIndex)
│
▼
RRF / 加权 RRF 融合
│
▼
轻规则或模型精排,取 top 5
│
▼
每文档 chunk 上限 + context pack
│
▼
Agent projection
```
```mermaid
flowchart TB
Q[Query] --> U[dense unfiltered top20]
Q --> F[dense filtered top10]
Q --> B[bm25/keyword top10]
U --> DEDUP[chunk 级去重<br/>docId#chunkIndex]
F --> DEDUP
B --> DEDUP
DEDUP --> RRF[RRF / 加权 RRF]
RRF --> RR[轻规则或模型精排 top5]
RR --> CAP[每文档 chunk 上限 + pack]
CAP --> AG[Agent projection]
```
### 8.2 分阶段推进
```mermaid
flowchart LR
P0[Phase0<br/>chunk 去重<br/>retrieve-k/return-n] --> P1[Phase1<br/>filtered+unfiltered RRF]
P1 --> P2[Phase2<br/>BM25 跨维度]
P2 --> P3[Phase3<br/>可插拔精排]
```
#### Phase 0:先修前提
否则后面多路都会被吞:
1. 去重 key 从“文档/source”改为 `docId + chunkIndex`(fallback 可用向量主键)
2. 配置拆分:
```properties
rag.retrieve-k=20
rag.return-n=5
rag.max-chunks-per-document=2
```
3. 明确 baseScore(可用性阈值)与融合分/精排分(排序)职责分离
#### Phase 1:同模态双路径 + RRF
1. filtered dense 与 unfiltered dense 都产出候选
2. 合并去重,不再整锅替换
3. RRF 融合后取 topN
4. trace 记录每路 rank 与 fused rank
这是最贴很多现有系统的一步,收益通常大于继续调 boost。
#### Phase 2:跨维度多路
1. 增加 BM25 / 关键词路
2. 继续 RRF,必要时给 `w_bm25` 微调
3. 增加字段级轻精排
#### Phase 3:可插拔模型 Rerank
```text
interface Reranker {
rerank(query, candidates, topN) -> rankedCandidates
}
```
实现可切换:
- `NoopReranker`
- `RuleReranker`
- `HttpCrossEncoderReranker`
让精排成为插件,而不是写死在业务里。
### 8.3 和 Agent 系统相关的额外约束
诊断 Agent 场景还有几个现实约束:
1. **工具预算有限**
检索本身只是工具循环的一环,延迟不能无限涨。
2. **证据要可引用**
最终给 Agent 的应是有界 excerpt,而不是内部全量 trace。
3. **投影会再裁一层**
即便内部排序很细,Agent 可见字段仍可能只有 document/source/title/breadcrumb/excerpt。
因此内部要保留完整 trace,外部保持契约稳定。
4. **安全发布与验真**
排序再好,也不能绕过证据引用与 guard;RAG 优化的是“更可能拿到对的证据”,不是“让模型自由发挥”。
---
## 9. 评测:没有回归集,权重都是感觉
多路和 RRF 最怕“上线凭体感”。最少准备 15~30 条固定 query,覆盖:
- 标准故障词
- 口语化换说法
- 专有名词 / 错误码
- 容易误判 domain 的问题
- 同文档多 chunk 才完整的流程题
- 负例:知识库本就没有答案
关注指标:
| 指标 | 看什么 |
|---|---|
| Recall@5 | 该出现的文档/chunk 是否进前 5 |
| nDCG@5 或人工 0/1/2 | 排序是否把更相关的放前面 |
| filter 误杀率 | 唯一 domain 是否经常害人 |
| 同文档多 chunk 保留率 | 去重是否过粗 |
| P95 延迟 | 多路是否打爆预算 |
| 无证据正确率 | 不该有答案时是否老实说没有 |
路权 `w_i` 的调整,应建立在这些数上,而不是单次手工 query。
---
## 10. 反模式清单
下面这些做法看起来勤快,实际常把系统带偏:
1. **只在 K=3 上接昂贵 rerank**
候选池不够,精排没有舞台。
2. **L2 和 BM25 直接加权相加**
分数不可比,调参不可迁移。
3. **把 L0 contains 当最终裁判**
词表质量绑死线上排序。
4. **文档级去重吞掉同文档多 chunk**
多路召回也会在终点被自己吃掉。
5. **filtered 失败就整锅替换**
丢掉本可保留的好结果。
6. **无评测调 w_i**
今天的“最优权重”往往是过拟合某几条样例。
7. **L0 命中全文直接当高置信 evidence**
导航信号被抬成事实,诊断场景尤其危险。
---
## 11. 可直接拿走的决策框架
遇到 RAG 排序问题时,按这个顺序问:
```text
Q1. 正确答案是否经常连候选池都进不来?
是 → 先扩召回 / 多路,不要先上复杂 rerank
Q2. 是否存在 filter 误杀?
是 → filtered + unfiltered 双路径融合
Q3. 是否大量依赖专有名词、错误码、配置键?
是 → 加 BM25 / 关键词路
Q4. 多路分数是否不可比?
是 → RRF,而不是原始分硬加
Q5. 融合后 topM 仍噪声大,且 K 已经够大?
是 → 再上规则精排或模型 rerank
Q6. 有没有固定回归集?
没有 → 先建评测,再谈“最优权重”
```
对应到一句话策略:
> **先扩召回,再用名次融合,最后才模型精排。**
---
## 12. 结语
RAG 排序讨论很容易变成模型名词竞赛。但在诊断 Agent 这种真实系统里,更常见的瓶颈是:
- 候选太少
- 过滤过猛
- 分数不可比
- 去重过粗
- 启发式加分承担了不该承担的最终裁决
RRF 的价值,不只是一个公式,而是一种工程态度:
```text
承认各路分数不可比
让每路先做好自己的排序
再用名次投票决定谁更值得进入下游
```
加权 RRF 也并不神秘:`w_i` 只是给整路调音量。
想让 BM25 更有话语权,就提高 `w_bm25`;它不会、也不应该要求你先把 BM25 分和向量分校准到同一宇宙。
如果只记住三句:
1. **K=3 时,复杂 rerank 不是第一优先级。**
2. **filtered + unfiltered 是值得做的同模态双路径;Dense + BM25 才是跨维度多路。**
3. **跨路融合优先 RRF;L0 做导航,精排做挑剔,原始分不要跨路硬加。**
按这个脉络演进,通常比“继续把 keyword boost 调大一点”更接近稳定、可解释、可评测的 RAG 排序系统。
---
## 附录 A:术语对照
| 术语 | 含义 |
|---|---|
| L0 | 基于文档元数据/关键词的 query understanding 或弱召回 |
| L1 | 向量语义召回 |
| retrieve-k | 召回阶段候选数 |
| return-n | 最终返回给 Agent 的证据数 |
| baseScore | 向量相似度等主相关性分,常用于可用性阈值 |
| finalScore / 精排分 | 用于排序的综合分 |
| RRF | 基于名次的多路融合 |
| w_i | 第 i 路在 RRF 中的路权 |
| Rerank | 对已有候选做精排,不负责凭空召回新文档 |
## 附录 B:最小配置示例(示意)
```properties
# 召回与返回分离
rag.retrieve-k=20
rag.return-n=5
rag.max-chunks-per-document=2
# RRF
rag.fusion.method=rrf
rag.fusion.rrf-k=60
rag.fusion.w-dense=1.0
rag.fusion.w-dense-filtered=1.0
rag.fusion.w-bm25=0.8
# 精排(先规则,后可插模型)
rag.rerank.mode=rule # rule | model | off
```
以上配置名是示意,重点在职责拆分,不在具体键名。
## 附录 C:和本文讨论直接对应的实现关注点
阅读或改造现有代码时,可重点核对:
1. 后处理是否把“规则 boost”命名成了 rerank,却未做候选扩展
2. filtered 低质时是整锅替换,还是双路融合
3. 去重 key 是 source/文档级,还是 chunk 级
4. `topK` 是否同时承担召回、排序、返回三种职责
5. 内部 `rerankTrace` 是否可观测,Agent 投影是否有意裁剪
这些点决定了:你写在黑板上的 RRF,能不能在系统里真正跑起来。