正在加载文章,请稍候…
从 ReActAgent 搜不到说起:知识库检索的六种方式与工程取舍
内容提要
结合类名漏召回、标题查不到和删除后仍命中的真实排障,比较精确字段、关键词/BM25、稠密向量、学习型稀疏、混合检索与重排,给出可落地的检索流程和评测方法。
知识库搜索要同时处理“按名字找东西”和“按意思找答案”。只依赖向量相似度,容易漏掉类名、错误码和标题;只依赖关键词,又容易漏掉同义表达。更可靠的做法是:用字段匹配守住明确名称,用关键词和向量补足召回,再按查询意图融合排序,并把资源状态作为硬约束。
这篇文章来自一次真实的知识库排障:ReActAgent 搜出来的是泛泛的 Agent 内容;标题叫“我的喜好”、正文只有“爱吃鱼”的记录按标题搜不到;已经删除的资源仍然出现在结果里。它们看起来都像“检索不准”,实际发生在三个不同环节。
一、先看三个失败样本,别急着换向量模型
| 用户操作 | 历史复现 | 定位结果 |
|---|---|---|
| 搜索 ReActAgent | 库中有 30 个分段包含该名称,前 10 条结果却没有命中它 | 只有向量召回;精确标识符信号缺失,纯标题分段也参与了排序 |
| 搜索“我的喜好” | 记录已启用,正文“爱吃鱼”能查到,按标题却查不到 | 标题只保存在资源元数据中,没有进入关键词召回与标题排序 |
| 删除英文第 12 章后搜索 | 资源记录已删除,向量库仍残留 592 个分段 | 删除与后台导入交错执行,后续向量写入成功,资源记录落库失败 |
这些是清空知识库重新录入之前的历史记录。修复后的定向检查中,ReActAgent 的前 10 条均能匹配该标识符,第一条是第四章的类定义;“我的喜好”“喜好”“爱吃鱼”都能把对应记录排在第一。它们证明相关回归样本通过,不构成对所有问题的准确率保证。
尤其要区分“分段包含这个词”和“分段足以回答这个问题”。章节标题可能在很多分段中重复,命中 ReActAgent 并不意味着每个分段都在解释它的实现。评测需要继续检查正文、定义位置和证据是否完整。
二、六种常见方案,分别解决什么问题
下面的六种方案不全是同一层的替代品。字段匹配、关键词、稠密向量和学习型稀疏决定候选从哪里来;混合检索决定如何合并候选;重排决定候选怎样排得更好。理解这个层次,比给知识库贴上“使用向量搜索”的标签更有用。
| 方式 | 主要优势 | 主要局限 | 适合场景 |
|---|---|---|---|
| 精确字段 / 标题匹配 | 明确、可解释,按名称定位稳定 | 不理解同义表达,依赖完整元数据 | 标题、资源 ID、类名、型号、版本 |
| 关键词 / BM25 | 擅长词项、术语和短语,通常成本较低 | 依赖分词与规范化;语义改写可能失配 | 技术文档、错误码、代码名称 |
| 稠密向量 Dense | 擅长自然语言改写和概念相关性 | 可能混淆精确符号;依赖模型与分段 | 问答、概念解释、跨表达检索 |
| 学习型稀疏 Sparse | 保留词项权重,并可学习词项扩展 | 增加模型、索引和领域适配成本 | 词面信号重要且需要语义扩展的资料库 |
| 混合检索 Hybrid | 让不同召回信号互补,便于处理多类查询 | 候选数、融合与去重要一起维护 | 同时有业务文档、标题和代码的知识库 |
| 召回 + 重排 | 对候选逐条比较,更关注问题与正文的关系 | 增加延迟与推理成本;补不回未召回的答案 | 答案已在候选中,但前几条排序不理想 |
1. 精确字段和标题匹配:先保证“按名字能找到”
资源名称、文档 ID、产品型号和版本号,都是有独立意义的检索字段。标题为“我的喜好”、正文为“爱吃鱼”时,把正文单独向量化,不等于模型知道这段内容的标题。按完整标题查询,应有独立的召回路径;不能指望语义模型自行补出没有编码的信息。
本次修复是在已有元数据中增加标题召回,并让完整标题匹配优先。它不需要重建向量,也不修改用户正文。标题包含匹配可作补充,但文档很多时,前后通配的 LIKE 查询可能变慢,需要结合规模采用合适的字段或全文索引。
这种方法对精确导航有效,但用户问“我平常喜欢吃什么”,未必包含标题中的字,仍需要其他检索方式。标题也不是天然权威:大量重名文档、重复导入或错误命名,都会削弱“完整标题优先”的效果。代码名称是否忽略大小写,还应遵守产品约定;搜索便利性不能改变语言本身的大小写语义。
“标题参与召回”与“元数据作为过滤条件”是两件事。前者帮助找到资源;后者约束哪些资源允许被检索,例如租户、权限、发布状态和版本。硬约束应覆盖所有召回路径。参见 Milvus Filtered Search。
2. 关键词与 BM25:给专有名词一条可靠通道
全文检索根据查询词与文档词项的匹配进行召回,BM25 结合词频、词项区分度和文档长度等信号排序。它适合 ReActAgent、特定错误码和产品术语这类“名字本身就很重要”的查询。Milvus 提供基于 BM25 的全文检索,但本项目实际使用的是 SQLite FTS5 的词面召回,不能把两者写成同一套实现。参见 Milvus Full Text Search。
本项目采用 FTS5 trigram,即连续三个字符形成检索单元,便于做子串匹配。它不是中文语义分词器,也不保证只匹配完整标识符:ReActAgent 可能出现在 ReActAgentFactory 中,需要额外检查边界。少于三个 Unicode 字符的 MATCH 查询无法直接依赖该索引,因此“喜好”这类短查询需要单独处理。参见 SQLite trigram tokenizer。
关键词方案的优点是匹配原因容易解释,缺点是对表达变化敏感。“支持离线部署”与“断网能不能用”不一定共享足够词项;代码里的下划线、连字符和大小写,也会影响分词结果。应把分词和规范化作为可测试的行为,而不是默认它们总能正确处理技术名称。
3. 稠密向量:按意思召回,但不承诺精确名称
稠密检索通常分别编码查询和分段,再根据余弦相似度、内积或其他匹配度量召回候选。文档向量可以预先计算,因此适合较大语料。它更容易连接不同说法,但模型可能把“与 Agent 主题相关”排在“正文真正解释 ReActAgent”之前。模型语言覆盖、查询与文档编码方式、输入长度及领域差异都需要验证。参见 Sentence Transformers 检索与重排文档。
向量分数表示模型空间中的相近程度,不是“这个分段有 85% 概率回答正确”。不同模型、度量和知识库的分数分布也不同,不能照搬一个通用阈值。资料不足时返回空结果可以是正确行为,强行凑满 Top K 反而会让用户误以为这些结果都有证据价值。
还要把表示质量和索引误差分开。FLAT 通常用于穷举比较,HNSW、IVF 等索引通过不同搜索结构减少计算;它们处理的是速度、内存和近邻召回之间的取舍。诊断时可对同一批向量比较穷举与近似结果:如果穷举也找不到正确资料,单纯调大近似索引的搜索参数通常无法补上标题缺失或表示失配。参见 Milvus Index Explained。
4. 学习型稀疏:让词项匹配带上模型学习能力
稀疏检索不只有传统 BM25。SPLADE 一类方法用模型产生带权重的稀疏词项表示,并学习词项扩展;它试图保留倒排检索和词面匹配的优势,同时改善表达不一致的问题。代价是增加模型推理、索引构建与领域验证的工作,扩展出来的词也不一定适合每个业务场景。参见 SPLADE 原始论文。
是否引入学习型稀疏,应由它相对 BM25 和稠密模型的额外收益决定。中文、代码标识符和业务缩写都需要单独评测,不能因为模型名称里有“语义”就假设一定更准。本次项目没有部署学习型稀疏模型,标题漏召回也不需要靠它解决。
5. 混合检索:合并证据,而不是随意相加分数
混合检索可以同时运行标题召回、关键词召回和向量召回,再按 chunk_id 合并。它有机会把“含有精确类名,但向量分数不高”的分段找回来,也保留自然语言查询所需要的语义候选。工程代价是多路结果的过滤、延迟、去重和排序都要协同维护。
RRF 根据各路排名进行融合,基本形式为:对每条召回路径,累加 1 / (k + rank)。它避免直接比较不同量纲的原始分数,但会丢失分数间距。加权融合更灵活,却需要处理分数方向、归一化和权重校准。BM25、余弦相似度与融合分数不能不经处理直接相加。参见 Milvus 排名融合说明。
RRF(d) = Σ 1 / (k + rank_i(d))
示例:k = 60
资料 A:关键词第 1,向量第 50
score(A) = 1/61 + 1/110 ≈ 0.0255
资料 B:关键词第 10,向量第 10
score(B) = 1/70 + 1/70 ≈ 0.0286例子中 B 高于 A,体现了 RRF 对多路一致性的偏好。但如果用户明确查找一个类名,A 可能恰好包含唯一的定义。因此融合之外仍需要查询意图规则,例如精确标题优先、完整标识符匹配、定义位置加分,以及正文提及优先于只在继承标题中出现。规则需要控制范围,避免把一次修复变成所有查询都必须走的硬过滤。
本次项目采用 FTS5 与 Milvus 双路召回、排名融合及上述规则;对单独输入的明确代码标识符,在没有精确匹配时允许返回空结果。这个产品选择适合符号查找,但不能无条件用于自然语言问答,否则又会丢掉有价值的同义表达。
6. 召回后重排:判断“是否回答问题”,不只是“是否同主题”
Cross-Encoder 把查询和候选正文一起输入模型,重新评价二者的相关性;它通常比单独编码更细致,但逐对计算更贵,适合处理已经缩小的候选集。它可以帮助区分“ReActAgent 的实现”和“顺带提到 Agent 的介绍”。具体收益、语言适配和延迟仍要测量。参见 Retrieve & Re-Rank。
重排无法补回没有进入候选集的资料。如果标题从未参与召回,“我的喜好”没有进入候选,再好的模型也无从挑选。通常先检查候选召回率,再判断重排是否值得增加。使用大模型逐条打分也属于重排,但还会增加成本、格式约束和结果波动等问题。
项目目前没有接入 Cross-Encoder 或大模型重排,现有“重排”指融合后的规则排序。二者不能混称为已经部署了模型级 reranker。
三、查询扩展与上下文补全:是增强环节,不是万能补丁
查询扩展可增加同义问法;HyDE 则先生成假设文档,再利用其向量检索真实资料。它们可能缓解提问方式与文档表达差异,也可能带入错误细节、改变意图和增加延迟。应保留原始查询路径,最终引用真实知识库证据,不能把假设文档当作事实。参见 HyDE 原始论文。
对类名、错误码和资源标题,先保留原始名称与稳定的规范化形式,再考虑扩展。对过短分段,可以在命中子块后补回父章节或相邻段落,以恢复定义、参数和使用条件;但扩展上下文会消耗更多 token,还需要去重、排序和长度控制。
本次分段改进保留了章节层级与代码围栏,同时在检索阶段过滤纯标题。这个规则的边界是“没有正文的分段”,不是“字符少的分段”。“爱吃鱼”只有三个字,仍是有效正文,不应该为了降噪而被长度阈值误删。
四、删除后还能搜到,要查一致性而不是相似度
本次残留的 592 个分段来自后台写入与删除的竞态:资源元数据已经删除,导入任务仍继续写入向量,随后保存分段元数据失败。结果是向量库里存在记录,但资源表里已经找不到对应对象。它与模型理解能力无关。
实际修复包含三部分:在项目现有 SQLite 事务边界内串行化资源变更与向量写入;写入后发生异常时补偿清理向量;知识库检索仅允许仍存在、已完成且已启用的资源进入结果。数据量更大或涉及多个独立服务时,还应考虑持久化任务状态、删除墓碑、幂等重试与定期对账。
Milvus 的 Strong 一致性可用于要求读取较新的数据视图,但不能阻止业务代码在删除之后再次插入,也不能把 SQLite 与 Milvus 自动变成同一个原子事务。进程崩溃或清理失败时,仍需要补偿与对账。参见 Milvus Consistency。
权限和状态过滤也要覆盖标题、关键词和向量等全部分支。只在前端隐藏结果,或者只过滤向量而遗漏关键词分支,都会留下错误返回甚至越权风险。
五、如何选型:从失败样本决定加哪一层
| 观察到的问题 | 先检查 | 优先尝试 |
|---|---|---|
| 原文类名存在,结果却不含该类名 | 分词、边界、关键词候选与最终名次 | 增加词面召回;精确符号与定义优先 |
| 按正文能找到,按标题找不到 | 标题是否进入任何召回路径 | 补齐标题召回与完整标题排序 |
| 换一种说法就找不到 | 查询与文档编码、语言和领域适配 | 稠密召回,再评估查询扩展或学习型稀疏 |
| 候选有正确资料,但第一屏不对 | 召回候选与最终结果是否被分别记录 | 融合、规则排序,必要时加入模型重排 |
| 命中了片段,却缺定义或限制条件 | 切分边界、父章节与上下文预算 | 结构化切分;按需补充父块或邻段 |
| 删除或停用后仍可检索 | 资源状态、并发任务、孤立向量 | 生命周期控制、补偿清理与一致性检查 |
对于同时包含中文说明、代码和短记录的知识库,可以先建立“标题 + 关键词 + 稠密向量”的基线,完成分支过滤、去重和定向排序,再根据评测决定是否增加学习型稀疏、查询扩展或模型重排。每增加一层,都应能指出它修复了哪类样本,以及付出了多少延迟和维护成本。
六、一条可落地的检索流程
用户查询
→ 保留原始输入,识别精确名称 / 普通问句等意图
→ 应用租户、权限、版本、启用状态等约束
→ 并行召回:资源标题 + 关键词/BM25 + 稠密向量
→ 按稳定 ID 合并;过滤不存在、失败和无正文的候选
→ 排名融合 + 有边界的标题/标识符/定义规则
→ 可选:Cross-Encoder 或其他模型重排
→ 相邻块或父章节补全、去重、上下文长度控制
→ 返回资料与来源;问答场景再据此生成答案候选数量要多于最终展示数量,才能给融合和重排留下空间。例如最终返回 10 条时,可以从每路几十条候选开始做实验;这只是调参起点,不是固定最佳值。小规模库直接穷举可能已经足够,较大语料再结合索引、批处理和并行请求控制成本。
标题、关键词和向量应共享稳定的 resource_id 与 chunk_id。保存原始语义分数、词面排名、融合结果、标题是否匹配和正文是否命中,便于解释“为什么排第一”。日志还应包含 limit、各阶段耗时与结果数量;涉及个人或业务信息时,需要制定脱敏、访问与保留策略。
七、如何证明变准:把召回、排序和业务状态分别评测
- 精确名称ReActAgent、reactagent、PlanAndSolveAgent,以及正文只出现相似前缀的负例;比较定义分段的位置。
- 标题与短正文“我的喜好”“喜好”“爱吃鱼”;验证按标题与按内容都能找到同一条记录。
- 自然语言改写同义问法、长问句、中文英文混合表达;检查修复类名时是否损害语义召回。
- 空结果与边界不存在的类名、无答案问题、只有章节标题的分段;检查是否被无关内容凑满。
- 生命周期删除、停用、重新启用、导入失败和导入中删除;检查所有召回分支是否一致。
数据集应为每个查询标注可回答问题的相关分段,并保留容易混淆的负例。Recall@K 看应有证据是否进入候选;Precision@K 看展示结果中有多少真正相关;MRR 看第一条相关结果排在哪里;nDCG@K 适合有相关性等级的排序;Hit@1 可用来检查按标题导航。与此同时记录 P95 延迟、超时和成本,不能只盯着一个分数。
做消融比较时,保持语料和标注一致,分别测试纯向量、纯关键词、加入标题、混合融合、再加入重排,并逐类观察变化。避免只用刚修好的几个问题反复调参;还要留出独立查询集,防止规则只对样例有效。
这次回归中“前 10 条都包含 ReActAgent”,只能叫标识符命中检查;没有逐条相关性标注,就不应写成 Precision@10 = 100%。同样,单元测试和接口测试通过说明预期行为得到验证,不能替代真实检索质量评测。
结语:让检索路径覆盖用户实际输入的信息
ReActAgent 的问题提醒我们,专有名称需要稳定的词面信号;“我的喜好”的问题提醒我们,标题不能只存着却不参与检索;删除后残留的问题提醒我们,资料是否有效必须由业务状态保证。选择方法时,应从可复现的失败样本出发,补上缺失的那一层,再用独立样本验证收益和边界。
参考资料
SQLite FTS5:trigram tokenizer 与全文检索
Sentence Transformers:Retrieve & Re-Rank
SPLADE:Sparse Lexical and Expansion Model
HyDE:Precise Zero-Shot Dense Retrieval without Relevance Labels
资料核对日期:2026 年 10 月 30 日。Milvus 文档会随版本更新,具体 API 与索引能力请对应已部署版本核对。
