企业知识库 RAG 做完第一版,几乎都会遇到同一个尴尬:明明都召回了,模型偏偏用了第二、第三条,第一条反而不是答案。
一、第一版 RAG 的通病
BizBuddy 的第一版 RAG 很「标准」:文档解析切片 → 向量化进 Milvus(COSINE)→ 提问时单路稠密检索取 Top-K → 拼进上下文。切片策略是「字符窗口 + 句边界对齐」(默认 500 字 / 50 字重叠),只为不把句子从中间劈开。
能用,但三个问题很典型:
- top1 不准------相关切片基本都在 Top-5 里,却排不在第一位;
- 上下文冗余------同一段的相邻切片重复进上下文;
- 上下文不可控------按固定字数硬截断。
为什么「top1 不准」?因为稠密向量衡量的是语义相近 ,而语义相近不等于答案就在这里。几段都在谈同一主题时,向量分往往咬得很紧,真正那句反而排在后面。
换个角度说:召回决定「有没有」,排序决定「用不用得上」。一次检索里,切好的切片各自是一个向量、存进 Milvus;提问也向量化,做余弦近邻取回一批候选------在重排介入之前,「排序」这件事基本就是「看谁离得近」。
于是有了那句总结:瓶颈是「排序」,不是「召回」。
二、先建评测,再动手
改 RAG 最怕「感觉好像好了一点」。所以我们先做评测闭环:一个零依赖脚本(随仓库开源),自动建评测库、按语料目录入库,跑 hit@1 / hit@3 / hit@5、recall@5、MRR,并按用例类别拆分统计。
三个指标各有分工:
- hit@k:正确答案是否出现在前 k 条里;
- recall@5:该召回的相关切片覆盖了多少;
- MRR:第一条正确答案的平均排名倒数------最能反映「排得准不准」。
评测集是自建的 56 条用例、5 类 :事实 / 术语 / 同义词 / 口语化 / 跨文档------前两类偏「关键词能命中」,后三类专门考语义与指代。基线(纯向量、threshold=0 纯排名口径)hit@1 64.3%,跨文档与口语化是主要短板。有了这把尺子,后面每一步改动都能前后对比。
脚本是零依赖的 Node ESM :自动建评测库(可 --rebuild 重建、--clean 清理)、按语料目录批量入库,输出整体 + 分类别的数字------跑一次就能对照,不靠手工记分。
三、第一招:上下文装配(去重 + 相邻合并)
对「已排序命中」做确定性后处理:去重 (规范化后互为子串,或 n-gram Jaccard ≥ 0.9)+ 同文档相邻切片合并,再加字符 / Token 上限。
效果:片段数 −22% ,关键词覆盖不降、排序不劣化,hit@1 从 64.3% 升到 67.9% 。注意它只改上下文、不改排序------真正的排序收益留给下一招。
四、胜负手:Rerank 重排
引入文本重排模型(DashScope gte-rerank-v2),链路变成:
稠密召回
max(topK, rerankTopN)→ 重排 → 融合 → 截断topK
融合策略我们做了有界对比:
| 策略 | hit@1 |
|---|---|
| 不重排(基线) | 67.9% |
| 纯替换 replace | 64.3% |
| 排名融合 rank_rrf | 66.1% |
| 分数融合 score_fusion(α=0.5) | 75.0% |
最终选 score_fusion + rerankTopN=30。相对基线:
- hit@1 67.9% → 75.0% 、MRR 0.772 → 0.829
- hit@5 89.3% → 98.2% 、recall@5 96.4% → 98.2%
- 代价仅延迟 +约 170ms
工程上还留了兜底:未配密钥 / 非 2xx / 超时 / 响应结构异常,一律降级为「保持原序」,检索永不因重排而中断。
为什么是「分数融合」?纯替换(replace)会丢掉向量分里已有的良好信号,排名融合(rank_rrf)又把分数信息压成了名次;把两路分别归一化后加权(α=0.5),既不浪费向量分,又引入重排模型的判断------实测也确实最稳。
五、走过的弯路:查询改写 + RRF 实测无收益
我们也试过「更重」的方案------查询改写 + 多路召回 + RRF 融合。结果实测 MRR 不升反略降、延迟涨到约 29 倍 。于是整体回退。
这条弯路值得写出来:不是所有论文里的招都适合你的语料。评测的价值,正是让你敢于「退掉一个看起来很先进的功能」。
六、检索也要守权限
检索从来不是「全库搜」:知识库有公开 / 本部门 / 仅本人三档生效范围;对话检索链路还多一层**「可见且启用」**------停用库不再被检索。这条过滤发生在检索入口,与智能体装配共用同一套可见性判定,因此不会出现「越权召回」。
这也解释了为什么「知识库检索」要作为一个显式工具绑定给智能体,而不是当内置黑盒:它既要按人过滤,也要能被权限与审计看见。
七、把开关交给运维,把尺子交给社区
能力集中在 bizbuddy.ai.rag.*:
| 配置 | 默认 | 说明 |
|---|---|---|
rerank-enabled |
开 | 重排总开关 |
rerank-mode |
score_fusion |
融合模式 |
rerank-top-n |
30 | 进重排的候选数 |
assemble-* |
开 | 去重 / 相邻合并 / 字符与 Token 上限 |
评测脚本与 56 条用例随代码开源,任何一次检索改动都能 A/B 出数字。
调优顺序也有讲究:先把上下文装配做对 (便宜、无外部依赖)→ 再上重排 (收益最大)→ 最后才考虑查询改写(最贵、最不确定)。我们就是按这个顺序走下来,才敢于在中途果断退掉改写。
八、为什么它是「Harness 平台」的 RAG
BizBuddy 的检索不是一个独立的「问答接口」,而是接入智能体的一个工具 (search_knowledge_base),因此天然继承平台工程能力:
- 权限------按提问用户的生效范围过滤,停用库不参与;
- 安全 ------命中片段进上下文前带
<untrusted>边界标记,入库前过内容审核; - 审计------每次工具调用留痕,可回溯引用了哪些片段。
这些能拼在一起,是因为内核是 AgentScope Java Harness :一个 JVM 内,工具、权限、安全、审计走同一条装配链路,没有跨语言的 sidecar。
换句话说,RAG 在 BizBuddy 里不是「另起一个服务」,而是在平台里长大的一个能力------它天然带上权限、安全与审计,也天然能被「入口智能体 → 专家」的编排链路复用。
九、纯 Java、全量开源
整条检索链路------自建 OpenAI 兼容向量客户端、Milvus 接入、重排与融合、上下文装配、评测脚本------全部是 Java / 标准协议,无 Python 依赖。
RAG 做到最后,拼的不是「接了个向量库」,而是有没有度量、敢不敢取舍、能不能运营。这三件事,BizBuddy 都交在了代码里。
BizBuddy 全量开源(MIT License) :gitee.com/zl3624/biz-...