跳到主要内容
SpringAI 深入解析 RAG 检索增强工作流程与调优 | 极客日志
Java AI java 算法
SpringAI 深入解析 RAG 检索增强工作流程与调优 综述由AI生成 基于 Spring AI 框架讲解 RAG(检索增强生成)应用开发。内容涵盖 RAG 核心工作流程,包括文档收集切割(ETL)、向量转换存储、文档过滤检索及查询增强。详细介绍了 Spring AI 中的 DocumentReader、DocumentTransformer 和 DocumentWriter 三大组件及其实现类,如 TokenTextSplitter、KeywordMetadataEnricher 等。同时介绍了 VectorStore 接口在向量数据库交互中的作用,为开发者构建知识库应用提供技术参考。
墨染流年 发布于 2026/4/6 更新于 2026/5/18 27 浏览SpringAI 深入解析 RAG 检索增强工作流程与调优
本节重点
以 Spring AI 框架为例,学习 RAG 知识库应用开发的核心特性和高级知识点,并且掌握 RAG 最佳实践和调优技巧。
具体内容包括:
RAG 核心特性
文档收集和切割(ETL)
向量转换和存储(向量数据库)
文档过滤和检索(文档检索器)
查询增强和关联(上下文查询增强器)
RAG 最佳实践和调优
RAG 高级知识
一、RAG 核心特性
RAG 检索增强工作流程
一、建立索引
1. 文档预处理和切割 ETL
首先对文档进行结构优化和内容清洗,让文档的每一个部分都有一个标题来划分内容。对一些大模型无法识别的图片链接、超链接等,以及代码块进行清洗,避免在后续切割文档时代码被切导致逻辑中断。使用固定大小、语义边界或递归分割等方法,将文档切割成多个文档切片。
2. 向量转换和存储
调用 Embedding 模型,将文档切片转为高维向量,并存储到对应的向量数据库中。这些文档切片也会作为大模型回答用户特定问题的知识库。
二、检索增强
1. 文档过滤和检索
用户问题向量化 :将用户问题输入 Embedding 模型,将用户输入的提示词转为向量。
相似度搜索和条件过滤 :在向量数据库中进行相似度计算,查询与用户问题向量相似度接近的文档切片。将查询到的切片结果进行过滤,如用户问题的语义、关键词可以提炼出一些标签、元信息,那么就根据这些信息,来进一步过滤查询的切片。
Rank 模型精排 :调用 Rank 模型对检索到的相关文档切片进行精排(Re-ranking),使用更复杂的算法重新排序和评分。筛选出相似度分数最高的前 N 个切片。
2. 查询增强和关联
构建增强提示词 :将排序得出相似度分数最高的前 N 个切片,拼接到用户的提示词中,得到最终的增强提示词。
生成最终答案 :将增强提示词输入给大模型,大模型会基于知识库内容生成最终的结果。
Spring AI 为这些流程的技术实现提供了支持,下面按照流程依次进行讲解。
文档收集和切割
向量转换和存储
文档过滤和检索
查询增强和关联
文档收集和切割 - ETL 文档收集和切割阶段,我们要对自己准备好的知识库文档进行处理,然后保存到向量数据库中。这个过程俗称 ETL(抽取、转换、加载),Spring AI 提供了对 ETL 的支持,参考 官方文档 。
文档 什么是 Spring AI 中的文档呢?文档不仅仅包含文本,还可以包含一系列元信息和多媒体附件:
ETL 在 Spring AI 中,对 Document 的处理通常遵循以下流程:
读取文档:使用 DocumentReader 组件从数据源(如本地文件、网络资源、数据库等)加载文档。
转换文档:根据需求将文档转换为适合后续处理的格式,比如去除冗余信息、分词、词性标注等,可以使用 DocumentTransformer 组件实现。
写入文档:使用 DocumentWriter 将文档以特定格式保存到存储中,比如将文档以嵌入向量的形式写入到向量数据库,或者以键值对字符串的形式保存到 Redis 等 KV 存储中。
我们利用 Spring AI 实现 ETL,核心就是要学习 DocumentReader、DocumentTransformer、DocumentWriter 三大组件。完整的 ETL 类图如下,先简单了解一下即可,下面分别来详细讲解这 3 大组件:
Spring AI 通过 DocumentReader 组件实现文档抽取,也就是把文档加载到内存中。
看下源码,DocumentReader 接口实现了 Supplier<List<Document>> 接口:主要负责从各种数据源读取数据并转换为 Document 对象集合。
实现了 Supplier<List<Document>> 接口,就可以调用 Document 就可以调用父类的定义的通用方法:get()
查看 DocumentReader 的具体实现类的源码,查看它们是如何抽取文档,并转换为 Document 对象的:
也就是说,在抽取文档的过程中,调用 DocumentReader 对应格式的实现类,抽取文档中的文本,如果不是 textReader,就可能包含一些媒体类型的信息,需要将这些信息一并抽取,并设置一些默认的元信息;将文档内容(文本、媒体信息)、元信息作为参数实例一个 Document 对象。
JsonReader:读取 JSON 文档
TextReader:读取纯文本文件
MarkdownReader:读取 Markdown 文件
PDFReader:读取 PDF 文档,基于 Apache PdfBox 库实现
PagePdfDocumentReader:按照分页读取 PDF
ParagraphPdfDocumentReader:按照段落读取 PDF
HtmlReader:读取 HTML 文档,基于 jsoup 库实现
TikaDocumentReader:基于 Apache Tika 库处理多种格式的文档,更灵活
以 JsonReader 为例,支持 JSON Pointers 特性,能够快速指定从 JSON 文档中提取哪些字段和内容:
{
"帅哥" : {
"name" : "小雷"
}
}
Spring AI 的 JsonReader 可以通过 JSON Pointers 特性,通过 帅哥.name 一次性读取到 name 中的值:
@Component
class MyJsonReader {
private final Resource resource;
MyJsonReader(@Value("classpath:products.json") Resource resource) {
this .resource = resource;
}
List<Document> loadBasicJsonDocuments () {
JsonReader jsonReader = new JsonReader (this .resource);
return jsonReader.get();
}
List<Document> loadJsonWithSpecificFields () {
JsonReader jsonReader = new JsonReader (this .resource, "description" , "features" );
return jsonReader.get();
}
List<Document> loadJsonWithPointer () {
JsonReader jsonReader = new JsonReader (this .resource);
return jsonReader.get("/items" );
}
}
此外,Spring AI Alibaba 官方社区提供了 更多的文档读取器 ,比如加载飞书文档、提取 B 站视频信息和字幕、加载邮件、加载 GitHub 官方文档、加载数据库等等。
💡 思考:如果让你自己实现一个 DocumentReader 组件,你会怎么实现呢?
比如一个邮件文档读取器的实现其实并不难,核心代码就是解析邮件文档并且转换为 Document 列表:
public class MsgEmailParser {
private MsgEmailParser () {
}
public static Document convertToDocument (MsgEmailElement element) {
if (element == null ) {
throw new IllegalArgumentException ("MsgEmailElement cannot be null" );
}
Map<String, Object> metadata = new HashMap <>();
if (StringUtils.hasText(element.getSubject())) {
metadata.put("subject" , element.getSubject());
}
String content = StringUtils.hasText(element.getText()) ? element.getText() : "" ;
return new Document (content, metadata);
}
}
邮件解析器获取文档的原理,就是以邮件中的一些信息(收信人、发信人等…)作为元信息,抽取出邮件的文本,结合元信息,实例一个 Document 对象;这个对象作为解析结果返回;
邮件文档读取器中的 get() 方法,会接收所有的解析结果,集成一个列表对象,作为文档对象列表,并返回,就实现了单次抽取一些邮件,转为 Document 的功能;
转换(Transform) Spring AI 通过 DocumentTransformer 组件实现文档转换。看下源码:
DocumentTransformer 接口实现了 Function<List<Document>, List<Document>> 接口,负责将一组文档转换为另一组文档。
public interface DocumentTransformer extends Function <List<Document>, List<Document>> {
default List<Document> transform (List<Document> documents) {
return apply(documents);
}
}
文档转换是保证 RAG 效果的核心步骤,也就是如何将大文档,合理拆分为便于检索的知识碎片,Spring AI 提供了多种 DocumentTransformer 实现类,可以简单分为 3 类。
1. TextSplitter 文本分割器 其中 TextSplitter 是文本分割器的基类,提供了分割单词的流程方法:
TokenTextSplitter 是其实现类,基于 Token 的文本分割器。它考虑了语义边界(比如句子结尾)来创建有意义的文本段落,是成本较低的文本切分方式。
@Component
class MyTokenTextSplitter {
public List<Document> splitDocuments (List<Document> documents) {
TokenTextSplitter splitter = new TokenTextSplitter ();
return splitter.apply(documents);
}
public List<Document> splitCustomized (List<Document> documents) {
TokenTextSplitter splitter = new TokenTextSplitter (1000 , 400 , 10 , 5000 , true );
return splitter.apply(documents);
}
}
TokenTextSplitter 提供了两种构造函数选项:
TokenTextSplitter():使用默认设置创建分割器。
TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator):使用自定义参数创建分割器,通过调整参数,可以控制分割的粒度和方式,适应不同的应用场景。
defaultChunkSize:每个文本块的目标大小(以 token 为单位,默认值:800)。
minChunkSizeChars:每个文本块的最小大小(以字符为单位,默认值:350)。
minChunkLengthToEmbed:要被包含的块的最小长度(默认值:5)。
maxNumChunks:从文本中生成的最大块数(默认值:10000)。
keepSeparator:是否在块中保留分隔符(如换行符)(默认值:true)。
官方文档有对 Token 分词器工作原理的详细解释,可以简单了解下:
使用 CL100K_BASE 编码将输入文本编码为 token。
根据 defaultChunkSize 将编码后的文本分割成块。
对于每个块:
将块解码回文本。
尝试在 minChunkSizeChars 之后找到合适的断点(句号、问号、感叹号或换行符)。
如果找到断点,则在该点截断块。
修剪块并根据 keepSeparator 设置选择性地删除换行符。
如果生成的块长度大于 minChunkLengthToEmbed,则将其添加到输出中。
这个过程会一直持续到所有 token 都被处理完或达到 maxNumChunks 为止。
如果剩余文本长度大于 minChunkLengthToEmbed,则会作为最后一个块添加。
2. MetadataEnricher 元数据增强器 元数据增强器的作用是为文档补充更多的元信息,便于后续检索,而不是改变文档本身的切分规则。包括:
KeywordMetadataEnricher:使用 AI 提取关键词并添加到元数据
SummaryMetadataEnricher:使用 AI 生成文档摘要并添加到元数据。不仅可以为当前文档生成摘要,还能关联前一个和后一个相邻的文档,让摘要更完整。
@Component
class MyDocumentEnricher {
private final ChatModel chatModel;
MyDocumentEnricher(ChatModel chatModel) {
this .chatModel = chatModel;
}
List<Document> enrichDocumentsByKeyword (List<Document> documents) {
KeywordMetadataEnricher enricher = new KeywordMetadataEnricher (this .chatModel, 5 );
return enricher.apply(documents);
}
List<Document> enrichDocumentsBySummary (List<Document> documents) {
SummaryMetadataEnricher enricher = new SummaryMetadataEnricher (chatModel, List.of(SummaryType.PREVIOUS, SummaryType.CURRENT, SummaryType.NEXT));
return enricher.apply(documents);
}
}
3. ContentFormatter 内容格式化工具 用于统一文档内容格式。我们不妨看它的实现类 DefaultContentFormatter 的源码来了解他的功能:
文档格式化:将文档内容与元数据,合并成特定格式的字符串,以便于后续处理。
元数据过滤:根据不同的元数据模式(MetadataMode)筛选需要保留的元数据项:
ALL:保留所有元数据
NONE:移除所有元数据
INFERENCE:用于推理场景,排除指定的推理元数据
EMBED:用于嵌入场景,排除指定的嵌入元数据
自定义模板:支持自定义以下格式:
元数据模板:控制每个元数据项的展示方式
元数据分隔符:控制多个元数据项之间的分隔方式
文本模板:控制元数据和内容如何结合
该类采用 Builder 模式创建实例,使用示例:
DefaultContentFormatter formatter = DefaultContentFormatter.builder()
.withMetadataTemplate("{key}: {value}" )
.withMetadataSeparator("\n" )
.withTextTemplate("{metadata_string}\n\n{content}" )
.withExcludedInferenceMetadataKeys("embedding" , "vector_id" )
.withExcludedEmbedMetadataKeys("source_url" , "timestamp" )
.build();
String formattedText = formatter.format(document, MetadataMode.INFERENCE);
在 RAG 系统中,这个格式化器可以有下面的作用:
提供上下文:将元数据(如文档来源、时间、标签等)与内容结合,丰富大语言模型的上下文信息
过滤无关信息:通过排除特定元数据,减少噪音,提高检索和生成质量
场景适配:为不同场景(如推理和嵌入)提供不同的格式化策略
结构化输出:为 AI 模型提供结构化的输入,使其能更好地理解和处理文档内容
加载(Load) Spring AI 通过 DocumentWriter 组件实现文档加载(写入)。
DocumentWriter 接口实现了 Consumer<List<Document>> 接口,负责将处理后的文档写入到目标存储中:
Spring AI 提供了 2 种内置的 DocumentWriter 实现:
FileDocumentWriter:将文档写入到文件系统
@Component
class MyDocumentWriter {
public void writeDocuments (List<Document> documents) {
FileDocumentWriter writer = new FileDocumentWriter ("output.txt" , true , MetadataMode.ALL, false );
writer.accept(documents);
}
}
VectorStoreWriter:将文档写入到向量数据库
@Component
class MyVectorStoreWriter {
private final VectorStore vectorStore;
MyVectorStoreWriter(VectorStore vectorStore) {
this .vectorStore = vectorStore;
}
public void storeDocuments (List<Document> documents) {
vectorStore.accept(documents);
}
}
当然,你也可以同时将文档写入多个存储,只需要创建多个 Writer 或者自定义 Writer 即可。
ETL 流程示例 将上述 3 大组件组合起来,可以实现完整的 ETL 流程:
PDFReader pdfReader = new PagePdfDocumentReader ("knowledge_base.pdf" );
List<Document> documents = pdfReader.read();
TokenTextSplitter splitter = new TokenTextSplitter (500 , 50 );
List<Document> splitDocuments = splitter.apply(documents);
SummaryMetadataEnricher enricher = new SummaryMetadataEnricher (chatModel, List.of(SummaryType.CURRENT));
List<Document> enrichedDocuments = enricher.apply(splitDocuments);
vectorStore.write(enrichedDocuments);
vectorStore.write(enricher.apply(splitter.apply(pdfReader.read())));
通过这种方式,我们完成了从原始文档到向量数据库的整个 ETL 过程,为后续的检索增强生成提供了基础。
向量转换和存储 上一节教程中有介绍过,向量存储是 RAG 应用中的核心组件,它将文档转换为向量(嵌入)并存储起来,以便后续进行高效的相似性搜索。
Spring AI 官方 提供了向量数据库接口 VectorStore 和向量存储整合包,帮助开发者快速集成各种第三方向量存储,比如 Milvus、Redis、PGVector、Elasticsearch 等。
VectorStore 接口介绍 VectorStore 是 Spring AI 中用于与向量数据库交互的核心接口,它继承自 DocumentWriter,主要提供以下功能:
public interface VectorStore extends DocumentWriter {
default String getName () {
return this .getClass().getSimpleName();
}
void add (List<Document> documents) ;
}
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online