Skip to content

RAGFlow 深入使用:从文档解析到一次可靠的知识库问答

约 3303 字大约 11 分钟

AIRAGRAGFlow检索

2025-05-12

知识库能够回答问题,只能说明流程跑通了;面对不同文档和不同问法时依然答得稳定,才是真正需要下功夫的地方。这篇继续拆解 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_thresholdvector_similarity_weighttop_krerank_idkeywordmetadata_condition 等参数。

相似度阈值

阈值决定一个候选片段至少要“像”到什么程度才有资格进入结果。

  • 阈值太高:表达稍有变化就可能召回不到;
  • 阈值太低:不相关内容更容易混进来。

对于无答案问题,我会特别关注阈值。系统不应该为了“必须回答”而拿一个勉强相关的片段凑数。

向量相似度权重

向量检索擅长找到语义相近的表达,关键词检索更擅长型号、编号、术语和精确名称。RAGFlow 返回的综合相似度,本身就包含向量相似度和词项相似度。

产品型号、合同条款号这类问题,我不会只依赖语义;用户用自然语言描述故障时,又需要给向量相似度足够空间。

Top-K、最终 Chunk 数量和 Rerank

我会把它们理解为两阶段筛选:

  1. 先从大范围中召回一批候选;
  2. 再用更精细的模型重新排序,选出真正进入上下文的片段。

候选太少,正确内容可能第一轮就被漏掉;候选太多,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,以及“不希望业务数据直接访问外网”时,架构上还需要做哪些事情。

相关资料