MCP 让 LLM 具备了调用外部程序的能力,能够完成自动化工作如获取上下文、操作文件系统等。而 RAG 同样能让 LLM 自动获取外部信息来增强上下文,不同的是 MCP 更偏向工具调用,用途广泛;RAG 则专注于知识检索,能从数据库中找出与问题相关的知识来增强 LLM 的上下文信息。
概述
背景
当前 LLM 生成的内容都基于训练时的已知信息,无法访问外部数据,因此难以回答训练数据以外的问题。例如询问内部文档内容时,LLM 可能产生错误回答,这种现象称为大模型幻觉。为解决幻觉问题,可以将文档内容与问题一起发送给 LLM,但当文档体量庞大时,相关上下文可能只是其中一小段,LLM 仍可能无法准确聚焦重点。这时需要具备检索能力的工具自动找出最相关的上下文片段交给 LLM,这正是 RAG 要解决的核心问题。
RAG 技术解析
RAG(Retrieval Augmented Generation)即检索增强生成,其核心思想是在 LLM 回答前,先通过检索系统从外部知识库找出相关问题内容,将这些内容与原始问题共同输入 LLM。
结合 RAG 的 LLM 回答流程如下:
- 用户提出问题
- 检索系统搜索知识库中的相关内容
- 知识库返回相关知识
- 检索系统将问题和知识发送给 LLM
- LLM 生成答案并返回给用户

Embedding 机制
RAG 实施过程中需要解决两个关键问题:如何高效检索相关知识内容,以及知识库的存储方式。
针对第一个问题,RAG 引入 Embedding 模型,该模型将输入文本转换为固定长度的浮点型数组。例如 OpenAI 的 text-embedding-3-small 模型输出 1536 维数组,text-embedding-3-large 输出 3076 维数组。内容越相似,生成的数组也越相似,通过计算数组间距离即可判断文本相似度。
这类似于坐标系概念,数组可映射到高维坐标系中的点,通过点间距离衡量相似度。以下图三维坐标系为例,越接近的文字其映射点也越接近。

针对第二个问题,传统关系型数据库适合精确匹配检索,而 RAG 需要语义相似度检索,因此引入向量数据库作为知识库。向量数据库专门存储和检索高维向量,支持相似性搜索,能存储文本、音频、视频等非结构化数据经过 Embedding 后生成的向量,并能快速找到最相似的向量。常用向量数据库包括 Pinecone、Chroma、PostgreSQL + PGVector 等。
技术原理
工作流程
RAG 工作流程分为离线和在线两部分:
- 离线部分:知识准备过程,提前上传文档并通过 Embedding 模型转换为向量存储到向量数据库
- 在线部分:实时问答过程,用户问题经 Embedding 转换后,在知识库中检索最相似知识片段,与问题一起传入 LLM 生成答案
完整流程如下图所示:

文档分块(Chunking)
Chunking 指将文档分割成多个片段,分割质量直接影响后续检索准确性和 LLM 回答效果。在 RAG 中进行分块操作是因为相关问题可能只涉及文档的一小部分,直接提交全部文档可能导致 LLM 无法聚焦重点。
常见分块策略:
| 策略 |
描述 |
优点 |
缺点 |
| 固定大小拆分 |
按指定字数或 token 数切分 |
实现简单、速度快 |
可能割裂语句完整性 |
| 结构拆分 |
基于文档格式(HTML、Markdown)拆分 |
保留原始结构逻辑 |
依赖规范文档结构 |
| 语义拆分 |
根据语义边界(段落、句子)拆分 |
符合人类理解 |
实现复杂、计算开销大 |
| 递归拆分 |
先按大分隔符拆分,再逐级细化 |
平衡语义完整性和长度控制 |
设计复杂 |
这些策略可组合使用,目前没有适用于所有文档的统一策略,需要根据具体情况选择。
索引构建(Indexing)
在 RAG 中需要对向量构建索引以实现高效相似度计算。向量索引是用于高效索引和检索高维向量的数据结构,常用 ANN(近似最近邻搜索算法)在牺牲少量准确性同时显著提升搜索速度。
常见实现方式:
LSH(局部敏感哈希算法)
基于哈希结构的算法,设计哈希函数使相似向量映射到相同哈希桶的概率高。查询时只搜索相同或相似桶中的数据,避免全表扫描。

Annoy算法
基于树结构的算法,构建"随机投影二叉树"划分向量空间。算法流程:随机选择两个向量生成超平面切割空间→投影所有向量→按中位数分割→递归划分直到向量数量小于阈值。搜索时递归查找最近叶节点作为候选向量。

HNSW(分层导航小世界算法)
基于图结构的算法,构建分层图网络,高层用于快速定位,低层用于精细搜索,类似跳表结构。

IVF(倒排文件索引)
基于聚类的算法,通过 k-means 聚类将向量分簇,每个簇建立倒排表。查询时找到最近几个簇,只在这些簇中搜索最近邻向量。

这些算法都不保证找出真正最相近的向量,而是在部分候选区域查找以换取速度,需要权衡性能、准确性和计算资源。
相似度搜索
RAG 中通过计算向量距离判断相似度,常见计算方式:
欧几里得距离
计算两个向量间的直线距离,公式:
优点:简单直观,适合距离感任务;缺点:对长度敏感,不适合语义相似向量。
点积
计算两个向量乘积之和,公式:
优点:简单高效,适合含权重的 Embedding 模型;组成:向量长度和语义方向相似性。
余弦相似度
计算两个向量夹角的余弦值,公式:
优点:与语义方向一致,长度不同的相似文本仍有高相似度;取值范围 [-1,1],越接近1越相似。
重排序(Re-ranking)
向量索引算法虽快但牺牲了准确性,检索出的文档可能相似度高却不真正相关。因为"相似度"≠"相关性",相似度衡量语义相似,但不一定对问题有帮助。
例如查询"李清照文学风格",向量相似度高的可能包括杜甫相关文档,因为都是古代诗人,语义相似但不相关。
因此需要重排序操作:初步检索时获取较多候选文档(如 top-20),然后使用其他模型进行精细匹配,选出最相关的少量文档(如 top-5)。重排序模型在相关性排序上更准确但计算代价更高,通常只在 top-20/50 上运行。

提示模板(Prompt Template)
在 RAG 中,Prompt Template 将检索到的文档和用户问题组合成格式化 prompt 交给 LLM,能让 LLM 更聚焦上下文,减少幻觉问题,使回答更稳定专业。
可根据任务类型自定义模板,常见格式:
You are a helpful assistant. Based on the following context, answer the question.
Context:
{retrieved_documents}
Question:
{user_query}
Answer:
流程总结
回顾完整流程图,详细描述如下:
离线部分:
- 对上传文档分块,分割成多个片段
- 使用 Embedding 模型将片段转换为向量
- 将向量存储到向量数据库并建立索引
在线部分:
- 使用 Embedding 模型将用户问题转换为向量
- 在向量数据库中检索最相似的 top-k 知识片段
- 对检索片段重排序,保留最相关的 top-n 个
- 将知识片段与问题组合成格式化 prompt
- 将 prompt 提交给 LLM 生成最终答案
实践应用
本节介绍使用 Spring AI 框架 开发基于 RAG 的应用。
环境配置
| 环境 |
说明 |
| JDK |
17 |
| SpringBoot |
3.5.0 |
| Spring AI |
1.0.0 |
| 构建工具 |
Maven |
| LLM |
Qwen2.5-72B-Instruct |
| Embedding |
text-embedding-ada-002 |
| 向量数据库 |
PostgreSQL + PGVector |
Embedding 实现
使用 OpenAI Embedding 模型进行向量化操作。
pom 依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
配置文件:
spring:
ai:
openai:
base-url: [这里填url]
api-key: [这里填密钥]
embedding:
options:
model: text-embedding-ada-002
代码示例:
@Autowired
private EmbeddingModel embeddingModel;
@GetMapping("/embedding")
public void embedding(String input) {
System.out.println("input = " + input);
float[] embeddings1 = embeddingModel.embed(input);
System.out.println("length = " + embeddings1.length + ", array = " + Arrays.toString(embeddings1));
}
测试结果文本被转换为 1536 维向量。
向量数据库配置
使用 PostgreSQL 配合 PGVector 插件作为向量数据库。
pom 依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
配置文件:
spring:
ai:
vectorstore:
pgvector:
initialize-schema: true
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536
max-document-batch-size: 10000
datasource:
url: jdbc:postgresql://localhost/postgres
username: [这里填用户名]
password: [这里填密码]
当 initialize-schema 为 true 时,Spring AI 自动初始化向量数据库,相当于执行:
CREATE TABLE IF NOT EXISTS vector_store (
id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
content text,
metadata json,
embedding vector(1536)
);
CREATE INDEX ON vector_store USING HNSW (embedding vector_cosine_ops);
代码示例:
@Autowired
private VectorStore vectorStore;
@GetMapping("/storeVector")
public void storeVector(@RequestParam List<String> input) {
List<Document> documents = input.stream().map(Document::new).collect(Collectors.toList());
vectorStore.add(documents);
}
@GetMapping("/similaritySearch")
public void similarSearch(String input) {
SearchRequest query = SearchRequest.builder().query(input).topK(2).build();
List<Document> similarDocuments = vectorStore.similaritySearch(query);
String result = similarDocuments.stream()
.map(Document::getText)
.collect(Collectors.joining(System.lineSeparator()));
System.out.println("result:\n" + result);
}
注意:调用 VectorStore 的 add 方法时无需手动调用 Embedding API,底层会自动转换,但需提前配置 Embedding 模型。
ETL 流程
ETL(提取、转换、加载)在 RAG 中对数据进行预处理,实现从原始数据源到结构化向量存储的流程。
Spring AI 提供 ETL 相关 API:
- DocumentReader:实现 Extract 操作,常用实现类包括 TextReader、JsoupDocumentReader、MarkdownDocumentReader、PagePdfDocumentReader
- DocumentTransformer:实现 Transform 操作,常用实现类包括 TokenTextSplitter、ContentFormatTransformer
- DocumentWriter:实现 Load 操作,常用实现类包括 FileDocumentWriter、各种 VectorStore 类
三个组件协作完成 ETL 流程:

演示 TextReader、TokenTextSplitter、PgVectorStore 组合:
@Autowired
private VectorStore vectorStore;
@Value("classpath:/file.txt")
private Resource resource;
@GetMapping("/etl")
public void etl() {
// extract
TextReader textReader = new TextReader(this.resource);
List<Document> extractedDoc = textReader.read();
System.out.println("extract result: " + extractedDoc);
// transform
TokenTextSplitter splitter = new TokenTextSplitter(200, 200, 5, 10000, true);
List<Document> transformedDoc = splitter.apply(extractedDoc);
System.out.println("transform length = " + transformedDoc.size() + ", result: " + transformedDoc);
// load
vectorStore.add(transformedDoc);
}
TokenTextSplitter 参数说明:
- chunkSize:每个文本块的目标 token 数,默认 800
- minChunkSizeChars:每个文本块最少字符数,默认 350
- minChunkLengthToEmbed:chunk 长度超过此值才包含进结果,默认 5
- maxNumChunks:从文本中最多切出的 chunk 数,默认 1000
- keepSeparator:是否保留分隔符,默认 true
RAG 在线部分
前面主要涉及数据处理(离线部分),现在介绍与 LLM 交互的在线部分。
pom 依赖:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
为显示 LLM 交互日志,配置日志级别:
logging:
level:
org:
springframework:
ai:
chat:
client:
advisor: DEBUG
LLM Bean 配置:
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
RAG 流程代码:
@Autowired
private ChatClient chatClient;
@Autowired
private VectorStore vectorStore;
@GetMapping("/rag")
public void chatWithRag(String input) {
System.out.println("input: " + input);
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.5)
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
String result = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(input)
.call()
.content();
System.out.println("result: " + result);
}
测试结果显示 Spring AI 在 RAG 中设置了 Prompt Template,不仅组合检索文档和问题,还指定回答规则:"如果答案不在上下文中,就说你不知道"、"避免使用'根据上下文'等说法"。
完整代码示例
pom 依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置文件:
spring:
ai:
openai:
base-url: [这里填url]
api-key: [这里填密钥]
chat:
options:
model: Qwen/Qwen2.5-72B-Instruct
embedding:
options:
model: text-embedding-ada-002
vectorstore:
pgvector:
initialize-schema: true
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536
max-document-batch-size: 10000
datasource:
url: jdbc:postgresql://localhost/postgres
username: [这里填用户名]
password: [这里填密码]
logging:
level:
org:
springframework:
ai:
chat:
client:
advisor: DEBUG
Java 代码:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AIConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder chatClientBuilder) {
return chatClientBuilder
.defaultAdvisors(new SimpleLoggerAdvisor())
.build();
}
}
@RestController
public class QwenController {
@Autowired
private ChatClient chatClient;
@Autowired
private EmbeddingModel embeddingModel;
@Autowired
private VectorStore vectorStore;
@Value("classpath:/file.txt")
private Resource resource;
@GetMapping("/embedding")
public void embedding(String input) {
System.out.println("input = " + input);
float[] embeddings1 = embeddingModel.embed(input);
System.out.println("length = " + embeddings1.length + ", array = " + Arrays.toString(embeddings1));
}
@GetMapping("/storeVector")
public void storeVector(@RequestParam List<String> input) {
List<Document> documents = input.stream().map(Document::new).collect(Collectors.toList());
vectorStore.add(documents);
}
@GetMapping("/similaritySearch")
public void similarSearch(String input) {
SearchRequest query = SearchRequest.builder().query(input).topK(2).build();
List<Document> similarDocuments = vectorStore.similaritySearch(query);
String result = similarDocuments.stream()
.map(Document::getText)
.collect(Collectors.joining(System.lineSeparator()));
System.out.println("result:\n" + result);
}
@GetMapping("/etl")
public void etl() {
TextReader textReader = new TextReader(this.resource);
List<Document> extractedDoc = textReader.read();
System.out.println("extract result: " + extractedDoc);
TokenTextSplitter splitter = new TokenTextSplitter(200, 200, 5, 10000, true);
List<Document> transformedDoc = splitter.apply(extractedDoc);
System.out.println("transform length = " + transformedDoc.size() + ", result: " + transformedDoc);
vectorStore.add(transformedDoc);
}
@GetMapping("/rag")
public void chatWithRag(String input) {
System.out.println("input: " + input);
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.5)
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
String result = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(input)
.call()
.content();
System.out.println("result: " + result);
}
}
未来展望
RAG 作为融合外部知识与 LLM 生成能力的技术路径,正在成为企业 AI 应用 的重要解决方案。它在增强 LLM 专业性和个性化能力方面展现出巨大潜力,随着技术演进和框架能力完善,RAG 将在更多真实场景中发挥关键作用。