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. 整体架构
链路很清晰:入口收问题 → 改写检索词 → 双库并行检索 → 合并去重 → 带引用回答 → 更新摘要。改写和摘要是这个架构的「记忆器官」——改写让每轮检索都对准用户真实意图,摘要让多轮对话不丢上下文。
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):多步推理——如何把复杂问题拆解成子问题逐个击破?
💬 你在这个实验的场景里踩过什么坑?欢迎评论区分享你的实战经验。