我更关心的是建立一套排查顺序:答案不准确时,先判断问题发生在文档、检索还是生成阶段,再调整对应环节。
RAGFlow 深入使用:从文档解析到一次可靠的知识库问答
知识库能够回答问题,只能说明流程跑通了;面对不同文档和不同问法时依然答得稳定,才是真正需要下功夫的地方。这篇继续拆解 RAGFlow 的文档解析、Chunk、召回、排序和生成链路。
在上一篇里,我用 RAGFlow 跑通了第一套知识库问答:上传文档、查看 Chunk、做一次 Retrieval Test,再创建 Chat Assistant。
能问出答案当然让人高兴,但继续换几种问法以后,问题很快就出现了:有的问题答得很好,有的问题只是换了一个说法,系统就找不到原来的内容;有时检索已经命中正确段落,大模型最后却补充了一些资料里没有的结论。
这让我意识到,“系统能回答”和“系统能够稳定、可追溯地回答”是两件事。这篇我想把文档解析、切片、检索和生成拆开,记录一下我是怎样理解和调整这条链路的。
一次回答经历了哪些阶段
RAGFlow 的页面把上传、解析、检索和聊天放在了一起,使用时很方便。但为了调试效果,我还是需要在脑子里把它们分开。
这条链路有一个很现实的特点:前面的错误会一直传到后面。
- 文档解析错了,Chunk 里就没有正确内容;
- Chunk 拆得不合理,检索到的只是半句话;
- 检索没命中,生成模型看不到答案;
- 检索命中了,Prompt 没有限制好,模型仍然可能自由发挥。
所以我不再用“这个模型行不行”来概括所有效果问题。
Chunk 不是把文字平均切开
最简单的切片可以按照字符数或 Token 数截断文本,但真实资料通常带有结构:标题属于后面的段落,表头属于下面的数据,操作步骤之间还有顺序关系。
如果问题是“退款审批前需要检查什么”,机械截断很可能把“适用条件”和“处理动作”放进两个 Chunk。两个片段单独看都不完整,检索和回答都会变得困难。
RAGFlow 提供了多种 Chunk Method,我现在会先根据资料本身选择,而不是所有数据集都沿用 General:
| 文档特点 | 可以优先尝试 | 我关注的内容 |
|---|---|---|
| 普通说明、Markdown、网页文本 | naive / General | 标题、段落和 Token 数 |
| 已整理好的问答对 | qa | 问题和答案不要被拆开 |
| 结构化表格 | table | 表头和数据行关系 |
| 论文或研究报告 | paper | 摘要、章节和引用 |
| 长篇书籍、手册 | book | 章节层级和上下文 |
| 法规、制度 | laws | 条款编号和条文边界 |
| 演示文稿 | presentation | 页面标题、文本框和图像 |
我判断 Chunk 好不好的简单标准
拿出任何一个 Chunk,不依赖前后片段,我能不能大致知道它在讲什么、属于哪份资料、对应哪个章节?
如果连人都看不懂这个孤立片段,Embedding 很难替它补出缺失的结构。
Chunk 大小不是越大越安全
Chunk 太小,语义容易被拆散;Chunk 太大,又会混入很多和问题无关的内容。检索命中一个大 Chunk,并不代表里面每一段都相关,还会占用更多上下文窗口。
我一般从下面几个方向观察,而不是直接照搬一个固定数值:
- 一条完整规则能否放进同一个 Chunk;
- 标题能否和正文一起保留;
- 表格的一行是否带着表头语义;
- 一个 Chunk 是否混入多个不相关主题;
- 引用回原文时能否快速定位。
RAGFlow 允许查看和手工修改解析后的 Chunk,这一点对排查特别有用。遇到少量关键资料时,给 Chunk 增加关键词或问题,也比盲目调全局参数更直接。
先测检索,再测回答
以前我容易直接在聊天框里反复问,然后凭答案好不好判断参数。现在我会先用 Retrieval Test,把大模型暂时放到一边。
我给脱敏资料准备了几类固定问题:
| 类型 | 示例 | 主要验证 |
|---|---|---|
| 精确关键词 | “A100 型号的额定功率是多少?” | 关键词和表格召回 |
| 同义改写 | “设备断电以后如何恢复?” | 语义召回 |
| 流程问题 | “客户申请退款前要完成哪些检查?” | 步骤完整性 |
| 跨文档问题 | “安装完成后,售后登记还需要哪些资料?” | 多资料召回 |
| 无答案问题 | “这款产品在海外的保修期是多少?” | 拒答边界 |
这个顺序能减少很多无效尝试。如果 Retrieval Test 已经找不到正确内容,我不会先换 Chat Model;如果检索结果正确、回答却出现发挥,再去看 Prompt 和生成模型。
我怎样理解几个检索参数
RAGFlow 的检索不是只有一次向量相似度比较。当前接口里可以看到 similarity_threshold、vector_similarity_weight、top_k、rerank_id、keyword 和 metadata_condition 等参数。
相似度阈值
阈值决定一个候选片段至少要“像”到什么程度才有资格进入结果。
- 阈值太高:表达稍有变化就可能召回不到;
- 阈值太低:不相关内容更容易混进来。
对于无答案问题,我会特别关注阈值。系统不应该为了“必须回答”而拿一个勉强相关的片段凑数。
向量相似度权重
向量检索擅长找到语义相近的表达,关键词检索更擅长型号、编号、术语和精确名称。RAGFlow 返回的综合相似度,本身就包含向量相似度和词项相似度。
产品型号、合同条款号这类问题,我不会只依赖语义;用户用自然语言描述故障时,又需要给向量相似度足够空间。
Top-K、最终 Chunk 数量和 Rerank
我会把它们理解为两阶段筛选:
- 先从大范围中召回一批候选;
- 再用更精细的模型重新排序,选出真正进入上下文的片段。
候选太少,正确内容可能第一轮就被漏掉;候选太多,Rerank 和后续生成的耗时、成本都会增加。这里没有脱离数据集的最佳值,只能用固定测试集比较。
Metadata Filter
当知识库里同时存在多个部门、产品线和版本时,仅靠相似度并不够。通过元数据先限制范围,再做检索,通常比在全部资料中搜索更可控。
例如用户已经选择“售后制度 / 2026 版”,就应该把这个条件传到检索层,而不是让模型从新旧制度中自己猜。
一套比较稳的调整顺序
参数很多,但我不会同时修改。否则答案变好以后,也不知道是哪一个调整起了作用。
步骤 1:确认原文里确实有答案
这是最容易被忽略的一步。资料本身没有明确结论时,继续调检索只会浪费时间。
步骤 2:查看解析文本和 Chunk
检查答案是否被正确提取,标题、表头和关键上下文是否还在。
步骤 3:使用 Retrieval Test 保存基线
记录正确片段的排名、相似度和来源,不先看大模型回答。
步骤 4:一次只调一类参数
先处理 Chunk,再比较阈值和向量权重,最后决定是否增加 Rerank。
步骤 5:回到 Chat Assistant
验证回答是否忠于资料、无答案时是否拒答、引用是否准确。
步骤 6:用同一套问题回归
不能只验证刚刚出错的那一道题。一个参数可能改善语义问题,却破坏型号类问题。
我会保留的最小测试记录
| 字段 | 说明 |
|---|---|
| question | 原始问题 |
| expected_source | 预期命中的文档与章节 |
| expected_answer | 资料中的答案要点 |
| retrieved_top_n | 实际召回片段 |
| correct_rank | 正确片段排名 |
| grounded | 回答是否完全有资料依据 |
| citation_ok | 引用是否能回到正确来源 |
| latency | 本次请求耗时 |
它不需要一开始就做成完整评测平台,一张表格也比凭印象调参可靠。
Chat Assistant 还需要约束生成
检索正确以后,Chat Assistant 的 Prompt 决定模型怎样使用这些资料。我会明确告诉它:
- 优先根据检索内容回答;
- 不把模型自身知识伪装成内部制度;
- 资料不足时直接说明不知道;
- 保留关键编号、条件和单位;
- 回答涉及规则时给出引用。
RAGFlow 还可以配置“没有检索到结果时”的统一回复。对于内部专业知识平台,我更倾向于明确拒答,而不是为了聊天流畅让模型继续发挥。
我的基本原则
知识库问答最重要的不是“每次都有答案”,而是有依据时认真回答,没有依据时保持克制。
把知识问答作为 API 使用
完成 Dataset 和 Chat Assistant 配置后,RAGFlow 可以通过 OpenAI-compatible API 对外提供问答能力。
下面保留一个最小调用示例。chat_id 指向已经配置好的 Chat Assistant,reference 用于返回引用片段:
from openai import OpenAI
client = OpenAI(
api_key="ragflow-api-key",
base_url="http://ragflow.example/api/v1/openai/<chat_id>/chat",
)
response = client.chat.completions.create(
model="model",
messages=[
{"role": "user", "content": "退款申请提交前需要核对哪些信息?"}
],
stream=False,
extra_body={
"reference": True,
"reference_metadata": {
"include": True,
"fields": ["source", "version"],
},
},
)
message = response.choices[0].message
print(message.content)
print(getattr(message, "reference", None))我会先用这个接口验证三个结果:
content是否是预期答案;reference是否包含正确 Chunk;- 多轮
messages传入后,追问是否仍然落在正确知识范围。
RAGFlow 能问答,但不必承担所有界面
做到这里,RAGFlow 已经完成了我最关心的部分:文档管理、解析、检索、回答和引用。它自带的页面也很适合管理员检查 Chunk、调试 Retrieval 和配置 Assistant。
但公司内部真正面向员工的 AI Chat,还会有另一组需求:
- 统一的聊天入口和品牌呈现;
- 普通问答、专业知识助手等多个模型;
- 用户、群组和模型可见范围;
- 更完整的聊天记录和多轮体验;
- 后续接入工具、联网搜索或其他内部能力。
如果直接深度修改 RAGFlow 前端,这些工作当然也能做,但会让知识引擎和门户界面绑在一起。以后升级 RAGFlow,还要持续处理自己修改的前端代码。
所以我们最后选择了另一条路:让 RAGFlow 专心做知识检索后端,再用 Open WebUI 作为统一的外部呈现。
下一篇,我会先单独认识 Open WebUI,看看它为什么适合用来搭建公司内部的 AI Chat,以及“不希望业务数据直接访问外网”时,架构上还需要做哪些事情。