0%

langchain4j RAG支持

RAG 流程

RAG 流程分为两个截然不同的阶段:索引阶段检索阶段,LangChain4j 为两个阶段都提供了工具支持。

索引阶段

在索引阶段,文档会被预处理,以便在检索阶段实现高效搜索。这个过程会根据所使用的信息检索方法而有所不同。对于向量搜索,通常包括:清洗文档、用额外数据和元数据丰富文档、将文档分割成更小的片段(即分块)、对这些片段进行嵌入编码,最后将它们存储到嵌入存储(即向量数据库)中。

索引阶段通常发生在离线状态下,这意味着不需要终端用户等待其完成。例如,可以通过 cron 定时任务在周末每周一次重新索引公司内部文档。负责索引的代码也可以是一个单独的应用程序,仅处理索引任务。然而,在某些场景下,终端用户可能希望上传自定义文档以使其可被 LLM 访问。在这种情况下,索引应在在线状态下执行,并作为主应用程序的一部分。

以下是索引阶段的简化示意图:

1
文档 → 清洗/丰富 → 分块 → 嵌入 → 嵌入存储(向量数据库)

langchain4j-rag-index.png

检索阶段

检索阶段通常发生在在线状态下,当用户提交一个需要使用已索引文档来回答的问题时。这个过程会根据所使用的信息检索方法而有所不同。对于向量搜索,通常包括:对用户查询(问题)进行嵌入编码,然后在嵌入存储中执行相似度搜索。相关片段(原始文档的片段)随后被注入到提示中并发送给 LLM。

以下是检索阶段的简化示意图:

1
用户查询 → 嵌入 → 相似度搜索 → 检索相关片段 → 注入提示 → LLM

langchain4j-rag-retri.png


LangChain4j 的三种 RAG 方式

LangChain4j 提供了三种 RAG 实现方式:

  1. Easy RAG — 最简单的入门方式
  2. Naive RAG — 使用 EmbeddingStoreContentRetriever 的基础 RAG
  3. Advanced RAG — 使用 RetrievalAugmentor 的高级 RAG 管道

Easy RAG

LangChain4j 有一个”Easy RAG”功能,让你尽可能轻松地开始使用 RAG。你无需了解嵌入、选择向量存储、寻找合适的嵌入模型、弄清楚如何解析和分割文档等。只需指向你的文档,LangChain4j 就会施展魔法。

当然,这种”Easy RAG”的质量会低于定制化的 RAG 设置。然而,这是开始学习 RAG 和/或制作概念验证的最简单方式。之后,你可以从 Easy RAG 平滑过渡到更高级的 RAG,逐步调整和优化更多方面。

Easy RAG 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 从文件系统加载文档
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/path/to/your/documents");

// 使用 EmbeddingStoreIngestor 进行索引
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.build();
ingestor.ingest(documents);

// 配置 AI Service 使用 RAG
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.chatMemory(chatMemory)
.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore, embeddingModel))
.build();

这将加载指定目录中的所有文件。

Apache Tika 库支持多种文档类型,用于检测文档类型并解析它们。由于我们没有明确指定使用哪个 DocumentParserFileSystemDocumentLoader 将通过 SPI 加载一个由 langchain4j-easy-rag 依赖提供的 ApacheTikaDocumentParser

如果你想从所有子目录加载文档,可以使用 loadDocumentsRecursively 方法:

1
List<Document> documents = FileSystemDocumentLoader.loadDocumentsRecursively("/path/to/your/documents");

此外,你可以使用 glob 或 regex 过滤文档:

1
2
3
4
List<Document> documents = FileSystemDocumentLoader.loadDocuments(
"/path/to/your/documents",
glob("*.pdf") // 只加载 PDF 文件
);

使用 loadDocumentsRecursively 方法时,你可能需要在 glob 中使用双星号(而不是单星号):glob:**.pdf

EmbeddingStoreIngestor 通过 SPI 从 langchain4j-easy-rag 依赖中加载一个 DocumentSplitter。每个 Document 被分割成更小的片段(TextSegment),每个片段最多包含 300 个 token,并有 30 个 token 的重叠。

EmbeddingStoreIngestor 通过 SPI 从 langchain4j-easy-rag 依赖中加载一个 EmbeddingModel。每个 TextSegment 使用 EmbeddingModel 转换为一个 Embedding

langchain4j选择了 bge-small-en-v1.5 作为 Easy RAG 的默认嵌入模型。它在 MTEB 排行榜 上取得了令人印象深刻的分数,其量化版本仅占用 24 MB 空间。因此可以轻松地将其加载到内存中,并使用 ONNX Runtime 在同一进程中运行。

没错,你可以在完全离线的状态下,在相同的 JVM 进程中将文本转换为嵌入,无需任何外部服务。LangChain4j 开箱即用地提供了一些流行的嵌入模型。在这里配置 Assistant 使用 OpenAI LLM 来回答用户问题,记住对话中最近的 10 条消息,并从包含我们文档的 EmbeddingStore 中检索相关内容。


RAG 核心 API

LangChain4j 提供了丰富的 API 集,让你轻松构建自定义 RAG 管道,从简单的到高级的都可以。在本节中,我们将介绍主要的领域类和 API。

Document(文档)

Document 类代表一个完整的文档,例如单个 PDF 文件或网页。目前,Document 只能表示文本信息,但未来的更新将使其支持图像和表格。每个 Document 都包含 Metadata,它存储有关 Document 的元信息,例如名称、来源、最后更新日期、所有者或任何其他相关详细信息。Metadata 存储为键值对映射,其中键为 String 类型,值可以是以下类型之一:StringIntegerLongFloatDoubleUUID

Metadata 在以下场景中非常有用:

  • 过滤和搜索文档
  • 在检索结果中提供上下文信息
  • 跟踪文档来源和版本

你可以从 String 创建 Document,但更简单的方法是使用库中包含的文档加载器:

1
Document document = Document.from("这是文档内容");

DocumentParser(文档解析器)

文档可以表示各种格式的文件,如 PDF、DOC、TXT 等。要解析每种格式,有一个 DocumentParser 接口,库中包含了多个实现:

解析器 说明
TextDocumentParser 解析纯文本文件
ApacheTikaDocumentParser 使用 Apache Tika 解析多种格式
PdfDocumentParser 解析 PDF 文件

以下是从文件系统加载一个或多个 Document 的示例:

1
2
3
4
List<Document> documents = FileSystemDocumentLoader.loadDocuments(
Paths.get("/path/to/document.pdf"),
new PdfDocumentParser()
);

你也可以在不显式指定 DocumentParser 的情况下加载文档。在这种情况下,将使用默认的 DocumentParser。默认解析器通过 SPI 加载(例如,如果导入了 langchain4j-document-parser-apache-tikalangchain4j-easy-rag)。如果通过 SPI 没有找到 DocumentParser,则使用 TextDocumentParser 作为回退。

DocumentTransformer(文档转换器)

DocumentTransformer 实现可以执行各种文档转换,例如:

  • 清洗和规范化文本
  • 提取特定内容
  • 添加或修改元数据

在此阶段也可以添加、修改或删除 Metadata 条目。

目前,开箱即提供的唯一实现是 langchain4j-document-transformer-jsoup 模块中的 HtmlToTextDocumentTransformer,它可以从原始 HTML 中提取所需的文本内容和元数据条目。

由于没有一个放之四海而皆准的解决方案,我们建议根据你的独特数据实现自己的 DocumentTransformer

GraphTransformer(图转换器)

GraphTransformer 是一个将非结构化 Document 对象转换为结构化 GraphDocument 的接口,通过提取语义图元素(如节点和关系)。它非常适合将原始文本转换为结构化语义图。

GraphTransformer 将原始文档转换为 GraphDocument,包括:

  • 节点(Nodes):实体及其属性
  • 关系(Relationships):实体之间的连接

默认实现是 LLMGraphTransformer,它使用语言模型(如 OpenAI)通过提示工程从自然语言中提取图信息。

DocumentSplitter(文档分割器)

加载 Document 后,是时候将它们分割成更小的片段了。LangChain4j 的领域模型包含一个 TextSegment 类,表示 Document 的一个片段。顾名思义,TextSegment 只能表示文本信息。

你可能只想在提示中包含几个相关片段而不是整个知识库,原因有以下几点:

  • 上下文窗口限制:LLM 的上下文窗口有限
  • 相关性:不相关的信息可能分散 LLM 的注意力
  • 成本:更长的提示意味着更高的 API 调用成本

我们可以通过将知识库分割成更小、更易消化的片段来解决这些问题。这些片段应该多大?这是一个好问题。一如既往,这取决于具体情况。

目前有两种广泛使用的方法:

方法 说明
按字符数分割 按固定字符数切分,简单但可能切断语义
按 token 数分割 按 token 数量切分,更精确

LangChain4j 有一个 DocumentSplitter 接口,提供了多个开箱即用的实现:

  • DocumentByCharacterSplitter — 按字符数分割
  • DocumentByTokenSplitter — 按 token 数分割
  • DocumentByLineSplitter — 按行分割
  • DocumentByParagraphSplitter — 按段落分割

它们的工作原理如下:将 Document 分割成具有指定大小和重叠的 TextSegment

TextSegmentTransformer(文本片段转换器)

TextSegmentTransformer 类似于 DocumentTransformer(如上所述),但它转换的是 TextSegment

DocumentTransformer 一样,没有一个放之四海而皆准的解决方案,因此我们建议根据你的独特数据实现自己的 TextSegmentTransformer

一种对改善检索效果很好的技术是在每个 TextSegment 中包含 Document 标题或简短摘要。

Embedding(嵌入)

Embedding 类封装了一个数值向量,代表被嵌入内容的”语义含义”(通常是文本,如 TextSegment)。

在此处阅读更多关于向量嵌入的内容。

EmbeddingModel 接口代表一种特殊类型的模型,它将文本转换为 Embedding

当前支持的嵌入模型可以在此处找到。

EmbeddingStore(嵌入存储)

EmbeddingStore 接口代表一个用于存储 Embedding 的存储,也称为向量数据库。它允许存储和高效搜索相似的(在嵌入空间中接近的)Embedding

当前支持的嵌入存储可以在此处找到。

EmbeddingStore 可以单独存储 Embedding,也可以与相应的 TextSegment 一起存储:

1
embeddingStore.add(embedding, textSegment);

EmbeddingSearchRequest(嵌入搜索请求)

EmbeddingSearchRequest 代表在 EmbeddingStore 中搜索的请求。它具有以下属性:

属性 说明
queryEmbedding 查询的嵌入向量
maxResults 返回的最大结果数
minScore 最小相似度分数阈值
filter 元数据过滤条件

Filter 允许在执行向量搜索时按 Metadata 条目进行过滤。

目前支持以下 Filter 类型/操作:

过滤器 说明
isEqualTo 等于指定值
isNotEqualTo 不等于指定值
isGreaterThan 大于指定值
isGreaterThanOrEqualTo 大于或等于指定值
isLessThan 小于指定值
isLessThanOrEqualTo 小于或等于指定值
containsString 包含指定字符串
in 在指定集合中

并非所有嵌入存储都支持按 Metadata 过滤,请参阅此处的”按元数据过滤”列。一些支持按 Metadata 过滤的存储不支持所有可能的 Filter 类型/操作。例如,ContainsString 目前仅由 Milvus、PgVector 和 Qdrant 支持。

关于 Filter 的更多详细信息可以在此处找到。

EmbeddingSearchResult(嵌入搜索结果)

EmbeddingSearchResult 代表在 EmbeddingStore 中搜索的结果。它包含 EmbeddingMatch 的列表。

EmbeddingMatch 代表一个匹配的 Embedding,以及其相关性分数、ID 和原始嵌入数据(通常是 TextSegment)。

EmbeddingStoreIngestor(嵌入存储摄取器)

EmbeddingStoreIngestor 代表一个摄取管道,负责将 Document 摄取到 EmbeddingStore 中。

在最简单的配置中,EmbeddingStoreIngestor 使用指定的 EmbeddingModel 将提供的 Document 嵌入,并将它们及其 Embedding 一起存储到指定的 EmbeddingStore 中:

1
2
3
4
5
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.build();
ingestor.ingest(documents);

EmbeddingStoreIngestor 中的所有 ingest() 方法都返回一个 IngestionResultIngestionResult 包含有用的信息,包括 TokenUsage,显示用于嵌入的 token 数量。

可选地,EmbeddingStoreIngestor 可以使用指定的 DocumentTransformer 转换 Document。如果你想在嵌入之前清洗、丰富或格式化 Document,这会很有用。

可选地,EmbeddingStoreIngestor 可以使用指定的 DocumentSplitterDocument 分割成 TextSegment。如果 Document 很大,并且你想将它们分割成更小的 TextSegment 以提高相似度搜索的质量并减少发送给 LLM 的提示的大小和成本,这会很有用。

可选地,EmbeddingStoreIngestor 可以使用指定的 TextSegmentTransformer 转换 TextSegment。如果你想在嵌入之前清洗、丰富或格式化 TextSegment,这会很有用。

示例:

1
2
3
4
5
6
7
8
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentTransformer(documentTransformer) // 可选:转换文档
.documentSplitter(documentSplitter) // 可选:分割文档
.textSegmentTransformer(textSegmentTransformer) // 可选:转换文本片段
.embeddingModel(embeddingModel) // 必需:嵌入模型
.embeddingStore(embeddingStore) // 必需:嵌入存储
.build();
ingestor.ingest(documents);

Naive RAG

一旦我们的文档被摄取(见前面的章节),我们可以创建一个 EmbeddingStoreContentRetriever 来启用朴素 RAG 功能。

1
2
3
4
5
6
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(3)
.minScore(0.6)
.build();

使用 AI Services 时,Naive RAG 可以配置如下:

1
2
3
4
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.contentRetriever(contentRetriever)
.build();

Advanced RAG

使用 LangChain4j 可以通过以下核心组件实现高级 RAG:

组件 说明
RetrievalAugmentor RAG 管道的入口点
Query 用户查询的表示
QueryTransformer 查询转换器
ContentRetriever 内容检索器
QueryRouter 查询路由器
ContentAggregator 内容聚合器
ContentInjector 内容注入器

下图展示了这些组件如何协同工作:

1
2
3
用户消息 → QueryTransformer → QueryRouter → ContentRetriever(s)

UserMessage ← ContentInjector ← ContentAggregator ← 检索到的内容

流程如下:

  1. 用户发送消息
  2. QueryTransformer 将消息转换为一个或多个 Query
  3. QueryRouterQuery 路由到适当的 ContentRetriever
  4. ContentRetriever 从底层数据源检索相关内容
  5. ContentAggregator 聚合来自多个来源的结果
  6. ContentInjector 将检索到的内容注入到 UserMessage
  7. 增强后的消息被发送给 LLM

请参阅每个组件的 Javadoc 以获取更多详细信息。

RetrievalAugmentor(检索增强器)

RetrievalAugmentor 是 RAG 管道的入口点。它负责用从各种来源检索到的相关 Content 来增强 ChatMessage

可以在创建 AI Service 时指定 RetrievalAugmentor 的实例:

1
2
3
4
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.build();

每次调用 AI Service 时,指定的 RetrievalAugmentor 将被调用来增强当前的 UserMessage

你可以使用 RetrievalAugmentor 的默认实现(如下所述)或实现自定义的。

LangChain4j 提供了一个开箱即用的 RetrievalAugmentor 接口实现:DefaultRetrievalAugmentor,它应该适用于大多数 RAG 用例。它的灵感来自这篇文章这篇论文。建议查阅这些资源以更好地理解这个概念。

Query(查询)

Query 代表 RAG 管道中的用户查询。它包含查询文本和查询元数据。Query 中的 Metadata 包含可能在 RAG 管道的各个组件中使用的有用信息,例如:

  • 用户身份信息
  • 会话上下文
  • 自定义参数

InvocationParameters 也可以在以下其他 AI Service 组件中访问:

  • QueryTransformer
  • ContentRetriever
  • ContentInjector
  • Tools

参数存储在一个可变的、线程安全的 Map 中。

数据可以在 AI Service 的单个调用期间,通过 InvocationParameters 在 AI Service 组件之间传递(例如,从一个 RAG 组件到另一个 RAG 组件,或从 RAG 组件到工具)。

QueryTransformer(查询转换器)

QueryTransformer 将给定的 Query 转换为一个或多个 Query。目标是通过修改或扩展原始 Query 来提高检索质量。

一些已知的改进检索的方法包括:

  • 查询压缩:将后续问题压缩为独立查询
  • 查询扩展:将单个查询扩展为多个变体
  • 查询重写:用不同的措辞重新表述查询

更多细节可以在此处找到。

LangChain4j 还有一个可选的社区 Prompt Repetition 模块,提供 RepeatingQueryTransformer。它在内容检索之前重复检索查询,应该用于转换查询本身,而不是发送到模型的最终增强提示。

DefaultQueryTransformer

DefaultQueryTransformerDefaultRetrievalAugmentor 中使用的默认实现。它不对 Query 做任何修改,只是将其传递下去。

CompressingQueryTransformer

CompressingQueryTransformer 使用 LLM 将给定 Query 和之前的对话压缩为一个独立的 Query。当用户可能提出涉及之前问题或答案信息的后续问题时,这非常有用。

示例:

1
2
3
4
5
对话历史:
用户:John Doe 是谁?
LLM:John Doe 是一位著名的科学家。

用户:他住在哪里?

查询”他住在哪里?”本身无法检索到所需信息,因为没有明确提及 John Doe,不清楚”他”指的是谁。

使用 CompressingQueryTransformer 时,LLM 将阅读整个对话并将”他住在哪里?”转换为”John Doe 住在哪里?”。

ExpandingQueryTransformer

ExpandingQueryTransformer 使用 LLM 将给定 Query 扩展为多个 Query。这很有用,因为 LLM 可以用各种方式重新措辞和重新表述 Query,这将有助于检索更多相关内容。

Content(内容)

Content 代表与用户 Query 相关的内容。目前,它仅限于文本内容(即 TextSegment),但未来可能支持其他模态(如图像、音频、视频等)。

ContentRetriever(内容检索器)

ContentRetriever 使用给定的 Query 从底层数据源检索 Content。底层数据源几乎可以是任何东西:

  • 向量数据库(嵌入存储)
  • 关系型数据库
  • 搜索引擎
  • 图数据库
  • Web 搜索

ContentRetriever 返回的 Content 列表按相关性从高到低排序。

EmbeddingStoreContentRetriever

EmbeddingStoreContentRetriever 使用 EmbeddingModel 嵌入 Query,从 EmbeddingStore 中检索相关 Content

示例:

1
2
3
4
5
6
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.minScore(0.5)
.build();

WebSearchContentRetriever

WebSearchContentRetriever 使用 WebSearchEngine 从 Web 检索相关 Content

所有支持的 WebSearchEngine 集成可以在此处找到。

示例:

1
2
3
4
ContentRetriever retriever = WebSearchContentRetriever.builder()
.webSearchEngine(webSearchEngine)
.maxResults(5)
.build();

完整示例可以在此处找到。

SqlDatabaseContentRetriever

SqlDatabaseContentRetrieverContentRetriever 的一个实验性实现,可以在 langchain4j-experimental-sql 模块中找到。

它使用 DataSource 和 LLM 为给定的自然语言 Query 生成并执行 SQL 查询。

有关更多信息,请参阅 SqlDatabaseContentRetriever 的 javadoc。

AzureAiSearchContentRetriever

AzureAiSearchContentRetriever 是与 Azure AI Search 的集成。它支持全文、向量和混合搜索,以及重新排序。它可以在 langchain4j-azure-ai-search 模块中找到。请参阅 AzureAiSearchContentRetriever Javadoc 以获取更多信息。

Neo4jContentRetriever

Neo4jContentRetriever 是与 Neo4j 图数据库的集成。它将自然语言查询转换为 Neo4j Cypher 查询,并通过在 Neo4j 中运行这些查询来检索相关信息。它可以在 langchain4j-community-neo4j-retriever 模块中找到。

ElasticsearchContentRetriever

ElasticsearchContentRetriever 是与 Elasticsearch 的集成。它支持全文、向量和混合搜索。它可以在 langchain4j-elasticsearch 模块中找到。请参阅 ElasticsearchContentRetriever Javadoc 以获取更多信息。

QueryRouter(查询路由器)

QueryRouter 负责将 Query 路由到适当的 ContentRetriever

DefaultQueryRouter

DefaultQueryRouterDefaultRetrievalAugmentor 中使用的默认实现。它将每个 Query 路由到所有配置的 ContentRetriever

LanguageModelQueryRouter

LanguageModelQueryRouter 使用 LLM 来决定将给定 Query 路由到哪里。

ContentAggregator(内容聚合器)

ContentAggregator 负责聚合来自以下来源的多个排序的 Content 列表:

  • 多个 ContentRetriever 返回的结果
  • 单个 ContentRetriever 返回的多个结果

DefaultContentAggregator

DefaultContentAggregatorContentAggregator 的默认实现,它执行两阶段互惠排名融合(RRF)。请参阅 DefaultContentAggregator Javadoc 了解更多详情。

ReRankingContentAggregator

ReRankingContentAggregator 使用 ScoringModel(如 Cohere)执行重新排序。支持的评分(重新排序)模型完整列表可以在此处找到。请参阅 ReRankingContentAggregator Javadoc 了解更多详情。

ContentInjector(内容注入器)

ContentInjector 负责将 ContentAggregator 返回的 Content 注入到 UserMessage 中。

DefaultContentInjector

DefaultContentInjectorContentInjector 的默认实现,它只是将 Content 附加到 UserMessage 的末尾,前缀为”Answer using the following information:”。

你可以通过以下 3 种方式自定义 Content 如何注入到 UserMessage 中:

  1. 使用自定义的 PromptTemplate
  2. 实现自定义的 ContentInjector
  3. 使用 DefaultContentInjector 并配置选项

请注意,PromptTemplate 必须包含 contents 变量。

DefaultContentInjector 还支持从检索到的 Content.textSegment() 注入 Metadata 条目:

1
2
3
DefaultContentInjector injector = DefaultContentInjector.builder()
.metadataKeysToInclude(List.of("source", "page"))
.build();

在这种情况下,TextSegment.text() 将以”content: “前缀开头,而 Metadata 中的每个值将以键作为前缀。最终的 UserMessage 将如下所示:

1
2
3
4
Answer using the following information:
content: 这是检索到的文档内容
source: document.pdf
page: 42

并发与性能

当只有一个 Query 和一个 ContentRetriever 时,DefaultRetrievalAugmentor 在同一线程中执行查询路由和内容检索。否则,将使用 Executor 来并行化处理。默认情况下,使用修改后的 Executors.newCachedThreadPool()keepAliveTime 为 1 秒而不是 60 秒),但你可以在创建 DefaultRetrievalAugmentor 时提供自定义的 Executor 实例:

1
2
3
4
5
6
RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.executor(customExecutor)
.build();

访问检索到的来源

当你使用 AI Services 时,如果希望访问来源(用于增强消息的检索到的 Content),可以通过将返回类型包装在 Result 类中轻松实现:

1
2
3
Result<String> result = assistant.chat(userMessage);
String answer = result.content();
List<Content> sources = result.sources();

流式传输时,可以使用 onRetrieved() 方法指定一个 Consumer<List<Content>>

1
2
3
4
5
6
7
assistant.chat(userMessage, new StreamingResponseHandler() {
@Override
public void onRetrieved(List<Content> contents) {
// 处理检索到的内容
}
// ... 其他方法
});

聊天记忆中的内容存储

当使用 RetrievalAugmentor 与 AI Services 时,你可以控制是将在聊天记忆中存储增强后的用户消息(注入了检索到的 Content)还是原始的用户消息。

此行为使用 AiServices builder 上的 storeRetrievedContentInChatMemory 选项进行配置:

说明
true(默认) 将增强后的 UserMessage(原始查询 + 检索到的内容)存储在聊天记忆中。相同的增强消息也会发送给 LLM。
false 仅将原始 UserMessage(不含检索到的内容)存储在聊天记忆中。推理期间仍会将增强消息发送给 LLM。

仅存储原始用户消息在你希望保持聊天历史简洁并与用户的实际输入一致时非常有用,同时仍为 LLM 提供检索到的上下文以生成答案。

1
2
3
4
5
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.retrievalAugmentor(retrievalAugmentor)
.storeRetrievedContentInChatMemory(false) // 仅存储原始用户消息
.build();