聊天记忆
大型语言模型(LLMs)是无状态的,这意味着它们不会保留先前交互的信息。当您希望在多次交互中保持上下文或状态时,这可能是一个限制。为解决这一问题,Spring AI 提供了聊天记忆功能,允许您在与 LLM 的多次交互中存储和检索信息。
ChatMemory 抽象允许您实现多种类型的内存以支持不同的使用场景。消息的底层存储由 ChatMemoryRepository 处理,其唯一职责是存储和检索消息。ChatMemory 的实现负责决定保留哪些消息以及何时删除它们。策略示例包括保留最后 N 条消息、保留特定时间段内的消息,或保留不超过特定令牌限制的消息。
在选择内存类型之前,理解聊天内存与聊天历史之间的区别至关重要。
-
聊天记忆。大型语言模型在整个对话过程中保留并用于维持上下文感知的信息。
-
聊天历史。完整的对话历史,包括用户与模型之间交换的所有消息。
ChatMemory 抽象层旨在管理对话记忆。它允许您存储和检索与当前对话上下文相关的消息。然而,它并不适合存储对话历史。如果您需要维护所有已交换消息的完整记录,应考虑采用不同的方法,例如依赖 Spring Data 来高效存储和检索完整的对话历史。
快速开始
Spring AI 会自动配置一个 ChatMemory bean,您可以直接在应用程序中使用。默认情况下,它使用一个内存存储库来存储消息(InMemoryChatMemoryRepository),并使用 MessageWindowChatMemory 实现来管理对话历史。如果已配置了不同的存储库(例如 Cassandra、JDBC 或 Neo4j),Spring AI 则会使用该存储库。
@Autowired
ChatMemory chatMemory;
以下章节将详细阐述Spring AI中可用的不同内存类型与存储库。
内存类型
ChatMemory 抽象层允许您实现多种类型的内存,以适应不同的使用场景。内存类型的选择会显著影响应用程序的性能和行为。本节将介绍 Spring AI 提供的内置内存类型及其特性。
消息窗口聊天记忆
MessageWindowChatMemory 会维护一个消息窗口,其大小不超过指定的最大值。当消息数量超过最大值时,会移除较早的消息,但系统消息会被保留。默认的窗口大小为 20 条消息。
MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
这是Spring AI默认使用的消息类型,用于自动配置ChatMemory bean。
内存存储
Spring AI 提供了 ChatMemoryRepository 抽象来存储聊天记忆。本节介绍 Spring AI 内置的存储库及其使用方法,但您也可以根据需要实现自己的存储库。
内存存储库
InMemoryChatMemoryRepository 使用 ConcurrentHashMap 在内存中存储消息。
默认情况下,如果未配置其他存储库,Spring AI 会自动配置一个类型为 InMemoryChatMemoryRepository 的 ChatMemoryRepository Bean,您可以直接在应用程序中使用它。
@Autowired
ChatMemoryRepository chatMemoryRepository;
如果你更希望手动创建 InMemoryChatMemoryRepository,可以按如下步骤操作:
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();
JdbcChatMemoryRepository
JdbcChatMemoryRepository 是一个内置实现,它使用 JDBC 将消息存储在关系型数据库中。它开箱即用地支持多种数据库,适用于需要持久化存储聊天记忆的应用程序。
首先,将以下依赖项添加到您的项目中:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-jdbc'
}
Spring AI为JdbcChatMemoryRepository提供了自动配置,你可以直接在应用程序中使用它。
@Autowired
JdbcChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
如果您更倾向于手动创建 JdbcChatMemoryRepository,可以通过提供 JdbcTemplate 实例和一个 JdbcChatMemoryRepositoryDialect 来实现:
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new PostgresChatMemoryRepositoryDialect())
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
支持的数据库与方言抽象
Spring AI 通过方言抽象支持多种关系型数据库。以下数据库已提供开箱即用的支持:
-
PostgreSQL
-
MySQL / MariaDB
-
SQL Server
-
HSQLDB
-
Oracle Database
当使用 JdbcChatMemoryRepositoryDialect.from(DataSource) 时,可以从 JDBC URL 自动检测正确的方言。你可以通过实现 JdbcChatMemoryRepositoryDialect 接口来扩展对其他数据库的支持。
配置属性
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.chat.memory.repository.jdbc.initialize-schema | 控制何时初始化 schema。可选值:embedded (默认), always, never。 | embedded |
spring.ai.chat.memory.repository.jdbc.schema | 用于初始化的 schema 脚本位置。支持 classpath: URL 和平台占位符。 | classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-@@platform@@.sql |
spring.ai.chat.memory.repository.jdbc.platform | 如果使用了 @@platform@@ 占位符,在初始化脚本中使用的平台。 | auto-detected |
模式初始化
自动配置会在启动时自动创建 SPRING_AI_CHAT_MEMORY 表,使用针对您数据库供应商的特定 SQL 脚本。默认情况下,模式初始化仅针对嵌入式数据库(H2、HSQL、Derby 等)运行。
你可以通过 spring.ai.chat.memory.repository.jdbc.initialize-schema 属性来控制模式(schema)的初始化:
spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded # Only for embedded DBs (default)
spring.ai.chat.memory.repository.jdbc.initialize-schema=always # Always initialize
spring.ai.chat.memory.repository.jdbc.initialize-schema=never # Never initialize (useful with Flyway/Liquibase)
要覆盖模式脚本位置,请使用:
spring.ai.chat.memory.repository.jdbc.schema=classpath:/custom/path/schema-mysql.sql
扩展方言
要为新增的数据库提供支持,请实现 JdbcChatMemoryRepositoryDialect 接口,并提供用于选择、插入和删除消息的SQL语句。然后,您可以将自定义的方言传递给存储库构建器。
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new MyCustomDbDialect())
.build();
CassandraChatMemoryRepository
CassandraChatMemoryRepository 使用 Apache Cassandra 来存储消息。它适用于需要持久化存储聊天记忆的应用程序,尤其是在需要高可用性、持久性、可扩展性以及利用生存时间(TTL)特性的场景下。
CassandraChatMemoryRepository 采用时序数据模式,能够完整记录所有历史聊天窗口,这对于治理和审计非常有价值。建议设置生存时间(TTL),例如设置为三年。
要使用 CassandraChatMemoryRepository,首先需要将依赖项添加到你的项目中:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-cassandra'
}
Spring AI 为 CassandraChatMemoryRepository 提供了自动配置,您可以直接在应用程序中使用它。
@Autowired
CassandraChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
如果你更倾向于手动创建 CassandraChatMemoryRepository,可以通过提供 CassandraChatMemoryRepositoryConfig 实例来实现:
ChatMemoryRepository chatMemoryRepository = CassandraChatMemoryRepository
.create(CassandraChatMemoryRepositoryConfig.builder().withCqlSession(cqlSession));
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
配置属性
| 属性 | 说明 | 默认值 |
|---|---|---|
spring.cassandra.contactPoints | 用于初始化集群发现的主机地址 | 127.0.0.1 |
spring.cassandra.port | 用于连接 Cassandra 原生协议的端口 | 9042 |
spring.cassandra.localDatacenter | 要连接到的 Cassandra 数据中心 | datacenter1 |
spring.ai.chat.memory.cassandra.time-to-live | 写入 Cassandra 的消息的生存时间 (TTL) | |
spring.ai.chat.memory.cassandra.keyspace | Cassandra keyspace | springframework |
spring.ai.chat.memory.cassandra.messages-column | 用于存储消息的 Cassandra 列名 | springframework |
spring.ai.chat.memory.cassandra.table | Cassandra 表 | ai_chat_memory |
spring.ai.chat.memory.cassandra.initialize-schema | 是否在启动时初始化数据库 schema | true |
模式初始化
自动配置将自动创建 ai_chat_memory 表。
你可以通过将属性 spring.ai.chat.memory.repository.cassandra.initialize-schema 设置为 false 来禁用模式初始化。
Neo4j ChatMemoryRepository
Neo4jChatMemoryRepository 是一个内置实现,它使用 Neo4j 将聊天消息作为节点和关系存储在属性图数据库中。它适用于希望利用 Neo4j 的图功能进行聊天记忆持久化的应用程序。
首先,将以下依赖项添加到你的项目中:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-neo4j</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-neo4j'
}
Spring AI 为 Neo4jChatMemoryRepository 提供了自动配置,你可以直接在应用程序中使用它。
@Autowired
Neo4jChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
如果您更倾向于手动创建 Neo4jChatMemoryRepository,可以通过提供一个 Neo4j Driver 实例来实现:
ChatMemoryRepository chatMemoryRepository = Neo4jChatMemoryRepository.builder()
.driver(driver)
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
配置属性
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.chat.memory.repository.neo4j.sessionLabel | 存储对话会话节点的标签 | Session |
spring.ai.chat.memory.repository.neo4j.messageLabel | 存储消息节点的标签 | Message |
spring.ai.chat.memory.repository.neo4j.toolCallLabel | 存储工具调用(例如在助手消息中)节点的标签 | ToolCall |
spring.ai.chat.memory.repository.neo4j.metadataLabel | 存储消息元数据节点的标签 | Metadata |
spring.ai.chat.memory.repository.neo4j.toolResponseLabel | 存储工具响应节点的标签 | ToolResponse |
spring.ai.chat.memory.repository.neo4j.mediaLabel | 存储与消息关联的媒体节点的标签 | Media |
索引初始化
Neo4j 仓库将自动确保为对话ID和消息索引创建索引,以优化性能。如果您使用自定义标签,也会为这些标签创建索引。无需进行模式初始化,但您应确保您的应用程序可以访问您的Neo4j实例。
CosmosDBChatMemoryRepository
CosmosDBChatMemoryRepository 是一个内置实现,它使用 Azure Cosmos DB NoSQL API 来存储消息。它适用于需要全球分布式、高度可扩展的文档数据库来进行聊天记忆持久化的应用程序。该存储库使用对话 ID 作为分区键,以确保高效的数据分发和快速检索。
首先,将以下依赖项添加到您的项目中:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-cosmos-db</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-cosmos-db'
}
Spring AI 为 CosmosDBChatMemoryRepository 提供了自动配置,您可以直接在应用程序中使用它。
@Autowired
CosmosDBChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
若你更倾向于手动创建 CosmosDBChatMemoryRepository,可以通过提供 CosmosDBChatMemoryRepositoryConfig 实例来实现:
ChatMemoryRepository chatMemoryRepository = CosmosDBChatMemoryRepository
.create(CosmosDBChatMemoryRepositoryConfig.builder()
.withCosmosClient(cosmosAsyncClient)
.withDatabaseName("chat-memory-db")
.withContainerName("conversations")
.build());
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
配置属性
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.chat.memory.repository.cosmosdb.endpoint | Azure Cosmos DB 端点 URI。自动配置所需。 | |
spring.ai.chat.memory.repository.cosmosdb.key | Azure Cosmos DB 主密钥或辅助密钥。如果未提供,将使用 Azure 身份验证。 | |
spring.ai.chat.memory.repository.cosmosdb.connection-mode | Cosmos DB 客户端的连接模式(direct 或 gateway)。 | gateway |
spring.ai.chat.memory.repository.cosmosdb.database-name | Cosmos DB 数据库的名称。 | SpringAIChatMemory |
spring.ai.chat.memory.repository.cosmosdb.container-name | Cosmos DB 容器的名称。 | ChatMemory |
spring.ai.chat.memory.repository.cosmosdb.partition-key-path | 容器的分区键路径。 | /conversationId |
身份验证
Cosmos DB 聊天记忆存储库支持两种身份验证方法:
-
基于密钥的身份验证:提供
spring.ai.chat.memory.repository.cosmosdb.key属性,并填入你的 Cosmos DB 主密钥或辅助密钥。 -
Azure Identity 身份验证:当未提供密钥时,存储库将使用 Azure Identity(
DefaultAzureCredential)通过托管身份、服务主体或其他 Azure 凭据源进行身份验证。
模式初始化
自动配置会在数据库和容器不存在时自动创建它们。容器配置为以对话ID作为分区键(/conversationId),以确保聊天内存操作的最佳性能。无需手动设置架构。
您可以使用上述配置属性自定义数据库和容器名称。
MongoChatMemoryRepository
MongoChatMemoryRepository 是一个内置实现,它使用 MongoDB 来存储消息。它适用于需要灵活、面向文档的数据库来持久化聊天记忆的应用程序。
首先,请将以下依赖项添加到您的项目中:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-mongodb</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-mongodb'
}
Spring AI为MongoChatMemoryRepository提供了自动配置,您可以直接在应用中使用它。
@Autowired
MongoChatMemoryRepository chatMemoryRepository;
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
如果您更倾向于手动创建 MongoChatMemoryRepository,可以通过提供一个 MongoTemplate 实例来实现:
ChatMemoryRepository chatMemoryRepository = MongoChatMemoryRepository.builder()
.mongoTemplate(mongoTemplate)
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
配置属性
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.chat.memory.repository.mongo.create-indices | 是否在启动时自动创建或重新创建索引。注意:更改 * TTL 值将会删除 TTL 索引并重新创建它。 | false |
spring.ai.chat.memory.repository.mongo.ttl | 写入 MongoDB 的消息的存活时间 (TTL),以秒为单位。如果未设置,消息将被无限期存储。 | 0 |
集合初始化
如果 ai_chat_memory 集合尚未存在,启动时自动配置将自动创建该集合。
聊天客户端中的记忆
使用ChatClient API时,您可以提供一个ChatMemory实现,以在多次交互中保持对话上下文。
Spring AI 提供了几个内置的 Advisor,您可以根据需求使用它们来配置 ChatClient 的内存行为。
目前,执行工具调用时与大型语言模型交互的中间消息尚未存储到记忆中。这是当前实现的一个限制,将在未来的版本中解决。如果你需要存储这些消息,请参阅用户控制工具执行的说明。
-
MessageChatMemoryAdvisor。该顾问使用提供的ChatMemory实现来管理对话记忆。在每次交互中,它从记忆中检索对话历史记录,并将其作为消息集合包含在提示中。 -
PromptChatMemoryAdvisor。该顾问使用提供的ChatMemory实现来管理对话记忆。在每次交互中,它从记忆中检索对话历史记录,并将其以纯文本形式附加到系统提示中。 -
VectorStoreChatMemoryAdvisor。该顾问使用提供的VectorStore实现来管理对话记忆。在每次交互中,它从向量存储中检索对话历史记录,并将其以纯文本形式附加到系统消息中。
例如,如果你想将 MessageWindowChatMemory 与 MessageChatMemoryAdvisor 结合使用,可以按以下方式配置:
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
在对 ChatClient 进行调用时,内存将由 MessageChatMemoryAdvisor 自动管理。系统将根据指定的会话 ID 从内存中检索对话历史记录:
String conversationId = "007";
chatClient.prompt()
.user("Do I have license to code?")
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
PromptChatMemoryAdvisor
自定义模板
PromptChatMemoryAdvisor 使用默认模板来增强系统消息,并加入检索到的对话记忆。您可以通过 .promptTemplate() 构建器方法提供自定义的 PromptTemplate 对象来定制这一行为。
此处提供的 PromptTemplate 用于自定义顾问如何将检索到的记忆与系统消息合并。这与在 ChatClient 自身配置 TemplateRenderer(通过 .templateRenderer() 方法)是不同的,后者影响的是顾问运行之前初始用户/系统提示内容的渲染。有关客户端级别模板渲染的更多细节,请参阅 ChatClient 提示模板。
自定义的PromptTemplate可以使用任何TemplateRenderer实现(默认情况下,它使用基于StringTemplate引擎的StPromptTemplate)。重要要求是模板必须包含以下两个占位符:
-
一个
instructions占位符,用于接收原始系统消息。 -
一个
memory占位符,用于接收检索到的对话记忆。
VectorStoreChatMemoryAdvisor
自定义模板
VectorStoreChatMemoryAdvisor 使用默认模板将检索到的对话记忆整合到系统消息中。你可以通过 .promptTemplate() 构建器方法提供自己的 PromptTemplate 对象来自定义此行为。
此处提供的 PromptTemplate 用于自定义顾问(advisor)如何将检索到的记忆(memory)与系统消息合并。这与在 ChatClient 自身配置 TemplateRenderer(通过 .templateRenderer() 方法)不同,后者影响的是顾问运行之前初始用户/系统提示内容的渲染。关于客户端级别的模板渲染详情,请参阅 ChatClient 提示模板。
自定义的 PromptTemplate 可以使用任何 TemplateRenderer 实现(默认情况下,它使用基于 StringTemplate 引擎的 StPromptTemplate)。重要要求是模板必须包含以下两个占位符:
- 一个
instructions占位符,用于接收原始的系统消息。 - 一个
long_term_memory占位符,用于接收检索到的对话记忆。
聊天模型中的记忆功能
如果你直接使用 ChatModel 而非 ChatClient,则可以显式管理内存:
// Create a memory instance
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = "007";
// First interaction
UserMessage userMessage1 = new UserMessage("My name is James Bond");
chatMemory.add(conversationId, userMessage1);
ChatResponse response1 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
chatMemory.add(conversationId, response1.getResult().getOutput());
// Second interaction
UserMessage userMessage2 = new UserMessage("What is my name?");
chatMemory.add(conversationId, userMessage2);
ChatResponse response2 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
chatMemory.add(conversationId, response2.getResult().getOutput());
// The response will contain "James Bond"