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 模型,处理速度慢,实现逻辑也更复杂。

实现思路

  1. 先按句子拆分文本

  2. 将所有句子向量化 (耗时/烧钱操作)

  3. 遍历计算相邻句子的余弦相似度

如果相似度低于阈值,说明话题变了,在此断开

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(知识图谱结合) 等进阶技术。

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
Mrchen
下载 APP