别急着把文档塞给模型:用 Dify 做一次真正的 RAG 实战

深夜书房里,空白文档被切分成蓝金色光片,经过透明漏斗检索后汇入模型并凝结成答案
不是把整摞文档塞给模型,而是先找到与问题最相关的那几页。

前五集里,我们已经让 Dify 学会了编排:从最小 Chatflow,到分支、变量,再到调用外部 API。流程越来越像应用,但它仍然回答不了一个很现实的问题:如果答案只存在于我们自己的教程、制度或产品手册里,模型从哪里知道?

这一集不继续改造“国家信息小助手”。国家事实更适合来自结构化 API,硬把它套成知识库问答,只会混淆两种数据来源。我们新建一个独立项目:把这个系列已经发布的教程送进知识库,再搭一个“Dify 系列问答助手”。

前情提要:EP5 用 HTTP Request 解决了“数据从外部服务来”的问题。这一集继续追问数据从哪里来,只是对象从公开 API 变成自己的资料。两者都强调可追溯,但 API 返回结构化事实,RAG 检索的是非结构化文档。

# 主题 一句话 状态
1 AI 应用平台通识与选型 看懂平台位置与选型边界 已发布
2 云版搭第一个 Chatflow 应用 Start、LLM、Answer 与发布 已发布
3 给应用装上“分岔路” If/Else 与问题分类器 已发布
4 变量,这个系列真正的地基 会话变量、环境变量与赋值 已发布
5 把外部世界接进来 HTTP Request、Code、参数提取器 已发布
6 知识库与 RAG 实战 文档处理、分段、检索策略与 Rerank ← 你在这里
7 批量处理与循环 Iteration、Loop、列表操作 已发布
8 工具与 Agent 工具市场、ReAct、Function Calling、MCP 已发布
9 从 Demo 到生产:运维篇 版本、日志、标注、可观测性与权限 已发布
10 私有化部署实战 Docker Compose、配置与排错 已发布

RAG 不是“上传文档”三个字

RAG 是 Retrieval-Augmented Generation,通常译作“检索增强生成”。它没有把模型重新训练一遍,也没有让模型永久记住上传的文件。一次问答至少经过四段:

文档摄取与清洗 → 分段与索引 → 根据问题检索 → 把召回片段交给模型生成答案

这四段任何一段出错,最后回答都会出问题。

  • 文档里混着导航、页脚和重复声明,垃圾也会进入索引;
  • 分段太碎,上下文被切断;分段太大,噪声一起进入 Prompt;
  • 检索没有召回正确片段,再强的模型也无米下锅;
  • Prompt 没有限制证据边界,模型仍可能绕过资料自由发挥。

所以,判断一个 RAG 应用不能只问“回答像不像人话”。先看它召回了什么,再看回答有没有忠实使用这些证据。

资料集为什么选自己的五篇教程

教程常用公司制度、产品手册或网上随便找的一份 PDF 做演示,但这些资料不是读者都有,也很难保证版本和版权。这里选择本系列前五篇已公开文章作为资料集,原因很简单:来源明确、可以复现,而且问题天然跨篇。

例如:

  • “会话变量和环境变量有什么区别?”对应 EP4;
  • “HTTP Request 的 Key 应该放在哪里?”会同时命中 EP4 和 EP5;
  • “什么时候用 Chatflow,什么时候用 Workflow?”主要来自 EP1 与 EP2;
  • “怎样用 Iteration 批量处理文件?”目前资料没有讲过,助手应该承认未找到,而不是现场编一段。

资料外问题尤其重要。RAG 的价值不只在于“知道什么”,还在于能够守住“不知道什么”。

创建知识库:先从真实限制开始

我在 Dify 云版新建了“从零开始学 Dify · 系列知识库”,上传 Markdown 教程,并在导入阶段检查分段预览。这个 Sandbox 工作空间当前原生消息额度为 0;高质量索引默认使用的 text-embedding-3-large 在界面中提示不兼容或额度不足,因此这次能够真实落地的是:

  • 分段模式:通用、自定义;
  • 最大分段长度:800 characters;
  • 分段重叠长度:80 characters;
  • 预处理:替换连续空格、换行和制表符;
  • 索引方式:经济;
  • 检索方式:倒排索引;
  • Top K:3。
Dify 知识库文档列表,展示已导入并处于可用状态的教程文档
判断导入成功要回到文档列表看“可用”状态;处理页出现完成提示还不够。

这里必须把边界说清楚:经济索引跑通,不等于向量检索、混合检索或 Rerank 已经跑通。 后三者依赖嵌入与排序模型,本次账号状态无法提供真实成功证据。下面会讲它们如何配置与判断,但不会把配置原理写成实测结论。

分段不是越大越好,也不是越小越准

分段的目标,是让一个块既能独立表达完整意思,又不会携带太多无关内容。

最大长度设为 800,不代表每个块都会刚好 800 字。Dify 会先按分隔符切,再受最大长度约束。如果 Markdown 每一行都换行,而分隔符又设置成单换行,标题、短句和列表项可能被拆成大量小块。界面看起来“块很多”,检索效果却未必更好。

重叠设为 80,是给相邻块保留一小段共同语境。例如定义出现在上一块结尾、使用方法出现在下一块开头时,适量重叠可以减少语义断裂。但重叠过多会让同一句话重复进入多个候选,浪费上下文并放大重复答案。

比较稳妥的调法是:

  1. 先按文章结构选择分隔符;
  2. 用真实问题观察召回片段是否完整;
  3. 只有发现答案跨块断裂,才增加块长度或重叠;
  4. 发现候选块总是夹带大段无关内容,再缩小块。

分段参数不是文档格式的装饰项,而是检索质量的第一层模型。

经济索引、高质量索引和三种检索思路

经济索引会从每个块提取关键词,使用倒排索引检索。它不消耗 embedding tokens,搭建成本低,对“HTTP Request”“变量赋值器”“Top K”这类术语明确的问题很友好。缺点是遇到同义表达时容易漏召回:文档写“凭证”,用户问“密钥放哪”,字面关键词未必重合。

Dify 知识库设置页,经济索引关键词数量为 10,检索方式为倒排索引,Top K 为 3
这张图确认的是本次真正保存并运行的基线:经济索引、每段 10 个关键词、倒排索引、Top K 3;它不是向量检索成功的证据。

高质量索引会为文本块生成向量。向量检索比较语义相似度,能处理措辞不同但意思接近的问题;全文检索重视词项匹配,对专有名词和精确字符串更稳;混合检索把两路结果合并,通常比单一路径更有韧性。

它们不是简单的升级阶梯:

方法 强项 常见盲点
倒排/全文检索 专有名词、代码标识符、精确词组 同义改写
向量检索 自然语言、语义近似 精确编号与稀有词
混合检索 兼顾字面与语义 成本、参数和结果融合更复杂

如果知识库里大量出现接口名、字段名和错误码,不要因为“向量更 AI”就关闭全文检索。生产里的好结果往往来自互补,而不是押注单一算法。

Top K、阈值和 Rerank 分别控制什么

Top K 决定送给下游的候选块数量。太小可能漏证据,太大则把噪声和重复片段一起塞给模型。本文经济索引保持界面默认值 3,适合先观察;它不是适用于所有文档的最佳答案。

相似度阈值解决另一个问题:最低要多像,才允许成为候选。阈值过高会让合理改写召回为空,过低会让资料外问题也勉强捞到“看起来有点像”的片段。

Rerank 则发生在初次召回之后。先由全文、向量或混合检索拿到一批候选,再用排序模型重新判断“哪个片段最能回答当前问题”。它能改善排序,却不能找回初检阶段根本没有召回的文档。

这次账号无法调用可用的嵌入与 Rerank 模型,因此正确做法不是勾上开关截张图,而是把它列为升级实验:补齐模型额度后,用同一组问题对比召回文档、排序、延迟和成本。

Dify 高质量索引设置页,Embedding 模型 text-embedding-3-large 显示额度已用尽,并展示向量、全文与混合检索选项
高质量模式的配置项确实存在,但当前 Embedding 模型明确显示“额度已用尽”。因此本文只介绍下一步怎么测,不宣称向量检索、混合检索或 Rerank 已验证。

召回测试要先于工作流测试

在连接 Chatflow 之前,应先在知识库的“召回测试”里跑问题。这样可以把故障域缩小:

  • 没召回正确块,是清洗、分段、索引或查询的问题;
  • 召回正确但回答错误,才去查 Prompt、上下文引用和模型;
  • 资料外问题仍召回高置信片段,要调阈值、语料或检索策略;
  • 同一段重复出现多次,要检查分段重叠与重复文档。

建议至少准备五类题:单篇定位、跨篇整合、同义改写、精确字段、资料外问题。每次改参数后重跑同一组,才能知道效果是变好还是只是“回答换了个说法”。

这次用“Dify 为什么适合做内部知识问答?”进行实测,系统返回 3 个段落,但第一名是 EP2 中关于 Workflow 与 Chatflow 起点的文字,另外两段分别讨论云版闭环和额度计数。它们都含有相关关键词,却没有直接回答问题。这正好暴露了经济索引的边界:召回到相关词,不等于把最能回答问题的证据排在前面。

Dify 召回测试页,问题为 Dify 为什么适合做内部知识问答,右侧返回 3 个来自 EP2 的片段
真实失败样本比一张“看起来成功”的答案更有教学价值:3 个片段都与 Dify 有关,但排序第一的内容并不能直接支撑问题。

下一轮优化不要先改 Prompt,而应按顺序处理:合并过短分段、补充更直接的知识问答语料、重跑同一测试集;有可用额度后,再对照向量或混合检索与 Rerank。否则模型可能凭自身记忆生成一段正确答案,把错误召回掩盖掉。

问答应用的最小链路

知识库本身只负责管理和检索资料。要交付给用户,还需要一个独立 Chatflow:

这里又遇到一个不能靠截图绕过去的限制:当前 Sandbox 已有 5 个应用,创建弹窗明确显示 5 / 5,因此无法再新建“Dify 系列问答助手”。我没有删除任何既有应用腾位置,也不会把下面的设计写成已经发布成功。此时真正完成并验证的是知识库导入与召回测试;独立 Chatflow 留待释放应用名额后补跑。

Dify 创建空白应用弹窗,构建应用程序数显示 5/5,创建按钮不可用
这张图证明的是阻塞条件,不是创建成功:Sandbox 应用数已到 5/5,创建按钮处于不可用状态。

Start → Knowledge Retrieval → LLM → Answer

Knowledge Retrieval 节点接收 sys.query,连接系列知识库,把候选块输出给 LLM。LLM 的 System Prompt 可以写成:

你是“从零开始学 Dify”系列问答助手。
只根据知识检索节点提供的上下文回答,不使用上下文之外的记忆补充事实。
如果资料不足,明确回复“当前系列资料中未找到”,并说明还缺少哪类信息。
回答结尾列出实际使用的来源文章名;不要编造来源。

这里有三个关键点。

第一,用户问题和检索上下文是两份变量,不要只把问题交给 LLM。第二,“只根据上下文”不能保证绝对不幻觉,仍要靠资料外测试验证。第三,来源名必须来自真实召回元数据,不能让模型凭印象生成一个看似合理的标题。

一次召回成功,离生产可用还很远

教程可以用几篇文章证明链路思路,但生产知识库还要面对:

  • 增量更新和旧版本失效;
  • 文档级权限,避免用户检索到无权查看的片段;
  • 扫描 PDF、表格、图片与复杂版式解析;
  • 多租户数据隔离;
  • embedding、检索、Rerank 与生成的成本和延迟;
  • 召回率、准确率、拒答率以及人工反馈;
  • Prompt 注入和恶意文档内容;
  • 答案引用能否回到原文位置。

一个问题召回到相关片段,只能证明检索路径在某一刻跑通。真正上线前,需要固定测试集、版本记录、失败样本和持续评估。尤其不要只看最终答案:召回错了、模型恰好凭记忆答对,反而是最危险的“假成功”。

这一集真正应该带走什么

RAG 的核心不是让模型“读过更多东西”,而是给每次回答建立一条可检查的数据路径:文档如何被清洗和切分,问题召回了哪些片段,候选怎样排序,模型依据什么生成,又在资料不足时怎样拒答。

这次实操也留下了一个诚实边界:Dify Sandbox 的经济索引与倒排检索能够建立低成本起点;当前账号的嵌入模型额度不支持我们把向量检索、混合检索和 Rerank 宣称为已验证。等模型资源补齐,最有价值的下一步不是换一套漂亮参数,而是拿同一组问题做对照实验。

下一集,我们会让流程一次处理一组数据:区分 Iteration 与 Loop,理解列表怎样进入、每一项怎样运行、失败怎样处理,再完成一次可观察的批量任务。