RAG 的完整流程是什么?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 流程完整性:能不能把 RAG 从离线建库到在线生成完整走一遍?中间有遗漏环节(比如漏掉 Rerank)会被扣分。

  2. 每个环节的深度理解:文档解析有哪些难点?Chunk 策略怎么选?Embedding 模型选型依据是什么?向量检索的原理了解吗?每个环节面试官都可能追问细节。

  3. 生产实战经验:你搭建 RAG 系统的时候踩过什么坑?知道怎么优化效果吗?能结合实际项目讲才最有说服力。

核心答案

RAG 的完整流程分为两大阶段、六个核心环节

离线阶段:文档解析 → 文本切分 → 向量化 → 存入向量库
在线阶段:向量检索 → 重排序 → LLM 生成

下面我把每个环节掰开揉碎地讲一遍。

深度解析

一、离线阶段:知识库构建

离线阶段的目标是把各种非结构化文档变成可检索的向量数据。这一步是整个 RAG 的地基,地基没打好,后面检索再怎么优化都是白搭。

1. 文档解析(Document Parsing)

文档解析看着简单,实际是整个流程中最容易被低估的环节。它的任务是把 PDF、Word、HTML、Excel、PPT 等各种格式的文档,转换成结构化的纯文本。

常见的坑

  • 表格数据丢失:很多解析器遇到表格直接丢弃或变成乱序文本。表格里的数据往往是最有价值的(比如产品参数、定价信息),丢了就检索不到
  • 图片中的文字:要不要做 OCR?做了准确率够不够?不做就丢失信息。这块需要根据业务场景权衡
  • 公式提取:学术论文、技术文档中的数学公式,大部分解析器处理得不好
  • 多栏排版:双栏论文解析后文字顺序经常错乱

生产环境推荐

  • 轻量级场景用 Apache Tika(Java 生态最成熟的文档解析库)就够了
  • 复杂文档(特别是含表格、图片、公式的)建议用 LlamaParseMarker 等专业工具
  • 国内也有一些不错的方案,比如 MinerU(开源的文档解析工具,对中文 PDF 支持较好)

2. 文本切分(Chunking)

文本切分是 RAG 效果的 "隐形杀手"——切得不好,后面所有环节都在为它买单。

常见的切分策略

  • 固定长度切分:按字符数或 Token 数切割,最简单但最粗暴。容易把一个完整的句子或段落从中间截断,破坏语义完整性
  • 递归字符切分:按分隔符的优先级逐级尝试切分(先按 \n\n,再按 \n,再按空格),尽量保留段落结构。Spring AITokenTextSplitterLangChain4jDocumentSplitters 都支持这种模式
  • 语义切分:用 Embedding 模型计算相邻文本的语义相似度,在语义断裂处切分。效果最好,但需要额外的模型调用,成本较高
  • 基于文档结构的切分:利用文档的标题层级、段落标记等结构信息来切分。对 Markdown、HTML 这类结构化文档效果很好

实战经验值

  • Chunk 大小:200~500 tokens 是比较通用的范围。太大了检索不精准(一个 Chunk 里塞太多信息),太小了上下文不完整(一个关键信息被拆成两半)
  • 重叠(Overlap):50~100 tokens。重叠的作用是防止关键信息恰好落在两个 Chunk 的边界上被截断
  • 别忘了存元数据!每个 Chunk 要关联它的来源文档、页码、标题等元信息,方便后续溯源展示

2025 年以来,业界还出现了 Parent-Child Chunking(父子分块)策略:用小块做检索(精准),命中后返回对应的大块给 LLM(上下文完整)。这个思路很巧妙,面试时提一下绝对加分。

3. 向量化编码(Embedding)

把每个 Chunk 用 Embedding 模型编码成高维向量(通常是 768 维或 1536 维),这个向量就是这个 Chunk 的 "语义指纹"。

Embedding 模型选型参考

模型 特点 适用场景
text-embedding-3-small/large(OpenAI) 效果好,API 调用,英文为主 英文场景、快速验证
bge-large-zh(BAAI) 中文优化,开源免费 中文场景首选
gte-large-zh(阿里) 中文效果好,MTEB 排名靠前 中文场景备选
text-embedding-v3(通义千问) 阿里云 API,中文优秀 Spring AI Alibaba 项目首选
Cohere Embed v3 多语言支持好 多语言混合场景

选型原则:中文场景优先选中文优化的开源模型(bgegte 系列);用 Spring AI Alibaba 的项目可以直接用通义千问的 Embedding API;注意 Embedding 模型的维度要和向量数据库的配置一致。

4. 存入向量数据库

向量数据库负责存储所有 Chunk 的向量,并在查询时做高效的相似度检索。

主流向量数据库对比

数据库 特点 适用场景
Milvus 分布式、高性能、功能丰富 大规模生产环境
Qdrant Rust 写的,性能好,轻量 中小规模、Docker 部署
Redis 利用已有基础设施,Spring AI 原生支持 中小规模、已有 Redis 的团队
Elasticsearch 支持稠密+稀疏混合检索,8.x 版本原生支持向量 需要同时做全文搜索和向量检索
Pinecone 全托管,免运维 不想管基础设施的团队

Java 生态里,Spring AI 通过统一的 VectorStore 接口屏蔽了底层差异,切换向量数据库基本只需要改配置文件,非常方便。

二、在线阶段:查询与生成

上图是在线阶段的完整流程。整体分为以下几个步骤:

  • Query 预处理:用户的原始问题可能表达模糊或口语化,直接拿去检索效果往往不好。常见的预处理包括 Query 改写(把口语化问题改写成更精确的检索 Query)、Query 扩展(生成多个同义 Query 扩大检索覆盖面)、意图识别(判断用户到底想问什么)。这一步很多人不做,但效果差异很明显

  • 向量化 + 检索:把问题用同一个 Embedding 模型编码成向量,在向量数据库中做相似度检索,返回 Top-K 个最相似的 Chunk(通常 K=3~10)

  • Hybrid Search(混合检索):单纯靠向量检索会漏掉一些精确匹配的场景(比如用户搜特定的产品型号、专有名词)。生产环境推荐同时做向量检索(语义匹配)和关键词检索(精确匹配),然后把两路结果融合。融合方法常用 RRF(Reciprocal Rank Fusion)或加权打分

  • Rerank(重排序):用 Cross-Encoder 模型对检索结果做精排。向量检索用的是 Bi-Encoder(问题和文档分别编码再算相似度),速度快但精度有限;Rerank 用的 Cross-Encoder(问题和文档拼在一起编码),精度高但速度慢,所以只对 Top-K 候选做精排。这一步是提升 RAG 效果性价比最高的手段之一,很多团队跳过了 Rerank,检索准确率直接差了一个档次

  • Prompt 组装:把 Rerank 后的 Top-N 个 Chunk(通常 N=3~5)和用户问题一起组装成 Prompt。Prompt 模板设计也有讲究——要明确告诉 LLM "只能基于以下上下文来回答,如果上下文中没有相关信息就说不知道",这样能有效抑制幻觉

  • LLM 生成:大模型基于 Prompt 中的上下文生成最终回答。生成时可以要求模型标注引用来源(比如 "根据文档第 3 段..."),增强可信度

三、代码示例

Java 生态做 RAG 主要有两个主流框架:Spring AILangChain4j。下面分别展示两种实现方式。

方式一:Spring AI + Milvus

Spring AI 的优势在于和 Spring Boot 深度集成,通过 QuestionAnswerAdvisor 一行代码就能完成 RAG 检索+生成,非常适合 Java 工程师上手。

Maven 依赖

<!-- Spring AI 核心 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

<!-- Milvus 向量数据库 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-milvus-store</artifactId>
</dependency>

配置文件 application.yml

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com  # 或换成国内代理地址
      chat:
        options:
          model: gpt-4o
          temperature: 0.7
      embedding:
        options:
          model: text-embedding-3-small
    vectorstore:
      milvus:
        client:
          host: localhost
          port: 19530
        database-name: default
        collection-name: rag_docs
        embedding-dimension: 1536  # 要和 Embedding 模型维度一致
        metric-type: COSINE

知识库构建(离线阶段)

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class KnowledgeBaseService {

    private final VectorStore vectorStore;

    public KnowledgeBaseService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    /**
     * 离线阶段:解析文档 → 切分 → 向量化 → 存入 Milvus
     */
    public void buildKnowledgeBase(String filePath) {
        // 1. 文档解析:读取文本文件
        //    生产环境可用 Apache Tika 解析 PDF/Word/Excel 等
        TextReader reader = new TextReader(new FileSystemResource(filePath));
        List<Document> documents = reader.get();

        // 2. 文本切分:按 Token 切分,每个 Chunk 最多 500 Token,重叠 50
        TokenTextSplitter splitter = new TokenTextSplitter(
            500,   // defaultChunkSize:每个 Chunk 最大 Token 数
            50,    // overlap:重叠 Token 数,防止关键信息被截断
            5,     // minChunkSizeChars:最小 Chunk 字符数
            10000, // maxNumChunks:最大 Chunk 数量
            true   // keepSeparator:是否保留分隔符
        );
        List<Document> chunks = splitter.apply(documents);

        // 3 + 4. 向量化 + 存入向量库(Spring AI 自动处理)
        //    VectorStore.add() 内部会自动调用 Embedding 模型编码
        vectorStore.add(chunks);

        System.out.println("知识库构建完成,共 " + chunks.size() + " 个 Chunk");
    }
}

RAG 查询(在线阶段)

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;

@Service
public class RagQueryService {

    private final ChatClient chatClient;

    public RagQueryService(ChatClient.Builder builder, VectorStore vectorStore) {
        // 构建 ChatClient,挂载 QuestionAnswerAdvisor 实现 RAG
        // 核心就这一行:自动完成 "问题向量化 → 检索 → Prompt 组装 → LLM 生成"
        this.chatClient = builder
            .defaultAdvisors(
                new QuestionAnswerAdvisor(
                    vectorStore,
                    // 检索配置:返回 Top-5 相似文档,相似度阈值 0.7
                    SearchRequest.builder()
                        .topK(5)
                        .similarityThreshold(0.7)
                        .build()
                )
            )
            .build();
    }

    /**
     * 在线阶段:用户提问 → 检索 → 生成回答
     */
    public String ask(String question) {
        return chatClient.prompt()
            .user(question)
            .call()
            .content();
    }
}

看到没?Spring AI 把整个 RAG 流程封装得非常简洁。QuestionAnswerAdvisor 内部自动完成了:问题向量化 → 向量检索 → 把检索结果拼到 Prompt 里 → 调用 LLM 生成。生产环境如果需要更细粒度的控制(比如 Hybrid Search、Rerank),可以使用 RetrievalAugmentationAdvisor 替代。

方式二:LangChain4j

LangChain4j 更灵活,适合需要深度定制 RAG 流程的场景。

Maven 依赖

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>1.0.0-beta3</version>
</dependency>
<!-- 模型供应商(以 OpenAI 为例) -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
    <version>1.0.0-beta3</version>
</dependency>
<!-- 向量存储(以 Milvus 为例) -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-milvus</artifactId>
    <version>1.0.0-beta3</version>
</dependency>

完整 RAG 示例

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
import dev.langchain4j.service.AiServices;

import java.io.InputStream;

public class LangChain4jRagExample {

    public static void main(String[] args) {

        // ===== 1. 初始化模型 =====
        ChatLanguageModel chatModel = OpenAiChatModel.builder()
            .apiKey("your-api-key")
            .modelName("gpt-4o")
            .build();

        EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
            .apiKey("your-api-key")
            .modelName("text-embedding-3-small")
            .build();

        // ===== 2. 初始化向量存储(Milvus) =====
        EmbeddingStore<TextSegment> embeddingStore = MilvusEmbeddingStore.builder()
            .host("localhost")
            .port(19530)
            .collectionName("rag_docs")
            .dimension(1536)
            .build();

        // ===== 3. 离线阶段:文档解析 → 切分 → 向量化 → 入库 =====

        // 使用 EmbeddingStoreIngestor 统一处理整个离线流程
        DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
        //    参数说明:500 = 每个 Chunk 最大 Token 数,50 = 重叠 Token 数

        EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
            .documentSplitter(splitter)
            .embeddingModel(embeddingModel)
            .embeddingStore(embeddingStore)
            .build();

        // 解析文档(这里用文本文件示例,生产环境可用 Apache Tika)
        DocumentParser parser = new TextDocumentParser();
        InputStream docStream = Thread.currentThread()
            .getContextClassLoader()
            .getResourceAsStream("产品手册.txt");
        Document document = parser.parse(docStream);

        // 一键完成:切分 → 向量化 → 入库
        ingestor.ingest(document);

        // ===== 4. 在线阶段:构建 RAG 服务 =====

        // 创建内容检索器:问题向量化 → 在向量库中检索 Top-5
        EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
            .embeddingStore(embeddingStore)
            .embeddingModel(embeddingModel)
            .maxResults(5)       // 返回 Top-5 最相似的文档片段
            .minScore(0.7)       // 最低相似度阈值
            .build();

        // 使用 AiServices 自动组装 RAG 链
        // LangChain4j 会自动:检索相关文档 → 拼入 Prompt → 调用 LLM 生成
        Assistant assistant = AiServices.builder(Assistant.class)
            .chatLanguageModel(chatModel)
            .contentRetriever(retriever)
            .build();

        // 5. 提问
        String answer = assistant.chat("产品的退换货政策是什么?");
        System.out.println(answer);
    }

    /**
     * 定义 Assistant 接口
     * LangChain4j 会自动实现这个接口,注入 RAG 检索逻辑
     */
    interface Assistant {
        String chat(String userMessage);
    }
}

两种框架怎么选?

维度 Spring AI LangChain4j
上手难度 更低,Spring Boot 开箱即用 稍高,需要手动组装组件
灵活性 中等,通过 Advisor 机制扩展 更高,每个组件可独立替换
生态集成 和 Spring 生态无缝集成 支持更多模型供应商和向量库
适合场景 Spring Boot 项目、快速验证 需要深度定制的 RAG Pipeline
国产模型 Spring AI Alibaba 支持通义千问 支持通义千问、DeepSeek 等

如果是 Java 后端团队做 RAG,推荐用 Spring AI 快速起步,遇到需要深度定制的场景再切 LangChain4j

四、Naive RAG → Advanced RAG → Agentic RAG 的演进

面试时如果能讲清楚 RAG 的三代演进,能体现出你对这个领域的持续关注。

代际 特点 关键技术
Naive RAG 基础的 "检索 → 生成" 流程 向量检索 + LLM 生成
Advanced RAG 在 Naive 基础上增加优化环节 Hybrid Search、Rerank、Query 改写、Parent-Child Chunking
Agentic RAG 引入 Agent 决策能力 自主判断是否检索、多轮检索、工具调用、反思评估

2025-2026 年的趋势是向 Agentic RAG 演进——RAG 不再是一个固定的 Pipeline,而是一个可以根据任务需要动态调整的智能体。模型可以自主决定 "这个问题我需不需要查资料"、"查到的资料够不够用"、"要不要换个角度再查一遍"。

面试高频追问

  1. 追问一:Chunk 策略怎么选?

    看文档类型和业务场景。结构化文档(Markdown、HTML)优先用基于文档结构的切分;非结构化长文本用递归字符切分;对精度要求极高且预算充足可以用语义切分。Chunk 大小一般 200~500 tokens,重叠 50~100 tokens。可以多试几种策略,用评估指标来选最优的。

  2. 追问二:向量检索和关键词检索哪个好?为什么要做 Hybrid Search?

    向量检索擅长语义匹配("怎么退货" 能匹配到 "退换货政策"),但对精确匹配不敏感(搜特定产品型号可能搜不到)。关键词检索(如 BM25)擅长精确匹配,但不理解语义。Hybrid Search 把两路结果融合,取长补短,是生产环境的标准做法。

  3. 追问三:Rerank 有必要做吗?用什么模型?

    非常有必要。向量检索用的是 Bi-Encoder,问题和文档是独立编码的,精度有限。Rerank 用 Cross-Encoder 把问题和文档一起编码,精度显著提升。推荐 bge-reranker-v2-m3(开源中文)或 Cohere Rerank(商业 API),性价比很高。

  4. 追问四:如何评估 RAG 系统的效果?

    常用指标包括召回率(检索到了多少相关文档)、答案准确率(生成的答案是否正确)、幻觉率(答案中有多少是编造的)。推荐用 RAGAS 框架做自动化评估,它提供了 Context Precision、Context Recall、Faithfulness 等指标,可以量化评估 RAG 各环节的效果。

常见面试变体

  • "介绍一下 RAG 的离线阶段和在线阶段分别做什么?"
  • "RAG 系统中的文本切分策略有哪些?各有什么优缺点?"
  • "你用过 Spring AI 或 LangChain4j 做 RAG 吗?讲一下技术方案"
  • "什么是 Hybrid Search?为什么要在 RAG 中使用混合检索?"

记忆口诀

离线四步:解析 → 切分 → 向量化 → 入库。口诀:"解析文本切小块,编码成量存入库"

在线六步:预处理 → 检索 → 融合 → 重排 → 组装 → 生成。口诀:"先改写再双路搜,融合之后做精排,塞进 Prompt 让模型来答"

总结

RAG 完整流程分两大阶段:离线阶段做知识库构建(文档解析 → 切分 → 向量化 → 入库),在线阶段做查询生成(Query 预处理 → 检索 → 融合 → Rerank → Prompt 组装 → LLM 生成)。Java 生态中 Spring AI 通过 QuestionAnswerAdvisor 一行代码搞定 RAG,LangChain4j 通过 AiServices 提供了更灵活的定制能力。面试中重点把每个环节的关键决策点讲清楚,再提一嘴 Hybrid Search 和 Rerank 这两个 "效果提升利器",基本就稳了。