1. 从标题拆解 deep-searcher 的真实定位
第一次看到zilliztech/deep-searcher这个仓库名,很多人会下意识把它归类成"又一个 RAG 框架"。但把名字拆开看,deep和searcher两个词其实已经把它的野心写在脸上了——它要解决的不是"能不能检索到",而是"检索得够不够深"。这个区别很关键,因为市面上大部分检索增强方案停留在"向量相似度 top-k"这一层,而 deep-searcher 想做的事情,是在这个基础之上再叠一层推理式的深度搜索。
我最初接触这个项目,是因为手上有一个私有文档问答的需求:几百份内部技术文档、会议纪要、产品规格书混在一起,用户提问往往不是"某份文档里写了什么",而是"我们去年在某个方向上做过哪些尝试,结论是什么"。这种问题用传统向量检索基本是灾难——单次 top-k 召回拿到的片段要么太碎,要么答非所问。deep-searcher 的思路正好切中这个痛点:它把"搜索"从一个单步动作,变成了一个可以多轮迭代、自我修正的过程。
从定位上说,deep-searcher 面向的是这样一类人:你手里有一堆非结构化的私有数据(PDF、Markdown、网页存档、数据库导出都算),你想让大模型基于这些数据回答问题,但你发现朴素的 RAG 效果不稳定,尤其是遇到需要跨文档、多跳推理的问题时。它适合有一定工程基础的开发者,也适合想快速验证"深度检索"到底能带来多少提升的技术决策者。哪怕你只是想搞明白"深度搜索"和"普通 RAG"的差别在哪,把这个项目跑一遍也是最快的路径。
需要说明的是,下面涉及的具体实现细节,一部分来自我对该项目的理解,一部分是基于同类深度检索系统的常见工程实践做的合理补全。如果你要直接照搬到生产环境,建议以仓库最新代码为准,我这里更多是讲清楚"为什么这么设计"以及"踩坑时该往哪个方向想"。
2. 深度搜索到底比普通 RAG 强在哪
2.1 普通 RAG 的天花板在哪里
要理解 deep-searcher 的价值,得先承认普通 RAG 的局限。标准 RAG 流程无非是:把文档切块、向量化、存进向量库、查询时把问题向量化、取相似度最高的几块、塞进 prompt 让模型回答。这套流程在"答案就藏在某一个 chunk 里"的场景下非常好用,比如"XX 接口的默认超时时间是多少"。
但一旦问题变成复合型,问题就来了。举个例子:"我们对比过哪几种向量索引方案,各自的取舍是什么?"这个问题的答案可能散落在三份文档里:一份讲 HNSW 的参数调优,一份讲 IVF 的召回率测试,一份是选型会议的纪要。普通 RAG 用问题去检索,很可能只召回讲 HNSW 的那份,因为"向量索引"这个词在那份文档里出现频率最高。结果就是模型只答了一半,甚至答偏。
更麻烦的是多跳问题。"A 方案依赖 B 组件,B 组件在 C 版本里被替换了,那现在 A 方案还能用吗?"这种问题需要先找到 A 和 B 的关系,再找到 B 和 C 的关系,最后做推理。单次检索根本覆盖不了这条链路。
2.2 deep-searcher 的核心思路:把检索变成迭代过程
deep-searcher 的核心洞察是:既然一次检索不够,那就多来几次,而且让模型自己决定下一次该搜什么。这听起来简单,但工程上要做对并不容易。它大致包含这么几个关键环节:
- 查询分解:拿到一个复杂问题后,先让模型把它拆成若干子问题或子查询。比如上面那个索引选型的问题,可能被拆成"我们测试过哪些索引""每个索引的召回率数据""选型时的决策依据"。
- 多轮检索:针对每个子查询分别去检索,拿到各自的候选片段。
- 结果反思与再检索:模型看完第一轮结果后,判断"信息够不够回答原问题",如果不够,它会生成新的查询继续搜。这一步是"deep"的精髓所在。
- 证据聚合与作答:把多轮检索到的证据汇总,去重、排序,最后生成带引用的答案。
这个流程和人类做深度调研的方式几乎一模一样:先粗搜一圈,发现线索,顺着线索再搜,直到信息足够支撑结论。deep-searcher 把这个过程自动化了。
2.3 为什么这个设计值得投入
有人会问:多轮检索不是更慢更贵吗?确实,token 消耗和延迟都会上去。但这里有个权衡:在私有知识库场景下,用户要的是"答对",而不是"答得快"。一个答错的答案,用户还得自己去翻文档验证,综合成本反而更高。deep-searcher 把这种"人工翻文档"的成本前置到了系统里。
另外一个容易被忽略的点是可解释性。因为每一轮检索的查询和结果都是显式的,你可以清楚地看到模型是怎么一步步逼近答案的。这在企业场景里非常重要——当模型给出一个结论时,你能追溯它是基于哪些片段、经过哪些推理步骤得出的。普通 RAG 的"黑盒 top-k"在这方面就差很多。
3. 核心组件与关键技术点解析
3.1 文档加载与切分策略
深度搜索的效果,一半取决于检索算法,另一半取决于最前端的文档处理。deep-searcher 支持多种数据源,常见的有本地文件(PDF、Markdown、TXT)、网页内容、以及结构化数据导出。这里第一个坑就是切分粒度。
切太碎,单个 chunk 语义不完整,检索出来的是半句话;切太大,一个 chunk 里混了好几个主题,向量表示被稀释,相似度反而不准。我的经验是:对于技术文档,按标题层级切分(H2 或 H3 作为边界)通常比固定字符数切分效果好得多。如果文档没有清晰结构,那就用带重叠的滑动窗口,重叠比例控制在 10% 到 20% 之间。
提示:切分时一定要保留元数据,比如来源文件名、章节标题、页码。deep-searcher 在最终作答时会用到这些信息做引用,缺了元数据,答案的可信度会大打折扣。
3.2 向量化与索引选型
向量化模型的选择直接决定检索质量。通用场景下,开源的 BGE 系列或者商业的 embedding 接口都能用。但要注意:embedding 模型必须和你的数据语言、领域匹配。如果你的文档大量是中文技术内容,用一个主要在英文语料上训练的模型,效果会明显打折。
索引方面,deep-searcher 底层通常对接向量数据库(这也是 zilliztech 背景的自然选择,Milvus 就是他们家的)。索引类型的选择要看数据规模:
| 数据规模 | 推荐索引 | 理由 |
|---|---|---|
| 万级以下 | 暴力检索或 IVF_FLAT | 数据量小,精度优先,延迟可接受 |
| 十万到百万级 | HNSW | 召回率和延迟平衡好,内存占用可控 |
| 千万级以上 | IVF_PQ 或 DiskANN | 必须考虑内存和磁盘成本,接受一定精度损失 |
这里有个实操心得:不要一上来就上最复杂的索引。我见过太多人数据才几千条就配了 PQ 量化,结果召回率掉得厉害,还以为是模型不行。先用简单索引把流程跑通,等数据量真的上来了再换。
3.3 查询分解与反思机制
这是 deep-searcher 区别于普通 RAG 的核心。查询分解通常靠 prompt 工程实现:给模型一个复杂问题,要求它输出一组子查询。这里的关键是约束输出格式,让模型输出结构化的 JSON 或者列表,方便程序解析。如果让模型自由发挥,它可能给你一段散文,解析起来很痛苦。
反思机制更微妙。模型需要判断"当前证据是否足够"。实践中常见两种做法:一种是让模型直接回答"够/不够",另一种是让模型尝试作答,如果它自己标注了"信息不足"或者答案里出现"根据现有资料无法确定",就触发再检索。第二种更稳,因为它把判断和作答合并在一次调用里,省了一轮交互。
注意:反思轮数一定要设上限。我一般设 3 到 5 轮。不设上限的话,模型可能陷入"永远觉得信息不够"的死循环,token 烧得飞快,问题还答不出来。
3.4 证据聚合与答案生成
多轮检索下来,你手里会有一堆候选片段,其中必然有重复和低相关的。聚合阶段要做的是去重(按内容哈希或语义相似度)、重排(可以用 cross-encoder 或者简单的相关性打分)、截断(控制塞进最终 prompt 的长度)。
答案生成时,强烈建议要求模型逐句标注引用来源。这不仅是给用户看的,也是给你自己调试用的。当答案出错时,你能快速定位是检索错了还是生成错了。deep-searcher 在这块的设计思路就是让每个结论都能追溯到具体的文档片段。
4. 从零跑通 deep-searcher 的实操流程
4.1 环境准备与依赖安装
假设你已经有一个 Python 环境(3.9 以上比较稳妥),第一步是把仓库拉下来装依赖。这类项目通常依赖几个大头:向量数据库客户端、embedding 模型库、以及大模型的调用 SDK。
git clone https://github.com/zilliztech/deep-searcher.git cd deep-searcher pip install -r requirements.txt装依赖时最容易出问题的是版本冲突,尤其是 PyTorch 和 CUDA 的匹配。如果你本地没有 GPU,或者不想折腾驱动,直接用 CPU 版本跑小规模验证完全够用。我建议第一次跑通流程时不要追求性能,先确保逻辑正确。
4.2 配置数据源与模型
接下来要配置两样东西:数据在哪,模型用哪个。数据源配置一般是个 YAML 或 JSON 文件,指定文档目录、文件类型过滤、切分参数。模型配置则包括 embedding 模型和大模型两部分。
data_source: type: local path: ./docs file_types: [".pdf", ".md", ".txt"] chunk_size: 512 chunk_overlap: 64 embedding: model: "BAAI/bge-large-zh-v1.5" dimension: 1024 llm: provider: "openai-compatible" model: "your-model-name" max_tokens: 2048这里chunk_size和chunk_overlap是最需要调的参数。512 是个比较通用的起点,但如果你文档里有很多长段落,可以适当放大到 800 甚至 1024。overlap 设 64 到 128 之间,保证跨 chunk 的语义连续性。
4.3 构建索引与首次检索测试
配置好之后,跑索引构建。这一步会把所有文档切分、向量化、写入向量库。数据量大的话可能要等几分钟到几十分钟,取决于你的 embedding 是本地跑还是调 API。
python build_index.py --config config.yaml索引建完后,先别急着上复杂问题,用几个简单查询验证一下基础检索是否正常。比如直接问"文档里提到的第一个方案是什么",看能不能召回相关片段。这一步是排除"数据没进去"或"向量维度对不上"这类低级错误。
4.4 运行深度搜索并观察中间过程
基础检索没问题后,就可以跑完整的深度搜索流程了。deep-searcher 通常会输出中间过程,包括每一轮生成的子查询、召回的片段、以及模型的反思结论。一定要把这些中间输出打开看,这是理解系统行为的最好方式。
python deep_search.py --query "我们对比过哪几种索引方案,各自取舍是什么" --max_rounds 3跑的时候重点观察三件事:第一,子查询拆得合不合理;第二,每轮召回的内容是不是在逐步逼近答案;第三,模型是在第几轮决定停止的。如果发现它第一轮就停了但答案明显不全,说明反思机制太宽松;如果它搜了五轮还在打转,说明查询分解有问题,可能一直在重复搜同一个方向。
4.5 参数调优的实操记录
我拿一个约 200 份文档的知识库做过一轮调参,记录如下:
| 参数 | 初始值 | 调整后 | 效果变化 |
|---|---|---|---|
| chunk_size | 256 | 512 | 答案完整度明显提升,碎片化减少 |
| max_rounds | 5 | 3 | 延迟下降约 40%,答案质量基本持平 |
| top_k per round | 10 | 5 | 噪声减少,但个别问题召回不足,最终定在 6 |
| overlap | 0 | 96 | 跨段落问题改善明显 |
这轮调参最大的体会是:参数没有万能值,必须拿你自己的数据去试。别人博客里写的"最佳实践"只能当起点,因为文档结构、问题类型、模型能力都会影响最优参数。
5. 常见问题与排查技巧实录
5.1 检索召回为空或明显不相关
这是最常见的问题,原因通常有三类。第一类是数据根本没进库,检查索引构建日志有没有报错,向量库里 count 一下记录数对不对。第二类是embedding 模型和查询语言不匹配,比如文档是中文但用了英文模型,这时候查询向量和文档向量根本不在一个语义空间里。第三类是切分把关键信息切断了,比如一个表格被从中间切开,检索时两边都拿不到完整信息。
排查顺序建议从数据量查起,再查模型,最后查切分。我踩过最坑的一次是切分参数写错了单位,以为是字符数结果是 token 数,导致 chunk 比预期小了一半,检索效果一直上不去,查了半天才发现。
5.2 多轮检索陷入重复
模型反复生成相似的子查询,搜来搜去都是那几篇文档。这通常是查询分解的 prompt 没约束好。解决办法是在 prompt 里明确要求"新查询必须和之前的查询有实质差异",并且把历史查询列表传给模型,让它自己避开。另一个技巧是给每轮检索的结果做去重,如果新一轮召回的内容和上一轮高度重叠,就强制换方向或者直接终止。
5.3 答案有引用但引用对不上
模型标注了来源,但你点进去发现那段话根本不在引用的文档里。这是典型的幻觉引用。根因是生成阶段模型没有严格基于给定片段作答。对策有两个:一是把片段编号后在 prompt 里明确要求"只能引用编号内的内容";二是在后处理阶段做校验,把答案里的引用和实际片段做匹配,对不上的直接标红或者丢弃。
5.4 延迟和成本失控
深度搜索天然比普通 RAG 贵,但贵到什么程度要心里有数。一个实用的监控方式是记录每次查询的轮数、token 消耗、耗时。如果发现平均轮数超过 4,或者单次查询 token 消耗超过某个阈值,就该回头看看是不是查询分解太发散,或者反思机制太激进。
提示:生产环境里可以给不同优先级的查询设不同的 max_rounds。实时交互的查询限制在 2 轮,后台批处理任务可以放宽到 5 轮,这样能在体验和成本之间找到平衡。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 召回为空 | 数据未入库/模型不匹配 | 查记录数、查 embedding 维度 |
| 答案不完整 | 轮数不足/切分过碎 | 调大 max_rounds、调大 chunk_size |
| 重复检索 | 查询分解无约束 | 加历史查询去重、改 prompt |
| 引用错误 | 生成阶段幻觉 | 加编号约束、后处理校验 |
| 延迟过高 | 轮数过多/片段过长 | 限制轮数、截断上下文 |
6. 深度检索的适用边界与扩展思路
6.1 什么场景该用,什么场景别硬上
deep-searcher 这类深度检索不是银弹。如果你的问题基本都是"单点事实查询",比如"某个配置项的默认值是多少",那普通 RAG 又快又准,上深度检索纯属浪费。它真正发光的场景是:问题需要跨多份文档、需要多跳推理、或者答案本身就是一个需要综合判断的结论。
另一个判断维度是数据规模。数据量很小(比如就十几份文档)的时候,你甚至可以把全文塞进长上下文模型里,根本不需要检索。深度检索的价值在数据量大到无法全量塞入上下文时才真正体现。
6.2 可以往哪些方向扩展
跑通基础流程后,有几个自然的扩展方向。一是接入更多数据源,比如把数据库查询也作为一种"检索"手段,让模型在需要结构化数据时去查库而不是查文档。二是加入重排模型,在每轮检索后用一个 cross-encoder 对候选片段精排,通常能明显提升最终答案质量。三是做查询缓存,把高频问题的检索路径缓存下来,既省成本又降延迟。
还有一个我觉得很有价值的方向是人工反馈闭环。让用户对答案点赞点踩,把踩的案例收集起来,分析是检索问题还是生成问题,然后针对性地调 prompt 或换模型。深度检索系统因为中间过程透明,做这种归因分析比黑盒 RAG 容易得多。
6.3 我个人的一点使用体会
用下来最深的感受是:深度检索系统的效果,七分靠数据治理,三分靠算法。文档切得干不干净、元数据全不全、embedding 模型选得对不对,这些"脏活累活"决定了上限。算法层面的多轮反思、查询分解,更多是在这个上限之内做优化。我见过太多团队一上来就研究怎么改反思 prompt,结果文档切分一塌糊涂,怎么调都上不去。
所以如果你正准备上手 deep-searcher,我的建议是先把文档处理这一环做扎实,用简单查询验证检索质量,确认基础没问题了,再去折腾多轮和反思。这个顺序反了,后面全是无用功。另外,别怕看中间输出,那些子查询和召回片段里藏着系统所有的秘密,看多了你对"模型到底在想什么"会有完全不一样的直觉。