简介ragflow-main 是一个基于流图Flow Graph的大模型代码仓库源自 GitHub 开源项目 Infiniflow/ragflow面向深度学习和自然语言处理方向的开发者、研究者与进阶学习者用于理解并实践大规模神经网络的构建、训练和部署。资源共 564 个文件以 Python 脚本核心算法与训练逻辑、TypeScript/TSX 前端代码交互界面、LESS/SVG 样式图标以及 YAML、conf、env 等配置文件为主压缩包整体约 33.52MB其中还包含 Dockerfile、CSV 数据样本与 Markdown 说明文档便于快速定位工程结构。通过源码可系统研读流图计算框架、动态计算图、模型与数据并行、GPU/CPU 异构调度、自动微分、模型保存与加载等关键模块项目内置的实验管理和日志记录工具也有助于复现训练流程、对比调优效果。目前已有 2023 人学习下载适合想深入掌握大模型底层原理、从事框架二次开发或研究分布式训练的进阶读者。1. 从大模型幻觉到可用知识库RAGFlow 到底解决了什么做过大模型落地的人都绕不开一个场景把公司内部文档交给大模型期望它“懂业务”结果模型一本正经地编造出根本不存在的流程和结论。这不是模型不够强而是传统的切片向量检索方案在遇到表格、复杂版式、长文档时召回质量根本撑不住生成环节。RAGFlow 这个项目给我的第一印象是把 RAG 从“能用”拉到了“可信”的层级——它内置了深度文档解析、混合检索、可编排的 Agent 工作流跑起来之后明显感觉到回答开始“有出处”了。这套项目适合两类人一类是正在做企业知识库、想快速搭建一套带引用溯源问答系统的开发者另一类是已经跑通过基础 RAG但被 PDF 表格解析、检索精度、多轮对话效果折磨过的熟手。接下来我会从架构、核心代码、部署、踩坑到改造完整拆一遍这个被我反复部署过多次的开源项目。2. 系统架构与模块设计一个 RAG 引擎的骨架长什么样2.1 整体架构从文档上传到答案生成的四层链路RAGFlow 的整体架构可以按数据流向拆成四层理解这四层后续看代码和改代码都会顺很多。第一层是接入层负责处理 HTTP 请求、鉴权和会话管理这一层主要由 gateway 和 API 服务组成。第二层是文档解析层也就是 RAGFlow 最有辨识度的 DeepDoc 模块它负责把 PDF、Word、Excel、PPT 等格式转换成结构化的 Chunk而不是简单按字符数硬切。第三层是检索层包含向量检索、全文检索基于 Elasticsearch 或 Infinity、重排序三个环节用 RRFReciprocal Rank Fusion把多路结果融合起来。第四层是生成层负责把检索到的 Chunk 和用户问题组装成 Prompt调用大模型生成答案并把引用来源一并返回。有意思的是RAGFlow 的 gateway 是用 Java 写的而核心解析和检索逻辑在 Python 侧两层之间通过 Thrift 通信。我第一次拆这个项目时就觉得这层设计有点“异类”但跑起来之后发现它其实是很务实的取舍Java 侧扛高并发请求接入Python 侧保留算法迭代的灵活性。前端则是 React 系的单页应用提供了可视化的知识库管理、文档标注和对话调试界面。2.2 目录结构快速定位你要改的代码ragflow-main/ ├── api/ # Python 后端 API 服务 │ ├── apps/ # 各功能路由如对话、知识库、文档 │ ├── db/ # MySQL 数据模型与初始化 │ ├── settings.py # 全局配置入口 ├── deepdoc/ # 文档深度解析核心 │ ├── chunk/ # 分块策略按文档类型分发 │ ├── recognize/ # OCR、版面分析、表格识别 ├── rag/ # 检索与生成核心 │ ├── prompt/ # Prompt 模板管理 │ ├── rag_server.py # Thrift 服务端 │ ├── utils/ # 排序、融合、重排 ├── docker/ # 部署编排文件 ├── web/ # React 前端 │ ├── src/ # 页面与组件其中deepdoc/chunk和rag/utils是我觉得阅读优先级最高的两个目录。前者决定了文档解析的质量下限后者决定了检索召回的上限。想改分块逻辑去deepdoc/chunk想调检索融合策略去rag/utils。2.3 文档解析链路DeepDoc 是如何把 PDF 变成 Chunk 的DeepDoc 的处理流程可以概括成“版面识别 → 内容抽取 → 结构化分块”三步。它先把 PDF 每一页渲染成图像通过版面分析模型识别出标题、段落、表格、图片区域然后对不同区域走不同的抽取逻辑表格用专门的表格结构识别模型还原成行列表格正文段落按阅读顺序重组OCR 兜底处理扫描件。这个设计直接解决了传统 RAG 的一个痛点按固定 token 数切分文档会把表格拦腰截断、把标题和正文拆散。DeepDoc 抽出来的 Chunk 自带版面语义比如一个表格 Chunk 会把整个表格的行列结构完整保留下来这样向量化之后检索到的内容才是“完整的一块”而不是“半行文字”。我实测过一份带复杂表格的 PDF用 PyPDF 粗暴抽取再按 500 token 切分检索命中率惨不忍睹走了 DeepDoc 之后同样的查询能直接定位到对应表格所在页。这就是解析层和检索层互相成就的关系。3. 核心代码拆解一个查询从进入到返回的完整链路3.1 分块策略从固定窗口到版面感知先看一个 DeepDoc 里deepdoc/chunk的简化实现理解它如何根据文档类型分发到不同分块器# deepdoc/chunk/__init__.py 简化示意 def chunk(parsed_content, chunk_type): 分块入口根据 chunk_type 分发到对应策略 if chunk_type paper: return chunk_paper(parsed_content) elif chunk_type presentation: return chunk_presentation(parsed_content) elif chunk_type table: return chunk_table(parsed_content) else: return chunk_general(parsed_content)这段代码的逻辑很直白parsed_content是 DeepDoc 识别后的结构化内容可能是带版面坐标的文本块列表可能是表格结构对象。chunk_type在创建知识库时由用户指定比如上传一份论文就选 paper上传一份产品手册就选 manual。分发机制让每种文档类型都能走最合适的分块逻辑而不是一刀切。以论文分块chunk_paper为例它的典型做法是先按标题结构切出章节再对章节内的段落做语义连贯性检查属于同一论点下的多个段落会合并成一个 Chunk。这样做的好处是 Chunk 与 Chunk 之间边界清晰不会出现一个完整论证被拆到两个 Chunk 里的情况。3.2 混合检索与重排BM25、向量、RRF 是怎么配合的检索阶段是 RAGFlow 召回质量的关键。它默认同时跑向量检索和全文检索再把结果做 RRF 融合最后用 Reranker 精排。下面这段代码是我从rag/utils里整理出来的融合逻辑核心def rrf_fusion(ranked_lists, k60): 多路检索结果融合RRF 算法 :param ranked_lists: 各路检索返回的文档 id 排名列表 :param k: 平滑系数控制融合后分数的分布 :return: 按融合分数降序排列的文档 id 列表 scores {} for rank_list in ranked_lists: for rank, doc_id in enumerate(rank_list): # 每路的排名越靠前贡献的分数越高 scores[doc_id] scores.get(doc_id, 0) 1.0 / (k rank 1) ranked sorted(scores.items(), keylambda x: x[1], reverseTrue) return [doc_id for doc_id, _ in ranked]这里的ranked_lists是两路结果的文档排名列表一路来自向量检索如 Elasticsearch 的 knn 查询另一路来自全文检索BM25。RRF 的核心思想是不比较不同检索路线的原始分数因为它们分布不一致直接相加没有意义而是看每个文档在各路线中的排名位次排名越靠前融合分越高。k是平滑系数测试下来 k60 是比较通用的值调小会让前列文档的优势更突出。融合后的结果会送进 Reranker 做精排。Reranker 通常是一个 Cross-Encoder 模型它把用户问题和每个 Chunk 拼接成一对输入输出相关性分数。这一环节能把 RRF 选出来的前 N 个结果重新排序显著提升排在第一位的准确率。在我实际测试中加了 Reranker 之后首答命中的概率提升非常明显。3.3 Agent 编排让 RAG 不止于一问一答RAGFlow 的另一个特征是内置了 Agent 编排能力。它把知识库检索、Web 搜索、问题拆解、答案生成都封装成可拖拽的组件用户可以在界面里编排一条工作流比如先判断问题是否涉及内部知识再决定走知识库还是走搜索引擎。# rag/agent 中一个简化的工作流节点逻辑 class RetrieveNode: def __init__(self, kb_ids, top_k10): self.kb_ids kb_ids self.top_k top_k def run(self, question): # 同时执行向量检索和全文检索 vector_hits self.search_vector(question, self.kb_ids, self.top_k) bm25_hits self.search_bm25(question, self.kb_ids, self.top_k) fused rrf_fusion([vector_hits, bm25_hits]) return fusedRetrieveNode这个节点把 3.2 节的融合逻辑封装成了一个可复用的工作流组件。kb_ids指定检索哪些知识库top_k控制每路检索返回的候选数量。值得留意的是Agent 场景下的top_k通常比单轮问答要大一些因为多轮对话里 Agent 可能还需要从候选结果中做二次筛选。我一般调 10~15太小容易漏召回太大对后续重排压力大。4. 部署与配置实践从裸机到一套可用的知识库问答4.1 Docker 部署三个命令把整套环境拉起来RAGFlow 官方推荐用 Docker Compose 部署这也是我最推荐的路径因为它的依赖组件很多手动部署会踩不少环境坑。项目根目录下的docker/目录里已经写好了编排文件主要包含 MySQL、Redis、MinIO、Elasticsearch或 Infinity、后端服务、前端服务几个容器。# 克隆源码后进入 docker 目录 cd ragflow-main/docker # 复制环境变量模板 cp .env.example .env # 启动所有服务首次启动会自动拉取镜像 docker compose up -d这里建议先打开.env看一眼里面有几个关键项需要改。SVR_HTTP_PORT是对外服务的 HTTP 端口默认 9380。MYSQL_PASSWORD、MINIO_PASSWORD这类密码项建议换成强密码尤其是如果服务器在公网可达。ELASTICSEARCH_PORT保持默认即可。改完这些再启动。启动之后浏览器访问http://服务器IP:9380首次会引导创建管理员账号。初始化完成后在界面上创建一个知识库上传文档选好 chunk 方式就能开始测试问答了。整个流程大概十分钟可以跑通。4.2 Embedding 与模型配置接入国产大模型的方式RAGFlow 的模型配置在界面里的“模型提供商”页面不同的大模型 API 接入方式大同小异。以 OpenAI 兼容接口为例配置内容包括 API 地址、API Key、模型的 chat 名称和 embedding 名称。模型提供商类型: openai-compatible API 地址: https://your-gateway.example.com/v1 API Key: sk-xxxxxxxx Chat 模型: your-model-name Embedding 模型: your-embedding-model有一个细节需要注意RAGFlow 的向量维度默认是 1024 维这对应它官方推荐的 embedding 模型。如果你换用了其他 embedding 模型维度可能不同比如有些常用模型是 768 维或 1536 维这时需要同步修改服务的向量维度配置否则创建知识库时就会报维度不一致的错误。这个问题在社区里不少人遇到过属于典型的“模型与索引维度不匹配”陷阱。4.3 性能调优内存占用与检索延迟的取舍影响 RAGFlow 性能的主要有四个地方文档解析并发数、Embedding 并发数、Elasticsearch 索引分片数、Reranker 的模型大小。文档解析和 embedding 属于计算密集型操作并发数设置过高会导致内存暴涨我一般在 4C16G 的机器上把并发控制在 4 以内。Elasticsearch 方面如果创建的索引特别多每个索引默认分片数会导致资源碎片化我习惯把index.number_of_shards调成 1对中小规模知识库完全够用。Reranker 模型如果部署的是 base 尺寸占内存约 1~2G如果你用 CPU 推理单次检索延迟会增加 200~500ms建议在有 GPU 的环境下开启 Reranker否则可以先关掉精排只看混合检索的效果。5. 避坑与排查连续部署多次之后攒下的血泪经验5.1 服务起来但前端页面打不开现象docker compose up 之后所有容器都显示 running但浏览器访问 9380 端口一直超时或连接被拒。原因大多数情况下不是服务挂了而是服务还在启动过程中尤其是 Elasticsearch 容器首次启动需要初始化索引模板耗时较长。另一个可能是.env里SVR_HTTP_PORT被本机其他进程占用docker 默默映射到了随机端口。解决先执行docker compose logs -f ragflow-server看看后端日志有没有输出Uvicorn running on之类的字样等日志稳定后再访问。同时用docker compose ps确认端口映射是否正确必要时直接查看容器 IP 加端口来排除映射问题。我排查过几次之后养成了习惯先用 curl 探活后端接口再考虑前端的问题。5.2 上传 PDF 后解析一直处于 pending现象上传的 PDF 文档状态一直显示等待解析点击强制解析也没反应日志里频繁出现超时错误。原因这是典型的资源不足问题。文档解析依赖 GPU 或 CPU 跑版面模型在 CPU-only 的机器上解析一个几十页的 PDF 可能要几分钟到十几分钟。如果同时塞了多个文档任务队列会排满后续文档全部等待。解决把任务并发数调低到 1 或 2逐份上传文档。另外确认容器内能访问到模型文件存放目录如果模型没有下载成功解析任务会一直卡住。我通常先上传一个小文件测通流程再批量上传大文件避免一上来就把队列堵死。5.3 问答回答时引用来源为空或答非所问现象对话能返回内容但 C 位引用区域一片空白或者答案内容明显没有依据文档而是在自由发挥。原因引用为空通常是检索阶段没召回任何 Chunk这可能是文档还没完成解析入索引或者检索的关键词与文档内容差异太大。答非所问则往往是 Prompt 里对模型的约束不够模型没有强制“只能依据知识库内容回答”。解决进入知识库的测试页面用同样的提问方式直接检索看能不能召回文档。如果召不回检查文档状态是否为“已完成”。如果召回正常通常调整 Prompt 模板加入“如果知识库中没有相关信息直接说明不知道”这类约束就能改善。这套排查路径我走过很多遍先检索后生成一环一环找问题。5.4 Elasticsearch 集群状态频繁变黄现象Elasticsearch 容器运行正常但分片状态是 yellow新建索引偶尔失败。原因RAGFlow 默认的 ES 是单节点部署单节点情况下副本无法分配状态一直是 yellow这其实不影响单机使用。但如果磁盘空间不足ES 会进入只读模式导致索引无法创建。解决如果只是 yellow 状态且能正常读写可以忽略。如果索引创建失败登录 ES 容器执行curl localhost:9200/_cat/allocation?v看磁盘水位清理多余日志或无用的旧索引就能恢复。磁盘水位超过 85% 时 ES 会自我保护性只读这是最常见的新索引失败原因清理出空间后手动执行解锁即可恢复写入。5.5 修改配置后不生效现象改完.env里的端口或者模型地址重启容器后发现还是旧配置。原因docker compose 的环境变量是在容器创建时注入的直接docker compose restart不会重建容器环境变量自然不更新。解决改了.env之后执行docker compose up -d --force-recreate强制重建容器。举一反三凡是改了端口、密码这类环境变量都需要重建而不是重启。这个细节第一次遇到时大概率会困扰一阵子记住“restart 不重新读取 .env”这一点就够了。6. 二次开发动手加一个自定义分块策略我自己最常做的二次开发就是给 DeepDoc 加自定义分块器。假设你拿到一批公司内部的制度文档它们的结构很统一标题 若干条款条款之间没有明显的段落只有换行符隔离。默认的分块逻辑会把整个文档切成一整块检索效果很差。这时候写一个简单的规则分块器就很有价值。# 自定义分块按第X条切分制度文档 import re def chunk_regulation(text): 按条款切分制度文本 :param text: 解析后的纯文本 :return: 条款块列表 pattern re.compile(r(第[一二三四五六七八九十\d]条)) matches list(pattern.finditer(text)) chunks [] for i, match in enumerate(matches): start match.start() end matches[i 1].start() if i 1 len(matches) else len(text) # 去掉条款编号本身的冗余信息保留条款正文 chunks.append({ keyword: match.group(1), content: text[start:end].strip() }) return chunks这个分块器的核心在于re.finditer找出了所有条款编号的位置然后把相邻编号之间的文本划为一个 Chunk。keyword字段保留了条款编号后续在检索时可以把keyword作为元数据展示用户一眼就能看到答案来自哪一条。接入方式是把它注册进 3.1 节的分发函数里给知识库新增一个regulation类型即可。做这种改造时我有几个习惯先在本地用几份真实文档做离线测试统计每个 Chunk 的长度分布确认没有过长或过短的异常块然后跑一圈检索测试对比改造前后同一批问题的召回命中率最后再接入界面验证。那一次改造最大的教训是不要只盯着分块代码本身还要检查 Prompt 模板里是否把keyword字段传给了模型否则即使检索到了条款编号生成的答案里也不会展示出处等于白改。从那以后我每次改完分块器都会强制走一遍“检索 → 生成 → 引用展示”的完整链路验证。希望这套拆解能帮你在 RAGFlow 上少走一些弯路。本文还有配套的精品资源点击获取