一、什么是 Dify
Dify 是一个开源的 LLM 应用开发平台。核心定位:通过可视化工作流编排、RAG 管线、Agent 能力、模型管理等功能,帮助开发者快速从原型走到生产。可以理解为 AI 应用的"低代码搭建平台"。
其知识库检索部分将分段和检索打包成三种模式提供给用户:
|--------|----------|--------------------|
| 模式 | 分段方式 | 检索方式 |
| 经济模式 | 普通分段 | 纯向量检索 |
| 高质量模式 | 父子分段 | 纯向量 或 多路召回(向量+关键词) |
| 多路召回 | 父子分段 | 向量 + 关键词(BM25) |
**注意:**在 Dify 里分段和检索是强绑定的,不能自由组合。普通分段下无法使用多路召回,要加关键词路就必须切到高质量模式。
二、分段方式与检索方式:两套独立体系
很多平台(包括 Dify)把分段和检索打包成"套餐"卖,导致容易搞混。
但实际上,分段和检索是两个完全解耦的独立步骤。
分段只告诉系统"文档长什么样",检索只告诉系统"怎么从里面找东西"。
两个步骤各自独立,理论上可以任意组合。
2.1 分段方式(怎么切文档)
分段发生在索引构建阶段,把原始文档切成一段一段的 chunk。
|--------|-------------------|---------------|
| 方式 | 做法 | 适用场景 |
| 固定大小分段 | 按字符数/token 数硬切 | 最简单粗暴,通用场景 |
| 语义分段 | 按段落、章节标题等语义边界切 | 结构化好的文档 |
| 父子分段 | 切两层:子块用于匹配,父块用于返回 | 需要大段上下文又不想搜太粗 |
| 递归分段 | 逐级按分隔符往下切 | 结构不规整的文档 |
| 句子级分段 | 以句子为单位 | 问答对等细粒度场景 |
2.2 检索方式(怎么搜)
检索发生在用户提问之后,决定用什么算法从知识库里把相关 chunk 找出来。
|-------------|------------------|--------------|
| 方式 | 做法 | 特点 |
| 向量检索(Dense) | 文本转向量,算相似度 | 理解语义,但可能漏精确词 |
| 关键词检索(BM25) | 传统词频 - 逆文档频率匹配 | 精确匹配强,但不懂同义词 |
| 混合检索/多路召回 | 向量 + 关键词同时跑,合并排序 | 互补,召回率最高 |
| 全文检索 | 纯文本模糊匹配 | 最原始,一般不用了 |
Dense VS Sparse
Dense 向量(稠密向量)
把一段文字喂给 Embedding 模型(如 bge-small),模型输出一个固定长度的向量,比如 1024 维。重点是每一维都有值,没有空位,所以叫"稠密"。每个值本身没有人类可读的含义------不是"第 3 维 = 主语",而是模型的全部理解均匀地"涂抹"在所有维度上。它捕捉的是语义:两个文本意思相近,即使用的词完全不同,向量在高维空间里也是邻居。
比如搜"怎么让程序跑得更快",Dense 向量能找到一篇写"性能调优方法论"的文档------词不同,意思同。
Sparse 向量(稀疏向量)
把一段文字拆成词,用 BM25 这类算法给每个词打一个权重分。向量的维度 = 整个语料库出现过多少个不同的词,可能是几万甚至几十万维。但对某一段文字来说,它只包含其中几十个词,剩下几万维全填 0,所以叫"稀疏"。每个维度代表一个具体的词,有值的位置直接对应"这个词出现了"。
比如搜"QPS",Sparse 向量精准命中所有含"QPS"这三个字母的文档------词相同才匹配,不管意思。
一句话:Dense 回答"意思像不像",Sparse 回答"词在不在了"。
TF-IDF vs BM25
TF-IDF 的问题
一篇 5000 字的文档里写了 10 次"数据库",另一篇 200 字的文档也写了 10 次"数据库"。TF-IDF 认为这两篇和"数据库"这个词的相关度一样高------因为它的逻辑很简单:出现次数 × 稀有程度。但实际上那篇 200 字的短文显然更聚焦于"数据库",5000 字的可能只是反复提到而已。
另外 TF-IDF 对高频词没有惩罚上限。一个词出现 50 次就是出现 5 次的 10 倍权重,但实际上出现 5 次已经足够说明相关性了,后面 45 次是冗余的。
BM25 怎么改的
- 给词频加了"饱和曲线"。词出现 3-5 次之后,再多次出现也几乎不加分了,杜绝了灌水的长文档霸占高分。
- 加了文档长度归一化。短文档天然更"浓缩",BM25 会自动补偿这一点------同样出现 10 次"数据库",200 字的短文得分远高于 5000 字的长文。
一句话:TF-IDF 是"出现几次算几次",BM25 是"出现足够多次就够了,短文档更值钱"。BM25 替代 TF-IDF 成了工业标准。
2.3 分段和检索可以自由组合吗?
理论上除了一个组合外,全部可以自由组合。
|----------|----------|----------------------------|
| 分段方式 | 检索方式 | 实际价值 |
| 固定大小 | 纯向量 | 最基础方案,Dify 经济模式 |
| 固定大小 | 向量 + 关键词 | LangChain、RAGFlow 等大量项目这么干 |
| 语义分段 | 纯向量 | 文档结构清晰时足够用 |
| 语义分段 | 向量 + 关键词 | 分段和检索各取所长 |
| 父子分段 | 纯向量 | Dify 高质量模式 |
| 父子分段 | 向量 + 关键词 | Dify 多路召回模式 |
**逻辑上不成立的唯一组合:普通分段 + 父子式召回。**父子式召回的前提是 chunk 具有父子层级结构,普通分段根本没有创建这套结构,所以不是技术做不到,而是概念上不成立------你没法从一个不存在的东西里找父块。
三、父子分段模式详解
3.1 为什么需要父子分段
问题:普通分段按固定大小切,比如每 500 字一块。检索时一个用户问"卷一卷二是什么意思",这个短语可能只出现在某块的第 3 句话里。向量检索能匹配到这一块,但返回给 LLM 的就只有这 500 字------可能前不着村后不着店,缺少上下文,LLM 看不懂。
解决:父子分段把文档同时切成两层------子块(小,如 200 字)用来做向量匹配,粒度细,搜得更精准;父块(大,如 800 字)用作文本返回,内容完整,上下文充足。
3.2 父子模式下的"召回次数"
"召回次数"指的是用多少个子块去匹配。假设召回次数 = 3:找到 3 个最佳子块,这 3 个子块属于 2 个不同父块,去重后最终返回 2 个父块给 LLM。所以召回次数设成 3,实际返回的父块数可能 ≤ 3。
3.3 父子分段是分段方式,不是检索方式
这一点特别容易搞混。父子分段的"分段"二字是关键词------它改变的是文档被怎么切,而不是怎么搜。分段阶段创建了"子块→父块"的映射关系;检索阶段照常做混合检索(向量 + 关键词),索引里只有子块参与打分;返回前,命中的子块按 mom_id 分组,每组按主键直接取出对应父块,合并成一条返回。条数只减不增,内容体积变大。
四、多路召回深度解析
4.1 工作流程
多路召回就是"多条腿同时走路,结果汇总后再精选"。以"两路"为例说明整个流程:
- 用户提问同时喂给向量检索(路1)和关键词检索 BM25(路2)
- 向量检索:问题转向量 → 用语义相似度匹配,擅长抓住"上限是多少"的理解
- 关键词检索:问题分词 → 倒排索引精确匹配,擅长命中"QPS"这类精确词
- 两路结果合并去重
- Rerank 模型重新打分排序(可选但强烈推荐)
- 截取最终的 Top K 条返回给 LLM
4.2 为什么单靠向量不够
向量检索虽然强大,但有天然盲区:搜"QPS 上限"能用语义找到"最大吞吐量",但一篇精确包含"QPS=10000"的技术指标表可能因为行文风格不同而向量距离较远。
反过来,搜具体型号"SafeLine-2024-X3",关键词能精准命中,向量可能把"SafeLine"和"安全线"(字面上近但意思不相关)误匹配。
向量擅长语义,关键词擅长精确。两维互补,召回率最高。
4.3 多路召回和父子分段的关系
多路召回和父子分段是两个独立维度的叠加。在 Dify 中,多路召回的向量那条路底层用的就是父子分段。所以多路召回本质是在高质量模式的基础上再加一条关键词路:经济模式 → 高质量模式(父子分段+纯向量)→ 多路召回(父子分段+向量+关键词)。
五、实战案例:kb-assistant 全链路拆解
5.1 项目概览
这是我自制的公司内部技术文档知识助手。核心逻辑:爬取内部文档 → 分段 → 向量化 → Chroma 存储 → 用户提问时检索 → LLM 回答。
5.2 分段实现
方案:按 Markdown 标题 + 固定大小滑动窗口
核心参数:chunk_size=600, chunk_overlap=100, min_chunk_size=80
具体算法:先按 Markdown 标题正则切 sections → 逐 section 往 buf 里拼 → 满了就吐出一个 chunk → buf 保留末尾 100 字符做重叠 → 单个超长 section 强制按 600 切成多块 → 最后剩下的吐出。
不是纯固定大小,也不是完全语义分段,而是一个低成本但有效的混合方案。
5.3 Embedding 实现
模型:BAAI/bge-small-zh-v1.5(HuggingFace sentence-transformers),BAAI(北京智源人工智能研究院)出品,1024 维向量,中文开源 Embedding 模型的标杆之一,small 版轻量但效果好,适合本地部署。编码时做归一化。
备选方案:也支持通过 OpenAI 兼容 API 调用 text-embedding-3-small。
5.4 向量库:Chroma
Chroma PersistentClient,数据存本地磁盘 data/index/,Collection 名 tech_support,距离度量使用 cosine。
Chroma 的 Collection 就是向量数据库里的一张表,类比关系型数据库:Database → Table → Row 对应 PersistentClient → Collection → (向量+文本+元数据)。
5.5 检索流程
**参数配置:**fetch_k=24, top_k=6, score_threshold=0.25
完整检索链路:
(1) 问题向量化:bge-small-zh-v1.5 encode → 1024维向量
(2) Chroma 粗筛:HNSW + cosine 距离 → 拉 fetch_k=24 候选 → 返回 ids + distances + documents + metadata
(3) 遍历结果:distance → score(1 - distance),这一步是格式转换不是二次计算
(4) 分数过滤:score < 0.25 → 丢弃,筛掉不相关的噪音
(5) 产品名 boost 重排:标题命中产品名的 chunk → score + 0.15,按新分重新排序(未引入rerank)
(6) 截取 top_k=6,返回给 LLM
5.6 fetch_k 与 top_k 的关系
fetch_k 是粗筛(从向量库捞出多少候选),top_k 是精选(最终返回几条给 LLM)。只捞 6 条直接返回质量波动大,先捞 4 倍(24 条)给后续过滤和重排留腾挪空间。
这是生产环境的标配做法,不同框架叫法不同但套路相同:Overfetch → Filter → Boost/Rerank → Truncate。
5.7 Score 值的来源
Score 在 Chroma 检索那一刻就产生了,不是后来算的。
Chroma 用 HNSW 索引搜索返回 cosine 距离(0 = 完全相同,1 = 不相关),代码做一步 1 - distance 转成相似度分数(0.85 = 很相关,0.1 = 不相关),纯粹为了让后续阈值判断更好读。
这里没有算两次,只算了一次 HNSW 搜索。几乎所有项目都这么做,因为 Chroma 的 query() 接口规范就是返回 distances。
5.8 两个阈值的作用
|------|-------------------|-------------------|
| | Score阈值(0.25) | 质量判定(0.45) |
| 作用对象 | 单个 chunk | 整批结果(6条) |
| 判断时机 | 检索过程中,逐条判断 | 检索完成后,整体计算 |
| 规则 | 单条 < 0.25 就丢弃 | 平均分 < 0.45 标低置信度 |
| 影响什么 | 减少传给 LLM 的噪音 | 只打标签,不影响实际返回 |
| 类比 | 海选门槛 | 终选评价 |
5.9 完整问答链路
从用户提问到用户收到回答的 11 个步骤:
- 步骤1:元问题判断------"知识库有哪些产品?"等直接返回统计数据
- 步骤2:产品名识别------从问题中抽取产品名供后续 boost 使用
- 步骤3:问题向量化------bge-small-zh-v1.5 encode → 1024维向量
- 步骤4:Chroma 检索------HNSW + cosine 距离,拉 fetch_k=24 候选
- 步骤5:分数过滤------遍历24条,score < 0.25 丢弃
- 步骤6:产品名 boost 重排------标题命中产品名 +0.15,按新分排序
- 步骤7:截取 top_k=6------前6条;0条则回退到 catalog 通用回答
- 步骤8:质量判定------avg(scores) >= 0.45 → ok,< 0.45 → low_confidence
- 步骤9:组装 prompt------系统提示词 + 知识库概览 + 6条chunk(每条≤500字符)
- 步骤10:调用 LLM------百智云 gateway,feature/coding,temperature=0.1,max_tokens=600
- 步骤11:记录日志------logs/qa_sessions/qa_YYYYMMDD_HHmmss.json
**当前链路最弱的一环:**步骤 6 的产品名 boost 只是字符串匹配 +0.15 分,精度有限。这是最值得升级的地方。
六、Rerank 模型
6.1 Rerank 解决什么问题
当前项目用标题字符串匹配做重排,问题很明显:标题含有关键词但内容可能完全不相关,而标题不含关键词但内容高度相关的反而得不到加分。Rerank 模型就是来替代这个粗糙的 boost 的。
6.2 Embedding 模型 vs Rerank 模型
|----------|----------------------------|
| 对比维度 | Embedding模型 / Rerank模型 |
| 输入 | 单段文本 / 问题+chunk对(交叉编码) |
| 输出 | 一个向量 / 一个相似度分数 |
| 评判方式 | 向量夹角 / 语义配对打分 |
| 速度 | 快 / 慢 |
| 精度 | 粗排级别 / 接近人工判断的精排级别 |
**关键:**两个模型如果选型不同,排序结果就不同。Embedding 用 bge-small,Rerank 用 Cohere Rerank v3,最终排序由更"聪明"的 Cohere 决定。
6.3 加上 Rerank 后的检索流程
之前:Chroma(24条) → 分数过滤(0.25) → 标题+0.15 boost → 截取 top_k(6条)
加上 Rerank:Chroma(24条) → 分数过滤(0.25) → Rerank 模型重新打分 → 截取 top_k(6条)
Rerank 内部:过滤后剩 N 条候选,逐对送给 Rerank 模型(问题+"文档内容"),模型给出新分,按新分排序取前 6 条。标题匹配 boost 那一步直接丢弃,Rerank 本身就是更聪明的打分器。
七、向量检索调优策略总结
把调优动作按发生的位置归到六个战场,每个战场有各自的症状、手段和代价。
①解析 → ②切分 → ③增强 → ④向量化 → ⑤检索 → ⑥生成
大多数人只在 ⑤⑥ 折腾,因为那里参数最多、改起来最快。但问题的重心在 ①②。离线侧决定「信息还在不在」,在线侧只决定「能不能被找到」------信息本身丢了,检索再强也无解。
7.1 解析(Parsing)
垃圾进,垃圾出。这一层的损失是不可逆的。
PDF 是给人看的,不是给机器读的。同一个 PDF 用不同解析器出来的文本可能天差地别。
|--------------------|-----------------------|
| 常见破坏 | 后果 |
| 双栏排版被按行读成「左半句+右半句」 | 整页语义全乱,无法挽救 |
| 表格拍平成一串数字 | 行列关系丢失,数值问答必错 |
| 页眉页脚混进正文 | 每个块都被「第 3 页 / 公司机密」污染 |
| 图片、公式、图表直接丢弃 | 关键信息永久消失 |
| 标题层级丢失 | 后面没法做层级切分、没法做目录检索 |
调优手段
|----------|---------------------------------------------------------------|
| 手段 | 说明 |
| 换解析后端 | 规则解析(快、糙)→ 版面模型(准、慢)→ 多模态大模型(最准、最贵)。先拿 10 个最难的文档横向对比,别一上来选默认的 |
| 表格单独处理 | 表格不参与正文切分,转成 Markdown 表或结构化行;数值密集场景直接走 Text2SQL |
| 图片转文字 | 用视觉模型给图/图表生成描述文字,一起入库,否则图里的信息等于不存在 |
| 清洗页眉页脚水印 | 高频重复行直接删;这类噪声会稀释每个块的向量 |
| 保留结构元数据 | 标题层级、页码、章节路径存进 metadata,后面切分和过滤都要用 |
怎么验收
别看指标,直接肉眼看解析结果。抽 5 个典型文档,把解析出的纯文本打印出来读一遍。读不通顺,后面全是白搭。这是唯一一个建议纯人工验收的环节。
7.2 切分(Chunking)
核心矛盾
块太小 → 语义不完整,检索到了也答不了(「根据上述规定」------上述是啥?)
块太大 → 向量被稀释,一个块讲三件事,检索精度暴跌
维度 1:块大小
|------------|-------------------|
| 场景 | 建议 |
| FAQ、问答对 | 一问一答一块,别切 |
| 通用文档 | 300~500 token 起步 |
| 法律、合同、技术手册 | 500~1000,条款完整性优先 |
| 代码 | 按函数/类切,别按 token |
维度 2:重叠(overlap)
- 建议 10%~20%
- 作用是防止关键句正好卡在边界上被劈成两半
- 超过 30% 纯浪费存储和检索位置,收益递减
维度 3:切分边界(最容易被忽略)
按优先级降级切:
标题 > 段落空行 > 句号 > 逗号 > 硬切 token
别用固定长度硬切。直接按 500 token 一刀切下去,句子被拦腰斩断,是最粗暴也最常见的错误。
维度 4:父子分段(小块检索、大块喂模型)
这个技巧值得单独强调,因为它直接化解了上面那个核心矛盾:
子块(100~200 token)→ 只用来做向量检索,语义聚焦,命中准
父块(800~1500 token)→ 命中后返回给大模型,上下文完整
一举两得,代价只是索引里多存一层映射。如果只能做一项优化,就做这个。
维度 5:结构化切分
有标题层级的文档,按章节树切,并把章节路径拼进块的开头:
块内容 = "第三章 > 3.2 退款政策 > " + 正文
这一步能同时提升向量检索和关键词检索------因为上下文路径本身就是强信号。
症状对照
|----------------|--------------|------------------|
| 症状 | 病因 | 药 |
| 检索到的块读起来「缺头少尾」 | 块太小 / 边界乱切 | 加大块、按语义边界切、上父子分段 |
| 检索到了但答非所问 | 块太大,一块混了多个主题 | 减小块 |
| 关键信息总是差一点 | 卡在块边界 | 加 overlap |
| 表格数值答错 | 表被切碎 | 表格独立成块 |
7.3块增强(Enrichment)
不改变检索算法,只改变被检索的内容本身。
问题的根源叫语义鸿沟:用户问「退货要多久」,文档里写的是「逆向物流时效标准」。两句话向量不接近,关键词也不重合------检索必然失败。
增强就是在入库时预先把这个鸿沟填上。
|--------|-----------------------------------|-------------------------------|
| 手段 | 做法 | 解决什么 |
| 假设性问题 | 让大模型给每个块生成 3~5 个「这段能回答什么问题」,一起入库 | 最有效。把「文档语言」翻译成「用户语言」,直接消除语义鸿沟 |
| 关键词抽取 | 抽出核心词存进单独字段,关键词检索时加权 | 提升术语、型号、专有名词的召回 |
| 块摘要 | 给每个块生成一句话摘要,用摘要做向量 | 长块降噪 |
| 上下文前缀 | 每块开头拼上「本文档讲 X,本章讲 Y」 | 解决孤立块缺上下文 |
| 元数据标注 | 打上时间、部门、文档类型、密级等标签 | 支持过滤式检索(见 10.5) |
代价
每个块都要过一次大模型,入库成本涨几倍到十几倍。策略:
- 只对核心文档、高频问答的文档做
- 用便宜的小模型做(这类任务不需要旗舰模型)
- 做好缓存,同样内容不重复调用
7.4 向量化(Embedding)
换模型收益明确,但代价是全量重建索引。
选型三条硬标准
|--------|----------------------------------------------------|
| 标准 | 说明 |
| 语言匹配 | 中文场景必须用中文或多语言模型,别用纯英文模型硬扛 |
| 领域匹配 | 通用模型不认识「熔断降级」「毛利率倒挂」这类术语。领域词多的场景,要么微调,要么加大关键词检索权重补 |
| 维度取舍 | 维度越高越准但越慢越占空间。先试中等维度,别一上来上最大的 |
三个容易忽略的点
-
非对称检索。「问题」和「文档」是两种文本。好的模型会提供不同的编码方式(query 前缀 vs passage 前缀)。用错了效果明显下降,而且这个错误很隐蔽------不报错,只是效果差一截。查一下你用的模型有没有这个要求。
-
索引时也要拼上下文。把标题、章节路径一起编码进向量(可以给不同权重),比只编码正文准得多。
-
微调是最后手段。需要标注数据、要重建索引、要维护模型。先把切分和精排做到位再考虑,那两项的收益通常更大且更便宜。
7.5检索(Retrieval)
7.5.1 查询侧改写
用户问得烂,检索就找不到。在检索前先修问题:
|--------|---------------------------------|------------|
| 手段 | 解决什么 | 代价 |
| 多轮指代消解 | 「它的价格呢」→「iPhone 16 的价格呢」。多轮对话必开 | 一次大模型调用 |
| 查询扩展 | 补同义词、术语、缩写全称 | 一次调用 |
| 查询分解 | 「A 和 B 哪个便宜」拆成两个子查询分别检索 | 多次检索 |
| HyDE | 先让模型编一个「假答案」,用假答案去检索 | 一次调用,效果看场景 |
| 多查询生成 | 同一问题换 3 种问法分别检索再合并 | 3 倍检索开销 |
注意:这些全在关键路径上,每一个都实打实加延迟。别一次全开------先只开指代消解,其他的按需加。
7.5.2 混合检索
为什么必须混合:
|----|---------------|---------------|
| | 向量检索 | 关键词检索 |
| 强项 | 同义、语义相近、口语化提问 | 精确术语、型号、人名、编号 |
| 弱项 | 认不出没见过的专有名词 | 换个说法就搜不到 |
「ORA-01555 错误怎么解决」------向量检索基本必挂,关键词检索一击即中。反过来「怎么让系统跑快点」关键词又完全没辙。所以两条都得有。
融合用 RRF(按排名倒数加权),因为它只看名次不看分数,天然免归一化。用加权求和的话必须先归一化------关键词分数无上界,余弦在 0~1,直接相加等于关键词吃掉全部权重。
权重怎么调:
- 术语密集(技术文档、法条、产品型号)→ 提高关键词权重
- 口语化提问(客服、通用问答) → 提高向量权重
7.5.3 元数据过滤 ------ 最被低估的一招
在向量检索之前,先用结构化条件把范围缩小:
"今年三季度华东区的销售政策"
→ 先过滤 时间=2025Q3 且 区域=华东 → 再在剩下的几十个块里做向量检索
从十万个块里找,和从五十个块里找,难度差几个数量级。而且这一步几乎零延迟。
前提是 10.3 里把元数据打好了。能用过滤解决的,就别指望向量。
7.5.4 精排(Rerank)
为什么它更准:向量检索是「问题和文档分别编码,再算距离」------两者从没见过面。精排是把问题和文档拼在一起送进模型,逐字交互,自然准得多。代价是没法预计算,只能对少量候选实时跑。
三条使用铁律:
- 精排池要是最终条数的 10 倍左右。倍数只有 1~2 时精排器没得挑,等于白开
- 精排池别超过 200 条。延迟线性增长,收益早就饱和了
- 精排分数才是可信的相似度。粗排那个分数分布混乱,拿它设阈值不可靠
7.6 生成(Generation)
检索对了,最后一步照样能翻车。
|--------|------------------------------------------|
| 手段 | 说明 |
| 上下文预算 | 检索内容别超过窗口的四成。超过一半会触发「中间遗忘」------再加片段反而变差 |
| 排序策略 | 最相关的放开头和结尾,别埋在中间 |
| 强制引用 | 要求每句话标注来源编号。这是抑制幻觉最有效的手段,因为它逼模型逐句回溯依据 |
| 兜底话术 | 提示词里明确写「检索内容不足以回答就直说不知道」。不写的话模型一定会自由发挥 |
| 块间去重 | overlap 会导致相邻块内容重复,喂进去浪费 token 还干扰模型 |
| 附带元数据 | 把来源文件名、章节、日期一起给模型,答案可追溯性大幅提升 |
8. 怎么定位问题:分层归因
别凭感觉调。
准备 50~100 条真实问题和标准答案,分三步测:
第一步:正确答案在粗排捞上来的那一百条里吗?
第二步:融合去重之后还在吗?第三步:精排把它排进最终几条了吗?
|-------------|----------------------------|
| 断在哪 | 该动哪个战场 |
| 第一步就不在 | ①解析 / ②切分 / ④向量化 ← 大多数情况在这 |
| 第一步在、第二步没了 | 融合逻辑、每路保底名额、归一化 |
| 第二步在、第三步排不上 | ⑤精排 |
| 排上了但答得差 | ⑥生成 |
核心原则:先修召回,再修排序,最后修生成。顺序反了就是白干------第一步就漏了的东西,后面调什么都找不回来。
附录
关键概念速查表
|----------|----------------------------------|
| 概念 | 一句话解释 |
| RAG | 检索增强生成 = 检索 + 增强 + 生成 |
| Dify | 开源LLM应用开发平台,分段和检索被打包成套餐 |
| 分段方式 | 怎么切文档(固定大小/语义/父子/递归),索引阶段 |
| 检索方式 | 怎么搜文档(向量/关键词/混合),查询阶段 |
| 父子分段 | 分段方式,子块匹配父块返回,不是检索方式 |
| 多路召回 | 向量+关键词并行搜,Dify中必须结合父子分段 |
| Top K | 最终返回几条给LLM,精选 |
| fetch_k | 从向量库先捞出多少候选,粗筛 |
| Score阈值 | 单条最低分数线,海选门槛 |
| 质量判定 | 整批结果的平均质量评价,只打标签 |
| Score来源 | Chroma返回cosine距离,1-distance转为相似度 |
| Chroma | 轻量向量数据库,Collection=表 |
| Rerank模型 | 问题+文档对打分,精排用,最值得加的一环 |
召回多少片段合适?
所以在一般场景下这个召回多少片段呢?有多少进入到rerank?多路召回时,每条路分别召回多少呢?根据情况而定!
RAG 检索是一个只减不增的漏斗:索引全量 → 粗排 → 融合 → 精排 → 上下文。每一层只能从上一层的结果里挑,不能凭空找回来。所以效果的天花板由第一层(粗排召回)决定,效果的下限由最后一层(精排+截断)决定------前者管"能不能找到",后者管"排不排得上"。调优的本质,就是在延迟和成本约束下,让每一层的漏斗收口尽量宽。
以下是一个参考模版,具体情况具体分析:
五层漏斗模板
L0 索引全量 N (百万~亿级)
↓ 粗排 / 召回(可多路并行)
L1 每路候选 recall_k 50 ~ 200 / 路
↓ 融合 + 去重
L2 融合候选池 fused_k 50 ~ 150
↓ 精排(Cross-Encoder / LLM)
L3 精排后 rerank_k = fused_k(全打分)
↓ 阈值过滤 + 截断
L4 进入 Prompt top_n 3 ~ 15
↓ (可选)上下文扩展
L5 实际 token 占用 top_n × 块体积
关键认知:L1 漏掉的东西,L2/L3/L4 再怎么调都找不回来。所以先保 L1 的召回率,再调 L3 的精度------顺序反了就是白干。
核心公式:三个倍数,从后往前推
- 精排池 = 最终条数 × 10 (8~15 倍都行)
- 每路召回 = 精排池 × 1.75 (1.5~2 倍,补去重损耗)
- 搜索深度 = 每路召回 × 3 (2~4 倍,防近似搜索漏近邻)
假设最终的top_k是6,带入6就是:
最终 6 条 → 精排池 60 → 每路召回 100 → 搜索深度 300