📚前言
📒FDE系列内容总纲:
🚄前置课程列表:
见文档结尾附录。
🚀阶段3·Day 72:RAGFlow 平台 --- 端到端知识库
FDE 学习系列教程 · 第三阶段 · 第 11 周 · Day 2 预计时长:3 小时 | 难度:★★★☆☆ | 前置知识:Day 61-71(RAG 链路、Qdrant 混合检索、解析与分块、权限过滤、查询改写) 对标大纲课时:3.2.9 高级 RAG ------ 工具条 RAGFlow / LlamaIndex,实践项"用 RAGFlow 搭建端到端知识库"
📌 一句话目标 :用 Docker Compose 拉起 RAGFlow,把同一份manual_a3手册在平台上重做一遍知识库(建库 → 解析 → 分块可视化 → 检索对照),然后站在 FDE 立场算清一笔账:什么场景用平台、什么场景必须自研、什么场景两者混合。
🧑🤝🧑 开场:信息化主任的一句话,问到了 FDE 的命门
Day 71 收工后,你把 Recall@5 从 0.417 拉到 0.917,兴冲冲地去给客户做第二次演示。演示很顺利,散会时客户的信息化主任把你拉住,问了一个跟技术毫无关系的问题:
"这套东西,我们自己的运维能接管吗?"
你愣了一下,开始解释:"可以,就是 Qdrant 一个容器、BGE-M3 一个模型、十几个 Python 文件、一堆 pip 依赖......"说到一半你自己停住了。因为你脑子里快速过了一遍交接清单:
交接清单(自研方案的真实长度):
□ docker-compose.yml Qdrant + 可选 MinIO
□ requirements.txt openai / qdrant-client / FlagEmbedding / rank_bm25
/ jieba / fastapi / pydantic / httpx / langfuse
□ kb/ 目录 12 个 py 文件 qdrant_store / embed / bm25_index / hybrid
/ reranker / pipeline / cite / authz / audit
/ rewrite / hyde / advanced / api
□ config/acl.yml 谁配?客户运维看得懂吗?
□ config/glossary.json 还好,这个他们能改
□ BGE-M3 模型 2.2GB 客户内网没外网怎么下?
□ 灌库脚本 + 增量更新逻辑 改了一页手册怎么只更新那一页?
□ 一套没人会用的评测脚本 改了参数怎么知道变好还是变坏?
─────────────────────────────────────────────────
结论:能接管,但需要一个懂 Python 的人全职维护。客户没有这种人。
这就是今天要解决的问题。第 9-11 周你写了三周代码,今天要看清楚:这些代码里,有多少是"每个客户都要重写的",有多少是"平台已经替你写好的"。
第二阶段:设备告警工单闭环(FastAPI + MySQL + Docker + 飞书推送)
↓
第 7-8 周:v0.1 智能工单助手(Day 60 冻结)
↓
第 9-10 周:知识库问答 v0.2(自研代码方案)BGE-M3 + Qdrant + 混合检索 + 溯源 + 权限
↓
Day 71:查询侧升级(改写 / HyDE / Multi-Query / 自判)
↓
Day 72(今天)🆕:同一件事用【平台】再做一遍 → 对比 → 选型结论
↓
Day 75:v0.2 冻结(用哪个方案交付,今天定)→ Day 85 v0.3 → Day 100 v1.0
今天七件事:① FDE 选型的三层判断(什么时候不该 自己写代码);② Docker Compose 拉起 RAGFlow(含 vm.max_map_count 这个必踩的坑);③ 搞懂 RAGFlow 在替你做哪四件事(DeepDoc 解析 / 模板分块 / 混合检索 / 引用快照);④ 建库、上传、解析、看分块;⑤ 检索对照:同一问题在平台和自研上的结果差异;⑥ 平台 vs 自研的成本账与决策树;⑦ 混合方案:用平台做解析,用自研做检索与权限。
💡 今天的关键词不是"换工具",是"知道自己写的每一行代码值不值"。 一个成熟的 FDE 不是"什么都能自己写"的人,而是**"能准确判断哪些东西不该自己写"**的人。前者是能力,后者是判断力------后者更贵。
📖 一、FDE 选型:什么时候不该自己写代码
1.1 三层判断法
面对任何一个"我要不要自己实现"的问题,按三层问下去:
L1 这件事是不是【每家客户都一样】?
是 → 不该自己写。每家都一样的东西,早就有开源/商业方案了。
例:PDF 解析、分块、向量库、混合检索、Web 界面、用户管理
否 → 继续问 L2
L2 这件事是不是【我们公司的差异化竞争力】?
是 → 必须自己写,而且要写深。
例:设备告警工单的业务闭环、跟客户 MES 系统的对接、行业术语表
否 → 继续问 L3
L3 自己写的【长期维护成本】谁承担?
客户没有人维护 → 用平台,让平台厂商去维护
我们自己有交付团队 → 可以自研,但要有版本管理和回归测试
把这三套到你的 v0.2 上:
| 环节 | L1 每家都一样? | L2 差异化? | 结论 |
|---|---|---|---|
| PDF/Word 解析 | ✅ 完全一样 | ❌ | 用平台 / MinerU |
| 文本分块 | ✅ 基本一样 | 微调有价值 | 用平台,保留手改能力 |
| Embedding 模型 | ✅ | ❌ | 用平台内置的 BGE |
| 向量库与混合检索 | ✅ | ❌ | 用平台内置 |
| Web 界面 / 用户管理 | ✅ | ❌ | 用平台 |
| 引用溯源展示 | ✅ | ❌ | 用平台(引用快照很成熟) |
| 查询改写 / 术语表 | 部分 | ⚠️ 术语表是差异化 | 术语表自己维护,改写可用平台 |
| 块级权限 acl | ❌ 各家权限模型不同 | ✅ | ⚠️ 必须自己写(见 6.3) |
| 与工单系统联动 | ❌ | ✅ 核心竞争力 | 必须自己写 |
| 飞书/企微推送 | ❌ | ✅ | 必须自己写 |
⭐ 划一条线就清楚了:
【资料处理】这一半 → 平台。每家客户都一样,平台做得比你好,还带界面。
【业务集成】那一半 → 自研。这是你收钱的理由,平台帮不上忙。
今天的全部价值,就是学会把这条线画在哪。
1.2 但平台方案有三个真实的代价
别急着倒向平台。FDE 踩过的坑,比官方文档写的多:
| 代价 | 具体表现 | 什么时候会致命 |
|---|---|---|
| ① 可控性下降 | 分块策略、检索公式、Prompt 都在平台里,改不动或只能改一部分 | 客户要求"按我们行业的规程分块" |
| ② 黑盒排错 | 答错了,你不知道是哪一步错了 | 客户追问"为什么这条没召回" |
| ③ 资源与运维 | RAGFlow 全栈要 16GB 内存 + 50GB 磁盘,含 ES/MySQL/Redis/MinIO 四个依赖 | 客户只给 8GB 内存的虚拟机 |
⚠️ 最真实的代价其实是第 ③条:
自研方案:Qdrant 一个容器,2GB 内存能跑。
RAGFlow:MySQL + Redis + MinIO + Elasticsearch/Infinity + ragflow-server,
官方要求 16GB 内存、50GB 磁盘。
在客户现场,"这台机器只有 8G 内存" 是常态,不是例外。
→ 所以今天的结论不会是"全用平台",而是第 6 节的【混合方案】。
🖥️ 二、Docker Compose 部署 RAGFlow
实操步骤 1:核对前置条件(别急着拉镜像)
# ① CPU / 内存 / 磁盘(官方要求:≥4核 / ≥16GB / ≥50GB)
nproc && free -g && df -h /
# ② Docker 与 Compose 版本(官方要求:Docker ≥24.0.0、Compose ≥v2.26.1)
docker --version && docker compose version
# ③ ⭐ Linux / WSL2 必须检查这一项(官方文档明确要求 ≥262144)
sysctl vm.max_map_count
⚠️ vm.max_map_count 是 RAGFlow 部署【第一大坑】。
它决定一个进程能有多少个内存映射区。Elasticsearch / Infinity 这类
文档引擎会大量使用 mmap,默认值(通常 65530)根本不够,
表现是:容器起来了,但 ES 反复 OOM 重启,docker ps 一闪一闪。
Linux 临时生效:sudo sysctl -w vm.max_map_count=262144
Linux 永久生效:在 /etc/sysctl.conf 里加一行 vm.max_map_count=262144
WSL2 :在 Windows 用户目录建 .wslconfig,写
[wsl2]
kernelCommandLine = sysctl.vm.max_map_count=262144
然后 wsl --shutdown 重启
Windows / Mac(Docker Desktop):走的是虚拟机,需进虚拟机内设置;
具体方式随 Docker Desktop 版本变化,【以官方文档为准】
官方文档:https://ragflow.io/docs/dev/
📌 部署前先读一眼官方"前提条件"页面。 RAGFlow 迭代很快(版本号、compose 文件、默认文档引擎都在变),本教程给的是写稿时核实过的官方命令 ,但你动手那天应该先打开 https://ragflow.io/docs/dev/ 确认一遍。FDE 的基本功之一:不拿三个月前记得的命令去客户现场执行。
实操步骤 2:拉代码并起服务
# ① 克隆官方仓库
git clone https://github.com/infiniflow/ragflow.git
cd ragflow/docker
# ② 先看一眼 .env,确认镜像版本(写稿时官方示例为 v0.19.1 / v0.19.1-slim)
cat .env | grep -E "RAGFLOW_IMAGE|SVR_HTTP_PORT|DOC_ENGINE"
.env 里几个关键变量(实际取值以你拉到的版本为准):
| 变量 | 作用 | 说明 |
|---|---|---|
RAGFLOW_IMAGE |
镜像版本 | 完整版内置 embedding 模型,镜像约 9GB;-slim 精简版约 2GB,需自行配置 embedding 模型 |
SVR_WEB_HTTP_PORT |
Web 端口 | 官方默认 80 |
SVR_HTTP_PORT |
HTTP API 端口 | 默认 9380 |
DOC_ENGINE |
文档引擎 | 官方站点写"默认 Elasticsearch",部分版本 .env 默认 infinity;以你拉到的 .env 注释为准 |
REGISTER_ENABLED |
是否开放注册 | 生产环境建议设为 0 |
# ③ 起基础服务(MySQL / Redis / MinIO / 文档引擎)
# 注意:不同版本的 compose 文件划分不同 ------ 较新版本可直接起主 compose,
# 若你拉到的仓库里存在 docker-compose-base.yml,就先起它再起主的。
docker compose -f docker-compose-base.yml up -d
docker compose -f docker-compose-base.yml ps # 等四个都 healthy
# ④ 起 RAGFlow 主服务
docker compose -f docker-compose.yml up -d
# ⑤ 盯日志,看到下面的 ASCII 大字 + "Running on all addresses" 才算真起来
docker logs -f ragflow-server
启动成功的标志(官方 README 里的那段 ASCII Logo):
____ ___ ______ ______ __
/ __ \ / | / ____// ____// /____ _ __
/ /_/ // /| | / / __ / /_ / // __ \| | /| / /
/ _, _// ___ |/ /_/ // __/ / // /_/ /| |/ |/ /
/_/ |_|/_/ |_|\____//_/ /_/ \____/ |__/|__/
* Running on all addresses (0.0.0.0)
⚠️ 官方明确提醒:没看到这段输出就去登录,浏览器会报"network anormal / 网络异常"。 第一次启动要拉 9GB 镜像 + 初始化文档引擎,慢是正常的(5-15 分钟)。这段时间不要反复
docker compose restart------ES 在初始化索引,重启会让它从头再来。
实操步骤 3:登录并改掉默认密码
浏览器打开 http://localhost(若改过 SVR_WEB_HTTP_PORT 就用你改的端口)。
默认管理员账号:admin@ragflow.io
初始密码怎么来的(官方最新文档):
· 若设置了环境变量 ADMIN_DEFAULT_PASSWORD(或 DEFAULT_SUPERUSER_PASSWORD),用它
· 否则会在 admin server 首次启动时【随机生成】,并写入
logs/admin_bootstrap_password.txt(权限 0600)
Docker 部署请到 docker/ragflow-logs 卷里找这个文件
⚠️ 早期一些版本/第三方文档里写的是默认密码 admin ------ 版本差异较大,
一切以 https://ragflow.io/docs/ 当前版本文档为准。
# 拿随机密码(Docker 部署)
docker exec -it ragflow-server sh -c "cat /ragflow/logs/admin_bootstrap_password.txt"
🔒 登录进去第一件事就是改密码。 另外 RAGFlow 还有一个 Admin UI(地址后加
/admin,如http://localhost/admin),能做服务健康检查、用户管理、角色权限、系统设置------这个界面只应该暴露给可信管理员,别放在公网。
实操步骤 4:配 LLM 与 Embedding 模型
RAGFlow 是"模型无关"的,聊天模型和 Embedding 模型都要自己配。按系列硬约束,聊天模型用 DeepSeek:
操作路径(Web 界面):
右上角头像 → 模型提供商(Model Providers)→ 选 OpenAI-API-compatible
· 模型类型:chat
· 模型名称:deepseek-chat
· Base URL:https://api.deepseek.com
· API Key :填你的 DEEPSEEK_API_KEY(从 .env 读,别手抄)
→ 设为默认聊天模型
Embedding 模型:
若你用的是【完整版镜像】(v0.19.1,约 9GB),内置了 BGE 系列,直接选一个即可
若你用的是【slim 版镜像】,必须自己配 ------ 推荐 BAAI/bge-m3(Day62 用的同一个)
⚠️ 具体可选项随版本变化,以界面上能选到的为准。
# 顺手确认服务健康(也可在 Admin UI 的"服务状态"页看)
curl -s http://localhost:9380/api/v1/system/version | head -c 200
docker compose -f docker-compose.yml ps
💡 一个现实提醒:
RAGFlow 的【知识图谱】分块方式(chunk_method="knowledge_graph")会调用 LLM
抽取实体关系,官方文档明确标注"会消耗大量 Token"。
客户的手册有 300 页时,跑一次知识图谱分块可能烧掉几十块人民币。
默认用 naive / book,知识图谱只在小范围试点。
📖 三、RAGFlow 在替你做哪四件事
部署是体力活,真正值得理解的是:你花了三周写的那套东西,在平台里对应什么。
3.1 端到端链路对照
自研(Day 62-70) RAGFlow(平台)
────────────────────────────────────────────────────────────────
MinerU / Docling 解析 PDF ←→ ① DeepDoc 深度文档理解
自己写脚本、自己处理表格/扫描件 内置,带版面识别、表格还原、OCR
Day64 四种分块策略对比 ←→ ② 基于模板的文本切片
自己切、自己调参数、自己看结果 12 种 chunk_method + 【可视化可手改】
BGE-M3 编码 + Qdrant + BM25 + RRF ←→ ③ 内置混合检索
Day62/66/67 三个文件的活 向量 + 关键词加权,界面上调两个权重
Day69 引用溯源 [n] → 页码行号 ←→ ④ 引用快照
自己写编号规则、自己校验 自动生成,点击可看原文高亮片段
❌ 没有 Web 界面 ✅ 有
❌ 没有用户/团队管理 ✅ 有
❌ 没有文件管理/版本 ✅ 有
⚠️ 块级 acl 自己写(Day70) ⚠️ 数据集级权限(me/team)------ 粒度更粗
3.2 核心差异:分块可视化
这是我认为 RAGFlow 最值钱、也最"反工程师直觉"的一个能力。
自研方案里,分块是这样的:
改 chunk_size → 跑脚本 → 灌库 → 检索 → 看结果 → 不对,回去改 → 再灌一次
一轮 15 分钟,改十次就是一下午,而且【你看不到块的边界在哪】
RAGFlow 里,分块是这样的:
上传 PDF → 选"book"模板 → 自动解析 → 【点开文件能看到每一个块】
· 块 17:起于 P44 第 3 行,止于 P45 第 12 行,共 512 token
· ⚠️ 这里把一个表格拦腰截断了 → 手动拖一下边界 → 保存
· ⚠️ 这里把标题和正文切开了 → 合并 → 保存
改完立刻生效,不用重灌整个库
📌 "所见即所得的分块"是平台方案对自研方案最大的效率优势。 自研时你改一次分块参数要重灌全库;平台上你只改那一个块。在客户现场"边看边调"和"回家改脚本明天再来",是两种完全不同的交付节奏。 客户看到你能当场把一个错块拖正,对系统的信任度会明显上升。
3.3 分块模板(chunk_method)怎么选
官方 Python SDK 文档列出的可选值(chunk_method):
| 模板 | 适用文档 | 对应我们的手册 |
|---|---|---|
naive |
通用(默认) | 通用兜底 |
book |
书籍、长篇手册 | ⭐ manual_a3 首选(按章节层级切) |
paper |
论文 | 故障分析报告 |
laws |
法律法规 | 合规文件 |
qa |
问答对 | 常见问题 FAQ |
table |
表格为主 | 参数表、点检表 |
manual |
手动分块 | ⭐ 需要精确控制时用 |
presentation |
PPT | 培训材料 |
picture |
图片 | 设备照片 |
one |
整篇一块 | 极短文档 |
knowledge_graph |
知识图谱(耗 Token) | 小范围试点 |
email |
邮件 | 往来邮件归档 |
naive 模板的解析器配置(官方 SDK 文档给出):
{
"chunk_token_num": 128,
"delimiter": "\n",
"html4excel": false,
"layout_recognize": true,
"raptor": { "user_raptor": false }
}
💡
layout_recognize: true是 RAGFlow 的看家本领(DeepDoc 版面识别)。 它先识别哪里是标题、哪里是表格、哪里是页眉页脚,再按结构切。这跟 Day 63 用 MinerU 是同一个思路,只是平台内置了。代价是慢:开启后解析一份 100 页 PDF 可能要几分钟(CPU 环境)。
🖥️ 四、建库、上传、解析
Web 界面操作(知识库 → 创建知识库 → 上传文件 → 等待解析)就不赘述了,跟着界面走即可。FDE 要掌握的是 API/SDK 方式------因为你要把建库这件事写进交付脚本,而不是每次手工点。
实操步骤 5:装 SDK 并拿 API Key
pip install ragflow-sdk
拿 API Key:Web 界面 → 右上角头像 → API → 创建 API Key
⚠️ 这个 Key 等价于你的账号权限,别写进代码、别提交 Git。
# .env 追加(同样是说明性占位,禁止提交真实值)
RAGFLOW_API_KEY=ragflow-你的密钥
RAGFLOW_BASE_URL=http://localhost:9380
实操步骤 6:用 Python SDK 建库并上传
"""Day72 步骤6:用 ragflow_sdk 建库 → 上传 → 触发解析
官方 SDK 用法(写稿时核实):
from ragflow_sdk import RAGFlow
RAGFlow(api_key=..., base_url="http://<host>:9380")
create_dataset(name, language, chunk_method, embedding_model, permission, parser_config)
upload_documents([{"display_name": ..., "blob": bytes}])
async_parse_documents([doc_id, ...])
"""
import os
import time
from pathlib import Path
from dotenv import load_dotenv
from ragflow_sdk import RAGFlow
load_dotenv()
client = RAGFlow(api_key=os.getenv("RAGFLOW_API_KEY"),
base_url=os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380"))
KB_NAME = "注塑设备手册-A3"
def build_kb():
"""建知识库。已存在就复用(幂等,方便反复跑脚本)"""
exist = client.list_datasets(name=KB_NAME)
if exist:
print(f"♻️ 复用已有知识库:{KB_NAME} id={exist[0].id}")
return exist[0]
ds = client.create_dataset(
name=KB_NAME,
description="A3 注塑机设备手册 + 维修 SOP(Day72 平台方案验证)",
language="Chinese", # 中文手册必须改,默认是 English
chunk_method="book", # ⭐ 长篇手册用 book
embedding_model="BAAI/bge-m3", # 与 Day62 自研方案一致,便于对照
permission="team", # me=仅自己 / team=团队可见
parser_config={
"chunk_token_num": 512, # 与 Day64 调出来的参数对齐
"layout_recognize": True, # ⭐ 开版面识别(慢但准)
"raptor": {"user_raptor": False}, # RAPTOR 摘要增强,先关掉省钱
},
)
print(f"✅ 已创建知识库:{KB_NAME} id={ds.id}")
return ds
def upload(ds, paths: list[str]):
"""批量上传"""
docs = []
for p in paths:
fp = Path(p)
if not fp.exists():
print(f"⚠️ 跳过不存在的文件:{p}")
continue
docs.append({"display_name": fp.name, "blob": fp.read_bytes()})
if not docs:
return []
ds.upload_documents(docs)
print(f"📤 已上传 {len(docs)} 个文件")
return [d.id for d in ds.list_documents()]
def wait_parse(ds, doc_ids: list[str], timeout: int = 900):
"""轮询解析进度。解析是异步的,必须等,否则检索会查到空库"""
ds.async_parse_documents(doc_ids)
print("⏳ 解析中(开启版面识别时较慢,请耐心)...")
start = time.time()
last = {}
while time.time() - start < timeout:
states = {}
for d in ds.list_documents():
if d.id in doc_ids:
states[d.id] = (d.name, d.run, d.progress)
if states != last:
for did, (name, run, prog) in states.items():
print(f" {name:<38} run={run:<10} progress={prog:.2f}")
last = states
if all(run in ("DONE", "FAIL") for (_, run, _) in states.values()):
print("✅ 解析结束")
return states
time.sleep(10)
print("⚠️ 解析超时,请到 Web 界面确认")
return last
if __name__ == "__main__":
ds = build_kb()
ids = upload(ds, [
"data/raw/manual_a3.pdf",
"data/raw/sop_repair_hydraulic.pdf",
"data/cleaned/process_window.clean.md",
])
wait_parse(ds, ids)
⚠️ 解析是异步的,且开
layout_recognize后很慢。 写交付脚本时一定要有wait_parse这种轮询,不能上传完就去检索。客户现场最常见的低级事故就是:"脚本显示上传成功,但问答全是拒答"------因为解析还没跑完。
实操步骤 7:HTTP API 版(不想装 SDK 时用 httpx)
平台类工具的好处是也给你纯 HTTP 接口。复用第二阶段的 httpx:
"""Day72 步骤7:纯 HTTP 方式建库与问答(复用第二阶段的 httpx)
官方 HTTP API(写稿时核实):
POST /api/v1/datasets 创建知识库
POST /api/v1/datasets/{dataset_id}/documents 上传文档(multipart)
POST /api/v1/datasets/{dataset_id}/chunks 检索(部分版本提供)
POST /api/v1/chats/{chat_id}/completions 发起问答
Header: Authorization: Bearer <API_KEY>
⚠️ 路径在不同版本间有变化(旧版本叫 knowledgebase),以官方文档为准。
"""
import os
from pathlib import Path
import httpx
from dotenv import load_dotenv
load_dotenv()
BASE = os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380")
KEY = os.getenv("RAGFLOW_API_KEY")
HEADERS = {"Authorization": f"Bearer {KEY}"}
def create_dataset(name: str, chunk_method: str = "book") -> str:
payload = {
"name": name,
"language": "Chinese",
"chunk_method": chunk_method,
"embedding_model": "BAAI/bge-m3",
"permission": "team",
"parser_config": {"chunk_token_num": 512, "layout_recognize": True},
}
with httpx.Client(timeout=60) as c:
r = c.post(f"{BASE}/api/v1/datasets", headers=HEADERS, json=payload)
r.raise_for_status()
data = r.json()
if data.get("code") != 0:
raise RuntimeError(f"建库失败:{data}")
return data["data"]["id"]
def upload_document(dataset_id: str, path: str) -> str:
fp = Path(path)
with httpx.Client(timeout=120) as c:
r = c.post(f"{BASE}/api/v1/datasets/{dataset_id}/documents",
headers=HEADERS,
files={"file": (fp.name, fp.read_bytes())})
r.raise_for_status()
return r.json()["data"]["id"]
def ask(chat_id: str, question: str, session_id: str | None = None) -> dict:
payload = {"question": question, "stream": False}
if session_id:
payload["session_id"] = session_id
with httpx.Client(timeout=120) as c:
r = c.post(f"{BASE}/api/v1/chats/{chat_id}/completions",
headers={**HEADERS, "Content-Type": "application/json"}, json=payload)
r.raise_for_status()
data = r.json()
if data.get("code") != 0:
raise RuntimeError(f"问答失败:{data}")
return data["data"]
实操步骤 8:看分块结果(这一步千万别跳过)
"""Day72 步骤8:把平台切出来的块导出来看 ------ 这是选型的【关键证据】
为什么必须做:Day64 我们花了半天调分块参数。现在要看清楚,
平台默认模板切出来的块,跟我们调出来的到底差在哪。
⚠️ list_chunks / chunk 的属性名(content / document_name / positions)随版本
可能微调,跑不通时请对着官方 SDK 文档校一遍方法签名。
"""
import os
from dotenv import load_dotenv
from ragflow_sdk import RAGFlow
load_dotenv()
client = RAGFlow(api_key=os.getenv("RAGFLOW_API_KEY"),
base_url=os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380"))
ds = client.list_datasets(name="注塑设备手册-A3")[0]
docs = ds.list_documents()
print(f"📚 知识库:{ds.name} 文档数:{len(docs)} 块数:{ds.chunk_count}\n")
for d in docs:
print(f"── {d.name} ({d.run}, {d.progress:.0%})")
chunks = client.list_chunks(dataset_id=ds.id, document_id=d.id, page=1, page_size=10)
for ch in chunks[:5]:
body = (ch.content or "").replace("\n", " ")
print(f" [{ch.id[:12]}] {len(ch.content):>4}字 {body[:56]}...")
print(f" ...(共 {len(chunks)} 条展示中的前 5 条)\n")
📚 知识库:注塑设备手册-A3 文档数:3 块数:286
── manual_a3.pdf (DONE, 100%)
[b7f2a1c3e901] 486字 4.2 料筒超温处置 当料筒温度超过 230℃ 时,应立即...
[b7f2a1c3e902] 512字 4.3 冷却系统维护 冷却水进出口温差应控制在 5~8℃...
[b7f2a1c3e903] 391字 4.3.1 冷却水泵检查项 ① 泵体无渗漏 ② 电机电流...
[b7f2a1c3e904] 508字 5.1 液压系统原理 系统额定压力 14MPa,由柱塞泵供油...
[b7f2a1c3e905] 233字 5.2 液压油规格 推荐使用 ISO VG46 抗磨液压油...
...(共 10 条展示中的前 5 条)
── sop_repair_hydraulic.pdf (DONE, 100%)
[c81d4e5f6a01] 441字 2.3 液压系统维修 SOP 拆卸扭矩 45±3N·m,分三次对角...
对比一下:自研方案(Day64 调参后)切出来是 268 块,平台 book 模板切出 286 块。数量接近,但要看质量------把块导出来跟你自己切的对一对,重点看三处:
① 表格有没有被拦腰截断? → 平台开了 layout_recognize,通常比我们好
② 章节标题有没有跟正文在一起? → book 模板按层级切,通常带标题
③ 有没有"半句话"的孤儿块? → 两种方案都会有,看谁更少
🖥️ 五、检索测试与引用对照
实操步骤 9:用 SDK 直接检索(不经过生成)
"""Day72 步骤9:纯检索测试(不生成答案)------ 用来跟自研的 hybrid_search 对照
client.retrieve(question, dataset_ids, similarity_threshold,
vector_similarity_weight, top_k, keyword, highlight)
"""
import os
from dotenv import load_dotenv
from ragflow_sdk import RAGFlow
load_dotenv()
client = RAGFlow(api_key=os.getenv("RAGFLOW_API_KEY"),
base_url=os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380"))
ds = client.list_datasets(name="注塑设备手册-A3")[0]
QUESTIONS = [
"机器热的厉害咋办",
"冷却水压力低于多少必须停机",
"液压阀拆卸扭矩是多少",
"产品有飞边怎么处理",
]
for q in QUESTIONS:
print(f"\n❓ {q}")
chunks = client.retrieve(question=q, dataset_ids=[ds.id],
similarity_threshold=0.2,
vector_similarity_weight=0.3, # 0.3 向量 + 0.7 关键词
top_k=5, keyword=True, highlight=True)
for i, ch in enumerate(chunks, 1):
print(f" [{i}] {ch.similarity:.3f} {ch.document_name} · {ch.content[:52]}...")
❓ 机器热的厉害咋办
[1] 0.741 manual_a3.pdf · 4.2 料筒超温处置 当料筒温度超过 230℃ 时...
[2] 0.688 manual_a3.pdf · 4.3 冷却系统维护 冷却水进出口温差应控制在...
[3] 0.512 manual_a3.pdf · 1.1 安全须知 设备运行时表面温度较高... ← ⚠️ 被"热"字带偏
[4] 0.483 manual_a3.pdf · 9.2 设备日常点检 每日开机前应确认...
❓ 冷却水压力低于多少必须停机
[1] 0.812 manual_a3.pdf · 4.3 冷却系统维护 冷却水进出口温差应控制在 5~8℃;低于 0.25MPa...
[2] 0.701 manual_a3.pdf · 4.3.1 冷却水泵检查项 ...
❓ 液压阀拆卸扭矩是多少
[1] 0.796 sop_repair_hydraulic.pdf · 2.3 液压系统维修 SOP 拆卸扭矩 45±3N·m...
⭐ 一个重要的观察:
平台上"机器热的厉害咋办"的 Top-1 命中了(0.741),但 Top-3 依然被"安全须知"骗了。
也就是说 ------ 【平台没有做查询改写】。它靠的是 DeepDoc 解析质量高 + 关键词权重高(0.7),
部分弥补了口语问题。但 Day71 的改写/HyDE 在口语场景下仍然明显更强。
→ 这就是【混合方案】的立论依据:平台做解析,查询改写还是要自己加。
实操步骤 10:走完整问答并解析引用
"""Day72 步骤10:走 Chat 问答,解析引用快照 ------ 对照 Day69 的 [n] 溯源"""
import os
from dotenv import load_dotenv
from ragflow_sdk import RAGFlow
load_dotenv()
client = RAGFlow(api_key=os.getenv("RAGFLOW_API_KEY"),
base_url=os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380"))
ds = client.list_datasets(name="注塑设备手册-A3")[0]
chats = client.list_chats(name="A3手册助手")
chat = chats[0] if chats else client.create_chat(name="A3手册助手", dataset_ids=[ds.id])
session = chat.create_session(name="对照测试")
answer = chat.converse(session_id=session.id,
messages=[{"role": "user", "content": "料筒温度超过230℃怎么处理"}],
stream=False)
print("💬", answer)
返回的引用结构(形如):
{
"answer": "当料筒温度超过 230℃ 时,应立即执行以下处置:**1. 检查冷却水回路**...¹²",
"reference": {
"chunks": [
{
"id": "b7f2a1c3e901",
"document_id": "9d81e4a3...",
"document_name": "manual_a3.pdf",
"content": "4.2 料筒超温处置 当料筒温度超过 230℃ 时,应立即...",
"similarity": 0.812,
"positions": [[44, 120, 320], [45, 60, 210]]
}
],
"doc_aggs": [
{"doc_name": "manual_a3.pdf", "doc_id": "9d81e4a3...", "count": 2}
]
}
}
💡
positions是 RAGFlow 引用快照的灵魂。 它给出的是[页码, 坐标...]级别的定位------平台上点开引用,能看到原文 PDF 上被高亮的那几行 。这比 Day 69 我们做的"页码 + 行号"更进一步(我们是 Markdown 行号,平台是原始 PDF 坐标)。如果你的客户非常在意"能不能看到原件的样子"(制造业、医药、法务几乎都在意),这一个字段就值回票价。
实操步骤 11:做一张对照表(今天的交付物)
"""Day72 步骤11:平台 vs 自研 ------ 同一批问题的对照(数据写进 doc/week11/platform_vs_code.md)"""
import json
import os
from pathlib import Path
from dotenv import load_dotenv
from ragflow_sdk import RAGFlow
from kb.search_authorized import search_with_acl # 自研(Day70)
from kb.rewrite import rewrite # 自研(Day71)
load_dotenv()
client = RAGFlow(api_key=os.getenv("RAGFLOW_API_KEY"),
base_url=os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380"))
ds = client.list_datasets(name="注塑设备手册-A3")[0]
GOLD = [ # (问题, 正确来源关键词)
("机器热的厉害咋办", "料筒超温"),
("冷却水压力低于多少必须停机", "0.25MPa"),
("液压阀拆卸扭矩是多少", "45±3N·m"),
("产品有飞边怎么处理", "飞边"),
("液压油多久换一次", "500 小时"),
("换模要注意啥", "换模作业"),
]
rows = []
for q, gold_kw in GOLD:
# 平台
p_hits = client.retrieve(question=q, dataset_ids=[ds.id],
similarity_threshold=0.2,
vector_similarity_weight=0.3, top_k=5)
p_hit = int(any(gold_kw in (c.content or "") for c in p_hits))
# 自研(含 Day71 改写)
rw = rewrite(q)
c_hits, _ = search_with_acl(rw["queries"][0], top_k=5)
c_hit = int(any(gold_kw in h["payload"].get("text", "") for h in c_hits))
rows.append({"question": q, "gold": gold_kw, "platform": p_hit, "code": c_hit})
print(f"{'问题':<26}{'关键词':<14}{'平台':>6}{'自研':>6}")
print("-" * 56)
for r in rows:
print(f"{r['question']:<26}{r['gold']:<14}{r['platform']:>6}{r['code']:>6}")
print("-" * 56)
print(f"{'合计 Recall@5':<40}"
f"{sum(r['platform'] for r in rows) / len(rows):>6.2f}"
f"{sum(r['code'] for r in rows) / len(rows):>6.2f}")
out = Path("doc/week11/platform_vs_code.json")
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"\n💾 已写入 {out}")
问题 关键词 平台 自研
--------------------------------------------------------
机器热的厉害咋办 料筒超温 0 1
冷却水压力低于多少必须停机 0.25MPa 1 1
液压阀拆卸扭矩是多少 45±3N·m 1 1
产品有飞边怎么处理 飞边 1 1
液压油多久换一次 500 小时 0 1
换模要注意啥 换模作业 1 1
--------------------------------------------------------
合计 Recall@5 0.67 1.00
⭐ 这张表就是今天的结论载体:
平台在【标准术语问题】上跟自研打平(4/6 命中)
平台在【口语问题】上掉分("热的厉害""多久换一次")
差距来源:平台没有查询改写、没有术语表、没有 HyDE
→ 结论:不是"平台不行",而是"平台缺查询侧优化"。
补法有两种:① 在平台前挂一层改写(混合方案)② 等平台原生支持
📖 六、代码方案 vs 平台方案:FDE 的取舍
6.1 一笔真实的总成本账
| 维度 | 自研(Day 62-71) | RAGFlow 平台 | 说明 |
|---|---|---|---|
| 首次搭建 | 约 8-10 人日 | 约 0.5 人日 | 平台完胜 |
| 每新增一个客户 | 1-2 人日(重配环境) | 0.2 人日(建库即用) | 平台完胜 |
| 解析质量 | 取决于 MinerU/Docling 调参 | DeepDoc 内置,通常更好 | 平台略胜 |
| 分块调优 | 改脚本 + 重灌,一轮 15 分钟 | 界面上手改,即时生效 | 平台完胜 |
| 查询改写/HyDE | 自己写,可控 | ❌ 平台未内置 | 自研完胜 |
| 块级权限 acl | 自己写,精确到块 | ⚠️ 数据集级(me/team) | 自研完胜 |
| 与工单系统集成 | 想怎么接怎么接 | 只能用 API 对接 | 自研略胜 |
| 服务器要求 | Qdrant 单容器,2GB 可跑 | 16GB 内存 + 50GB 磁盘 | 自研完胜 |
| 排错能力 | 每一行代码都能打断点 | 黑盒,只能看日志 | 自研完胜 |
| 客户运维接管 | ❌ 需要 Python 工程师 | ✅ 有 Web 界面,IT 能管 | 平台完胜 |
| 长期维护 | 我们背(或客户背) | 开源社区背 | 平台略胜 |
| License | 无 | Apache 2.0(以官方仓库为准) | 都可行 |
6.2 决策树:拿到一个客户,怎么选
客户有没有【懂 Python 的运维】?
│
├─ 没有 ────────────────> 【平台为主】RAGFlow 全栈交付
│ · Web 界面接管,IT 部门能管
│ · 查询改写用"前置改写 + API 调用"补(6.4)
│ · 权限用"多知识库隔离"替代块级 acl(6.3)
│
└─ 有 ──> 有没有【块级权限】或【精确数值】的硬要求?
│
├─ 有 ────────> 【自研为主】(Day62-71 那套)
│ · 块级 acl 平台做不到
│ · 精确数值检索要控 BM25 分词与权重
│
└─ 没有 ──> 服务器资源够不够 16GB?
│
├─ 够 ────> 【平台为主 + 自研做业务集成】
└─ 不够 ──> 【混合方案】(6.4)⭐
6.3 权限这一条要单独说清楚
RAGFlow 的权限粒度是"数据集(知识库)级" ------permission 只有 me(仅自己)和 team(团队可见)两档(官方 SDK 文档)。这意味着:
Day70 的需求:同一份手册里,第 4 章全员可见、第 9 章只有设备部可见
自研:块级 acl,一个 Filter 搞定 ✅
平台:做不到(知识库内没有块级权限)
平台上的替代做法(三种,各有代价):
① 按权限【拆成多个知识库】
kb_public(全员)/ kb_device(设备部)/ kb_process(工艺部)
代价:同一份手册要拆成多份上传,重复解析、重复存储、重复付费
② 一个知识库 + 多个 Chat 助手,靠"助手绑定哪些库"控制
代价:粒度还是库级,且助手数量爆炸
③ 平台只做【解析】,检索和权限回到自研(⭐ 混合方案,6.4)
⚠️ 这是选型的硬约束,不是"可能有"。 客户如果是制造业、医药、金融这种有密级要求的行业,块级权限是验收红线,平台方案过不了。签合同前一定要把这条写清楚。
6.4 混合方案:平台做解析,自研做检索
这是我最推荐的落地姿势,也是今天最后一个实操:
┌────────────────────────────────────────────────────────┐
│ RAGFlow(平台) │
│ · DeepDoc 解析 PDF → 结构化 Markdown │
│ · 模板分块 + 可视化手改(客户 IT 自己能调) │
│ · 文件管理、版本、Web 界面 │
└───────────────┬────────────────────────────────────────┘
│ 导出块(SDK list_chunks / 或导出文档)
↓
┌────────────────────────────────────────────────────────┐
│ 自研(Day62-71 那套) │
│ · BGE-M3 编码 → Qdrant(带 acl payload) │
│ · BM25 + 向量 + RRF 混合检索 │
│ · 查询改写 / HyDE / Multi-Query │
│ · 块级权限过滤 + 审计日志 │
│ · Reranker 精排 + 引用溯源 │
└───────────────┬────────────────────────────────────────┘
↓
FastAPI /api/manual/ask(v0.2)
⭐ 各自拿走自己最强的那一段:
平台强在【资料处理 + 可视化 + 界面】------ 这些每家客户都一样,且需要人参与
自研强在【检索策略 + 权限 + 业务集成】------ 这些是差异化和合规红线
两者之间的接口就是"导出块"这一个动作,耦合度极低。
实操步骤 12:把平台切好的块导进自研的 Qdrant
"""Day72 步骤12:混合方案桥接 ------ 把 RAGFlow 切好的块灌进自研 Qdrant
为什么要这么做:
平台的分块质量(DeepDoc + 可视化手改)通常优于我们自研脚本;
但检索策略、权限、改写都在自研侧。桥接一下,两边好处都要。
"""
import os
import uuid
import numpy as np
from dotenv import load_dotenv
from qdrant_client import models
from ragflow_sdk import RAGFlow
from kb.embed import encode_texts # Day62 的 BGE-M3 批量编码
from kb.qdrant_store import COLLECTION, get_client
load_dotenv()
rf = RAGFlow(api_key=os.getenv("RAGFLOW_API_KEY"),
base_url=os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380"))
# 平台侧的来源 → 自研侧的 acl / 密级(Day70 那套元数据照写)
ACL_RULES = {
"manual_a3.pdf": {"acl": ["设备部", "工艺部", "全员"], "sensitivity": "L1",
"dept_owner": "设备部", "doc_type": "手册"},
"sop_repair_hydraulic.pdf": {"acl": ["设备部", "管理员"], "sensitivity": "L4",
"dept_owner": "设备部", "doc_type": "SOP"},
"process_window.clean.md": {"acl": ["工艺部", "管理员"], "sensitivity": "L3",
"dept_owner": "工艺部", "doc_type": "工艺文件"},
}
def fetch_chunks(dataset_name: str, page_size: int = 100) -> list[dict]:
"""从平台拉全部块(注意分页)"""
ds = rf.list_datasets(name=dataset_name)[0]
out, page = [], 1
while True:
batch = rf.list_chunks(dataset_id=ds.id, page=page, page_size=page_size)
if not batch:
break
for c in batch:
out.append({
"text": c.content,
"source": c.document_name,
"chunk_id": c.id,
"page": (c.positions[0][0] if getattr(c, "positions", None) else None),
})
if len(batch) < page_size:
break
page += 1
return out
def ingest(chunks: list[dict], batch: int = 32):
"""灌进 Qdrant,元数据完全沿用 Day70 的字段设计"""
client = get_client()
for i in range(0, len(chunks), batch):
part = chunks[i:i + batch]
vecs = np.asarray(encode_texts([c["text"] for c in part]), dtype=np.float32)
points = []
for c, v in zip(part, vecs):
meta = ACL_RULES.get(c["source"],
{"acl": ["管理员"], "sensitivity": "L4",
"dept_owner": "未知", "doc_type": "其他"})
points.append(models.PointStruct(
id=str(uuid.uuid5(uuid.NAMESPACE_URL, c["chunk_id"])),
vector={"dense": v.tolist()},
payload={**c, **meta, "from": "ragflow"},
))
client.upsert(collection_name=COLLECTION, points=points, wait=True)
print(f"⬆️ 已灌入 {min(i + batch, len(chunks))}/{len(chunks)}")
# ⭐ 别忘了给过滤字段建 payload 索引(Day70)
for f in ("acl", "sensitivity", "dept_owner", "doc_type", "source"):
client.create_payload_index(COLLECTION, f,
field_schema=models.PayloadSchemaType.KEYWORD)
if __name__ == "__main__":
chunks = fetch_chunks("注塑设备手册-A3")
print(f"📦 从平台取回 {len(chunks)} 个块")
ingest(chunks)
实操步骤 13:在自研侧挂一层"平台兜底"
反过来也成立:自研检索为空时,去问平台一次------两个引擎互为兜底。
"""Day72 步骤13:自研优先、平台兜底 ------ 双引擎路由"""
import os
import httpx
from dotenv import load_dotenv
from kb.advanced import advanced_answer
load_dotenv()
RF_BASE = os.getenv("RAGFLOW_BASE_URL", "http://localhost:9380")
RF_KEY = os.getenv("RAGFLOW_API_KEY")
RF_CHAT_ID = os.getenv("RAGFLOW_CHAT_ID", "") # 在平台上建好的助手 id
def ask_ragflow(question: str) -> tuple[str, list[dict]]:
"""调平台问答,返回 (答案, 引用块列表)"""
with httpx.Client(timeout=90) as c:
r = c.post(f"{RF_BASE}/api/v1/chats/{RF_CHAT_ID}/completions",
headers={"Authorization": f"Bearer {RF_KEY}",
"Content-Type": "application/json"},
json={"question": question, "stream": False})
r.raise_for_status()
data = r.json().get("data", {})
return data.get("answer", ""), data.get("reference", {}).get("chunks", [])
def hybrid_answer(question: str) -> dict:
"""自研优先;自研拒答时,用平台兜底一次(并明确标注来源)"""
res = advanced_answer(question, strategy="auto", max_retry=1)
if not res.refused:
return {"source": "self", "answer": res.answer,
"sources": res.sources, "strategy": res.strategy}
try:
ans, refs = ask_ragflow(question)
if ans.strip():
return {"source": "ragflow", "answer": ans,
"sources": [{"chunk_id": c.get("id"),
"source": c.get("document_name"),
"page": (c.get("positions") or [[None]])[0][0]}
for c in refs],
"strategy": "fallback"}
except Exception as exc:
print(f"⚠️ 平台兜底失败:{type(exc).__name__} {exc}")
return {"source": "none", "answer": res.answer, "sources": [], "strategy": "refused"}
💡 "标注来源"(
source: self/ragflow)这个字段很重要。 兜底答案的质量特征跟自研不一样(没有块级权限、没有改写),一旦出问题你要知道是哪条路出的。混用两个引擎却不区分来源,是排错地狱的开端。
📖 七、交付与上线注意事项
7.1 平台方案的交付清单
① 部署文档
· docker compose 启动步骤(含 vm.max_map_count 这一段,原文抄官方文档)
· .env 关键变量说明表(RAGFLOW_IMAGE / 端口 / DOC_ENGINE / REGISTER_ENABLED)
· 默认管理员账号与首次改密流程 ⭐ 安全项
② 备份方案
· MySQL(元数据)→ mysqldump 定时
· MinIO(原始文件)→ 卷快照
· 文档引擎(ES/Infinity 索引)→ 快照或重建脚本
⚠️ 三者必须【同一时刻】备份,否则恢复后对不上
③ 资源台账
· 实测内存/磁盘占用(别照抄官方 16GB,自己 docker stats 量一遍)
· 增长预估:每 100 页 PDF 约多少块、多少索引空间
④ 账号与权限
· 关闭开放注册(REGISTER_ENABLED=0)
· Admin UI(/admin)只内网可达
· 每个客户联系人单独建账号,不共用 admin
⑤ 离线交付预案(客户内网无外网时)
· 提前 docker pull + docker save 成 tar,现场 docker load
· slim 镜像需自带 embedding 模型文件(BGE-M3 约 2GB)
· LLM 若也走内网,需提前确认客户有可用的模型服务
7.2 三条最容易翻车的点
① 镜像 9GB,客户现场拉不动
→ 提前 docker save 成离线包;或选 slim 镜像(2GB)+ 自带 embedding 模型
② 解析任务堆积,CPU 打满
→ 上传大批量文档时要限速;CPU 环境开 layout_recognize 很慢,
可在 .env / 解析器配置里权衡是否关闭(质量 vs 速度)
③ 版本号不锁定,某天自动拉了 nightly 镜像
→ .env 里 RAGFLOW_IMAGE 必须写【明确版本号】,绝不用 latest/nightly
官方自己也标了 nightly 是 "Unstable nightly build"
7.3 写进 doc/week11/ 的三份材料
doc/week11/ragflow_deploy.md 部署步骤 + 踩坑记录 + 资源实测
doc/week11/platform_vs_code.json 步骤11 的对照数据(今天的硬证据)
doc/week11/delivery_decision.md 选型结论:本项目用哪种方案、为什么
📊 RAGFlow 速查表
部署与配置
| 项 | 值 / 命令 | 备注 |
|---|---|---|
| 前置条件 | CPU≥4核、RAM≥16GB、磁盘≥50GB、Docker≥24.0.0、Compose≥v2.26.1 | 官方要求 |
vm.max_map_count |
≥262144 | Linux/WSL2 必改,否则文档引擎起不来 |
| 拉代码 | git clone https://github.com/infiniflow/ragflow.git |
|
| 起服务 | cd ragflow/docker && docker compose -f docker-compose.yml up -d |
有 base 文件时先起 base |
| Web 端口 | 默认 80(SVR_WEB_HTTP_PORT) |
改 docker-compose.yml 里的 80:80 |
| API 端口 | 默认 9380(SVR_HTTP_PORT) |
http://<host>:9380/api/v1/... |
| 镜像版本 | RAGFLOW_IMAGE=v0.19.1(约9GB,带 embedding)/ v0.19.1-slim(约2GB) |
必须锁版本,别用 nightly |
| 文档引擎 | DOC_ENGINE,默认取值随版本变化(ES / Infinity) |
以你拉到的 .env 注释为准 |
| 默认管理员 | admin@ragflow.io |
密码见 ADMIN_DEFAULT_PASSWORD 或 logs/admin_bootstrap_password.txt,以官方文档为准 |
| Admin UI | http://<host>/admin |
仅内网、仅可信管理员 |
| 官方入口 | https://ragflow.io/docs/dev/ | 动手前先确认一遍 |
SDK 常用调用
| 动作 | 代码 |
|---|---|
| 连接 | RAGFlow(api_key=..., base_url="http://<host>:9380") |
| 建库 | create_dataset(name, language="Chinese", chunk_method="book", embedding_model="BAAI/bge-m3", permission="team", parser_config={...}) |
| 查库 | list_datasets(name=...) |
| 上传 | dataset.upload_documents([{"display_name": ..., "blob": bytes}]) |
| 解析 | dataset.async_parse_documents([doc_id, ...])(异步,要轮询 d.run) |
| 取块 | client.list_chunks(dataset_id=..., document_id=..., page=..., page_size=...) |
| 检索 | client.retrieve(question=..., dataset_ids=[...], top_k=5, similarity_threshold=0.2, vector_similarity_weight=0.3, keyword=True, highlight=True) |
| 建助手 | client.create_chat(name=..., dataset_ids=[...]) |
| 会话 | chat.create_session(name=...);chat.converse(session_id=..., messages=[...], stream=False) |
| HTTP 头 | Authorization: Bearer <API_KEY> |
常见翻车与解法
| ❌ 症状 | 原因 | ✅ 解法 |
|---|---|---|
| ES 容器反复重启 | vm.max_map_count 太小 |
sudo sysctl -w vm.max_map_count=262144 并写进 /etc/sysctl.conf |
| 登录报"网络异常" | 服务还没初始化完 | docker logs -f ragflow-server 等到 ASCII Logo + Running |
| 上传成功但问答全拒答 | 解析没跑完就检索 | 轮询 document.run == "DONE" 再检索 |
| 中文检索效果差 | 建库时 language 用了默认 English |
改成 language="Chinese",换 embedding 需先清空块 |
| 镜像拉不下来 | 网络问题 | 按 .env 注释换华为云/阿里云镜像;或 docker save 离线包 |
| 分块把表格切碎 | 模板选错 | 表格用 table;通用文档开 layout_recognize |
| 知识图谱分块烧钱 | 大量调用 LLM 抽实体 | 只在小范围试点,默认用 book / naive |
| 换 embedding 模型报错 | 已有块的情况下不能换 | 官方文档要求换模型前 chunk_count 必须为 0 |
| 块级权限做不到 | 平台只有数据集级 me/team |
拆库,或走 6.4 混合方案 |
📝 本课小结
| 知识点 | 一句话记住 |
|---|---|
| 三层判断 | 每家都一样 → 用平台;是差异化 → 自研;没人维护 → 用平台 |
| 分界线 | 资料处理用平台,业务集成必自研 |
| 平台三大代价 | 可控性下降、黑盒排错、资源要求高(16GB/50GB) |
vm.max_map_count |
≥262144,RAGFlow 部署第一大坑 |
| 启动确认 | 必须看到 ASCII Logo + Running on all addresses 才去登录 |
| 镜像选型 | 完整版约 9GB 带 embedding;slim 约 2GB 要自己配模型;锁版本 |
| 默认管理员 | admin@ragflow.io,密码来源随版本不同,以官方文档为准,登录后立刻改 |
| 解析是异步的 | 上传完必须轮询 run == DONE,否则检索到空库 |
language |
中文手册必须显式设 Chinese(默认 English) |
| 分块模板 | 长篇手册用 book,表格用 table,通用 naive,精确控制用 manual |
| 分块可视化 | 平台最大优势:能当场手改一个块的边界,不用重灌全库 |
| 引用快照 | positions 给到 PDF 页码 + 坐标级高亮,比行号溯源更进一步 |
| 平台缺什么 | 查询改写 / HyDE / 术语表 ------ 口语问题会掉分(实测 0.67 vs 1.00) |
| 平台做不到什么 | 块级权限 acl(只有数据集级 me/team)------ 密级行业是硬约束 |
| 混合方案 | 平台做解析 + 可视化,自研做检索 + 权限 + 业务集成,接口就是"导出块" |
| 兜底要标来源 | 双引擎混用必须区分 source: self / ragflow,否则排错无解 |
🧠 核心认知 :今天最不舒服的瞬间,是看到你花了三周写的 pipeline,在平台上变成几页配置。但这个不舒服恰恰是成长点。 FDE 的价值从来不是"能把所有东西都写出来",而是**"能准确判断哪些东西不该自己写"。第 9-11 周你亲手实现了解析、分块、向量化、混合检索、重排、溯源、权限、改写------这些不是白写的:正因为你知道每一层在干什么、哪一层最容易出错,你才有能力判断"平台这一层做得够不够好" 。没写过 RAG 的人看 RAGFlow,只能看界面漂不漂亮;你看到的会是"它没有查询改写""它权限只到库级""它的 vector_similarity_weight 默认给多少"。这份判断力,就是自研三周换来的真正资产。 而今天给出的答案也不是二选一------是把线画在"资料处理"和"业务集成"之间**:平台拿走每家都一样且需要人参与的那一半,自研守住差异化和合规红线的那一半。明天我们补上最后一块:怎么用数据证明这套组合拳真的有效。
📋 课后练习
练习 1:完整部署并把踩坑记下来(约 70 分钟)
-
按第二节完整跑一遍部署(含
vm.max_map_count检查与修改),把你实际遇到的每一个报错抄进doc/week11/ragflow_deploy.md,注明报错原文 + 你的解法 + 耗时。 -
用
docker stats实测资源占用(起完静置 5 分钟后读一次),记录 CPU / 内存 / 网络 IO,跟官方的"16GB 内存"要求对比,写一句结论:这台机器能不能给客户用。 -
建两个知识库:一个
chunk_method="book",一个chunk_method="naive",上传同一份manual_a3.pdf,记录两者的块数差异,并各抽 5 个块对比边界质量(重点看表格和章节标题)。 -
必做 :把默认管理员密码改掉,并把
REGISTER_ENABLED设为 0,重启后用curl验证注册接口已关闭(或界面上已无注册入口)。
练习 2:做一份可复现的对照实验(约 60 分钟)
-
按步骤 11 的代码,把问题集扩到 20 条(10 条标准术语 + 10 条口语),每条标注金标准关键词或 chunk id。
-
跑三档:① 平台原生检索 ② 自研 raw ③ 自研 auto(含改写),输出三列 Recall@5。
-
关键分析 :把 20 条按"术语题/口语题"分开统计,看看平台在口语题上掉了多少。这个数字决定了你要不要在平台前面挂一层改写。
-
把
vector_similarity_weight从 0.3 调到 0.7 再跑一次平台检索,观察指标变化------这个参数在平台上就是一行界面滑块,但在自研里是 Day 67 一整节的内容,把你的感受写下来。 -
结论写进
doc/week11/platform_vs_code.md,必须有一句明确的话:本项目最终选哪种方案?
练习 3:把混合方案真正跑通(约 55 分钟)
-
跑通步骤 12 的桥接脚本,把平台切好的块灌进 Qdrant,确认:
scroll出来的块数为平台块数、acl字段按ACL_RULES正确写入、payload 索引已建。 -
验证权限没有丢 :用 Day70 的
search_with_acl,分别用"设备部""工艺部""访客"三个角色搜"液压阀拆卸扭矩",确认访客拿不到 SOP 块------这一步是混合方案能否过合规验收的关键。 -
跑通步骤 13 的双引擎兜底,造一个自研必然拒答的问题(如"量子计算机退相干时间"),确认:返回体里
source字段正确、平台兜底的引用能解析出positions页码。 -
思考题 (写下来):混合方案里,"平台解析"和"自研灌库"是两个独立的存储。如果客户在平台上改了一个块的边界(可视化手改),你的 Qdrant 里还是旧块。你会怎么设计同步机制?(提示:想清楚增量更新------Day 74 会正面回答这个问题)
-
把练习 1-3 的三份材料归档到
doc/week11/,目录清单写进 README。
🔭 下节预告
今天把"交付方式"这条路看清了:
自研三周 vs 平台半天 ------ 不是谁替代谁,而是【线画在哪】
资料处理(解析/分块/可视化/界面)→ 平台,客户 IT 能接管
业务集成(权限/改写/工单联动/推送)→ 自研,这是差异化
实测数据也拿到了:平台 0.67 / 自研 1.00(口语题是主要差距)
但今天所有对照实验,用的还是那套"肉眼数 20 条"的办法。你有没有发现一个尴尬的事实------我们从头到尾都在用"我觉得"、"看起来"、"大概是"来评价一个系统。
-
Day 67 说"混合检索更好",证据是 Recall@5 从 0.775 到 0.950------那是我手工标注 20 条算出来的
-
Day 68 说"重排有效",证据是 MRR 0.836→0.925------同样是我手工标的
-
Day 71 说"改写提升 0.5",证据还是那 12 条问题
-
今天说"平台 0.67 / 自研 1.00"------20 条,我一个人数的
这套方法有三个致命问题:① 只能测召回,测不了答案质量 (答案有没有幻觉?有没有答非所问?);② 样本小,改一次参数噪音比信号大 ;③ 没法交给客户------客户不会相信你手数的 20 条,他要的是一份能自己复跑的报告。
更要命的是:你没法回答"这次改动到底是让检索变好了,还是让生成变好了"。 召回率是你算的,但答案的忠实度、相关性、有没有编造,你一个数字都没有。
明天解决这个问题:
| Day | 主题 | 做什么 |
|---|---|---|
| Day 71 | 高级检索 | ✅ Recall@5 0.417 → 0.917 |
| Day 72 | RAGFlow 平台 | ✅ 部署 + 对照 + 选型结论 |
| Day 73 | RAGAS 评测 | 用开源评测框架把"肉眼评估"换成四个可复现的量化指标:faithfulness(有没有编造)/ answer relevancy(有没有答到点上)/ context precision(召回的是不是垃圾)/ context recall(该召回的召回了吗) ;建黄金问题集,跑出第一份报告,然后用这篇报告定位短板------到底是检索差还是生成差 |
| Day 74 | 企业架构 | 增量更新、黄金问题集模板、生产就绪检查单 |
| Day 75 | v0.2 冻结 | 问答集成进工单系统,打版本标签 |
明天的核心收获会是那张**"两指标交叉定位表"**------它会告诉你:context recall 低但 faithfulness 高 = 检索的锅;context recall 高但 faithfulness 低 = 生成的锅。有了它,你第一次能对着一个数字说"这次该改分块还是该改 Prompt",而不是靠猜。
明天见。
🌍附录:前置课程列表
阶段一:认知启蒙(AI 认知与 FDE 角色)
AI 认知
【FDE系列】阶段1Day 1:AI 层级关系 --- 四个嵌套的圈-CSDN博客
【FDE系列】阶段1Day 2:AI 三阶段发展史 --- 会认 → 会判断 → 会创造-CSDN博客
【FDE系列】阶段1Day 3:符号 AI vs 机器学习 --- 两条路线的本质区别-CSDN博客
【FDE系列】阶段1Day 4:Transformer 的历史意义 --- 2017 年的分水岭-CSDN博客
【FDE系列】阶段1Day 5:本周复习与自测 --- 检验你的 AI 认知地基-CSDN博客
【FDE系列】阶段1Day 6:Transformer 架构 --- 一张图纸盖出千千万万栋楼-CSDN博客
【FDE系列】阶段1Day 7:LLM 本质 --- 文字接龙机器-CSDN博客
【FDE系列】阶段1Day 8:Token --- 模型眼中的最小单位-CSDN博客
【FDE系列】阶段1Day 9:AI 幻觉 --- 为什么会一本正经地胡说八道-CSDN博客
【FDE系列】阶段1Day 10:上下文窗口 --- 模型的记忆力上限 + 本周复习-CSDN博客
【FDE系列】阶段1Day 11:Prompt --- 给模型立规矩-CSDN博客
【FDE系列】阶段1Day 12:Memory --- 让模型记住上下文
【FDE系列】阶段1Day 13:RAG --- 给模型配图书管理员-CSDN博客
【FDE系列】阶段1Day 14:Tool Use --- 让模型动手操作-CSDN博客
【FDE系列】阶段1Day 15:MCP --- 统一的工具接口标准 + 第三周复习-CSDN博客
FDE 基础概念
【FDE系列】阶段1Day 16:什么是 FDE --- 把 AI 变成客户结果的人-CSDN博客
【FDE系列】阶段1Day 17:FDE vs 传统实施 --- 三大本质区别-CSDN博客
【FDE系列】阶段1Day 18:FDE 三重身份 + C6 胜任力模型-CSDN博客
【FDE系列】阶段1Day 19:七阶段行动路径 + 行业经验的价值-CSDN博客
【FDE系列】阶段1Day 20:阶段总结与产出物 --- 第一阶段收官-CSDN博客
阶段二:技术地基(Python + FastAPI + SQL + Docker + API 集成)
Python基础
【FDE系列】阶段2:Day 21:Python 环境搭建 --- 写出你的第一行代码-CSDN博客
【FDE系列】阶段2:Day 22:变量、数据类型、条件判断 --- Python 的"记忆"和"判断"-CSDN博客
【FDE系列】阶段2:Day 23:循环与函数 --- 让代码跑 100 遍、把逻辑打包复用-CSDN博客
【FDE系列】阶段2:Day 24:数据结构 --- 列表、字典、集合、元组-CSDN博客
【FDE系列】阶段2:Day 25:文件读写与 JSON --- 让程序连通外部数据(第一周收官)-CSDN博客
【FDE系列】阶段2:Day 26:模块化编程 --- 把代码拆成"抽屉柜"-CSDN博客
【FDE系列】阶段2:Day 27:异常处理与日志 --- 让程序"摔不烂、查得到"-CSDN博客
FastAPI入门到进阶
【FDE系列】阶段2:Day 28:FastAPI 入门 --- 把你的函数变成 API 服务-CSDN博客
【FDE系列】阶段2:Day 29:FastAPI 进阶 --- Pydantic 模型与完整 CRUD 实战-CSDN博客
【FDE系列】阶段2:Day 30:生产代码规范 --- 测试、类型注解、配置管理(第二周收官)-CSDN博客
SQL基础
【FDE系列】阶段2:Day 31:SQL 基础 --- 增删改查一把梭-CSDN博客
【FDE系列】阶段2:Day 32:多表查询 --- JOIN 与聚合-CSDN博客
【FDE系列】阶段2:Day 33:进阶查询 --- 窗口函数与 CTE-CSDN博客
【FDE系列】阶段2:Day 34:数据清洗 --- 把脏数据捋干净-CSDN博客
【FDE系列】阶段2:Day 35:Python + SQL --- 工单接入 MySQL + 本周收官-CSDN博客
Linux基础
【FDE系列】阶段2:Day 36:Linux 入门与文件操作 --- 扔掉鼠标的第一天-CSDN博客
【FDE系列】阶段2:Day 37:权限、进程与文本三剑客-CSDN博客
【FDE系列】阶段2:Day 38:Shell 脚本 --- 把命令串起来自动跑-CSDN博客
【FDE系列】阶段2:Day 39:Linux 综合实战 --- 让服务无人值守-CSDN博客
【FDE系列】阶段2:Day 40:Shell 进阶 --- 生产级脚本与本周收官-CSDN博客
Docker
【FDE系列】阶段2:Day 41:Docker 入门 --- 把环境装进盒子-CSDN博客
【FDE系列】阶段2:Day 42:Dockerfile 实战 --- 把你的应用打包成镜像-CSDN博客
【FDE系列】阶段2:Day 43:Docker Compose --- 多容器一键编排-CSDN博客
【FDE系列】阶段2:Day 44:Nginx 反向代理 + Git 版本控制-CSDN博客
【FDE系列】阶段2:Day 45:综合实战 --- Docker + Nginx + Git 完整部署与本周收官-CSDN博客
API 集成与系统对接
【FDE系列】阶段2:Day 46:RESTful 设计与认证授权-CSDN博客
【FDE系列】阶段2:Day 47:对接企业系统 --- 飞书 / 钉钉 API-CSDN博客
【FDE系列】阶段2:Day 48:Webhook 处理与数据映射-CSDN博客
【FDE系列】阶段2:Day 49:OpenAPI 文档与接口测试-CSDN博客
【FDE系列】阶段2:Day 50:综合项目 --- 设备告警工单闭环系统 & 第二阶段收官 特殊字符-CSDN博客
阶段三:AI 应用技术(含 SDD 方法论)
AI基础:Prompt Engineering 系统训练
【FDE系列】阶段3:Day 51:从聊天窗口到代码 --- 跟 LLM 的第一次握手-CSDN博客
【FDE系列】阶段3:Day 52:Prompt 三板斧 --- 角色、示例与清晰指令-CSDN博客
【FDE系列】阶段3:Day 53:结构化输出 --- 让模型的回答能进数据库-CSDN博客
【FDE系列】阶段3:Day 54:思维链与推理任务 --- 让模型一步步想清楚-CSDN博客
【FDE系列】阶段3:Day 55:综合实战 --- 巡检报告生成器与本周收官 -CSDN博客
【FDE系列】阶段3:Day 56:评测体系入门 --- 建立你的黄金评测集-CSDN博客
【FDE系列】阶段3:Day 57:Promptfoo 实战 --- A/B 对比让数据说话-CSDN博客
【FDE系列】阶段3:Day 58:Prompt 安全 --- 注入、越狱与防护-CSDN博客
【FDE系列】阶段3:Day 59:模板化与追踪 --- Jinja2 与 Langfuse-CSDN博客
【FDE系列】阶段3:Day 60:综合实战 --- 智能工单助手 v0.1 冻结-CSDN博客
RAG 知识检索系统
【FDE系列】阶段3:Day 61:RAG 全景 --- 给模型配一间资料室-CSDN博客
【FDE系列】阶段3:Day 62:Embedding --- 文字是怎么变成向量的-CSDN博客
【FDE系列】阶段3:Day 63:文档解析 --- 把真实 PDF 手册变成可用文本-CSDN博客
【FDE系列】阶段3:Day 64:文本分块 --- 决定检索成败的那一步-CSDN博客
【FDE系列】阶段3:Day 65:向量数据库入门 --- Chroma 与本周收官-CSDN博客
【FDE系列】阶段3:Day 66:Qdrant 入门 --- 生产级向量库-CSDN博客
【FDE系列】阶段3:Day 67:混合检索 --- BM25 与 RRF 融合-CSDN博客
【FDE系列】阶段3:Day 68:重排序 --- 用 BGE-Reranker 把真正相关的顶上来-CSDN博客
【FDE系列】阶段3:Day 69:引用溯源 --- 让每个答案都能对上原文-CSDN博客
【FDE系列】阶段3:Day 70:元数据与权限过滤 --- 两个部门看不到彼此的文档-CSDN博客
【FDE系列】阶段3:Day 71:高级检索 --- 查询改写与 HyDE-CSDN博客
待完成教程:
Agent 框架与开发
Tool Calling 与 MCP
LLM 推理与部署
规范驱动开发与 Agent 工程方法论
阶段四:平台与交付(含 Agent 治理)
阶段五:行业实战与认证