【岁月留声】知识库检索能力演进史

接触人工智能这几年,我做过不少实验。真正让我反复煎熬的,倒不是模型参数或者提示词,而是一个看起来基础、实际很容易被忽略的问题:模型回答之前,究竟有没有拿到该拿的证据?

我参与搭建过一个面向中医药业务的 RAG 应用。遇到回答不理想时,我也试过换更新的生成模型。有些问题改善并不明显,甚至越换越差。后来我逐渐意识到,不能把所有问题都归到生成模型头上:如果关键资料根本没进候选集,后面的模型再强,也只能拿着不完整的材料作答。

这次我会复盘一下这个 RAG 系统检索链路从 v1 到 v3 的真实演进:从单体应用里的向量检索,到 BM25 与向量混合召回,再到带反馈的 Agentic Search Loop。中间还有一个不太光彩、但值得公开记录的事故:配置里已经恢复了多个知识库,代码却仍只检索其中一两个。系统没报错、页面能回答、耗时甚至更短,这个问题持续了四个多月。

文中的数据来自当时的代码演进记录和一套检索评测集。为了适合公开分享,内部仓库路径、服务地址和索引内部名称不再展开;重点放在技术选择、失误原因以及怎样验证改动是否真的有效。

先看三次演进:每一步都解决了问题,也带来了新问题

阶段 检索方式 主要变化 后来发现的代价
v1:单体检索 关键词向量 + Elasticsearch 脚本打分 + 本地重排组件 实现直接,配置中的 6 个知识库都会参与查询 召回条数随分词结果变化;重排结果缺乏明确的排序与条数约束;异常可能被折叠成空结果
v2:服务拆分 BM25 + 向量 kNN + RRF + 远程重排 增加文本召回一路,把业务服务与推理/检索能力分开 这版实际只启用了 2 个知识库;去重流程里还藏着与相关性无关的截断
v3:Agent 循环 多轮查询规划 + ES 混合检索 + QuestDB 结构化查询 + 缺口分析 检索不再局限于一次查询,可以根据已找到的材料继续换角度搜索 硬编码索引范围与扩大的配置清单失去同步;RRF 参数还被候选条数意外影响
评测与修复 跨索引融合 + 每库保底配额 + 黄金集回归 第一次系统度量检索质量,并恢复合法知识库的召回 仍有中文分析器、重排深度和查询向量策略等问题需要继续验证

这几次迭代不是"旧技术落后,所以升级新技术"这么简单。很多时候,上一版的设计是合理的;真正的问题是,它解决了一个局部瓶颈,却没有把副作用一起纳入验证。


1. v1:先让知识能被找到

1.1 当时的检索路径

最早的版本在 2024 年左右完成,整个链路跑在一个进程里。大致思路是:对用户问题做分词和领域术语提取,为关键词生成向量,再让 Elasticsearch 检索候选,最后交给本地模型组件处理,并把参考内容交给生成模型。

至于为什么选 Elasticsearch,之前的文章有提到过:

【从零开始】5. 向量数据库选型与搭建(番外篇)

这里有个细节值得说清:v1 并不是简单地"把所有关键词向量平均成一个向量,然后结束"。平均向量主要用于 kNN 的候选预筛;进入候选打分阶段后,Painless 脚本会分别计算文档向量与多个关键词向量的余弦相似度,再按权重累加。换句话说,它在同一条检索路径里同时使用了整体语义和词项级信号。

flowchart TD U([用户问题]) --> APP[主程序] APP --> KW[Jieba 分词与中药领域词典] KW --> QV[生成多个关键词向量] CFG[知识库配置:6 个索引] --> FAN[按索引并发发起查询] QV --> FAN FAN --> KNN[ES kNN:用平均向量预筛候选] QV --> SCORE[Painless:逐个计算关键词余弦相似度并加权] KNN --> SCORE SCORE --> MERGE[汇总各索引候选] MERGE --> RERANK[本地重排组件] RERANK --> GEN[生成模型组织答案]

这张图里,kNN 和 Painless 的职责不同:前者先把全库缩小到可处理的候选范围,后者再给候选文档计算更细的分数。如果跳过预筛、让脚本对大量文档逐一打分,成本会很高;如果只保留一个平均向量,多个专业词语对结果的独立贡献又可能被稀释。

当时按配置中的 6 个知识库分别启动检索线程。概念上的时序如下,图中把多个并发分支折叠成三个示例分支:

sequenceDiagram autonumber participant U as 用户 participant APP as 主程序 participant ES as Elasticsearch participant RR as 本地重排组件 participant LLM as 生成模型 U->>APP: 提交问题 APP->>APP: 分词、领域词过滤、生成关键词向量 par 知识库 A APP->>ES: kNN 候选检索 + 脚本打分 and 知识库 B APP->>ES: kNN 候选检索 + 脚本打分 and 其他已配置知识库 APP->>ES: 并发执行同类查询 end ES-->>APP: 各检索任务返回候选,完成顺序不固定 APP->>RR: 处理候选结果 Note over APP,RR: 历史实现主要按分数是否大于 0 过滤,未明确执行排序和 Top-N 截断 RR-->>APP: 返回筛选后的文本 APP->>LLM: 提供参考上下文 LLM-->>U: 生成回答

1.2 v1 的想法并不差,问题在实现边界

我现在回头看,v1 有几处值得保留的设计:知识库通过配置管理;多个知识库能并发查询;部分数据有时效性需求,因此索引可以单独配置时间过滤;中药领域词典也能避免一些关键术语在通用分词中被切坏。

但它也留下了几个隐患。

第一,候选条数由关键词数量决定,top_k 大致按"关键词数 × 5"计算,最低分门槛也与关键词数量挂钩。同一个问题改写一种说法,分词结果变了,检索范围可能也随之变化。它不一定立刻造成错误,却增加了调试难度:很难分辨效果变化来自语义本身,还是候选池大小改变。

第二,代码里虽然接入了本地 bge-reranker-v2-m3,但有一段历史实现只是保留分数大于 0 的结果,并没有在那段逻辑里显式重新排序,也没有落实注释里写的条数上限。接入了重排模型,不等于系统就真正完成了重排。 常见的两阶段检索设计,是先用便宜的检索算法找出一批候选,再让 cross-encoder 对"问题---候选文本"配对打分,按新分数排序,并控制送入上下文的数量。cross-encoder 更贵,所以通常不会直接扫完整个语料库。Sentence Transformers 对 CrossEncoder 的说明

第三,异常处理过于宽松。向量转换或重排出错时,部分逻辑只记日志并返回空列表。这样一来,"确实没有相关知识"和"检索组件发生故障"在上层看来可能完全一样。没有明确的错误状态与观测指标,排查只能靠猜。

第四,时间字段分支并不完全遵循主路径。一个带日期过滤的资讯索引,在历史实现中有机会绕开 kNN 预筛,退化为对时间范围内的文档执行脚本打分。这样的分支问题不一定影响所有知识库,却会让某个索引突然变慢。架构图只能概括主流程,真正上线前还得检查每个条件分支。

现在回看 v1 能返回结果只是链路跑通,不代表候选完整、排序正确,更不代表后续生成拿到了足够的证据。


2. v2:BM25 与向量检索各做擅长的事

2.1 服务拆分,检索逻辑也换了

应用投入使用后立马引起中药材行业内关注,带来了爆发性的访问量。经评估,最后决定对架构进行改造,把业务对话和模型推理、向量计算、检索等工作分开管理。于是系统拆成了 Application 与 Inference 两个服务:前者处理业务流程和对话,后者负责 embedding、Elasticsearch 检索以及重排调用。

这次重构最重要的变化不是服务数量,而是检索从单路向量扩成了两路并行:

  • 向量路把完整问题编码成稠密向量,通过 kNN 找语义相近的内容。用户不一定使用文档中的原词,语义检索有机会找到表达不同但内容相关的段落。
  • 文本路把关键词送入 BM25 等全文检索机制。中药材名称、标准编号、数字、固定术语等内容,往往需要字面匹配的帮助。文本路还结合了短语匹配加权;对带时间字段的数据,查询会使用时间衰减,让较新的材料更有机会排在前面。

两路召回的分数不能直接简单相加:BM25 的分值范围与向量相似度不同,而且分数的大小不代表同一个尺度。v2 因此引入了 RRF(Reciprocal Rank Fusion,倒数排名融合)。

flowchart TD Q([用户问题]) --> ANALYZE[整理问题与检索关键词] ANALYZE --> VEC[完整问题生成查询向量] ANALYZE --> TERMS[提取文本检索词] subgraph RETRIEVAL[双路并行召回] direction LR VEC --> VPATH[向量路:kNN 语义检索] TERMS --> TPATH[文本路:BM25 与短语匹配] DATE[时间字段与时间衰减规则] -.适用时参与.-> TPATH end VPATH --> RRF[RRF 按名次融合] TPATH --> RRF RRF --> DEDUPE[内容去重与历史截断逻辑] DEDUPE --> POOL[整理候选池,最多约 50 条进入重排] POOL --> RR[远程 Cross-Encoder 重排] RR --> TOP[选取 Top-10] TOP --> GEN[生成模型组织答案]

图中有意把"召回"和"重排"画成两个阶段。召回的目标是尽量把可能相关的材料带进候选池;重排的目标是在这个候选池里,把更能回答问题的内容排到前面。重排模型通常不负责搜索整个知识库,所以若正确材料根本没进候选池,后面的重排没有机会把它"变出来"。Elastic 的检索与重排说明

2.2 RRF 到底解决什么问题?

RRF 不尝试把 BM25 分数和向量分数校准到同一尺度,而是利用每份结果清单中的名次。其常见形式为:

RRF_Score(d)= ∑m∈{Vector,Text} 1 k+rm(d) \text{RRF\Score}(d) = \sum{m \in \{\text{Vector}, \text{Text}\}} \frac{1}{k + r_m(d)} RRF_Score(d)=∑m∈{Vector,Text}k+rm(d)1

其中,d 是某个文档,r_m(d) 是该文档在第 m 路检索结果中的名次(从 1 开始),k 是平滑常数。文档如果在多路结果中都排得靠前,就会累积更高的融合分数。v2 当时使用的平滑常数是 100。实现代码使用从 0 开始的列表下标,因此分母写成 k + rank + 1,与上面的 1-based 名次公式一致。

举个直观的例子:一篇资料在向量路排第 3,在文本路排第 2,说明它既有语义相关性,也有字面匹配证据;另一篇只在向量路排第 1,却没有进入文本路前列。RRF 会综合两路名次,而不是只看某一路的原始分数。它不保证永远更准,但避免了直接相加两种异尺度分数的问题。Elastic 对 RRF 的解释也强调了这一点:Reciprocal rank fusion。

不过,RRF 有两个完全不同的参数概念,不能混用:

  • 每路召回多少条,决定有多少候选有资格参加融合。
  • RRF 平滑常数 k,决定不同名次之间的分数差异有多明显。

当时的代码把两者都交给了名为 RRF_TOP_K 的变量,这在 Agent 循环中埋下了问题,后面会说到。另一个细节是,旧版去重函数不只去重,还会在保留了大约一半的结果后提前结束;这相当于在相关性排序之外再加了一道数量截断。

2.3 v2 的另一面:服务拆了,检索范围却变窄了

在 v2 的实际配置中,真正启用的知识库从 v1 的 6 个缩到了 2 个,其他知识库被注释掉了。这是性能和资源之间取舍平衡的行为,系统将从剩下的两个知识库找依据。若恰好问的是市场资讯或标准文献,体验可能看起来不错;但如果问题需要古籍、中药品种或方剂知识,召回能力已经发生了实质变化。

另一个细节是,向量路从 v1 的"多个关键词向量参与脚本打分",变成了 v2 的"完整问题生成一个查询向量",关键词主要交给文本路。前者会给各术语独立的相似度贡献,后者对整句语义表达更完整。这个也是已知行为,只不过没有做 A/B test 这个倒是真的,没有对比数据实在遗憾。


3. v3:从一次检索,变成一轮轮带反馈的搜索

到了 v3,业务侧任务已经不太适合一次搜索就结束:例如生成一份中药材市场报告,可能同时需要市场资讯、价格变化、供需信息和结构化行情数据。系统需要先判断要找哪些材料,拿到第一批结果后再检查缺口,必要时换一个角度继续搜索。

因此我加入了 Agentic Search Loop。整个循环最多 5 轮,时间预算为 180 秒。每一轮会形成搜索策略,查询 Elasticsearch;如果问题涉及价格或行情,也会并行查询 QuestDB。检索结果汇总后,系统生成摘要、分析当前缺口,再决定是否继续。

sequenceDiagram autonumber participant LLM as Agent participant Engine as 检索循环引擎 participant ES as Elasticsearch participant QDB as QuestDB participant RR as Rerank LLM->>Engine: 启动任务 [最多5轮/180s预算] loop 多轮迭代 (Round 1-5) Engine->>LLM: 分析当前信息缺口 (Gap Analysis) LLM-->>Engine: 生成下一轮搜索策略与关键词 par 并行查询 Engine->>ES: 混合检索 (BM25 + Vector) and 并行查询 Engine->>QDB: 查行情/价格数据库 end ES-->>Engine: 返回文本文档 QDB-->>Engine: 返回指标数据 Engine->>RR: 候选池二次重排 RR-->>Engine: 返回 Top-N 结果 Engine->>Engine: 检查是否满足需求/时间预算 end Engine-->>LLM: 整合多轮上下文,生成完整回答

这张图需要注意三个边界。

第一,QuestDB 不是另一个文本知识库。它承担的是价格、时间序列等结构化数据查询,与 ES 文本检索互补。第二,Agent 每轮换搜索角度,不代表结果必然更好;如果缺口分析不准,它可能反复搜索相似内容。第三,多轮循环会增加延迟、模型调用和系统状态复杂度,因此轮数上限、总时间预算、停止条件都必须明确。最多 5 轮 是上限,不是每次都应该跑满 5 轮。

3.2 一个参数耦合,让 RRF 的行为跟着候选条数一起变化

Agent 循环为了让每轮快一点,把每一路召回条数从 100 下调到 top_n × 3,当时得到的是 30。问题在于,这个值不仅决定召回多少条,还一路传进了 RRF 融合函数,被当作平滑常数使用。

于是原本想调的是"每一路检索的候选深度",意外改变的却还有"融合时对名次差异的敏感程度"。平滑常数从 100 变成 30 后,前几名之间的相对影响更突出,RRF 的排序行为跟主路径不再一致。

这不是 RRF 算法本身的问题,而是参数设计把两个独立职责绑在了一起。后来调整为单独的 RRF_K = 60,让它与每路召回条数解耦。这里的 60 是当前实现采用的参数,不代表它对所有语料都最优;参数职责拆分之后,仍然需要用评测比较不同取值。


4. 那个持续四个多月的 bug:配置有七个知识库,代码只查两个

这个锅的确是我来背的。

v3 初期把 v2 的混合检索内核搬进了新结构。最开始,代码按配置遍历知识库。后来为了做知识库的数据安全审计,代码加了一道索引白名单。2026 年 5 月 20 日的提交把检索限制到少数几个索引;第二天,名单又收窄到只剩市场资讯索引(这是因为修复 v2 (6 ->2)缺陷的时候已经做过一次市场资讯索引的审查了,所以先解放这个索引)。再后来,2026 年 7 月 9 日,配置层重新补回了多个知识库,但代码中的名单没有同步更新。

结果就是,配置已经扩到 7 个知识库,运行中的检索代码却一直只查其中 2 个。从 2026 年 5 月下旬持续到 10 月 8 日,约 30 万条中药材相关知识一直没有机会进入候选池,哪怕后面安全审计结束了也没有。它们不是已经检索但排在后面,而是压根没有参与检索。

这里也需要谨慎交代白名单的来历。代码 diff 能确认的是:这次改动直接收窄了检索范围,提交记录使用的是优化类说明;仅凭 diff,无法严谨证明最初的业务动机只有性能,还是同时包含审核考虑。后来业务确实需要严格限制某些知识的使用,但数据能不能被检索 与本次问题应该搜哪些库是两件事,不应该长期共用一份隐式硬编码名单。

这条演变线可以简化为:

flowchart TD CFG1[配置中有 2 个知识库] --> CODE1[代码遍历配置,范围一致] CODE1 --> WL[加入硬编码索引白名单] WL --> NARROW[白名单收窄到 1 个索引] NARROW --> EXPAND[配置后来扩展到 7 个知识库] EXPAND --> DRIFT[代码名单未同步,仍只检索 2 个] DRIFT --> SILENT[接口正常、能生成回答、耗时更短] SILENT --> EVAL[21 题黄金集暴露召回缺失] EVAL --> FIX[改为配置驱动 + 跨索引全局融合 + 守卫检查]

为什么这个问题能活这么久?因为每一层看起来都没有坏:

  • 接口返回成功,说明请求执行完了;但它不证明应该参与的索引都参与了。
  • 页面能正常回答,说明模型拿到了一些文本;但这些文本可能来自错误或不完整的知识范围。
  • 查询耗时缩短,表面上还像是一次性能优化;但少查几个索引本来就会更快。
  • 索引总量大屏显示数据存在;但"索引里有多少数据"和"本次检索实际查了哪些索引"是两个指标。
  • 如果回归测试只断言"不抛异常"或"返回非空结果",这种缺陷很可能被放过去。

少做了一件事,有时不会造成故障,而是会制造一个看似正常、却逐渐失真的系统。


5. 修复不能只把白名单删掉:要让检索结果真正能够全局竞争

仅仅恢复索引遍历,还不足以解决所有问题。旧实现里,各个索引完成查询后,会把文档文本追加到共享结果列表。索引内部算过 RRF 分数,但上层接口把候选压成纯文本,文档 ID、名次和融合分数没有保留下来;多个线程又可能按完成先后写入共享列表。最后送去重排的候选顺序,就不再是一个明确的跨索引相关性排序。

不是说分别计算的 RRF 分数在数学上必然不可比,而是旧接口丢掉了这些分数和名次,上层已没有依据做有意义的全局排序。 多线程完成顺序只是这个实现缺陷的一个表现。

因此 v3 我做了三件事来修复这个问题:

  1. 每个索引返回结构化候选。 除文本外,还保留文档 ID、索引内融合分数、来源信息等字段,避免在检索阶段就丢掉排序依据。
  2. 跨索引统一融合。 各索引先完成自己的混合召回,再由上层根据索引权重与候选信息做全局融合。索引权重用于表达业务优先级,不应该代替内容相关性;它需要通过评测确认,而不是凭感觉指定。
  3. 给每个索引留候选配额。 在总候选池有限的情况下,为各知识库留出最低配额,避免文档量特别大的索引把小而重要的知识类型完全挤出去。配额的作用是保护召回多样性,不是保证配额内的每条文档都相关,因此后面仍要靠重排筛选。

改完后,检索路径从"并发查完,把文本按线程完成顺序拼在一起",变成"每库返回带有排序信息的候选,再在上层进行统一排序和截断"。这才是跨索引检索该有的基本结构。

另一个修复点也很关键:合规约束单独处理。 后续审查确认,问诊记录中的临床个案信息不适合作为这个产品的通用检索依据。因此,这类数据最终在配置层下线,代码层加入兜底拒绝,并用守卫断言确保它不会进入检索。参与检索的知识库由 7 个调整为 6 个。

这个变化看起来像是"又从 7 个变回 6 个",但它和之前的硬编码白名单不是一回事。之前是检索代码意外漏掉已配置的合法知识;现在是基于产品边界,明确禁止某一类数据进入上下文。一个是召回缺陷,一个是准入规则。不能为了追求更高的召回率,就把不应该使用的数据也召回进来。


6. 第一次认真测量检索

在这次修复之前,系统有很多耗时记录,却没有一套足以回答"到底准不准"的量尺。于是我整理了代表性的专业问题库,为每题标注正确依据应当出现的关键字或文本片段,并写了一个评测脚本。

这套问题库并不复杂,也没有要求为每题建立一套完整的文档 ID 标注体系。它更像一套针对当前业务的回归问题:问某种中药材的标准、方剂组成或古籍记载时,正确依据应当包含哪些可识别的信息?运行检索后,这些信息是否出现在前 10 条候选里?

flowchart TD GS[21 个固定业务问题] --> EXPECT[每题标注应命中的文本片段或关键词] EXPECT --> RUN[在固定配置与数据条件下运行检索] RUN --> POOL[检查初始候选池是否包含目标依据] POOL --> RANK[检查目标依据是否进入重排后的 Top-10] RANK --> METRICS[汇总 hit@10、MRR@10、pool_hit 与重复运行一致性] METRICS --> COMPARE[对比改动前后结果,定位召回或排序问题]

评测结果给了一个很明确的信号:在同一套问题上,改造前 hit@10 为 0.190 ,改造后升到 0.810 ;MRR@10 从 0.148 提升至 0.579 ;候选池平均条数从 25.5 增到 120 。同题重复运行的一致性指标为 0.994。

这些数字要按它们实际能说明的范围来理解。

  • hit@10 在这套评测里,指预先标注的目标依据是否能在前 10 条检索结果中被检测到。它适合快速验证业务问题是否找到了关键材料,但依赖标注规则,也不是所有 RAG 项目都用完全相同的口径。
  • MRR@10 关注首个相关结果出现得有多靠前。若第一条就命中,贡献较高;越往后命中,贡献越小;前 10 条没有命中则没有贡献。因此,它可以补充 hit@10,反映目标材料的排序位置。
  • 候选池 用来判断目标依据有没有进入更早期的候选池。如果候选池能命中、最终 Top-10 却没命中,问题就更可能出在重排或截断,而不是初始召回。

我的问题库当然算不上大规模基准测试,也不能据此宣布这套架构普遍优于其他方案。它的价值在于:针对当前系统、当前数据和当前问题集合,召回范围修复前后的差异非常明显,足以证明改动不是单纯"代码更整洁了"。后续每次调整分词、召回深度、权重或重排模型,都可以用同一套题库进行回归,而不是只凭几次人工试问决定保留与否。

还有一点很重要:这轮 hit@10 大幅提升,主要验证的是检索范围与跨索引融合修复的收益,不应该把全部增幅归因于某一个 RRF 参数,更不应该归因于生成模型。我的经验是做评测时,尽量一次只改变一个关键变量,并记录数据版本、模型版本和参数,否则改前改后可能根本不是同一场比较。


7. 为什么我仍然选择 Elasticsearch 混合检索

不是因为 Elasticsearch 可以解决所有检索问题,而是它和当前业务的条件比较匹配:语料类型不止一种,既有非结构化文献,也有市场资讯和带时间字段的数据;从系统 v1 版本开始就已经使用 Elasticsearch,几年下来沉淀的数据也比较多,不宜大量迁移;需要关键词匹配、语义检索、字段过滤、时间范围和审核状态一起工作。

路线 适合解决的问题 需要付出的代价 我的判断
Elasticsearch 混合检索(BM25 + 向量 + RRF) 专业词、编号、语义表达、时间与元数据过滤同时存在的检索 需要维护分析器、映射、查询权重、索引和评测;集群资源也要算成本 现有系统已经使用 ES 时,是一条可以逐步改进、运维边界相对清晰的路线。Elastic 官方也将混合检索与 RRF 作为组合全文与向量结果的方案之一:Hybrid search
单纯稠密向量召回 同义表达、自然语言语义相似性较强,字面匹配不是唯一目标的场景 对精确术语、短编号、别名与专业写法,需要额外验证;通常还要搭配元数据过滤或词法检索 向量数据库不等于只能做纯向量检索,很多产品也支持过滤或混合检索。真正需要比较的是具体检索管道,而不是数据库标签。
长上下文 / CAG 一类方案 知识范围相对有限、更新频率较低、重复查询较多,可以把相关资料预载入模型上下文或缓存的场景 上下文容量、预载入成本、知识更新方式和运行成本需要综合评估 它能在合适的条件下减少实时检索步骤,但不是所有业务的通用替代品。CAG 原始研究讨论的也是知识规模可控时预载入与缓存的取舍:论文。
KAG / GraphRAG 一类图谱增强方案 实体关系、多跳问题、跨文档关联和面向整个语料的综合性问题 图谱抽取、实体对齐、索引构建、更新与查询成本不低;质量依赖数据和建图过程 适合有明确关系推理需求的场景,不必为了"先进"而把普通文档问答全部改成图谱。GraphRAG 项目也明确提醒,索引构建可能有较高成本:项目文档。

如表格所示。这些方案解决的问题有交集,但各有适用条件。对于我当前的业务范围而言,先把现有 ES 的词法召回、向量召回、时间与元数据过滤、融合和评测做好,比换一个名词更有把握。

修复后的这套方案(v4)还有什么没解决?

中文分析器会直接影响召回。 评测中有些问题没有命中,并不是向量模型一定不理解,而是中文切词或索引分析方式可能把关键短语切得不合适。例如,旧文献检索中曾出现"甘草"匹配到约 4.9 万条记录的情况,说明当前分析器没有给这个词形成足够有效的区分。这类问题可能需要比较 cjk_bigram、IK 或其他中文分析方案;更换分析器通常涉及重建索引,不能只在线改一个参数就假定问题解决。

召回命中,不代表重排命中。 有些问题的正确资料已经进入候选池,但没有排到最终 Top-10。这时继续扩大知识库或改 embedding 其实未必有效,我们更应单独比较重排深度、模型、文档长度和问题类型(当前正在处理当中,下一个版本上线)。

单个问题向量还是多个关键词向量,仍需要 A/B test。 v1 和 v2 分别选择了不同做法,这并不能说明其中一个天然更好。 我觉得可以尝试让两种策略使用相同的索引、候选深度、重排器和黄金集,然后比较命中率、排序质量与耗时,再对比结果来决定(这个之前一直都没有做好)。

这些问题都没有"换一个更大的模型"那么醒目,却更适合按证据逐项推进。


结尾

v1 的多关键词打分有它的道理,v2 的混合召回补上了词法检索,v3 的 Agent 循环适合需要多种证据的报告任务;它们都解决过真实问题,也都留下过新的边界条件。

其实我觉得,作为 RAG 应用检索系统不能只观察它跑得多快,还得知道它实际查了什么、漏了什么,以及为什么这些结果排在前面。

生成模型决定怎样用户得到什么结果,而检索系统决定哪些材料有机会被它看到。两者都重要,但在排查 RAG 问题时,证据链量清晰往往比先换一个更大的模型更有效。

相关推荐
昨夜见军贴06161 小时前
原始采样记录数字化落地,IACheck AI 报告文档审核 Agent 实现监测文档校验自动化
人工智能·算法·自动化
Aoiner1 小时前
模型部署笔记
大数据·elasticsearch·搜索引擎
在夜里告别9891 小时前
视频关键词文字怎么突出?平衡重点突出与画面可读性
人工智能
阿Q的AI笔记1 小时前
英伟达开源SoL-Pi:AI自己研究出优化方案,为Agent Harness省下64% Token
人工智能
微学AI1 小时前
从历史曲线到提前预警:TimechoAI 的时序预测与业务落地实践
人工智能·算法
玫瑰互动GEO2 小时前
通义千问GEO优化占位工程化:结构化事实+场景内容+可引用卖点,接住导购推荐
人工智能·ai·gem·geo优化·gem优化
sel_92 小时前
论文阅读笔记:Learning Multimodal Embeddings with Evidence-Aligned Readout
人工智能·深度学习
霍格沃兹测试学院-小舟畅学2 小时前
Playwright MCP:AI Agent 驱动 Web 自动化测试的架构与执行机制
前端·人工智能·架构
thinking_talk2 小时前
开源AI记忆框架选型与对比
人工智能·开源·ai记忆