🔥 一文彻底搞懂 RAG(检索增强生成):原理、流程与架构演进(Python + Java 双版本实战)

作者: 你的CSDN昵称
专栏: 大模型技术栈
标签: RAG LLM 向量检索 Embedding 检索增强生成 Spring AI LangChain4j


一、引言

在大型语言模型(LLM)席卷全球的今天,一个无法回避的问题始终存在:模型知识有截止日期,且容易产生"幻觉"(Hallucination)。当用户询问最新事件、企业内部文档或专业领域知识时,纯靠模型参数记忆的回答往往不可靠。

RAG(Retrieval Augmented Generation,检索增强生成) 正是为解决这一痛点而生的核心技术。它通过将外部知识库"嫁接"到大模型上,让 LLM 在生成回答前先"查资料",从而显著提升回答的准确性、时效性和可溯源性。

本文将从你看到的原理图出发,逐层拆解 RAG 的完整工作流程,深入讲解 Embedding、向量相似度、检索策略等核心概念,并提供 Python + Java 双版本实战代码,无论你是哪个技术栈都能快速上手。


二、RAG 是什么?

RAG,全称 Retrieval Augmented Generation,中文译为检索增强生成

其核心思想非常直观:通过检索外部知识库的方式,增强大模型的生成能力

传统的 LLM 交互是"端到端"的:

用户提问 → LLM → 生成回答

而 RAG 在此基础上增加了一个检索环节

用户提问 → 检索相关知识 → 将知识注入Prompt → LLM → 生成回答

这个看似简单的改动,却从根本上解决了 LLM 的三大顽疾:

问题 传统 LLM RAG 增强后
知识时效性 训练数据有截止日期,无法回答最新信息 实时检索最新文档,知识随时更新
领域专业性 对垂直领域(法律、医疗、企业内部)知识薄弱 接入专业领域知识库,精准回答
幻觉问题 可能编造不存在的事实 基于检索到的真实文档生成,可溯源

三、RAG 核心工作流程详解

下面这张图展示了 RAG 的完整工作流程,我们将按照数据流向,逐个环节拆解:

在这里插入图片描述

3.1 用户查询输入(Query)

一切从用户的自然语言提问开始,例如:

“如何学习 Python?”

这是整个流程的触发点。用户的原始查询往往存在表述模糊、口语化、缺乏关键词等问题,这也是后续"查询改写"环节存在的原因。

3.2 Embedding 编码:文本 → 向量

这是 RAG 中最关键的"翻译"步骤。

Embedding 模型(如 OpenAI 的 text-embedding-ada-002、BGE、M3E 等)会将人类可读的文本转换为机器可计算的高维向量(通常 768 维或 1536 维)。

"如何学习 Python?"  →  Embedding模型  →  [0.21, -0.5, 0.2, ..., 0.23]

为什么必须转成向量?

因为计算机无法直接理解"语义",但它可以高效计算向量之间的距离。语义相近的文本,在向量空间中的距离也更近——这是整个 RAG 检索的理论基石。

3.3 向量检索:在知识库中找"最像"的

向量数据库(如 Milvus、Pinecone、Chroma、FAISS 等)中预先存储了大量文档片段的向量。当查询向量到来时,系统会计算它与库中所有向量的相似度,返回最相似的前 K 个结果(Top-K)。

在这里插入图片描述

🔍 相似度计算:余弦相似度

RAG 中最常用的相似度度量方式是余弦相似度(Cosine Similarity)

cos⁡(θ)=q⃗⋅d⃗∣∣q⃗∣∣⋅∣∣d⃗∣∣\cos(\theta) = \frac{\vec{q} \cdot \vec{d}}{||\vec{q}|| \cdot ||\vec{d}||}cos(θ)=∣∣q ∣∣∣∣d ∣∣q d

余弦相似度值 含义
→ 1 两个向量方向完全相同,文本语义高度相似
→ 0 两个向量正交,文本语义无关
→ -1 两个向量方向相反,文本语义对立

核心结论:余弦相似度越大,说明向量方向越接近,两点之间的距离越小。由于 RAG 中的向量都是由文本转换而来的,不同文本对应的向量余弦相似度越大,距离越近,文本相似度就越高

在实际应用中,通常会设置一个阈值(如 > 0.5),只有相似度超过阈值的文档才会被召回。

3.4 结果召回与重排序(Retrieval & Rerank)

初步检索可能返回数十甚至上百个候选文档,但并非所有都真正相关。因此需要:

  1. 召回(Recall):从海量文档中快速筛选出候选集(追求"不漏")
  2. 精排(Rerank):用更精细的模型对候选集重新打分排序(追求"更准")

常用的重排序模型有 Cohere Rerank、BGE-Reranker 等。

3.5 Prompt 组装:构造增强提示词

这是 RAG 的"灵魂步骤"。系统将检索到的相关文档片段与用户的原始问题拼接,构造出一个增强型 Prompt

你是一位专业的技术顾问。请根据以下参考资料回答用户问题。
如果参考资料不足以回答问题,请明确说明。

【参考资料】
1. Python入门教程:Python是一种解释型、面向对象的高级编程语言...
2. Python进阶指南:学习Python需要掌握数据结构、算法和面向对象编程...
3. Python数据分析:NumPy和Pandas是Python数据分析的核心库...

【用户问题】
如何学习Python?

请给出详细回答:

3.6 LLM 生成:基于上下文作答

增强后的 Prompt 被送入大语言模型。此时 LLM 拥有了"外挂大脑"——检索到的知识库上下文,能够:

  • 基于真实文档生成回答,而非凭空编造
  • 引用具体来源,提高可信度
  • 处理超出训练数据的最新/私有知识

四、关键技术深度解析

4.1 Embedding 模型选型

模型 维度 特点 适用场景
OpenAI text-embedding-ada-002 1536 通用性强,API便捷 快速原型、英文为主
OpenAI text-embedding-3 3072 最新版本,多语言优化 生产环境、多语言
BGE (BAAI) 1024 开源可本地部署,中文优秀 中文场景、私有化部署
M3E 768 轻量高效,中文语义好 资源受限、中文应用
E5 (Microsoft) 768 多任务训练,检索精度高 跨领域检索

4.2 向量数据库对比

特性 Milvus Pinecone Chroma FAISS Weaviate
部署方式 本地/云 纯SaaS 本地/轻量 本地库 本地/云
开源
十亿级规模
混合检索 需开发
易用性 中等 极高 极高 中等
最佳场景 企业级大规模 快速上线 原型开发 科研实验 多模态应用

4.3 相似度算法对比

算法 公式特点 优势 劣势 适用场景
余弦相似度 只考虑方向,不考虑长度 对文本长度不敏感,语义匹配稳定 忽略向量模长信息 文本语义检索(最常用)
欧氏距离 计算空间直线距离 直观,适合稠密向量 受向量模长影响大 图像向量、归一化后向量
点积 简单高效 计算快,适合内积型索引 需向量已归一化 特定ANN索引库
BM25 基于词频和文档长度 关键词匹配精准,可解释性强 无法理解语义 稀疏检索、混合检索

💡 实践建议:生产环境中推荐余弦相似度 + 混合检索(BM25 + 向量),兼顾语义理解和关键词精确匹配。


五、RAG 架构演进:从朴素到智能

RAG 并非一成不变,其架构经历了三个阶段的演进:

在这里插入图片描述

5.1 Naive RAG(朴素 RAG)

最基础的实现方式:查询 → Embedding → 向量检索 → Prompt 组装 → LLM 生成。

存在的问题

  • 检索精度低:用户查询表述不清,导致召回文档不相关
  • 上下文冗长:召回文档过多,超出 LLM 上下文窗口
  • 生成质量不稳定:缺乏对检索结果的验证机制

5.2 Advanced RAG(高级 RAG)

在朴素 RAG 基础上增加了一系列优化模块:

优化模块 作用 代表技术
查询重写(Query Rewriting) 将模糊查询改写为更精准的检索式 HyDE、Query2Doc
混合检索 同时用向量检索 + 关键词检索 BM25 + Dense Retrieval
重排序(Rerank) 对召回结果精细打分排序 BGE-Reranker、Cohere Rerank
上下文压缩 去除冗余信息,保留关键内容 LongLLMLingua、选择性上下文
多路召回 从多个索引源同时检索 多向量索引、父子文档索引

5.3 Agentic RAG(智能体 RAG)

最新的演进方向,将 RAG 与 AI Agent 结合:

能力 说明
路由决策 自动判断问题类型,选择最合适的知识库或工具
多 Agent 协作 多个专业 Agent 分别检索不同领域知识,再整合答案
工具调用 不仅检索文档,还能调用 API、数据库、计算工具
Self-RAG / 反思验证 LLM 自我评估检索结果是否足够,不足则主动重新检索
迭代优化 多轮检索-生成-验证循环,直到获得满意答案

六、极简实战:Python + Java 双版本

在这里插入图片描述

🎯 核心原则:无论 Python 还是 Java,RAG 的核心流程完全一致:文本 → Embedding → 向量检索 → Prompt 增强 → LLM 生成。区别只在于技术栈选型API 调用方式


6.1 Python 版本

基于 sentence-transformers + FAISS,最轻量的本地 RAG 实现:

from sentence_transformers import SentenceTransformer
import faiss
import numpy as np

# ========== 1. 加载 Embedding 模型 ==========
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')

# ========== 2. 准备知识库文档 ==========
documents = [
    "Python是一种解释型、面向对象的高级编程语言,语法简洁优雅。",
    "Java是一种静态类型、跨平台的编程语言,广泛应用于企业级开发。",
    "机器学习是人工智能的一个分支,通过数据训练模型进行预测。",
    "深度学习是机器学习的一个子集,使用多层神经网络提取特征。",
]

# ========== 3. 文档向量化并构建索引 ==========
doc_embeddings = model.encode(documents, normalize_embeddings=True)
dimension = doc_embeddings.shape[1]
index = faiss.IndexFlatIP(dimension)  # 内积索引(归一化后 = 余弦相似度)
index.add(np.array(doc_embeddings))

# ========== 4. 用户查询 ==========
query = "什么是Python?"
query_embedding = model.encode([query], normalize_embeddings=True)

# ========== 5. 检索 Top-3 ==========
scores, indices = index.search(np.array(query_embedding), k=3)

print(f"查询: {query}\n")
print("检索结果:")
for score, idx in zip(scores[0], indices[0]):
    print(f"  [相似度: {score:.4f}] {documents[idx]}")

# ========== 6. 组装 Prompt ==========
context = "\n".join([f"{i+1}. {documents[idx]}" for i, idx in enumerate(indices[0])])
prompt = f"""根据以下参考资料回答问题:

{context}

问题:{query}
请给出准确、简洁的回答:
"""

print("\n" + "="*50)
print("组装后的 Prompt:")
print(prompt)

运行结果:

查询: 什么是Python?

检索结果:
  [相似度: 0.9234] Python是一种解释型、面向对象的高级编程语言,语法简洁优雅。
  [相似度: 0.3456] 机器学习是人工智能的一个分支,通过数据训练模型进行预测。
  [相似度: 0.3123] Java是一种静态类型、跨平台的编程语言,广泛应用于企业级开发。

6.2 Java 版本(方案一:Spring AI 推荐)

Spring AI 是 Spring 官方推出的 AI 应用开发框架,是目前 Java 生态中实现 RAG 最标准、最优雅的方式。

依赖配置(pom.xml)
<dependencies>
    <!-- Spring AI Core -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>
    
    <!-- 向量数据库:以 Redis Vector 为例 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
    </dependency>
    
    <!-- 文档加载与解析 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
    </dependency>
</dependencies>
配置文件(application.yml)
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      embedding:
        options:
          model: text-embedding-3-small
    vectorstore:
      redis:
        index: rag-documents
        prefix: doc
        initialize-schema: true
RAG 核心实现代码
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/rag")
public class RagController {

    @Autowired
    private VectorStore vectorStore;  // 自动注入 Redis Vector Store
    
    @Autowired
    private ChatClient chatClient;    // 自动注入 OpenAI ChatClient

    // ========== 1. 文档入库(知识库构建) ==========
    @PostMapping("/ingest")
    public String ingestDocuments(@RequestBody List<String> texts) {
        // 将文本转换为 Document 对象并写入向量库
        List<Document> documents = texts.stream()
            .map(text -> new Document(text))
            .collect(Collectors.toList());
        
        vectorStore.add(documents);
        return "成功入库 " + documents.size() + " 条文档";
    }

    // ========== 2. RAG 问答 ==========
    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        // Step 1: 向量检索 —— 查询相似度最高的 Top-3 文档
        SearchRequest searchRequest = SearchRequest.builder()
            .query(question)
            .topK(3)
            .similarityThreshold(0.5)  // 相似度阈值过滤
            .build();
        
        List<Document> relevantDocs = vectorStore.similaritySearch(searchRequest);
        
        // Step 2: 提取检索到的上下文
        String context = relevantDocs.stream()
            .map(Document::getContent)
            .collect(Collectors.joining("\n---\n"));
        
        // Step 3: 组装增强 Prompt
        String systemPrompt = """
            你是一位专业的技术顾问。请严格根据以下参考资料回答用户问题。
            如果参考资料不足以回答问题,请明确说明"根据现有资料无法回答"。
            
            【参考资料】
            {context}
            """;
        
        SystemPromptTemplate promptTemplate = new SystemPromptTemplate(systemPrompt);
        Prompt prompt = promptTemplate.create(Map.of("context", context));
        
        // Step 4: 调用 LLM 生成回答
        String answer = chatClient.prompt(prompt)
            .user(question)
            .call()
            .content();
        
        // 返回带溯源信息的回答
        String sources = relevantDocs.stream()
            .map(doc -> "- " + doc.getContent().substring(0, Math.min(50, doc.getContent().length())) + "...")
            .collect(Collectors.joining("\n"));
        
        return "【回答】\n" + answer + "\n\n【参考来源】\n" + sources;
    }
}
使用方式
# 1. 向知识库添加文档
curl -X POST http://localhost:8080/api/rag/ingest \
  -H "Content-Type: application/json" \
  -d '["Python是一种解释型编程语言...", "Java广泛应用于企业级开发..."]'

# 2. 提问
curl "http://localhost:8080/api/rag/ask?question=什么是Python?"

6.3 Java 版本(方案二:LangChain4j 轻量版)

如果你不想用 Spring Boot,LangChain4j 提供了更轻量的纯 Java API:

依赖(pom.xml)
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>0.31.0</version>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-embeddings-bge-small-zh</artifactId>
    <version>0.31.0</version>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai</artifactId>
    <version>0.31.0</version>
</dependency>
纯 Java RAG 实现
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.bge.small.zh.BgeSmallZhEmbeddingModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;

import java.util.List;

public class SimpleRagExample {
    
    public static void main(String[] args) {
        // ========== 1. 初始化 Embedding 模型(本地运行,无需 API Key) ==========
        EmbeddingModel embeddingModel = new BgeSmallZhEmbeddingModel();
        
        // ========== 2. 初始化内存向量存储(生产环境可替换为 Milvus/Redis) ==========
        EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        
        // ========== 3. 知识库文档入库 ==========
        String[] documents = {
            "Python是一种解释型、面向对象的高级编程语言,语法简洁优雅。",
            "Java是一种静态类型、跨平台的编程语言,广泛应用于企业级开发。",
            "机器学习是人工智能的一个分支,通过数据训练模型进行预测。",
            "深度学习是机器学习的一个子集,使用多层神经网络提取特征。"
        };
        
        for (String text : documents) {
            TextSegment segment = TextSegment.from(text, new Metadata());
            Embedding embedding = embeddingModel.embed(segment).content();
            embeddingStore.add(embedding, segment);
        }
        System.out.println("✅ 知识库入库完成,共 " + documents.length + " 条文档\n");
        
        // ========== 4. 用户查询 ==========
        String query = "什么是Python?";
        System.out.println("🔍 用户查询: " + query + "\n");
        
        // ========== 5. 查询向量化 + 相似度检索 ==========
        Embedding queryEmbedding = embeddingModel.embed(query).content();
        List<EmbeddingMatch<TextSegment>> matches = embeddingStore
            .findRelevant(queryEmbedding, 3);  // Top-3
        
        // ========== 6. 打印检索结果 ==========
        System.out.println("📚 检索结果:");
        StringBuilder context = new StringBuilder();
        for (int i = 0; i < matches.size(); i++) {
            EmbeddingMatch<TextSegment> match = matches.get(i);
            System.out.printf("  [%d] 相似度: %.4f | %s%n", 
                i + 1, match.score(), match.embedded().text());
            context.append(match.embedded().text()).append("\n");
        }
        
        // ========== 7. 组装 Prompt 并调用 LLM(可选) ==========
        String prompt = String.format("""
            根据以下参考资料回答问题:
            
            %s
            
            问题:%s
            请给出准确、简洁的回答:
            """, context, query);
        
        System.out.println("\n" + "=".repeat(50));
        System.out.println("📝 组装后的 Prompt:");
        System.out.println(prompt);
        
        // 如需调用 LLM,取消下面注释(需要配置 OpenAI API Key)
        /*
        OpenAiChatModel chatModel = OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-3.5-turbo")
            .build();
        String answer = chatModel.generate(prompt);
        System.out.println("\n🤖 LLM 回答:\n" + answer);
        */
    }
}

6.4 Python vs Java 实现要点对比

对比项 Python Java
Embedding 模型加载 sentence-transformers 一行代码 BgeSmallZhEmbeddingModel 或 Spring AI 自动配置
向量存储 FAISS / Chroma 轻量库 Redis Vector / PGVector / Milvus(企业级)
框架成熟度 LangChain / LlamaIndex 生态最丰富 Spring AI / LangChain4j 快速追赶中
部署方式 脚本运行 / FastAPI 服务 Spring Boot 微服务 / 企业级架构
学习曲线 更平缓,AI 生态最完善 需要 Spring 基础,但企业集成度更高
推荐场景 算法实验、快速原型、研究 企业级应用、现有 Java 系统接入

七、总结

RAG 的本质是给大模型装上"外接大脑"。它通过三个核心步骤实现知识增强:

  1. 向量化:用 Embedding 模型将文本语义映射到高维向量空间
  2. 相似检索:通过余弦相似度等度量,在向量库中找到语义最相关的文档
  3. 提示增强:将检索结果注入 Prompt,引导 LLM 基于真实知识生成回答

从 Naive RAG 到 Advanced RAG 再到 Agentic RAG,这一技术栈正在快速进化。无论你是 Python 开发者还是 Java 开发者,掌握 RAG 原理都是每一个大模型应用开发者的必修课。


📌 推荐阅读

  • 《LangChain 实战:从入门到精通》
  • 《Spring AI 官方文档》:https://docs.spring.io/spring-ai/reference/
  • 论文:Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (Lewis et al., 2020)
  • 论文:Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection (Asai et al., 2023)

如果这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、评论 💬,你的支持是我持续创作的动力!


更多推荐