springAI+向量数据库(SimpleVectorStore)+RAG深度理解

前言:本文是我在学习过程中的总结和思考,希望能帮助正在学习 Spring AI 的 Java 开发者少走弯路。经过前篇Spring AI入门之后,发现还是有些概念比较模糊不清,本文主要重在概念与流程上的理解,主要是加强向量数据库与RAG这块检索的理解过程,学习本文前请先学习springAI+向量数据库+RAG入门案例-CSDN博客,然后借助本文加强理解,彻底初步掌握springAI+向量数据库+RAG,由于前文为了方便学习,使用的是SimpleVectorStore(静态向量数据库),后续会继续学习介绍市面主流数据库,比如chroma等

为什么要写这篇文章?

网上关于 Spring AI 的中文资料还比较少,很多都是理论介绍,缺少完整的实战代码。我希望通过这篇文章,分享我踩过的坑、理解的原理、以及最终的工作代码。

首先:一句话理解核心

RAG 的本质:让大模型学会"先查资料,再回答",而不是"凭记忆瞎编"。

Spring AI 的作用:提供统一接口,让你用一套代码调用任何 AI 模型和向量数据库。

向量数据库的角色:充当"智能书架",通过语义理解找到最相关的知识。

其次:知识模块串联

1:为什么需要 RAG?

XML 复制代码
传统 LLM 调用:
用户问:"Spring AI 支持哪些向量数据库?"
LLM 答:"支持 Chroma、Pinecone..."(可能编造,可能过时)

RAG 增强后:
用户问:"Spring AI 支持哪些向量数据库?"
    ↓
1. 从向量数据库检索 → 找到官方文档中的准确信息
2. 构建 Prompt → "基于以下文档回答:..."
3. LLM 生成 → 准确答案(有据可查)

核心价值 :让 AI 的回答准确、可溯源、实时更新

2:完整数据流

XML 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        第一阶段:准备知识库                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  原始文档                    向量化                    存储       │
│  ┌─────────────┐         ┌─────────────┐         ┌───────────┐ │
│  │ "Spring AI  │ ──────→ │ [0.12, -0.34, │ ──────→ │ 向量数据库 │ │
│  │  是一个..." │         │  0.56, ...]  │         │  Chroma/  │ │
│  └─────────────┘         └─────────────┘         │  Simple   │ │
│                                                   └───────────┘ │
│  EmbeddingModel 将文本 → 向量(语义编码)          存储供后续检索 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      第二阶段:RAG 问答流程                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  用户问题             向量检索               上下文              │
│  ┌─────────────┐     ┌─────────────┐      ┌─────────────┐      │
│  │ "什么是     │ ──→ │ 找最相似的  │ ──→  │ 拼接成      │      │
│  │  Spring AI?"│     │ 3个文档"    │      │ 文本块"     │      │
│  └─────────────┘     └─────────────┘      └─────────────┘      │
│        │                   │                     │              │
│        │                   │                     ↓              │
│        │                   │              ┌─────────────┐      │
│        │                   │              │ 构建Prompt  │      │
│        │                   │              │ "基于以下   │      │
│        │                   │              │  上下文回答"│      │
│        │                   │              └─────────────┘      │
│        │                   │                     │              │
│        ↓                   ↓                     ↓              │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    LLM 生成答案                         │   │
│  │  "Spring AI 是 Spring 官方推出的 AI 应用开发框架..."    │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3:核心技术概念串联

概念 一句话解释 在你的项目中的体现
向量 文本的数字表示,语义相似则向量相近 [0.12, -0.34, ...]
Embedding 把文本变成向量的过程 embeddingModel.embed(text)
向量数据库 存储和检索向量的数据库 SimpleVectorStore
相似度检索 找与问题最像的文档 vectorStore.similaritySearch()
Prompt 给 AI 的指令+上下文 buildPrompt(question, context)
LLM 生成答案的大模型 通义千问 (qwen-turbo)
RAG 检索+增强+生成的架构 askQuestion() 方法

4:代码与概念的对应

java 复制代码
// ========== 代码 ==========
public String askQuestion(String question) {
    // 1. 检索阶段 ------ 这就是 R 的体现
    List<Document> docs = vectorStore.similaritySearch(
        SearchRequest.builder()
            .query(question)      // 用户问题 → 向量化 → 匹配
            .topK(3)              // 返回最相似的3个
            .build()
    );
    
    // 2. 增强阶段 ------ 这就是 A 的体现
    String context = docs.stream()
        .map(Document::getText)   // 提取文本
        .collect(joining("\n\n---\n\n"));  // 拼接
    
    String prompt = buildPrompt(question, context);
    // prompt 内容:
    // "基于以下上下文回答:
    //  上下文:{检索到的文档}
    //  问题:{用户问题}"
    
    // 3. 生成阶段 ------ 这就是 G 的体现
    return chatClient.prompt(prompt).call().content();
    // 调用 LLM,基于增强后的 Prompt 生成答案
}

5:技术栈全景图

XML 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                         RAG 系统                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Spring Boot 3.5.13                   │   │
│  │  ┌─────────────────────────────────────────────────┐   │   │
│  │  │              Spring AI 1.1.3                    │   │   │
│  │  │  ┌───────────┐  ┌───────────┐  ┌───────────┐  │   │   │
│  │  │  │ChatClient │  │VectorStore│  │Embedding  │  │   │   │
│  │  │  │  抽象层   │  │  抽象层   │  │  抽象层   │  │   │   │
│  │  │  └─────┬─────┘  └─────┬─────┘  └─────┬─────┘  │   │   │
│  │  └────────┼───────────────┼───────────────┼────────┘   │   │
│  │           │               │               │            │   │
│  │           ↓               ↓               ↓            │   │
│  │  ┌─────────────────────────────────────────────┐      │   │
│  │  │              Spring AI Alibaba              │      │   │
│  │  │          (DashScope 适配器)                 │      │   │
│  │  └─────────────────────────────────────────────┘      │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                 │
│                              ↓                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                   阿里云 DashScope                       │   │
│  │  ┌───────────────┐  ┌───────────────────────────────┐  │   │
│  │  │ 通义千问 LLM  │  │ text-embedding-v2 向量模型    │  │   │
│  │  │ (qwen-turbo) │  │                               │  │   │
│  │  └───────────────┘  └───────────────────────────────┘  │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                   向量存储层                             │   │
│  │  ┌─────────────────────────────────────────────────┐   │   │
│  │  │        SimpleVectorStore (当前)                  │   │   │
│  │  │              ↓ 可切换                            │   │   │
│  │  │        Chroma / Pinecone / Qdrant               │   │   │
│  │  └─────────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6:请求处理全链路

XML 复制代码
用户: curl -X POST ... -d '{"question": "什么是Spring AI?"}'
                    ↓
┌─────────────────────────────────────────────────────────────┐
│ 1. 控制器层 (RagController)                                 │
│    @PostMapping("/ask")                                     │
│    String answer = ragService.askQuestion(question)         │
└─────────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 服务层 (RagService.askQuestion)                          │
│                                                             │
│    步骤1: 向量检索                                          │
│    vectorStore.similaritySearch(question)                  │
│        → EmbeddingModel 将 question 转成向量                │
│        → 计算与所有文档向量的相似度                          │
│        → 返回相似度最高的3个文档                            │
│                                                             │
│    步骤2: 构建上下文                                        │
│    文档1: "Spring AI 是一个 Java AI 框架..."                │
│    文档2: "Spring AI 1.1.x 支持 Chroma..."                  │
│    → 拼接成: "文本1\n\n---\n\n文本2"                        │
│                                                             │
│    步骤3: 构建 Prompt                                       │
│    "请基于以下上下文回答:                                   │
│     上下文:{拼接后的文档}                                   │
│     问题:什么是Spring AI?"                                 │
│                                                             │
│    步骤4: 调用 LLM                                          │
│    chatClient.prompt(prompt).call().content()              │
│        → HTTP 请求到阿里云 DashScope                        │
│        → 通义千问生成答案                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 返回响应                                                 │
│    {"answer": "Spring AI 是 Spring 官方推出的..."}          │
└─────────────────────────────────────────────────────────────┘

7:关键决策点串联

XML 复制代码
决策1: 使用什么 LLM?
    → 选择阿里云通义千问
    原因: 中文友好、免费额度、国内服务稳定

决策2: 使用什么向量数据库?
    → 先用 SimpleVectorStore(内存)
    原因: 无需外部服务,快速上手
    后续: 可平滑切换到 Chroma

决策3: 如何存储知识?
    → 硬编码示例文档 + CommandLineRunner 自动加载
    后续: 从文件/数据库动态加载

决策4: 如何构建 Prompt?
    → 角色设定 + 上下文注入 + 规则约束
    核心: 明确告诉 AI "有信息就答,没信息就说不知道"

决策5: 如何部署?
    → 先本地运行,后续 Docker 容器化

8:掌握的核心能力清单

能力 具体体现
架构理解 能画出 RAG 完整流程图
代码实现 能独立编写完整的 RAG 服务
问题定位 能解决依赖冲突、配置问题
原理掌握 理解向量化、相似度计算、Prompt 工程
扩展能力 知道如何切换模型、数据库
生产思维 考虑过 Docker 部署、健康检查

用一句话串联所有知识

复制代码
用户在 Spring Boot 应用中通过 REST API 提问,
Spring AI 框架使用 EmbeddingModel 将问题向量化,
在 VectorStore 中检索最相关的知识文档,
将检索结果作为上下文增强 Prompt,
最后通过 ChatClient 调用通义千问 LLM 生成精准答案,
整个过程就是 RAG(检索增强生成)。

知识关联图

┌─────────────────┐

│ 用户问题 │

└────────┬────────┘

┌────────────────────────────────────────────────────────────┐

│ Spring AI │

│ ┌──────────────────────────────────────────────────────┐ │

│ │ ChatClient │ │

│ │ ┌────────────────────────────────────────────────┐ │ │

│ │ │ RAG 流程 │ │ │

│ │ │ │ │ │

│ │ │ EmbeddingModel → VectorStore → Prompt │ │ │

│ │ │ ↓ ↓ ↓ │ │ │

│ │ │ 向量化 语义检索 Prompt工程 │ │ │

│ │ │ ↓ ↓ ↓ │ │ │

│ │ │ [0.12,...] 找到文档 "基于上下文..." │ │ │

│ │ │ ↓ │ │ │

│ │ │ 调用 LLM │ │ │

│ │ └────────────────────────────────────────────────┘ │ │

│ └──────────────────────────────────────────────────────┘ │

└────────────────────────────────────────────────────────────┘

┌─────────────────┐

│ 精准答案 │

└─────────────────┘

向量数据库深度理解

1、核心本质

向量数据库 = 专门为"找相似"而生的数据库

sql 复制代码
传统数据库:回答"有没有"
    SELECT * FROM users WHERE id = 100
    → 精确查找,结果确定

向量数据库:回答"像不像"
    vectorStore.similaritySearch("汽车")
    → 相似度查找,结果按相似度排序

2、为什么需要专门的数据结构?

java 复制代码
// 如果用手工计算(不可行)
for (Document doc : allDocuments) {
    float similarity = cosine(queryVector, doc.vector);
    if (similarity > threshold) {
        results.add(doc);
    }
}
// 问题:O(n) 复杂度,100万文档需要1秒+,太慢!

// 向量数据库的解决方案
// 使用索引结构(如 HNSW、IVF),将复杂度降到 O(log n)

向量化深度解析

1、向量的数学本质

java 复制代码
// 一个文本的向量表示
"Spring AI" → [0.12, -0.34, 0.56, 0.78, -0.23, ...]
             ↑
             这个向量在高维空间中的位置
             决定了它的"语义"

// 向量的维度
- 1536维(OpenAI ada-002)
- 1024维(阿里云 text-embedding-v2)
- 768维(BERT)

// 每个维度代表什么?
实际上无法直接解释------这是模型学习到的"特征"
就像人脑的神经元,知道它工作,但不知道为什么

2、相似度计算的三种方式

java 复制代码
// 1. 余弦相似度(最常用)
// 关注"方向"而非"长度"
// 公式:cos(θ) = (A·B) / (|A|×|B|)
// 范围:[-1, 1],越大越相似
// 适合:文本相似度

float cosineSimilarity = dotProduct(a, b) / (norm(a) * norm(b));

// 2. 欧氏距离(关注绝对距离)
// 公式:d = √Σ(ai - bi)²
// 范围:[0, ∞),越小越相似
// 适合:图像识别、聚类

float euclideanDistance = sqrt(sumOfSquares(a, b));

// 3. 点积(关注投影)
// 公式:dot = Σ(ai × bi)
// 范围:(-∞, ∞),越大越相似
// 适合:已归一化的向量

float dotProduct = sum(a[i] * b[i]);

3、在前文springAI+向量数据库+RAG入门案例-CSDN博客代码中的体现

java 复制代码
// 前文项目中
SearchRequest.builder()
    .query(question)
    .similarityThreshold(0.5)  // 余弦相似度阈值
    .build();

// 内部计算(简化)
float[] queryVector = embeddingModel.embed(question);
float[] docVector = getDocumentVector(doc);

float similarity = cosine(queryVector, docVector);
if (similarity > 0.5) {
    // 保留该文档
}

向量数据库的核心算法

1、近似最近邻搜索(ANN)

java 复制代码
// 暴力搜索(精确,但慢)
// 时间复杂度:O(n)
// 100万文档 × 1536维 ≈ 需要扫描所有

// 近似搜索(快,但可能遗漏极少数)
// 时间复杂度:O(log n)
// 通过索引结构,只扫描一小部分

// 类比:找一本特定的书
// 暴力搜索:检查每一本书
// 索引搜索:去"计算机-编程-Java"书架找

2、主流索引算法对比

算法 原理 优点 缺点 适用场景
HNSW 分层导航小世界图 查询极快,精度高 内存占用大 高精度要求
IVF 倒排文件索引 内存占用小 精度略低 海量数据
PQ 乘积量化 压缩率高 精度损失 内存受限
LSH 局部敏感哈希 理论成熟 需要调参 近似查询

3、前文项目springAI+向量数据库+RAG入门案例-CSDN博客中的索引

java 复制代码
// SimpleVectorStore(你当前使用的)
// 实现:简单的列表存储,无索引
// 每次检索都 O(n) 扫描所有文档
// 适合:< 10000 文档,开发测试

// Chroma(后续可切换)
// 实现:HNSW 索引
// 检索速度:O(log n)
// 适合:生产环境,大规模数据

向量数据库的存储结构

1、前文项目springAI+向量数据库+RAG入门案例-CSDN博客中数据存储模型

bash 复制代码
// 向量数据库中的一条记录
{
  "id": "doc_001",
  "text": "Spring AI 是一个 Java AI 框架",
  "vector": [0.12, -0.34, 0.56, ...],  // 1024维
  "metadata": {
    "source": "official",
    "topic": "spring-ai",
    "created_at": "2026-01-01"
  }
}

2、内存 vs 磁盘存储

java 复制代码
内存存储(SimpleVectorStore):
┌─────────────────────────────────────┐
│ 向量数据全部在内存中                  │
│ 优点:查询极快                       │
│ 缺点:重启丢失,内存占用大            │
│ 适合:开发测试、小数据量              │
└─────────────────────────────────────┘

磁盘存储(Chroma、Qdrant):
┌─────────────────────────────────────┐
│ 向量数据持久化到磁盘                  │
│ 优点:数据持久,可扩展                │
│ 缺点:查询稍慢                       │
│ 适合:生产环境、大数据量              │
└─────────────────────────────────────┘

RAG 深度理解

RAG 的本质:解决大模型的"记忆困境"

1、核心问题:为什么需要 RAG?

java 复制代码
传统大模型(LLM)的局限:

┌─────────────────────────────────────────────────────────────┐
│                    LLM 的知识来源                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  训练数据(截至某个时间点)                                  │
│       ↓                                                     │
│  模型参数中编码的知识                                        │
│       ↓                                                     │
│  无法访问私有数据、实时数据、更新数据                         │
│                                                             │
│  问题:                                                      │
│  ❌ 知识过时(训练数据截止后的事件不知道)                    │
│  ❌ 无法访问私有数据(公司内部文档、个人笔记)                │
│  ❌ 产生幻觉(不知道的事会编造)                             │
│  ❌ 无法溯源(不知道答案从哪来)                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2、RAG 的解决方案

java 复制代码
RAG = 检索 + 增强 + 生成

┌─────────────────────────────────────────────────────────────┐
│                      RAG 工作流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户问题                                                    │
│      ↓                                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 1. 检索 (Retrieval)                                 │   │
│  │    从外部知识库中找到相关信息                         │   │
│  │    → 向量检索、关键词检索、混合检索                    │   │
│  └─────────────────────────────────────────────────────┘   │
│      ↓                                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 2. 增强 (Augmented)                                 │   │
│  │    将检索结果注入 Prompt                             │   │
│  │    → "基于以下上下文回答:{检索到的内容}"             │   │
│  └─────────────────────────────────────────────────────┘   │
│      ↓                                                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 3. 生成 (Generation)                                │   │
│  │    LLM 基于增强后的 Prompt 生成答案                   │   │
│  │    → 准确、可溯源、实时                              │   │
│  └─────────────────────────────────────────────────────┘   │
│      ↓                                                      │
│  精准答案                                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

RAG 架构深度解析

java 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                           RAG 系统架构                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                      离线阶段(索引构建)                        │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │                                                                 │   │
│  │  原始数据 ──→ 文档加载 ──→ 文本分割 ──→ 向量化 ──→ 向量存储    │   │
│  │    ↓            ↓            ↓           ↓           ↓          │   │
│  │  PDF/Word    Document    Chunks     Embedding    VectorDB      │   │
│  │  HTML/DB     Reader      Splitter    Model       Index         │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                    ↓                                    │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                      在线阶段(问答推理)                        │   │
│  ├─────────────────────────────────────────────────────────────────┤   │
│  │                                                                 │   │
│  │  用户问题 ──→ 问题向量化 ──→ 向量检索 ──→ 上下文构建          │   │
│  │      ↓            ↓            ↓            ↓                   │   │
│  │  "什么是     Embedding    similarity   Document → Prompt       │   │
│  │   RAG?"      Model        Search       拼接                     │   │
│  │                                          ↓                      │   │
│  │                                    ┌─────────────┐             │   │
│  │                                    │   Prompt    │             │   │
│  │                                    │ 增强+问题   │             │   │
│  │                                    └─────────────┘             │   │
│  │                                          ↓                      │   │
│  │                                    ┌─────────────┐             │   │
│  │                                    │   LLM 生成  │             │   │
│  │                                    │  最终答案   │             │   │
│  │                                    └─────────────┘             │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

1、核心组件职责

组件 职责 输入 输出 在你的项目中
文档加载器 从各种源读取文档 PDF/Word/URL Document 列表 addDocuments()
文本分割器 将长文档切分为块 Document 多个 Chunk 未实现(可扩展)
Embedding 模型 将文本转为向量 文本 向量数组 text-embedding-v2
向量数据库 存储和检索向量 向量 + 文本 相似文档 SimpleVectorStore
检索器 找到相关文档 问题向量 文档列表 vectorStore.similaritySearch()
Prompt 模板 构建增强提示 上下文+问题 完整 Prompt buildPrompt()
LLM 生成答案 Prompt 答案文本 通义千问

2、RAG 的三种实现模式

1 Naive RAG(基础 RAG)

java 复制代码
// 前文项目就是这种模式
public String askQuestion(String question) {
    // 1. 检索
    List<Document> docs = vectorStore.similaritySearch(question);
    
    // 2. 增强
    String context = docs.stream()
        .map(Document::getText)
        .collect(joining("\n\n---\n\n"));
    String prompt = buildPrompt(question, context);
    
    // 3. 生成
    return chatClient.prompt(prompt).call().content();
}

特点

  • ✅ 简单直接

  • ✅ 适合大多数场景

  • ❌ 检索质量影响整体效果

  • ❌ 无法处理复杂查询

2、Advanced RAG(高级 RAG)

java 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      高级 RAG 流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  用户问题                                                        │
│      ↓                                                          │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 1. 查询重写 (Query Rewriting)                           │   │
│  │    "Spring AI 支持哪些数据库?"                         │   │
│  │    → "Spring AI supported vector databases list"       │   │
│  └─────────────────────────────────────────────────────────┘   │
│      ↓                                                          │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 2. 混合检索 (Hybrid Search)                             │   │
│  │    向量检索 → [Doc1, Doc2]                               │   │
│  │    关键词检索 → [Doc3, Doc4]                             │   │
│  │    融合 → [Doc1, Doc3, Doc2, Doc4]                      │   │
│  └─────────────────────────────────────────────────────────┘   │
│      ↓                                                          │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 3. 重排序 (Reranking)                                   │   │
│  │    使用交叉编码器重新计算相关性                           │   │
│  │    [Doc1:0.95, Doc3:0.89, Doc2:0.76, Doc4:0.45]        │   │
│  └─────────────────────────────────────────────────────────┘   │
│      ↓                                                          │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 4. 上下文压缩 (Context Compression)                     │   │
│  │    提取最相关的段落,去除冗余                             │   │
│  └─────────────────────────────────────────────────────────┘   │
│      ↓                                                          │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 5. 生成 + 引用 (Generation with Citation)               │   │
│  │    答案 + 来源引用                                        │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3、Modular RAG(模块化 RAG)

java 复制代码
// 可插拔的模块化设计
public interface RAGModule {
    List<Document> retrieve(String query);
    String rerank(List<Document> docs);
    String compress(String context);
    String generate(String prompt);
}

// 可以根据场景灵活组合
RAGPipeline pipeline = RAGPipeline.builder()
    .retriever(new HybridRetriever())      // 混合检索
    .reranker(new CrossEncoderReranker())  // 重排序
    .compressor(new LLMCompressor())       // 上下文压缩
    .generator(new ChatGenerator())        // LLM 生成
    .build();

RAG 的关键优化技术

1、检索优化

java 复制代码
// 1. 查询重写(Query Rewriting)
public String rewriteQuery(String originalQuery) {
    // 将口语化问题转换为更适合检索的形式
    return chatClient.prompt("""
        将以下问题改写为更清晰、更适合检索的形式:
        原始问题:%s
        改写后:
        """.formatted(originalQuery)).call().content();
}

// 2. 多路检索融合
public List<Document> hybridSearch(String query) {
    // 向量检索
    List<Document> vectorResults = vectorSearch(query);
    
    // 关键词检索(BM25)
    List<Document> keywordResults = bm25Search(query);
    
    // 融合(RRF - Reciprocal Rank Fusion)
    return fusion(vectorResults, keywordResults);
}

// 3. 检索后重排序
public List<Document> rerank(String query, List<Document> candidates) {
    // 使用交叉编码器精排
    return candidates.stream()
        .sorted((a, b) -> Double.compare(
            crossEncoder.score(query, b.getText()),
            crossEncoder.score(query, a.getText())
        ))
        .limit(3)
        .collect(Collectors.toList());
}

2、索引优化

java 复制代码
// 1. 文档分块策略
public List<Document> chunkDocument(Document doc) {
    // 不同策略
    // 固定大小分块
    List<Document> fixedChunks = splitByFixedSize(doc, 500);
    
    // 语义分块(按段落、句子)
    List<Document> semanticChunks = splitBySemantic(doc);
    
    // 重叠分块(避免上下文丢失)
    List<Document> overlappingChunks = splitWithOverlap(doc, 500, 50);
    
    return semanticChunks;  // 选择最佳策略
}

// 2. 元数据增强
public Document enrichMetadata(Document doc) {
    Map<String, Object> metadata = new HashMap<>();
    metadata.put("source", doc.getSource());
    metadata.put("topic", extractTopic(doc.getText()));
    metadata.put("entities", extractEntities(doc.getText()));
    metadata.put("timestamp", System.currentTimeMillis());
    return new Document(doc.getText(), metadata);
}

3、生成优化

java 复制代码
// 1. 动态 Prompt 构建
public String buildAdaptivePrompt(String question, List<Document> docs) {
    // 根据检索结果数量调整 Prompt
    if (docs.isEmpty()) {
        return "没有找到相关信息,请告知用户无法回答。";
    } else if (docs.size() == 1) {
        return buildSingleSourcePrompt(question, docs.get(0));
    } else {
        return buildMultiSourcePrompt(question, docs);
    }
}

// 2. 引用溯源
public String generateWithCitation(String question, List<Document> docs) {
    String prompt = """
        基于以下上下文回答问题,并在答案中引用来源。
        
        上下文:
        %s
        
        问题:%s
        
        要求:
        1. 在引用处标注 [来源N]
        2. 答案结束后列出所有来源
        """.formatted(buildContextWithId(docs), question);
    
    return chatClient.prompt(prompt).call().content();
}

4、前文springAI+向量数据库+RAG入门案例-CSDN博客项目优化

java 复制代码
// 可以添加评估代码
@Test
public void evaluateRAG() {
    List<TestCase> testCases = List.of(
        new TestCase("什么是Spring AI?", "Spring AI 是一个Java AI框架"),
        new TestCase("RAG是什么技术?", "检索增强生成")
    );
    
    for (TestCase tc : testCases) {
        String answer = ragService.askQuestion(tc.question);
        double score = evaluateAnswer(tc.expected, answer);
        System.out.printf("问题: %s\n答案: %s\n得分: %.2f\n\n", 
            tc.question, answer, score);
    }
}
java 复制代码
// 1. 添加缓存
@Cacheable(value = "ragCache", key = "#question")
public String askQuestion(String question) {
    // ...
}

// 2. 添加降级策略
public String askQuestionWithFallback(String question) {
    try {
        return askQuestion(question);
    } catch (Exception e) {
        // 降级到直接 LLM 调用
        return chatClient.prompt(question).call().content();
    }
}

// 3. 添加置信度评分
public AnswerWithConfidence askQuestionWithConfidence(String question) {
    List<Document> docs = vectorStore.similaritySearch(question);
    double confidence = calculateConfidence(docs);
    String answer = generateAnswer(question, docs);
    return new AnswerWithConfidence(answer, confidence);
}

一句话总结

RAG 不是简单的"检索+生成",而是一套让 LLM 能够"知之为知之,不知为不知"的完整技术体系,通过引入外部知识库,实现准确、可溯源、实时更新的智能问答。

  • ✅ 搭建生产级 RAG 系统

  • ✅ 诊断和优化检索问题

  • ✅ 设计适合业务场景的 RAG 架构

  • ✅ 评估和改进 RAG 效果

学习心得

1. 理解本质,比学会 API 更重要

刚开始学习时,我急于写代码,想快速看到效果。但很快发现,如果不理解 RAG 的本质,代码出了问题都不知道从哪里排查。

后来我花时间理解了:

  • 向量是文本的数字表示,语义相似则向量相近

  • RAG 的核心是"检索 → 增强 → 生成"三步骤

  • Prompt 工程是告诉 AI "有资料就答,没资料就说不知道"

理解了这些,写代码就变成了"用 Spring AI 的 API 实现这些概念",思路清晰了很多。

2. 问题是最好的老师

这一周遇到的坑不少:

  • 依赖版本不兼容

  • Chroma 启动失败

  • 集合自动创建不生效

  • 406 响应错误

每次遇到问题,我都是先看日志、再查资料、然后尝试解决。这个过程很费时间,但解决后的成就感是纯粹的。

一个建议:不要害怕问题。每一个错误都是一次学习的机会。当你解决了 10 个问题,你就掌握了 10 种排错能力。

3. 边学边写,是最高效的学习方式

我的学习路径很简单:定一个小目标 → 写代码实现 → 遇到问题 → 解决问题 → 总结记录

每一段代码,我都要求自己理解每一行的作用。每一个概念,我都尝试用自己的话解释清楚。这篇博客,就是我边学边写的产物。

写下来,是最好的学习。

完成了这个 RAG 项目,并不意味着学习的结束,而是新的开始。接下来打算:

方向 内容 目标
生产化 Docker 部署、性能优化、监控告警 让项目能真正上线
数据扩展 PDF 加载、文本分块、增量更新 处理真实业务数据
体验优化 流式响应、引用溯源、多轮对话 让用户用得更舒服
架构升级 Chroma 持久化、混合检索、重排序 提升检索质量

后续会继续分享学习文章,如果这篇文章对你有帮助,欢迎分享给更多需要的人。如果有任何问题或建议,欢迎在评论区交流。

一句话送给你

RAG 的本质,是让 AI 学会"知之为知之,不知为不知"。而学习的本质,是让我们自己,从"不知道"走向"知道"。

相关推荐
程序员鱼皮4 小时前
刚刚 Claude Code 源码泄露!我扒出了 11 个隐藏秘密
ai·程序员·编程·ai编程·claude
常利兵4 小时前
Spring Boot 牵手Spring AI,玩转DeepSeek大模型
人工智能·spring boot·spring
sg_knight4 小时前
使用 Claude Code 写单元测试的实战方法
单元测试·log4j·ai编程
可观测性用观测云5 小时前
Claude Code 意外开源:我们看到了每一个企业级 Agent 都需要行为分析
ai编程·监控
星如雨グッ!(๑•̀ㅂ•́)و✧5 小时前
Spring WebFlux中DataBufferLimitException异常的解决方案
java·后端·spring
心勤则明5 小时前
使用 Spring AI Alibaba MCP 结合 Nacos 实现企业级智能体应用
java·人工智能·spring
evan20205 小时前
FireRed-Image-Edit 一键衣物提取 懒人整合包
aigc
li星野6 小时前
AIGC简介
aigc