从零搭一个中文电子书 RAG:为什么先做最小闭环
从零搭一个中文电子书 RAG:为什么先做最小闭环
系列定位:这是一个从 MVP 开始打磨 RAG 系统的实战记录,不是“企业级最终方案”的包装稿。
阅读时间:8 分钟
适合读者:想自己搭 RAG、正在做知识库问答、希望理解工程取舍的开发者
关键词:RAG、中文电子书、MVP、Spring Boot、PostgreSQL、pgvector
先把预期说清楚
我最开始也很容易把 RAG 系统想大:多租户、权限体系、异步队列、监控告警、缓存、对象存储、模型网关、自动评测,最好再配一个漂亮的后台。
但真正动手之后会发现,如果第一版就奔着“企业级完整架构”去,很快会被细节淹没。RAG 的核心价值其实很朴素:
- 用户能上传资料。
- 系统能把资料解析成可检索的文本块。
- 文本块能被向量化并持久化。
- 用户提问时,系统能召回相关片段。
- 大模型回答时,能带上来源,避免凭空编造。
所以 my-rag 的第一阶段没有追求大而全,而是先做一个中文电子书 RAG 的最小闭环:上传、解析、切块、向量化、检索、问答、引用来源。
这篇文章讲的不是“我设计了一个完美架构”,而是“我为什么先把系统压到这个范围,以及这个范围里的关键取舍是什么”。
一、RAG MVP 的边界
1.1 先解决一个具体场景
泛泛地说“企业知识库”太宽了。不同企业的文档来源、权限模型、更新频率、合规要求都不一样,第一版很难一次吃下。
我选择的起点是中文电子书和文档资料:
- 文档通常比较长,能真实暴露分块和上下文构建问题。
- 中文内容多,对检索和 token 估算更敏感。
- 用户问题往往需要引用出处,适合验证 RAG 的可信度。
- 文件上传、解析、索引、问答可以形成完整闭环。
这个场景足够小,可以落地;也足够复杂,不至于只做一个玩具 demo。
1.2 第一版只保留必要能力
当前 my-rag 的能力可以概括为七件事:
- 上传文档并持久化元数据。
- 文档先解析并切块,进入
CHUNKED状态。 - 用户确认 embedding 费用后,再生成向量,进入
READY状态。 - 使用 PostgreSQL + pgvector 做相似度检索。
- 可选启用关键词索引,和向量召回做混合排序。
- Chat 问答只基于召回片段回答,并返回来源引用。
- 记录 Chat log,方便复盘“召回错了还是回答错了”。
这里故意没有把权限、多租户、对象存储、消息队列、Redis 缓存放进第一版。它们都重要,但不是证明 RAG 闭环成立的前置条件。
1.3 一个容易踩的坑:过早企业级
“企业级”这个词很诱人,但它也很容易让系统在还没验证核心价值之前就变复杂。
比如消息队列当然可以提升文档处理的可靠性;但如果还不知道 chunk 的质量是否够用,先引入队列只会让问题更难定位。Redis 缓存也很有用;但如果召回策略本身不稳定,缓存只是在加速错误结果。
所以第一版的原则是:先让问题暴露在简单链路里,再决定哪里值得加复杂度。
二、整体链路长什么样
my-rag 的主流程可以拆成两段。
第一段是文档进入知识库:
第二段是用户提问:
这两个流程共同回答一个问题:系统是否真的能把“我的资料”变成“可追溯的回答”。
三、技术选型:够用比漂亮更重要
3.1 后端:Spring Boot + Maven 多模块
后端选择 Java 17 和 Spring Boot 3。原因很简单:这套技术栈成熟、调试方便,适合把业务状态流转写清楚。
项目拆成几个模块:
rag-api:请求和响应 DTO。rag-common:公共响应、错误码等基础能力。rag-service:核心业务逻辑,包括文档、切块、embedding、检索和 chat。
这不是为了显得架构复杂,而是为了让 API 契约和业务实现分开,后面前后端联调时会舒服很多。
3.2 数据库:PostgreSQL + pgvector
第一版没有引入专门的向量数据库,而是使用 PostgreSQL 加 pgvector。
这个选择背后的考虑是:
- 文档、chunk、chat log 本来就需要关系型数据。
- MVP 阶段的数据规模可控,PostgreSQL 足够承载。
pgvector可以直接在数据库里完成向量相似度排序。- 迁移和本地开发都比较简单。
数据库里最核心的三类表是:
rag_document:文档元数据和状态。rag_document_chunk:解析后的文本块。rag_chunk_embedding:chunk 对应的向量。
这个结构比“把所有东西塞进一个 JSON”麻烦一点,但它让状态追踪、错误排查和后续统计都更清楚。
3.3 前端:React + Ant Design
前端没有做复杂的视觉设计,重点是把关键操作做完整:
- Dashboard 看整体状态。
- Documents 上传和管理文档。
- Document Detail 查看文档、chunk、索引进度。
- Chat 进行问答。
- Chat Logs 回看历史回答和来源。
- Settings 配置模型参数。
RAG 产品里,前端不是只有一个聊天框。文档处理状态、费用确认、来源引用、失败提示,都会直接影响用户是否信任系统。
3.4 模型:embedding 和 chat 分开配置
系统把 embedding 和 chat 拆成两类模型配置。
embedding 用于把文档 chunk 和用户问题转成向量;chat 用于根据召回上下文生成回答。这样做的好处是,后续可以单独替换其中一个模型,而不影响另一条链路。
本地开发默认可以走 mock provider,避免每次启动都依赖真实 API Key。接真实服务时,再通过本地 secret YAML 或环境变量配置。
四、最重要的设计不是架构图,而是状态流转
RAG 系统最容易混乱的地方,是文档状态。
如果一个文档刚上传,还没解析,能不能被检索?如果解析成功但还没 embedding,能不能进入 Chat?如果 embedding 失败,用户应该看到什么?
我把文档流程拆成几个明确状态:
UPLOADED:文件已上传。PARSING:正在解析。PARSED:解析完成。CHUNKED:已经生成 chunk,可以估算 embedding 成本。EMBEDDING:正在生成向量。READY:可以参与检索和问答。FAILED:处理失败,需要展示错误原因。
这个状态机不花哨,但非常关键。它让前端可以明确提示用户下一步该做什么,也让后端避免把半成品文档混进检索结果。
五、为什么要让用户确认 embedding 成本
RAG 的费用常常不是来自聊天,而是来自批量 embedding。
如果用户上传一本很长的电子书,系统直接开始向量化,既不透明,也容易让调试成本失控。因此 my-rag 选择把索引拆成两步:
- 先 parse + chunk,得到 chunk 数和本地 token 估算。
- 前端展示预估费用,用户确认后再调用 embedding API。
这个交互看起来多一步,但它让系统从一开始就有成本意识。
当然,本地 token 估算不会和模型服务账单完全一致,所以界面也应该明确提示:预估只用于决策,最终费用以服务商账单为准。
六、第一版架构的取舍
第一版架构的核心取舍,是先让闭环稳定、可解释,再决定哪些地方值得增加复杂度。文档处理确实适合异步化,但第一阶段先用简单链路跑通状态流转和错误处理,等文档处理量上来后,再把 parse、chunk、embedding 拆到队列里会更稳;本地文件系统也已经足够支撑 MVP,等需要多实例部署、备份、跨机器访问时,再接 MinIO 或云对象存储;权限、多租户、审计日志都应该做,但它们应该建立在知识库问答闭环稳定之后;检索也是同样的原则,向量检索、关键词检索、RRF、rerank 都可以逐步加入,但每加一个环节,都要能回答:它解决了哪个召回问题?它让结果更好,还是只是让链路更复杂?
七、这一版真正想验证什么
第一版 my-rag 不是为了证明“我能搭一个完整企业知识库平台”,而是验证三个更基础的问题:
- 文档处理是否稳定:长文档能不能解析、切块、索引,并在失败时给出清晰状态?
- 检索是否可调试:回答不好时,能不能看到到底召回了哪些 chunk?
- 答案是否可信:大模型是否只根据资料回答,并带上来源引用?
这三个问题如果没解决,后面加再多企业级能力也只是外壳。
结语:先让闭环跑起来
RAG 系统最怕一开始就被“大架构”带偏。真正值得先做的,是一条能被用户亲手验证的闭环:上传自己的资料,提出自己的问题,看到回答和来源,然后判断这个系统是否值得继续打磨。
my-rag 的第一阶段就是围绕这个目标展开。下一篇我会继续拆文档处理流水线:一个文件从上传到 READY,中间到底经过了哪些步骤,哪些地方最容易翻车。
下一篇:《RAG 文档处理流水线:从上传文件到可检索 chunk》