主题
ETL 管道
抽取、转换、加载(ETL)框架是检索增强生成(RAG)用例中数据处理的骨干。
ETL 管道编排从原始数据源到结构化向量库的流转,确保数据以最适合 AI 模型检索的格式存在。
RAG 用例的用途是通过从一批数据中检索相关信息来增补生成式模型的能力,从而提升生成输出的质量与相关性。
API 概览
ETL 管道创建、转换并存储 Document 实例。
Document 类包含文本、元数据,以及可选的额外媒体类型,如图像、音频和视频。
ETL 管道有三个主要组件,
- 实现
Supplier<List<Document>>的DocumentReader - 实现
Function<List<Document>, List<Document>>的DocumentTransformer - 实现
Consumer<List<Document>>的DocumentWriter
Document 类的内容是在 DocumentReader 的帮助下,从 PDF、文本文件及其他文档类型创建的。
要构建一个简单的 ETL 管道,你可以把每种类型的一个实例串接起来。
假设我们有以下这三个 ETL 类型的实例
PagePdfDocumentReader是DocumentReader的一个实现TokenTextSplitter是DocumentTransformer的一个实现VectorStore是DocumentWriter的一个实现
要把数据基本加载进向量数据库以配合检索增强生成模式使用,用下面这段 Java 函数式语法的代码。
java
vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));或者,你可以使用更符合领域表达习惯的方法名
java
vectorStore.write(tokenTextSplitter.split(pdfReader.read()));ETL 接口
ETL 管道由以下接口与实现组成。 详细的 ETL 类图见 etl-class-diagram 一节。
DocumentReader
从各种来源提供文档。
java
public interface DocumentReader extends Supplier<List<Document>> {
default List<Document> read() {
return get();
}
}DocumentTransformer
在处理流程中转换一批文档。
java
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
default List<Document> transform(List<Document> transform) {
return apply(transform);
}
}DocumentWriter
管理 ETL 流程的最后阶段,为存储准备文档。
java
public interface DocumentWriter extends Consumer<List<Document>> {
default void write(List<Document> documents) {
accept(documents);
}
}ETL 类图
下面的类图展示了 ETL 接口与实现。
// image::etl-class-diagram.jpg[align="center", width="800px"]
DocumentReader
DANGER
下面描述的大多数 DocumentReader 和 DocumentWriter 实现 是用一个 Resource 或资源模式构造,并在内部使用 DefaultResourceLoader 或其变体来访问存储。应注意不要用直接由用户提供的 URL 来构造这样的实例,因为这存在安全隐患。
JSON
JsonReader 处理 JSON 文档,把它们转换成 Document 对象的列表。
示例
java
@Component
class MyJsonReader {
private final Resource resource;
MyJsonReader(@Value("classpath:bikes.json") Resource resource) {
this.resource = resource;
}
List<Document> loadJsonAsDocuments() {
JsonReader jsonReader = new JsonReader(this.resource, "description", "content");
return jsonReader.get();
}
}构造器选项
JsonReader 提供若干构造器选项:
JsonReader(Resource resource)JsonReader(Resource resource, String... jsonKeysToUse)JsonReader(Resource resource, JsonMetadataGenerator jsonMetadataGenerator, String... jsonKeysToUse)
参数
resource:一个指向 JSON 文件的 SpringResource对象。jsonKeysToUse:JSON 中应被用作所生成Document对象文本内容的一些键的数组。jsonMetadataGenerator:一个可选的JsonMetadataGenerator,为每个Document创建元数据。
行为
JsonReader 按如下方式处理 JSON 内容:
- 它能处理 JSON 数组和单个 JSON 对象。
- 对每个 JSON 对象(无论位于数组中还是单个对象):
- 它依据指定的
jsonKeysToUse提取内容。 - 若未指定键,则把整个 JSON 对象用作内容。
- 它用所提供的
JsonMetadataGenerator(若未提供则用空的)生成元数据。 - 它创建一个带有所提取内容与元数据的
Document对象。
- 它依据指定的
使用 JSON 指针
JsonReader 现在支持使用 JSON 指针检索 JSON 文档的特定部分。这一特性让你能轻松从复杂 JSON 结构中提取嵌套数据。
get(String pointer) 方法
java
public List<Document> get(String pointer)该方法让你能用一个 JSON 指针检索 JSON 文档的特定部分。
参数
pointer:一个 JSON 指针字符串(如 RFC 6901 所定义),用于在 JSON 结构中定位期望的元素。
返回值
- 返回一个
List<Document>,包含从指针所定位的 JSON 元素解析出的文档。
行为
- 该方法使用所提供的 JSON 指针导航到 JSON 结构中的特定位置。
- 若指针有效且指向一个存在的元素:
- 对于一个 JSON 对象:它返回一个只含单个 Document 的列表。
- 对于一个 JSON 数组:它返回一个 Document 列表,数组中每个元素一个。
- 若指针无效或指向一个不存在的元素,它会抛出
IllegalArgumentException。
示例
java
JsonReader jsonReader = new JsonReader(resource, "description");
List<Document> documents = this.jsonReader.get("/store/books/0");示例 JSON 结构
json
[
{
"id": 1,
"brand": "Trek",
"description": "A high-performance mountain bike for trail riding."
},
{
"id": 2,
"brand": "Cannondale",
"description": "An aerodynamic road bike for racing enthusiasts."
}
]在本例中,如果 JsonReader 以 "description" 作为 jsonKeysToUse 配置,它会创建 Document 对象,其内容是数组中每辆自行车的 "description" 字段的值。
说明
JsonReader使用 Jackson 进行 JSON 解析。- 它能通过对数组使用流式处理,高效处理大型 JSON 文件。
- 若在
jsonKeysToUse中指定了多个键,内容将是这些键对应值的拼接。 - 该读取器很灵活,可通过定制
jsonKeysToUse和JsonMetadataGenerator来适配各种 JSON 结构。
Text
TextReader 处理纯文本文档,把它们转换成 Document 对象的列表。
示例
java
@Component
class MyTextReader {
private final Resource resource;
MyTextReader(@Value("classpath:text-source.txt") Resource resource) {
this.resource = resource;
}
List<Document> loadText() {
TextReader textReader = new TextReader(this.resource);
textReader.getCustomMetadata().put("filename", "text-source.txt");
return textReader.read();
}
}构造器选项
TextReader 提供两个构造器选项:
TextReader(String resourceUrl)TextReader(Resource resource)
参数
resourceUrl:表示要读取资源 URL 的字符串。resource:一个指向文本文件的 SpringResource对象。
配置
setCharset(Charset charset):设置读取文本文件所用的字符集。默认为 UTF-8。getCustomMetadata():返回一个可变 map,你可以在其中为文档添加自定义元数据。
行为
TextReader 按如下方式处理文本内容:
- 它把文本文件的整个内容读入单个
Document对象。 - 文件的内容成为该
Document的内容。 - 会自动向
Document添加元数据:charset:读取文件所用的字符集(默认:"UTF-8")。source:源文本文件的文件名。
- 通过
getCustomMetadata()添加的任何自定义元数据都会被包含在Document中。
说明
TextReader会把整个文件内容读入内存,因此可能不适合非常大的文件。- 若你需要把文本切分成更小的块,可以在读取文档后使用诸如
TokenTextSplitter的文本切分器:
java
List<Document> documents = textReader.get();
List<Document> splitDocuments = TokenTextSplitter.builder().build().apply(this.documents);- 该读取器使用 Spring 的
Resource抽象,使其能从各种来源(classpath、文件系统、URL 等)读取。 - 可以使用
getCustomMetadata()方法向该读取器创建的所有文档添加自定义元数据。
HTML(JSoup)
JsoupDocumentReader 使用 JSoup 库处理 HTML 文档,把它们转换成 Document 对象的列表。
依赖
使用 Maven 或 Gradle 把依赖添加到你的项目。
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-jsoup-document-reader</artifactId>
</dependency>示例
java
@Component
class MyHtmlReader {
private final Resource resource;
MyHtmlReader(@Value("classpath:/my-page.html") Resource resource) {
this.resource = resource;
}
List<Document> loadHtml() {
JsoupDocumentReaderConfig config = JsoupDocumentReaderConfig.builder()
.selector("article p") // Extract paragraphs within <article> tags
.charset("ISO-8859-1") // Use ISO-8859-1 encoding
.includeLinkUrls(true) // Include link URLs in metadata
.metadataTags(List.of("author", "date")) // Extract author and date meta tags
.additionalMetadata("source", "my-page.html") // Add custom metadata
.build();
JsoupDocumentReader reader = new JsoupDocumentReader(this.resource, config);
return reader.get();
}
}JsoupDocumentReaderConfig 让你能定制 JsoupDocumentReader 的行为:
charset: 指定 HTML 文档的字符编码(默认为 "UTF-8")。selector: 一个 JSoup CSS 选择器,指定从哪些元素提取文本(默认为 "body")。separator: 用于拼接多个所选元素文本的字符串(默认为 "\n")。allElements: 若为true,从<body>元素提取所有文本,忽略selector(默认为false)。groupByElement:若为true,为每个匹配selector的元素创建一个独立的Document(默认为false)。includeLinkUrls: 若为true,提取绝对链接 URL 并添加到元数据(默认为false)。metadataTags: 要从中提取内容的<meta>标签名列表(默认为["description", "keywords"])。additionalMetadata: 允许你向所有创建的Document对象添加自定义元数据。
示例文档:my-page.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Web Page</title>
<meta name="description" content="A sample web page for Spring AI">
<meta name="keywords" content="spring, ai, html, example">
<meta name="author" content="John Doe">
<meta name="date" content="2024-01-15">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Welcome to My Page</h1>
</header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<article>
<h2>Main Content</h2>
<p>This is the main content of my web page.</p>
<p>It contains multiple paragraphs.</p>
<a href="https://www.example.com">External Link</a>
</article>
<footer>
<p>© 2024 John Doe</p>
</footer>
</body>
</html>行为:
JsoupDocumentReader 处理 HTML 内容,并依据配置创建 Document 对象:
selector决定用哪些元素进行文本提取。- 若
allElements为true,<body>内的所有文本被提取进单个Document。 - 若
groupByElement为true,每个匹配selector的元素创建一个独立的Document。 - 若
allElements与groupByElement都不为true,所有匹配selector的元素的文本用separator拼接。 - 文档标题、来自指定
<meta>标签的内容,以及(可选的)链接 URL 会被添加到Document元数据。 - 用于解析相对链接的基础 URI 将从 URL 资源中提取。
该读取器保留所选元素的文本内容,但会移除其中任何 HTML 标签。
Markdown
MarkdownDocumentReader 处理 Markdown 文档,把它们转换成 Document 对象的列表。
依赖
使用 Maven 或 Gradle 把依赖添加到你的项目。
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>示例
java
@Component
class MyMarkdownReader {
private final Resource resource;
MyMarkdownReader(@Value("classpath:code.md") Resource resource) {
this.resource = resource;
}
List<Document> loadMarkdown() {
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeCodeBlock(false)
.withIncludeBlockquote(false)
.withAdditionalMetadata("filename", "code.md")
.build();
MarkdownDocumentReader reader = new MarkdownDocumentReader(this.resource, config);
return reader.get();
}
}MarkdownDocumentReaderConfig 让你能定制 MarkdownDocumentReader 的行为:
horizontalRuleCreateDocument:设为true时,Markdown 中的水平分割线会创建新的Document对象。includeCodeBlock:设为true时,代码块会与其周围文本包含在同一Document中。为false时,代码块创建各自的Document对象。includeBlockquote:设为true时,引用块会与其周围文本包含在同一Document中。为false时,引用块创建各自的Document对象。additionalMetadata:允许你向所有创建的Document对象添加自定义元数据。
示例文档:code.md
markdown
This is a Java sample application:
```java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
```
Markdown also provides the possibility to `use inline code formatting throughout` the entire sentence.
---
Another possibility is to set block code without specific highlighting:
```
./mvnw spring-javaformat:apply
```行为:MarkdownDocumentReader 处理 Markdown 内容,并依据配置创建 Document 对象:
- 标题成为 Document 对象中的元数据。
- 段落成为 Document 对象的内容。
- 代码块可以被分离进各自的 Document 对象,或与周围文本一起包含。
- 引用块可以被分离进各自的 Document 对象,或与周围文本一起包含。
- 水平分割线可用于把内容切分成各自的 Document 对象。
该读取器在 Document 对象的内容中保留诸如内联代码、列表和文本样式之类的格式。
PDF 页面
PagePdfDocumentReader 使用 Apache PdfBox 库解析 PDF 文档。
依赖
使用 Maven 或 Gradle 把依赖添加到你的项目。
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>示例
java
@Component
public class MyPagePdfDocumentReader {
List<Document> getDocsFromPdf() {
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader("classpath:/sample1.pdf",
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.withPagesPerDocument(1)
.build());
return pdfReader.read();
}
}PDF 段落
ParagraphPdfDocumentReader 使用 PDF 目录(例如 TOC)信息把输入的 PDF 切分成文本段落,并为每个段落输出一个 Document。
NOTE
并非所有 PDF 文档都包含 PDF 目录。
依赖
使用 Maven 或 Gradle 把依赖添加到你的项目。
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>示例
java
@Component
public class MyPagePdfDocumentReader {
List<Document> getDocsFromPdfWithCatalog() {
ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader("classpath:/sample1.pdf",
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.withPagesPerDocument(1)
.build());
return pdfReader.read();
}
}Tika(DOCX、PPTX、HTML……)
TikaDocumentReader 使用 Apache Tika 从多种文档格式中提取文本,例如 PDF、DOC/DOCX、PPT/PPTX 和 HTML。完整受支持格式列表参见 Tika 文档。
依赖
使用 Maven 或 Gradle 把依赖添加到你的项目。
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>示例
java
@Component
class MyTikaDocumentReader {
private final Resource resource;
MyTikaDocumentReader(@Value("classpath:/word-sample.docx")
Resource resource) {
this.resource = resource;
}
List<Document> loadText() {
TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(this.resource);
return tikaDocumentReader.read();
}
}转换器
TextSplitter
TextSplitter 是一个抽象基类,帮助切分文档以适应 AI 模型的上下文窗口。
TokenTextSplitter
TokenTextSplitter 是 TextSplitter 的一个实现,它依据令牌数把文本切分成块。它支持可配置的编码类型(例如 CL100K_BASE、P50K_BASE、O200K_BASE),默认为 CL100K_BASE。
用法
基本用法
java
@Component
class MyTokenTextSplitter {
public List<Document> splitDocuments(List<Document> documents) {
TokenTextSplitter splitter = TokenTextSplitter.builder().build();
return splitter.apply(documents);
}
public List<Document> splitCustomized(List<Document> documents) {
TokenTextSplitter splitter = TokenTextSplitter.builder()
.withChunkSize(1000)
.withMinChunkSizeChars(400)
.withMinChunkLengthToEmbed(10)
.withMaxNumChunks(5000)
.withKeepSeparator(true)
.build();
return splitter.apply(documents);
}
}自定义编码类型
你可以配置用于分词的编码类型。当你使用采用不同分词器的模型时,这很有用:
java
TokenTextSplitter splitter = TokenTextSplitter.builder()
.withEncodingType(EncodingType.O200K_BASE)
.withChunkSize(1000)
.build();自定义标点符号
你可以定制用于把文本切分成语义块的标点符号。这对国际化尤其有用:
java
@Component
class MyInternationalTextSplitter {
public List<Document> splitChineseText(List<Document> documents) {
// Use Chinese punctuation marks
TokenTextSplitter splitter = TokenTextSplitter.builder()
.withChunkSize(800)
.withMinChunkSizeChars(350)
.withPunctuationMarks(List.of('。', '?', '!', ';')) // Chinese punctuation
.build();
return splitter.apply(documents);
}
public List<Document> splitWithCustomMarks(List<Document> documents) {
// Mix of English and other punctuation marks
TokenTextSplitter splitter = TokenTextSplitter.builder()
.withChunkSize(800)
.withPunctuationMarks(List.of('.', '?', '!', '\n', ';', ':', '。'))
.build();
return splitter.apply(documents);
}
}配置
使用 TokenTextSplitter.builder() 创建实例。所有构造器都已被弃用,改用构建器。
参数
encodingType:要使用的分词器编码类型(默认:CL100K_BASE)。受支持的值包括CL100K_BASE、P50K_BASE和O200K_BASE。chunkSize:每个文本块以令牌计的目标大小(默认:800)。minChunkSizeChars:每个文本块以字符计的最小大小(默认:350)。minChunkLengthToEmbed:一个块被纳入的最小长度(默认:5)。maxNumChunks:从一段文本生成的最大块数(默认:10000)。keepSeparator:是否在块中保留分隔符(如换行)(默认:true)。punctuationMarks:用作切分句子边界的字符列表(默认:.、?、!、)。
行为
TokenTextSplitter 按如下方式处理文本内容:
- 它使用 CL100K_BASE 编码把输入文本编码为令牌。
- 它依据
chunkSize把编码后的文本切分成块。 - 对每个块: a. 它把块解码回文本。 b. 仅当令牌总数超过块大小时,它才尝试在
minChunkSizeChars之后寻找一个合适的断点(使用配置的punctuationMarks)。 c. 若找到断点,它在该点截断该块。 d. 它裁剪该块,并依据keepSeparator设置可选地移除换行字符。 e. 如果所得的块长于minChunkLengthToEmbed,它被加入输出。 - 此过程持续进行,直到所有令牌被处理完或达到
maxNumChunks。 - 任何剩余文本若长于
minChunkLengthToEmbed,会作为最后一个块被加入。
WARNING
基于标点的切分仅在令牌数超过块大小时才适用。恰好等于或小于块大小的文本会作为单个块返回,不做基于标点的截断。这防止了对小文本不必要的切分。
示例
java
Document doc1 = new Document("This is a long piece of text that needs to be split into smaller chunks for processing.",
Map.of("source", "example.txt"));
Document doc2 = new Document("Another document with content that will be split based on token count.",
Map.of("source", "example2.txt"));
TokenTextSplitter splitter = TokenTextSplitter.builder().build();
List<Document> splitDocuments = splitter.apply(List.of(doc1, doc2));
for (Document doc : splitDocuments) {
System.out.println("Chunk: " + doc.getContent());
System.out.println("Metadata: " + doc.getMetadata());
}说明
TokenTextSplitter使用来自jtokkit库的 CL100K_BASE 编码,与较新的 OpenAI 模型兼容。- 该切分器尽量在句子边界断开,以创建语义上有意义的块。
- 原文档的元数据被保留并复制到由该文档派生的所有块。
- 原文档的内容格式化器(若设置了)在
copyContentFormatter设为true(默认行为)时也会被复制到派生块。 - 该切分器对于为有令牌限制的大语言模型准备文本尤其有用,确保每个块都在模型的处理能力之内。
- 自定义标点符号:默认标点符号(
.、?、!、)适合英文文本。对于其他语言或专门内容,使用构建器的withPunctuationMarks()方法定制标点符号。 - 性能考量:尽管切分器能处理任意数量的标点符号,但建议把列表保持在合理的小规模(少于 20 个字符)以获得最佳性能,因为每个标记都会对每个块检查一次。
- 可扩展性:
getLastPunctuationIndex(String)方法是protected的,允许子类为专门用例覆盖标点检测逻辑。 - 小文本处理:自 2.0 版起,小文本(令牌数不超过块大小)不再在标点符号处切分,防止对已适合大小限制的内容做不必要的碎片化。
ContentFormatTransformer
确保所有文档间统一的内容格式。
KeywordMetadataEnricher
KeywordMetadataEnricher 是一个 DocumentTransformer,它使用生成式 AI 模型从文档内容提取关键词并作为元数据添加。
用法
java
@Component
class MyKeywordEnricher {
private final ChatModel chatModel;
MyKeywordEnricher(ChatModel chatModel) {
this.chatModel = chatModel;
}
List<Document> enrichDocuments(List<Document> documents) {
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordCount(5)
.build();
// Or use custom templates
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" 键下被添加到文档元数据。
- 返回被增补后的文档。
定制
你可以使用默认模板,或通过 keywordsTemplate 参数定制模板。 默认模板是:
java
\{context_str}. Give %s unique keywords for this document. Format as comma separated. Keywords:其中 +{context_str}+ 被替换为文档内容,%s 被替换为指定的关键词数量。
示例
java
ChatModel chatModel = // initialize your chat model
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordCount(5)
.build();
// Or use custom templates
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(new PromptTemplate("Extract 5 important keywords from the following text and separate them with commas:\n{context_str}"))
.build();
Document doc = new Document("This is a document about artificial intelligence and its applications in modern technology.");
List<Document> enrichedDocs = enricher.apply(List.of(this.doc));
Document enrichedDoc = this.enrichedDocs.get(0);
String keywords = (String) this.enrichedDoc.getMetadata().get("excerpt_keywords");
System.out.println("Extracted keywords: " + keywords);说明
KeywordMetadataEnricher需要一个能正常工作的ChatModel来生成关键词。- 关键词数量必须大于等于 1。
- 该增补器会为每个处理的文档添加 "excerpt_keywords" 元数据字段。
- 生成的关键词以逗号分隔的字符串返回。
- 该增补器对于提升文档可搜索性、以及为文档生成标签或类别尤其有用。
- 在构建器模式中,若设置了
keywordsTemplate参数,keywordCount参数将被忽略。
SummaryMetadataEnricher
SummaryMetadataEnricher 是一个 DocumentTransformer,它使用生成式 AI 模型为文档创建摘要并作为元数据添加。它能为当前文档、以及相邻文档(上一个和下一个)生成摘要。
用法
java
@Configuration
class EnricherConfig {
@Bean
public SummaryMetadataEnricher summaryMetadata(OpenAiChatModel aiClient) {
return new SummaryMetadataEnricher(aiClient,
List.of(SummaryType.PREVIOUS, SummaryType.CURRENT, SummaryType.NEXT));
}
}
@Component
class MySummaryEnricher {
private final SummaryMetadataEnricher enricher;
MySummaryEnricher(SummaryMetadataEnricher enricher) {
this.enricher = enricher;
}
List<Document> enrichDocuments(List<Document> documents) {
return this.enricher.apply(documents);
}
}构造器
SummaryMetadataEnricher 提供两个构造器:
SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes)SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes, String summaryTemplate, MetadataMode metadataMode)
参数
chatModel:用于生成摘要的 AI 模型。summaryTypes:一组SummaryType枚举值,指示要生成哪些摘要(PREVIOUS、CURRENT、NEXT)。summaryTemplate:用于摘要生成的自定义模板(可选)。metadataMode:指定生成摘要时如何处理文档元数据(可选)。
行为
SummaryMetadataEnricher 按如下方式处理文档:
- 对每个输入文档,它用文档内容和指定的摘要模板创建一个提示词。
- 它把这个提示词发给所提供的
ChatModel以生成一个摘要。 - 依据指定的
summaryTypes,它为每个文档添加以下元数据:
section_summary:当前文档的摘要。prev_section_summary:上一个文档的摘要(若可用且被请求)。next_section_summary:下一个文档的摘要(若可用且被请求)。
- 返回被增补后的文档。
定制
摘要生成提示词可以通过提供一个自定义 summaryTemplate 来定制。默认模板是:
java
"""
Here is the content of the section:
{context_str}
Summarize the key topics and entities of the section.
Summary:
"""示例
java
ChatModel chatModel = // initialize your chat model
SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(chatModel,
List.of(SummaryType.PREVIOUS, SummaryType.CURRENT, SummaryType.NEXT));
Document doc1 = new Document("Content of document 1");
Document doc2 = new Document("Content of document 2");
List<Document> enrichedDocs = enricher.apply(List.of(this.doc1, this.doc2));
// Check the metadata of the enriched documents
for (Document doc : enrichedDocs) {
System.out.println("Current summary: " + doc.getMetadata().get("section_summary"));
System.out.println("Previous summary: " + doc.getMetadata().get("prev_section_summary"));
System.out.println("Next summary: " + doc.getMetadata().get("next_section_summary"));
}所提供的示例演示了预期行为:
- 对于两个文档的列表,两个文档都会收到一个
section_summary。 - 第一个文档收到一个
next_section_summary但没有prev_section_summary。 - 第二个文档收到一个
prev_section_summary但没有next_section_summary。 - 第一个文档的
section_summary与第二个文档的prev_section_summary相符。 - 第一个文档的
next_section_summary与第二个文档的section_summary相符。
说明
SummaryMetadataEnricher需要一个能正常工作的ChatModel来生成摘要。- 该增补器能处理任意大小的文档列表,正确处理好第一个和最后一个文档的边界情况。
- 该增补器对于创建上下文感知的摘要尤其有用,能更好地理解序列中的文档关系。
MetadataMode参数让你能控制如何把既有元数据纳入摘要生成过程。
写入器
文件
FileDocumentWriter 是一个 DocumentWriter 实现,它把 Document 对象列表的内容写入一个文件。
用法
java
@Component
class MyDocumentWriter {
public void writeDocuments(List<Document> documents) {
FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, false);
writer.accept(documents);
}
}构造器
FileDocumentWriter 提供三个构造器:
FileDocumentWriter(String fileName)FileDocumentWriter(String fileName, boolean withDocumentMarkers)FileDocumentWriter(String fileName, boolean withDocumentMarkers, MetadataMode metadataMode, boolean append)
参数
fileName:要写入文档的文件名。withDocumentMarkers:是否在输出中包含文档标记(默认:false)。metadataMode:指定要写入文件的文档内容(默认:MetadataMode.NONE)。append:若为 true,数据将被写到文件末尾而非开头(默认:false)。
行为
FileDocumentWriter 按如下方式处理文档:
- 它为指定的文件名打开一个 FileWriter。
- 对输入列表中的每个文档: a. 如果
withDocumentMarkers为 true,它会写一个包含文档索引和页码的文档标记。 b. 它依据指定的metadataMode写入文档格式化后的内容。 - 在所有文档写完后关闭文件。
文档标记
当 withDocumentMarkers 设为 true 时,写入器会按以下格式为每个文档包含标记:
### Doc: [index], pages:[start_page_number,end_page_number]元数据处理
该写入器使用两个特定的元数据键:
page_number:表示文档的起始页码。end_page_number:表示文档的结束页码。
它们在写文档标记时使用。
示例
java
List<Document> documents = // initialize your documents
FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, true);
writer.accept(documents);这会把所有文档写到 "output.txt",包含文档标记,使用所有可用的元数据,并在文件已存在时追加到其后。
说明
- 该写入器使用
FileWriter,因此它以操作系统的默认字符编码写文本文件。 - 如果写入期间发生错误,会抛出一个以原始异常为原因的
RuntimeException。 metadataMode参数让你能控制如何把既有元数据纳入所写内容。- 该写入器对于调试或创建文档集合的人类可读输出尤其有用。
VectorStore
提供与各种向量库的集成。 完整列表参见 向量数据库文档。