最小完整 RAG:在做 AI Agent System 过程中迭代出来的一份检索实践

2026-08-26

最小完整 RAG:在做 AI Agent System 过程中迭代出来的一份检索实践

项目背景:多 Agent 内容生产平台(AI Agent System)迭代过程中,需要让 Agent 具备"读文档、查知识、答问题"的检索能力。这份 RAG 实践就是围绕这个能力做的一次完整落地实证——不是 demo,而是一条真实可用的检索链路。

开场:Agent 缺的不是"会说话",是"有据可依"

在 AI Agent System 的迭代里,一个核心矛盾逐渐浮出水面:Agent 能调模型、能写文章、能走流程,但它的"知识"只来自模型参数——用户问它"钱大妈的日清模式是什么"、让它写"晚市清货文案"时,它要么胡编,要么泛泛而谈。Agent 缺少一个"有据可依"的检索能力:给定一批文档,能真正读到内容、找到依据、再组织回答。

于是有了这份实践:一个最小但完整的 RAG 系统——解析、切块、向量化、检索、重排、生成、引用校验,一条链全真。它是 Agent 迭代过程中"检索能力"这条支线的独立验证:先在最小规模上把链路跑通、把坑踩透,再谈接入 Agent 编排。

一、过程:一次完整迭代的节奏

1. 目标函数是"最小完整",不是"效果最好"

RAG 最容易死在 demo 病:向量检索漂亮、生成华丽,但解析是假的、检索是玩具、没有评估。所以第一版就强制五段全真:

解析 → 切块 → 向量化入库 → 检索 → 生成(含引用校验)

每一段都接真实服务:MinerU 云解析 PDF、FastEmbed 本地向量、Qdrant 落盘存储、DeepSeek 生成。宁可真而小,不要假而全——链路每个环节都真实存在,才谈得上后续优化,也才敢把它作为能力接回 Agent 系统。

2. 迭代的提交节奏:核心 → 壳 → 界面 → 测试 → 打磨

提交干了什么
chore项目骨架(pyproject / .python-version / skills 文档)
feat(rag)RAG 核心全链路 + 14 单测
feat(app)FastAPI 壳 + rag 融合层(sys.path 注入 + 全局锁)+ REST API
feat(web)Ant Design X 前端(对话 + 知识库管理)
docs产品总览 + 架构文档(9 个关键设计决策)+ API 参考
test测试补全:rag 30 例 + API 14 例 = 44
styleUI 打磨(对话区、卡片化知识库、引用样式)

观察这条提交线:先让核心链路跑通(骨架),测试紧随其后(肌肉),界面和打磨最后(皮肤)。测试与功能几乎同步出现,避免了"功能写完再补测试"的经典拖延;每个提交带明确 scope、test 与 feat 分开。这和我们在 Agent System 里定的提交纪律完全一致——迭代的节奏感本身就是刻意练习

3. 集成方式的一个取舍:sys.path 注入 + 全局锁

rag 没有拆成独立微服务,而是作为一个模块嵌进 FastAPI 应用,通过 sys.path 注入让 rag 的模块直接 import,再用全局锁规避并发 ingest 踩 Qdrant local 的单进程锁。对一个能力验证项目,这比"为了架构好看拆微服务"正确得多——不为不存在的并发需求付复杂度税。同样的判断我们在 Agent System 里也做过:M0 用固定流水线起步,supervisor 动态分流留到 M3——演进要有触发条件,不提前上复杂度。

二、收获:五个设计与 Agent System 迭代的呼应

1. 降级链:核心链路不被最脆的环节阻塞

解析是 RAG 最脆弱的一环(PDF 格式、云 API、网络……)。设计成三级:MinerU 云解析 → markitdown 本地轻量 → 纯文本兜底,每级失败自动降级,每步打印实际走的路径 + 结果抽样(前 80 字),一眼看出"这个 PDF 是乱码还是正常"。

呼应:这和 Agent System 里"无外部依赖(LLM/DB)时给降级路径"的设计纪律一脉相承——脆弱环节必须有降级,且降级路径必须可观测。RAG 的质量瓶颈不在模型,在解析;Agent 的质量瓶颈也往往不在模型,在它依赖的外部能力。

2. 解析缓存:给"反复实验"上锁

MinerU 云解析按文件内容 sha256 落盘缓存(data/_parse_cache/)。收益立竿见影:调 chunk_size 实验要反复 ingest,没有缓存就是反复烧云 API 的钱 + 排队;有缓存后实验迭代快几个数量级。

呼应:Agent 系统的迭代同样依赖"试错要便宜"——这也是为什么我们在 Agent System 里用 mock 模式跑确定性测试、用事件日志做可回放轨迹。缓存不是优化,是让迭代变快的基建

3. 混合检索 + RRF:不赌单一信号

纯向量对"术语精确命中"(编号、专名如 JFT-300M、DeepFace)不稳;纯关键词抓不到语义("土豆" ↔ "马铃薯")。所以双路召回:

  • 关键词路:中文 2-gram + 英文单词,BM25 式命中计数(零依赖、纯 Python、可单测)
  • 向量路:FastEmbed 语义相似
  • RRF 融合:按"排名倒数"加权,不需要归一化分数

再用 bge-reranker 精排 top3 注入生成。

呼应:检索系统"不赌单一信号"和 Agent 编排里"审核循环 + 多角色交叉验证"是同一个思想——单一来源的判断都不可靠,融合多路证据再下结论。RRF 的优雅在于只用排名不用分数,两路召回分数尺度不同也没关系。

4. 防幻觉不靠 prompt 靠程序:引用闭环

生成端强制"每条素材带编号、正文用 [来源N] 标注",生成后程序校验引用序号是否越界——越界 = 模型编造,直接拦截。防幻觉不是靠 system prompt 喊口号(模型该编还编),而是把"可验证性"做进输出协议里。

呼应:这与 Agent System 的"日志即真相"(任何进入模型的内容必须先落事件日志)和"审核通过前不发布"是同一条原则的两种落地——凡是能被程序验证/留痕的,就不要交给模型自觉。引用校验是 RAG 里低成本高收益的防幻觉锚点。

5. 评估驱动调参:先让评估能区分好坏

chunk_size 实验(400/800/1500/3000)第一版跑出来四组全是 100%——评估条件是"关键词是否出现在 topK 拼接文本里",太宽松,任何参数都命中。升级为 expect_doc(期望命中文档,Recall@K 的简化版)+ expect_kw(关键词)双条件后,区分度立刻出来。

呼应:Agent System 迭代里我们同样反复强调"验收标准可验证"(每个里程碑写清可验证条件)。评估集先要能区分好坏,否则调参是盲调——无区分度的 100% = 没有指标,这条对 RAG 调参和 Agent 迭代一视同仁。

三、踩过的坑:9 个真坑,4 类共性

第一类:版本生态坑(Python 3.14 装不了 fastembed)

onnxruntime / tokenizers 在 3.14 没有 wheel。教训:先查依赖 wheel 再选 Python 版本——AI/ML 生态对新版本支持滞后,"最新"不是"最稳"。(和 Agent System 里"Next.js 版本差异大、先读官方文档"是同一种意识:新版本不等于兼容。)

第二类:国内网络坑(HF 超时 + xet 401)

直连 huggingface.co 超时;换 hf-mirror.com 镜像后又报 cas-server.xethub.hf.co 401——HF 新版默认走 xet 存储后端,而 xet 不走 HF_ENDPOINT 镜像。修复是两行环境变量:HF_ENDPOINT=hf-mirror.com + HF_HUB_DISABLE_XET=1教训:镜像 ≠ 万能,新存储协议可能绕开你配的镜像;排查网络问题要看到"数据实际从哪里下载"

第三类:同名包坑(mineru ≠ mineru-open-sdk)

pypi 的 mineru本地推理引擎(3.x),云 API 在 mineru-open-sdk 里。装错包,PDF 解析静默降级成乱码。教训:装 Python 包前先确认包名和 API 版本——同名包可能完全是两个东西,而"静默降级"让错误藏得很深。

第四类:数据因果坑(最深刻的一组)

  1. 修好了解析器,ask 还是乱码——因为 Qdrant 里存的是历史乱码 chunk:修解析器 ≠ 数据自动更新,ask 读的永远是库里已有的数据。修复:清库重建再重新 ingest。
  2. 换 embedding 模型后检索报维度错误(512 维 → 1024 维)——向量维度变了,旧 collection 里全部向量失效。Qdrant 集合的 vectors_config 在建库时固化,不随模型变。修复:改模型必须清库重建。

这两个坑本质是同一个:你改的是"上游产数据的逻辑",但下游消费的是"已经产出的数据"——管线的因果链不会因为你改了源头就自动传导。教训:① 数据管线的 bug 修复必须配套"清库→重入库",索引陈旧是隐藏坑;② embedding 模型是数据 schema 的一部分,换模型是破坏性变更,不是配置热更新。(Agent 系统里"改模型必须跑 smoke test"、"配置与契约默认落表、变更走版本管理",是同一思维的另两个切面。)

(另外三个零散坑:Qdrant local 单进程锁——本地工具的单进程语义;ingest 不递归子目录——批量入库要明确"是否递归、文件名是否全局唯一";FastEmbed 会额外拉 clip 模型——"你以为只下载一个模型"的意外。)

四、沉淀:可复用并反哺 Agent System 的方法论

  1. 最小完整 > 局部炫技:先让每条链路真实存在,再逐项加固;
  2. 降级 + 可观测是脆弱环节的护甲:解析三级降级 + 每步打印解析路径;
  3. 缓存让试错变便宜:外部调用成本高,就给反复实验上缓存;
  4. 能被程序验证的,不交给模型自觉:引用校验闭环(↔ Agent 的日志即真相);
  5. 评估先能区分好坏,再谈调参:无区分度的 100% = 没有指标;
  6. 数据管线的 bug 修复要配"清库重建":改上游逻辑不等于改下游数据;
  7. schema 思维:embedding 模型、向量维度都是数据契约,变更按"破坏性变更"管理。

结尾

这段实践的收获,表面上是"会搭 RAG 了",实质是:在 AI Agent System 的迭代中,把 Agent 的"检索能力"从概念变成了可运行、可评估、可复用的真实链路。解析要可降级、实验要有缓存、输出要可验证、调参要有区分度、数据变更要有纪律——这些道理没有一条是 RAG 专属的,但 RAG 这条链把它们全部暴露了一遍,而它们又全部回灌到了 Agent System 的迭代决策里。这大概就是"在系统迭代中做能力验证"的意义:用最小的规模,把最大的工程问题各碰一遍,再把答案带回系统