01 Naive RAG 的标准处理流程 —— Building-RAG-Systems 项目系列第一篇
关于本项目
项目名称:🚀 Building-RAG-Systems: From Theory to Production
本项目致力于提供一条平滑且深入的 RAG(检索增强生成)学习与实践路径。通过深度文章解析、配套 Demo代码 以及系统级项目实战,带你从零构建工业可用的大模型应用。 代码仓库链接:Github
📂 仓库结构 (Repository Structure)
本项目按照“理论 -> 配套demo -> 系统级实践”的逻辑组织,主要包含以下三个核心模块:
▼text复制代码📦 Building-RAG-Systems ┣ 📂 articles # 📖 核心知识库:RAG 理论解析、架构设计及痛点解决方案 ┣ 📂 articles_demo # 💻 Demo代码:与文章配套的开箱即用 Demo 代码 ┗ 📂 project # 🏗️ 系统级项目:融合所有知识点的全功能系统架构
1️⃣ articles:深入浅出的理论与思考
这里不只是空洞的理论,更是实打实的架构设计经验与问题解决思路。
- 探索 RAG 的核心组件与工作流。
- 深入剖析在 Chunking、Embedding、Retrieval 和 Generation 环节遇到的常见痛点(如语意鸿沟、上下文割裂、检索不精准等)及其解决方案。
2️⃣ articles_demo:配套的 Demo 代码
Show me the code! 每篇核心文章都配备了独立的、可运行的 Demo。代码保持极简,屏蔽无关框架干扰,方便你快速运行和理解。
3️⃣ project:从 Demo 到生产级系统
这是本仓库的终极目标。我们将前两个模块中沉淀的知识与技巧,融合并构建成一个完整的、具备 Agent 协同和复杂任务处理能力的系统级项目。
🗺️ 内容导航 (Roadmap & Navigation)
| 状态 | 文章 (Articles) | 核心要点 | 对应 Demo (Code Module) |
|---|---|---|---|
| 🟢 | 01 Native RAG 的标准处理流程 | 离线数据建库与在线相似度检索链路 | demo-01-naive-rag |
| 🟡 | 02 Naive RAG 存在的致命问题与解决方案 | 四大痛点:语意鸿沟、上下文割裂、多级跳转推理与全文总结能力缺失、缺乏纠错闭环 | demo-02-critical-issues |
| 🟡 | 03 工业级 RAG 项目还需要解决哪些问题 | 数据一致性同步、细粒度权限隔离、系统可观测性、自动化评估方案等多方面问题 | demo-03-other-issues |
| 🟡 | 04 RAG的元数据设计 | 元数据的三大应用场景,以及利用元数据解决资料冲突的方案设计 | demo-04-metadata-design |
| ⚪️ | 05 企业级 RAG 项目的架构参考 | 六层架构设计,从接入与网关层到最终的监控与测试层的全链路设计方案 | demo-05-system-architecture |
| ⚪️ | 06 RAG 的全链路耗时与优化 | 全面分析 RAG 的全链路耗时,并针对耗时过高的环节进行优化设计 | demo-06-latency-optimize |
| ⚪️ | 07 RAG 的评估维度以及标准评估方法 | 基于 RAGAS 的四大评估维度(精确度、召回率、忠实度、相关性),及结合 JUnit 的自动化裁判测试驱动开发 | demo-07-automated-evaluation |
| ⚪️ | 08 针对长短不一的文档的双轨制存储方案设计 | 结合全文检索与父子块检索,利用 Metadata 路由实现离线分轨入库与在线统一解析组装 | demo-08-dual-track-storage |
(注:🟢 已完成 🟡 进行中 ⚪️ 计划中)
- 关于文章: 文章初稿已全部推送到仓库,如果近期有 Agent 开发面试,希望提前看下八股的可以去仓库自取(顺手帮忙点个🌟就更好啦),当前为初稿,后续可能会随着code demo 做一些修改完善。后续每更新完一章的code demo会往导航同步更新一篇文章。
- 关于Demo Code: 所有文章配套的 demo code 会在Github仓库,均为本人本地跑通后的完整代码,clone后配置好相关后端组件后,可直接运行,如有需要请前往仓库自取。
- 关于生产级项目: 当前后端部分已写完近 4W 行代码,因为最近比较忙,同时多线程忙很多事,所以预计真正完成可能还需要1-2个月。完成后也会开源在Github仓库。项目参考了claude code 的一些实现,综合考虑了很多方面,包含:上下文窗口结构设计、长短期记忆管理机制(长短期转换、记忆编辑、遗忘、归档等)、多意图路由、复杂任务的执行清单拆分、Skills 渐进式纰漏手动实现、Mysql + Milvus + ElasticSearch + Redis 的多数据源协同链路等。
本篇正文
RAG(Retrieval-Augmented Generation,检索增强生成)的标准处理流程通常被分为两个主要阶段:离线数据处理(Offline Data Ingestion) 和 在线检索生成(Online Query & Generation)。
在Java 生态中,开发 RAG 最主流且成熟的框架是 LangChain4j(类似于 Python 生态的 LangChain)和 Spring AI。
第一阶段:离线数据处理 (建库)
1. 文档解析与分块 (Document Loading & Chunking)
- 具体实现:首先提取 Markdown、Word、HTML 等不同格式文件中的纯文本。由于大模型每次处理的上下文长度有限(Token 限制),且整篇文档的语义容易稀释,我们需要将长文档切割成一段段较短的文本块(Chunks)。
- 主流技术方案:
- 框架:Unstructured.io、LangChain、LlamaIndex。
- 分块策略:固定大小分块(Fixed-size chunking)、基于规则的递归分块(Recursive character splitting,目前最常用)、基于语义的分块(Semantic chunking)。
三种分块策略具体解释:
1. 固定大小分块
这是最简单粗暴的分块方式。系统不考虑文本的任何语法结构或段落意义,单纯按照设定的字符数(或 Token 数)进行“一刀切”。
- 具体实现:设定一个阈值(比如 200 个字符)。系统从头开始数,满 200 个字符就截断作为一个 Chunk,然后继续数下一个 200 个字符。
- 优点:实现极度简单,计算开销几乎为零,向量数据库的资源消耗非常可控。
- 缺点:极其容易破坏语义完整性,导致检索时丢失关键上下文。
2. 基于规则的递归分块
这是目前业界最常用、性价比最高的默认策略。它通过一组预设的标点符号分隔符(比如双换行
\n\n、单换行\n、句号。、空格 ),像剥洋葱一样从大到小去尝试切分文本。
- 具体实现:系统会优先尝试用最大的结构(如段落双换行
\n\n)来切分。如果切出来的段落还是超过了设定的大小限制,它就会“递归”地降级,尝试用单换行\n切分;如果还大,就用句号切分成句子;最后才是按字符硬切。同时,为了防止相邻块的上下文丢失,通常会设置一个 Overlap(重叠区)。- 优点:兼顾了文本大小的限制和自然语言的语义边界,泛化能力强。
- 缺点:仍然依赖硬性规则和标点符号,对于格式混乱的文本效果一般。
3. 基于语义的分块
这是目前比较前沿的高阶玩法。它抛弃了固定的长度和标点符号规则,而是让机器真正去“读懂”文本,在语义发生转折的地方进行切分。
- 具体实现:通常的做法是将文本先按句子切开,然后使用 Embedding 模型计算相邻两个句子之间的“语义相似度”。如果两句话讲的是同一件事(向量距离近),就合并成一个 Chunk;如果相邻两句话的语义差异突然变大(向量距离远,超过某个阈值),就认为这里发生了“话题切换”,从而在这里断开,生成一个新的 Chunk。
- 场景举例:小说前三句话在描写主角在精灵森林中采集草药(语义 A),第四句话突然话锋一转,切到了反派在黑暗城堡里密谋造反(语义 B)。语义分块算法会敏锐地捕捉到这两种场景的差异,精准地在这两段剧情之间切开,哪怕反派剧情的第一句话和草药剧情在同一个自然段里。
- 优点:最大程度保证了每一个 Chunk 内的信息高度内聚,检索准确率极高。
- 缺点:计算成本高昂。在切分阶段就需要频繁调用大模型或 Embedding 模型,处理速度慢,实现逻辑也更复杂。
实现思路:
先按句子拆分文本
将所有句子向量化 (耗时/烧钱操作)
遍历计算相邻句子的余弦相似度
如果相似度低于阈值,说明话题变了,在此断开
2. 文本向量化 (Embedding)
- 具体实现:将上一步得到的每一个文本块转换成高维的浮点数数组(向量)。在向量空间中,语义相近的文本块,它们对应的向量距离也会非常接近。
- 主流技术方案:
- 闭源模型:OpenAI text-embedding-3-small/large、Qwen的 embedding 模型等。
3. 向量存储 (Vector Storage)
- 具体实现:将文本块(原数据)和它们对应的向量保存到专用的向量数据库中。向量数据库针对高维相似度搜索(如计算余弦相似度)进行了极其深度的优化。
- 主流技术方案:
- 专用向量库:Milvus(开源、适合大规模)、Pinecone(SaaS,全托管)、Qdrant、Chroma(适合轻量级/本地开发)。
- 传统数据库扩展:Elasticsearch(Dense Vector)、PostgreSQL + PgVector、Redis。
第二阶段:在线检索生成 (问答)
当用户提出问题时,系统会实时处理问题,去第一阶段建好的库中找答案,最后让大模型总结。
4. 相似度检索 (Retrieval)
- 具体实现:把用户提出的自然语言问题,用同一个 Embedding 模型也变成向量。然后在向量数据库中寻找与“问题向量”距离最近的 Top-K 个“文档向量”,提取出对应的文本块。
- 主流技术方案:
- 检索算法:HNSW(层级导航小世界算法,目前最主流的近似最近邻搜索算法)。
- 高级检索技术 (Advanced RAG):混合检索(Hybrid Search,即向量检索 + BM25 关键词检索)、重排序(Re-ranking,使用专门的交叉编码器模型如 BGE-Reranker 对召回的结果重新打分排序)。
检索算法详解:
1. HNSW 算法
在向量数据库中,面对千万级甚至亿级的文本向量,如果我们拿着用户问题的向量去和库里的向量挨个计算距离(暴力搜索/KNN),速度会慢到无法忍受。HNSW 就是目前最主流的近似最近邻(ANN)搜索算法,它在查询速度和召回率之间做到了极佳的平衡。
- 实现原理:
- 跳表 (Skip List) 思想:HNSW 构建了一个多层的图结构。最上层极其稀疏,只有少数几个节点(向量);越往底层节点越密集,最底层包含所有节点。
- 导航小世界 (NSW):在每一层里,相近的向量会相互连接成图。
- 检索过程:搜索时从最顶层开始,快速跳跃到大概的区域(粗筛),然后进入下一层继续寻找更近的节点,不断向下“钻取”(Zoom in),直到最底层找到距离最近的 Top-K 个向量。这就像你找一个人:先定省份(顶层),再定城市(中层),最后定街道(底层)。
- 技术实现方案:
- 在业务代码中,我们通常不需要手写 HNSW 算法,因为它已经内嵌在所有主流向量数据库(如 Milvus、Elasticsearch、Qdrant)的底层引擎中了。
- 我们需要做的是在创建索引时,配置 HNSW 的核心参数:
M:每个节点在图中最多保留的连接数(通常设为 16-64)。M 越大,召回率越高,但内存消耗和构建时间也越大。efConstruction:建库时用于搜索的候选节点数量。越大图质量越高,建库越慢。efSearch:查询时搜索的候选集大小。越大查询越准,但速度越慢。2. 混合检索 (向量 + BM25)
单纯的向量检索擅长理解“语义”。比如搜“苹果手机”,它能找出“iPhone”的文档。但它不擅长精确匹配,比如用户搜特定的专有名词、订单号、错误代码时,向量检索常常会“翻车”。
- 实现原理:
- 双路召回:系统同时执行两种查询。一路执行向量检索(捕捉语义),另一路执行传统的 BM25 关键词检索(类似于 ES 的倒排索引,捕捉字面精准匹配和词频)。
- 结果融合 (RRF):由于向量的得分(如 0.85)和 BM25 的得分(如 15.2)完全不在一个维度,无法直接相加。业界通用的融合算法是 RRF (Reciprocal Rank Fusion,倒数排名融合)。它不看绝对得分,只看两路结果的排名。
- RRF 的计算公式为:RRF_score = 1/(k + rankvector) + 1/(k + rankkeyword)(其中 k 常设为 60)。
- 技术实现方案:
- Elasticsearch 8.x 版本原生支持了完整的混合检索和 RRF。
- Milvus v2.4+ 也引入了 BGE-M3 等支持稀疏向量的混合搜索能力。
3. 重排序
即使是混合检索,为了保证速度,其计算模型依然是相对“粗糙”的(称为双编码器 Bi-encoder:问题和文档分别计算向量再点乘)。大批量的文档被召回后,里面通常会夹杂大量看似相关实则无关的“噪音”文本。
- 实现原理:
- 我们引入一个更慢、更消耗算力,但极其精准的交叉编码器模型 (Cross-encoder)。
- 重排序模型会将“用户的原始问题”和“初筛召回的一个个文档块”拼接在一起同时输入神经网络。这样模型就能在底层运用自注意力机制(Self-Attention)去逐字比对问题和文档的逻辑关系,输出一个极其精确的相关性得分(0~1)。
- 流程:混合检索粗筛出 Top 50 结果 -> 送入 Re-ranker 模型打分 -> 重新排序 -> 取前 5 个真正相关的送给 LLM。
- 技术实现方案:
- 主流重排模型:Cohere Rerank 3(API调用,效果极好)、BGE-Reranker-Large(开源,常配合本地 GPU 或 Ollama 部署)。
总结串联
在生产级别的 RAG 系统中,这三者的配合流程通常是:
用户提问 ➡️ 混合检索 (BM25 + HNSW向量检索) 初筛出 50 篇文档 ➡️ RRF 算法融合排名 ➡️ 重排序模型进行精细打分 ➡️ 提取 Top 5 给大模型生成。
5. 提示词增强与生成 (Prompting & Generation)
- 具体实现:将用户的原始问题,以及我们刚刚从数据库里捞出来的“参考答案(Context)”,一起塞进一个精心设计的 Prompt 模板中,发送给 LLM。LLM 基于这些参考信息,生成最终的准确回答。
Java 代码示例:
▼java复制代码import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; // 初始化聊天大模型 ChatLanguageModel chatModel = OpenAiChatModel.builder() .apiKey("YOUR_API_KEY") .modelName("qwen-max") .build(); // 构建 Prompt:结合检索到的上下文和用户问题 String promptTemplate = "你是一个 AI 助手。请仅基于以下提供的背景信息来回答用户的问题。\n" + "如果背景信息中找不到答案,请诚实地回答'我不知道',不要编造信息。\n\n" + "背景信息:\n%s\n\n" + "用户问题:%s"; String finalPrompt = String.format(promptTemplate, context, userQuery); // 调用大模型生成最终回答 String response = chatModel.generate(finalPrompt); System.out.println("AI 回答: " + response);
总结
完整的 RAG 就是:加载切分 -> 向量存储 -> 用户提问 -> 向量检索 -> 组装 Prompt -> 大模型生成。
上面展示的是最基础的 Naive RAG(朴素 RAG)。在实际生产中,为了提高准确率,通常还会引入 Query Rewrite(问题重写)、Re-rank(重排) 或 GraphRAG(知识图谱结合) 等进阶技术。

