Dify 高级实验(04):RAG 增强问答——如何让知识库问答更准还能多轮追问?

Dify 高级实验(04):RAG 增强问答——如何让知识库问答更准还能多轮追问?

Dify 高级实验(04):RAG 增强问答——如何让知识库问答更准还能多轮追问?

Dify 实验系列 · 高级 04/10 | 实验编号:DIFY-103-04
基于 Dify 1.16.1 实测(2026-08)

1. 业务场景

先讲一个我们实际遇到的场景。

一家智能硬件公司的技术支持团队,每天在社群里回答用户提问:「X200 手表防水吗?」「充不进电怎么办?」「它的续航怎么样?」。他们搭过一个最基础的问答机器人——知识库检索 → 拼接 → LLM 回答,结果三个问题全露馅:第一个问题检索到了对的文档却答得含糊;第二个问题引用了无关内容;第三个问题直接答非所问,因为机器人根本不记得上一轮聊的是 X200。用户追问两句就不耐烦了:「这 AI 还不如百度」。

我们第一次搭这种问答机器人时,第一反应也是「检索 → 拼接 → 回答,三步不就完了」。真正跑起来才发现——单轮孤立问题能答对,一追问答非所问,这才意识到「能检索」和「答得准、聊得下去」之间隔着一整层工程:检索词要对、召回要全、回答要能溯源、上下文要能记住,缺一环,体验就塌。

这不是个例。任何「靠知识库问答做产品支持」的场景都是这个模式:SaaS 厂商的文档问答、银行的产品手册咨询、设备厂商的售后支持——简单「拼接式 RAG」能答对单轮、孤立的问题,一旦涉及多知识库、多轮追问,检索不全、引用跑偏、上下文丢失三个毛病全冒出来。

2. 场景痛点

这个流程的痛点,在技术支持团队身上体现得最直接:

  • 检索不全面:产品文档和技术 FAQ 分属两个知识库,单库检索只能命中一半,答案经常「差一点」——用户拿着残缺答案去操作,操作失败又回来投诉。
  • 引用无关内容:检索召回一堆相似但无关的片段,LLM 把不相关的内容也揉进回答,用户照着做反而出错——错误的答案比没有答案更伤信任。
  • 追问丢上下文:「它的续航怎么样」里的「它」指代谁,机器人不知道——每轮都当新问题处理,多轮对话体验崩塌,用户被迫把背景从头再说一遍。
  • 回答不可溯源:答案没有来源编号,用户质疑「你凭什么这么说」时无从查证,客服也无法快速复核回答是否正确。

本质上,「能检索」和「答得准、聊得下去」之间隔着一整层工程——RAG 的问题从来不是知识库有没有内容,而是检索质量、上下文管理和溯源这三件事没做好。

3. 方案:为什么是这套增强型 RAG 链路

Dify 工作流里的「Query 改写 → 多路检索 → 合并去重 → 带引用回答 → 对话摘要」链路,正好把这三个毛病逐个治掉。

选它的理由,我们实际对比过:

  • Query 改写解决追问丢上下文:LLM 节点结合对话摘要补全指代——「它」被改写成「智能手表 X200」,检索词对了,召回自然就对了;
  • 并行分支解决检索不全面:产品文档和技术 FAQ 两个知识库同时检索,再合并去重排序,命中面比单库宽一倍;
  • 带引用 + 对话摘要解决溯源与多轮:回答标注 [1][2] 来源编号可查证,每轮滚动更新对话摘要支持多轮追问——这套能力全是平台原生节点拼出来的。

这篇文章我们就用它搭一个「产品支持问答助手」:双知识库并行检索,带来源编号回答,还支持多轮追问。

4. 整体架构

开始:用户输入 sys.query

LLM lm_rewrite:Query 改写(结合对话摘要补全指代)

知识库 kb_docs:产品文档,single 检索 TopK=3

知识库 kb_faq:技术 FAQ,single 检索 TopK=2

代码 cd_merge:合并 + 去重 + 排序 + 截断 → docs_text

LLM lm_answer:带引用标注回答

回复 ans_answer

代码 cd_summary:更新对话摘要

结束:映射回 conversation_summary

链路很清晰:入口收问题 → 改写检索词 → 双库并行检索 → 合并去重 → 带引用回答 → 更新摘要。改写和摘要是这个架构的「记忆器官」——改写让每轮检索都对准用户真实意图,摘要让多轮对话不丢上下文。

5. 模块设计

5.1 Query 改写 lm_rewrite

改写节点引用对话变量(注意是{{#conversation.xxx#}}前缀,不是 start)与{{#sys.query#}}

你是一个检索优化专家。根据用户当前问题和对话历史,生成一个更适合知识库检索的查询。 对话摘要:{{#conversation.conversation_summary#}} 用户当前问题:{{#sys.query#}} 要求: 1. 如果用户追问(如"它的续航怎么样"),补充上下文(如"智能手表X200的续航怎么样") 2. 去掉口语化表达 3. 如果有多个意图,拆成多个查询,用分号隔开 4. 输出仅检索查询文本

5.2 并行检索 kb_docs / kb_faq

两个知识库检索节点共用改写后的文本做 query,检索模式 single、阈值 0.0,TopK 不同(文档 3、FAQ 2):

kb_docs:dataset_ids:[产品文档库]retrieval_mode:single top_k:3 score_threshold:0.0kb_faq:dataset_ids:[技术FAQ库]retrieval_mode:single top_k:2 score_threshold:0.0query_variable_selector:[lm_rewrite,text]# 两节点都用改写后的查询

5.3 合并去重排序 cd_merge

按内容前 80 字符去重、分数降序、超 3000 字符截断前 4 条,并拼成docs_text供 LLM 引用:

defmain(docs_a,docs_b):seen,merged=set(),[]fordocs,sourcein[(docs_a,"product_doc"),(docs_b,"faq")]:fordocindocsor[]:key=(doc.get("content","")or"")[:80]ifkeynotinseen:seen.add(key)merged.append({"content":doc.get("content",""),"title":doc.get("title",""),"score":doc.get("score",0),"source":source})merged.sort(key=lambdax:x.get("score",0),reverse=True)ifsum(len(d["content"])fordinmerged)>3000:merged=merged[:4]docs_text="\n".join("[{}] {}(来源: {})\n{}".format(i+1,d["title"],d["source"],d["content"])fori,dinenumerate(merged))return{"merged_docs":merged,"docs_text":docs_text,"doc_count":len(merged),"total_chars":sum(len(d["content"])fordinmerged)}

5.4 带引用回答 lm_answer

直接引用{{#cd_merge.docs_text#}}(已带 [1][2] 编号的文本),要求关键信息标注来源、找不到就明说:

你是一个产品技术支持。根据提供的文档资料回答用户问题。 参考资料: {{#cd_merge.docs_text#}} 回复要求: 1. 仅基于提供的文档回答,不要自行编造 2. 关键信息后标注来源编号,如 [1][2] 3. 如果提供的信息不足以回答,明确说"提供的文档中未找到相关信息" 4. 如果有对话上下文,结合上下文理解用户的意图

5.5 更新对话摘要 cd_summary → 结束节点

每轮把「用户问 + 回答」追加进摘要并截断(超 1000 保留末尾 800),防止摘要无限膨胀污染上下文;结束节点把updated_summary映射回对话变量conversation_summary,实现跨轮记忆:

end.outputs:-variable:conversation_summaryvalue_selector:[cd_summary,updated_summary]

6. 运行验证

轮次输入期望行为实测
第 1 轮「X200 手表防水吗?」改写后检索产品文档,回答 IP68 防水并标 [1]与预期一致
第 2 轮「充不进电怎么办」检索 FAQ,给出排查步骤并标来源编号与预期一致
第 3 轮「它的续航怎么样」Query 改写补全为「X200 续航」,命中产品文档规格段与预期一致,多轮上下文生效

7. 实战坑

现象修复
追问不补全直接检索「它的续航怎么样」检索不到「智能手表 X200 续航」相关内容Query 改写节点结合conversation_summary补全指代后再检索(dify103_04 实测)
知识库分段不自包含single 检索单段命中,片段缺上下文,LLM 答不全或答错入库分段保证每段自含答案(自包含分段),TopK 检索才有效
两个知识库内容重叠同一信息被检索两次,回答重复引用、上下文被浪费cd_merge 按内容前 80 字符去重 + 分数降序 + 超 3000 字符截断(dify103_04 实测)
对话摘要不截断多轮后摘要无限增长,token 爆炸、旧信息淹没新问题摘要超 1000 保留末尾 800,结束节点映射回conversation_summary(dify103_04 实测)
DSL 导入后直接跑对话变量映射丢失,conversation_summary为空,多轮上下文失效导入后先按导入验证流程跑一轮端到端(查节点/边/变量映射),再正式测试
开启 rerank 精排缺配置检索报 provider 错误,召回直接失败rerank 需三段式 provider 配置(rerank 模型 + API 地址 + 密钥),缺一不可;本实验用 single 检索可不开

8. 实验文档及源码获取

  • 实验文档(完整操作步骤):DIFY-103-04:RAG增强问答系统.md
  • 源码(可直接导入):dify103_04_RAG增强问答系统.yml

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。


下一篇:Dify 高级实验(05):多步推理——如何把复杂问题拆解成子问题逐个击破?

💬 你在这个实验的场景里踩过什么坑?欢迎评论区分享你的实战经验。