一、API 概述
在RAG(Retrieval-Augmented Generation)系统中,ETL(Extract, Transform, Load)管道是将原始数据转换为AI模型可处理格式的核心组件。Transform阶段负责对文档进行转换,确保数据以最适合AI模型处理的格式存在。
Spring AI Alibaba 提供了强大的Transformers接口和实现,专注于文档内容的转换处理,包括文本分割和内容增强。这些转换器是构建高质量RAG系统的基石,它们将原始文档转换为结构化、可检索的格式,为后续的向量化和检索提供高质量输入。
二、ETL 接口
ETL Pipeline由以下三个核心接口组成,它们定义了数据处理的三个关键阶段:
DocumentReader
java
public interface DocumentReader extends Supplier<List<Document>> {
default List<Document> read() {
return get();
}
}
作用 :从各种数据源(文件、数据库、云存储等)读取文档内容,返回List<Document>。
关键点:
-
实现
Supplier<List<Document>>,提供数据源访问 -
read()方法是默认实现,调用get()方法 -
用于数据提取阶段
DocumentTransformer
java
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
default List<Document> transform(List<Document> transform) {
return apply(transform);
}
}
作用:转换一批文档,作为处理工作流的一部分。
关键点:
-
实现
Function<List<Document>, List<Document>>,输入和输出都是文档列表 -
transform()是默认实现,调用apply()方法 -
用于数据转换阶段
DocumentWriter
java
public interface DocumentWriter extends Consumer<List<Document>> {
default void write(List<Document> documents) {
accept(documents);
}
}
作用:管理ETL过程的最后阶段,准备文档以供存储。
关键点:
-
实现
Consumer<List<Document>>,仅接受文档列表 -
write()是默认实现,调用accept()方法 -
用于数据加载阶段
三、DocumentReaders(Markdown为例子)
MarkdownDocumentReader
MarkdownDocumentReader用于处理Markdown文档,将它们转换为Document对象列表。
使用示例
java
@Component
public class MarkdownDocumentReaderLoader {
@Value("classpath:documents/psychology.md")
private Resource resource;
public List<Document> loadMarkdown() {
// 创建配置
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeCodeBlock(false)
.withIncludeBlockquote(false)
.withAdditionalMetadata("filename", "psychology.md")
.build();
// 使用配置和资源创建 MarkdownDocumentReader
MarkdownDocumentReader reader = new MarkdownDocumentReader(this.resource, config);
// 读取文档并返回
return reader.get();
}
}
配置选项详解
| 配置项 | 说明 | 默认值 |
|---|---|---|
horizontalRuleCreateDocument |
当设置为 true 时,Markdown 中的水平规则将创建新的 Document 对象 | false |
includeCodeBlock |
当设置为 true 时,代码块将包含在与周围文本相同的 Document 中。当 false 时,代码块创建单独的 Document 对象 | true |
includeBlockquote |
当设置为 true 时,引用块将包含在与周围文本相同的 Document 中。当 false 时,引用块创建单独的 Document 对象 | true |
additionalMetadata |
允许向所有创建的 Document 对象添加自定义元数据 | 无 |
行为说明
MarkdownDocumentReader 处理 Markdown 内容并根据配置创建 Document 对象:
-
标题成为 Document 对象中的元数据
-
段落成为 Document 对象的内容
-
代码块可以分离到它们自己的 Document 对象中,或者与周围文本一起包含
-
引用块可以分离到它们自己的 Document 对象中,或者与周围文本一起包含
-
水平规则可用于将内容拆分为单独的 Document 对象
四、Transformers(转换器)
1. TextSplitter(文本分割器)
概述
TextSplitter 是一个抽象基类,有助于将文档分割以适合 AI 模型的上下文窗口。
TokenTextSplitter(基于token的文本分割)
TokenTextSplitter 是 TextSplitter 的实现,它使用 CL100K_BASE 编码基于 token 计数将文本拆分为块。
使用示例
java
import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.stereotype.Component;
import java.util.List;
/*
author: atg
*/
// 文本分割器
@Component
public class MarkdownTokenTextSplitter {
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 |
行为说明
TokenTextSplitter 按以下方式处理文本内容:
-
使用 CL100K_BASE 编码将输入文本编码为 token
-
根据
defaultChunkSize将编码的文本拆分为块 -
对于每个块: a. 将块解码回文本 b. 尝试在
minChunkSizeChars之后找到合适的断点(句号、问号、感叹号或换行符) c. 如果找到断点,它会在该点截断块 d. 修剪块,并根据keepSeparator设置可选地删除换行符 e. 如果结果块长于minChunkLengthToEmbed,则将其添加到输出中 -
此过程继续进行,直到处理完所有 token 或达到
maxNumChunks -
如果剩余文本长于
minChunkLengthToEmbed,则将其作为最终块添加
最佳实践
-
根据模型调整
defaultChunkSize:-
对于大多数模型,800 token是合理的默认值
-
对于更长上下文的模型(如GPT-4),可以增加到1500-2000
-
对于资源受限的环境,可以减少到500
-
-
平衡
minChunkSizeChars和defaultChunkSize:-
minChunkSizeChars应小于defaultChunkSize -
例如,如果
defaultChunkSize是800,可以设置minChunkSizeChars为350
-
-
使用
keepSeparator:-
保留分隔符可以保持文本的可读性
-
对于需要保持段落结构的文档,建议设置为true
-
-
处理超长文档:
-
设置
maxNumChunks防止无限分割 -
例如,对于超长文档,可以设置为5000
-
-
针对特定文档类型调整参数:
-
技术文档:
defaultChunkSize=1000,minChunkSizeChars=500 -
长篇小说:
defaultChunkSize=800,minChunkSizeChars=300 -
会议记录:
defaultChunkSize=600,minChunkSizeChars=200
-
2. KeywordMetadataEnricher(关键词元数据增强器)
概述
KeywordMetadataEnricher 是一个 DocumentTransformer,它使用生成式 AI 模型从文档内容中提取关键字并将它们添加为元数据。
使用示例
java
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.stereotype.Component;
import java.util.List;
/*
author: atg
*/
// 关键词元数据增强器
@Component
public class MarkdownKeywordEnricher {
private final ChatModel dashScopeChatModel;
public MarkdownKeywordEnricher(ChatModel dashScopeChatModel) {
this.dashScopeChatModel = dashScopeChatModel;
}
List<Document> enrichDocuments(List<Document> documents) {
KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(this.dashScopeChatModel, 5);
// Or 使用自定义模板增强器
// KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
// .keywordsTemplate(YOUR_CUSTOM_TEMPLATE)
// .build();
return enricher.apply(documents);
}
}
}
}
构造函数选项
KeywordMetadataEnricher 提供两个构造函数选项:
-
KeywordMetadataEnricher(ChatModel chatModel, int keywordCount):使用默认模板并提取指定数量的关键字。 -
KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate):使用自定义模板进行关键字提取。
行为说明
KeywordMetadataEnricher 按以下方式处理文档:
-
对于每个输入文档,它使用文档的内容创建提示。
-
它将此提示发送到提供的
ChatModel以生成关键字。 -
生成的关键字作为键 "excerpt_keywords" 添加到文档的元数据中。
-
返回丰富的文档。
默认模板
默认模板是:
java
{context_str}. Give %s unique keywords for this document. Format as comma separated. Keywords:
其中 {context_str} 被文档内容替换,%s 被指定的关键字计数替换。
自定义模板
java
PromptTemplate customTemplate = new PromptTemplate(
"Extract 5 important keywords from the following text and separate them with commas:\n{context_str}"
);
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(customTemplate)
.build();
最佳实践
-
关键词数量选择:
-
对于一般用途,3-5个关键词是合适的
-
对于更详细的分类,可以使用5-10个关键词
-
关键词数量必须为1或更大
-
-
使用自定义模板:
-
根据特定领域调整提示词
-
例如,对于医学文档,可以自定义提示词:"Extract 5 important medical terms from the following text and separate them with commas:\n{context_str}"
-
-
性能考虑:
-
关键词提取需要调用外部AI模型,会增加处理时间
-
对于大量文档,考虑批量处理或异步处理
-
-
错误处理:
-
确保dashScopeChatModel正常工作
-
添加异常处理以应对AI模型调用失败
-
-
与RAG系统集成:
-
增强后的元数据可以用于更精确的检索
-
例如,搜索"machine learning"时,包含"machine learning"关键词的文档将获得更高的相关性分数
-
五、结合使用示例
完整的ETL Pipeline示例
java
import com.atg.airag.rag.loader.MarkdownDocumentReaderLoader;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;
import java.util.List;
/*
author: atg
time: 2026/1/3 20:21
*/
@Component
public class RAGPipeline {
private final MarkdownDocumentReaderLoader markdownReader;
private final TokenTextSplitter textSplitter;
private final KeywordMetadataEnricher keywordEnricher;
private final VectorStore vectorStore;
public RAGPipeline(
MarkdownDocumentReaderLoader markdownReader,
TokenTextSplitter textSplitter,
KeywordMetadataEnricher keywordEnricher,
VectorStore vectorStore
) {
this.markdownReader = markdownReader;
this.textSplitter = textSplitter;
this.keywordEnricher = keywordEnricher;
this.vectorStore = vectorStore;
}
public void processDocument() {
// 1. 读取Markdown文档
List<Document> documents = markdownReader.loadMarkdown();
// 2. 文本分割
List<Document> splitDocuments = textSplitter.apply(documents);
// 3. 关键词元数据增强
List<Document> enrichedDocuments = keywordEnricher.apply(splitDocuments);
// 4. 存储到向量数据库
vectorStore.write(enrichedDocuments);
System.out.println("成功处理文档: " + documents.size());
System.out.println("分割文档数量: " + splitDocuments.size());
System.out.println("增强文档数量: " + enrichedDocuments.size());
}
}
详细工作流程
-
文档读取:
-
读取Markdown文件
-
使用MarkdownDocumentReader解析为Document列表
-
-
文本分割:
-
使用TokenTextSplitter将文档分割为适合AI模型的块
-
每个块的大小约为800 tokens
-
-
关键词增强:
-
使用KeywordMetadataEnricher为每个文档块提取关键词
-
关键词作为"excerpt_keywords"元数据添加到文档中
-
-
向量存储:
-
将增强后的文档块存储到向量数据库
-
为后续的RAG查询做好准备
-
优势与价值
-
提高检索质量:
-
关键词元数据使检索系统能够更精确地匹配查询
-
例如,查询"machine learning"将优先返回包含"machine learning"关键词的文档
-
-
保持文档结构:
-
文本分割器保持了文档的语义结构
-
保留了段落和句子边界,提高可读性
-
-
灵活的配置:
-
可以根据不同的文档类型和用途调整分割和增强参数
-
例如,对于技术文档,可以增加关键词数量
-
-
可扩展性:
-
可以轻松添加新的Transformer(如摘要生成、情感分析等)
-
为未来的RAG系统扩展提供基础
-
六、高级配置与技巧
以下是一些例子
1. 为不同文档类型定制处理流程
java
import com.atg.airag.rag.loader.MarkdownDocumentReaderLoader;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/*
author: atg
*/
/**
* 为不同文档类型定制处理流程
*/
@Component
public class DocumentProcessor {
private final MarkdownDocumentReaderLoader markdownReader;
private final TokenTextSplitter textSplitter;
private final KeywordMetadataEnricher keywordEnricher;
private final VectorStore vectorStore;
private final ChatModel dashScopeChatModel;
public DocumentProcessor(MarkdownDocumentReaderLoader markdownReader,
TokenTextSplitter textSplitter,
KeywordMetadataEnricher keywordEnricher,
VectorStore vectorStore, ChatModel dashScopeChatModel) {
this.markdownReader = markdownReader;
this.textSplitter = textSplitter;
this.keywordEnricher = keywordEnricher;
this.vectorStore = vectorStore;
this.dashScopeChatModel = dashScopeChatModel;
}
public void processMarkdownDocument() {
// 为Markdown文档使用定制参数
TokenTextSplitter customSplitter = new TokenTextSplitter(1000, 500, 10, 5000, true);
KeywordMetadataEnricher customEnricher = new KeywordMetadataEnricher(
this.dashScopeChatModel, 5
);
// 加载文档
List<Document> documents = markdownReader.loadMarkdown();
// 分割
List<Document> splitDocuments = customSplitter.apply(documents);
// 增强
List<Document> enrichedDocuments = customEnricher.apply(splitDocuments);
// 写入向量存储
vectorStore.write(enrichedDocuments);
}
// 为技术文档使用更严格的分割
public void processTechnicalDocument() {
// 为技术文档使用更严格的分割
TokenTextSplitter techSplitter = new TokenTextSplitter(800, 400, 15, 3000, false);
KeywordMetadataEnricher techEnricher = new KeywordMetadataEnricher(
this.dashScopeChatModel, 10
);
List<Document> documents = markdownReader.loadMarkdown();
List<Document> splitDocuments = techSplitter.apply(documents);
List<Document> enrichedDocuments = techEnricher.apply(splitDocuments);
vectorStore.write(enrichedDocuments);
}
}
2. 为特定领域优化关键词提取
java
/*
author: atg
*/
// 以下是一个例子、特地为医疗文档设计的增强器
public class MedicalKeywordEnricher {
private final ChatModel dashScopeChatModel;
public MedicalKeywordEnricher(ChatModel dashScopeChatModel) {
this.dashScopeChatModel = dashScopeChatModel;
}
public List<Document> enrichMedicalDocuments(List<Document> documents) {
// 为医学文档定制提示词
PromptTemplate medicalTemplate = new PromptTemplate(
"Extract 7 important medical terms from the following text related to cardiology and heart disease:\n{context_str}"
);
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(medicalTemplate)
.build();
return enricher.apply(documents);
}
}
七、常见问题与解决方案
1. 问题:关键词提取结果不理想
原因:
-
AI模型对特定领域的理解不足
-
提示词不够明确
解决方案:
java
// 使用更具体的自定义提示词
PromptTemplate customTemplate = new PromptTemplate(
"Extract 5 important technical keywords from the following text related to artificial intelligence and machine learning:\n{context_str}"
);
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(customTemplate)
.build();
2. 问题:文本分割导致语义断裂
原因:
-
minChunkSizeChars设置过小 -
没有在合适的语义边界处断开
解决方案:
java
// 使用更大的minChunkSizeChars
TokenTextSplitter splitter = new TokenTextSplitter(
1000, // defaultChunkSize
500, // minChunkSizeChars (增加)
10, // minChunkLengthToEmbed
5000, // maxNumChunks
true // keepSeparator
);
3. 问题:处理超长文档时性能低下
原因:
- 文档过长导致分割和关键词提取时间过长
解决方案:
java
// 使用更小的默认块大小
TokenTextSplitter splitter = new TokenTextSplitter(
500, // 降低默认块大小
200, // 降低最小块大小
5, // 保持最小长度
1000, // 限制最大块数
true
);
4. 问题:关键词提取重复
原因:
- AI模型生成了重复的关键词
解决方案:
java
// 使用自定义提示词,要求唯一关键词
PromptTemplate customTemplate = new PromptTemplate(
"Extract 5 unique important keywords from the following text, separated by commas:\n{context_str}"
);
八、总结
在RAG系统中,ETL Pipeline的Transform阶段是实现高质量文档处理的关键。Spring AI Alibaba提供了强大的工具,特别是:
-
TokenTextSplitter:基于token的文本分割,确保文档块适合AI模型处理
-
KeywordMetadataEnricher:通过AI生成关键词,增强文档的元数据,提高检索质量
通过合理配置和使用这些工具,您可以构建出高效的RAG系统,为用户提供更准确、更相关的回答。记住,高质量的文档处理是RAG系统成功的基石。
关键配置建议:
-
对于大多数场景,使用默认的TokenTextSplitter配置
-
关键词数量设置为5个左右
-
对于特定领域,使用自定义的KeywordMetadataEnricher模板
-
在处理大型文档时,适当调整分割参数
最佳实践总结:
-
从简单开始:使用默认配置开始,然后根据实际效果调整
-
监控处理结果:定期检查分割和增强后的文档质量
-
针对不同文档类型定制:不要使用单一配置处理所有文档
-
优化性能:考虑文档大小和系统资源,合理设置分割参数
-
测试与迭代:通过实际查询测试RAG系统的检索效果,持续优化配置
通过实践和调整,您可以根据自己的具体需求优化这些配置,构建出最适合您应用场景的RAG系统。这些Transformers组件是Spring AI 中非常强大的工具,它们将帮助您在RAG系统中实现高质量的文档处理。