聊天记忆
大型语言模型(LLM)是无状态的,这意味着它们不会保留之前交互的信息。当你希望在多次交互中保持上下文或状态时,这可能会成为一个限制。为了解决这一问题,Spring AI 提供了聊天记忆功能,使你能够在多次与 LLM 的交互中存储和检索信息。
ChatMemory 抽象允许你实现多种类型的记忆,以支持不同的使用场景。消息的底层存储由 ChatMemoryRepository 负责,其唯一职责是存储和检索消息。由 ChatMemory 的实现决定哪些消息需要保留,以及何时删除它们。常见策略示例包括保留最近的 N 条消息、保留特定时间段内的消息,或保留到达某个 token 限制的消息。
在选择记忆类型之前,理解聊天记忆(Chat Memory)与聊天历史(Chat History)的区别非常重要:
- 聊天记忆(Chat Memory):大型语言模型保留并用于在对话中维持上下文感知的信息。
- 聊天历史(Chat History):完整的对话历史,包括用户与模型之间交换的所有消息。
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 的默认消息类型。
MessageWindowChatMemory 会依赖一个存储库(例如 InMemoryChatMemoryRepository)来实际存储消息。
记忆存储
Spring AI 提供了 ChatMemoryRepository 抽象,用于存储聊天记忆。本节介绍了 Spring AI 内置的存储库及其使用方法,但如果需要,你也可以自行实现自定义存储库。
内存存储库
InMemoryChatMemoryRepository 使用 ConcurrentHashMap 将消息存储在内存中。
默认情况下,如果没有配置其他存储库,Spring AI 会自动配置一个类型为 InMemoryChatMemoryRepository 的 ChatMemoryRepository Bean,你可以直接在应用中使用:
@Autowired
ChatMemoryRepository chatMemoryRepository;
如果你希望手动创建 InMemoryChatMemoryRepository,也可以这样做:
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();
JdbcChatMemoryRepository
JdbcChatMemoryRepository 是一个内置实现,使用 JDBC 将消息存储在关系型数据库中。它开箱即用地支持多种数据库,适合需要持久化存储聊天记忆的应用场景。
首先,将以下依赖添加到项目中:
<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 | 控制何时初始化数据库表,取值: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 | 当 schema 脚本使用 @@platform@@ 占位符时指定平台 | 自动检测 |
Schema 初始化
自动配置会在启动时创建 SPRING_AI_CHAT_MEMORY 表,使用针对不同数据库的 SQL 脚本。默认情况下,初始化仅对嵌入式数据库(H2、HSQL、Derby 等)执行。
通过 spring.ai.chat.memory.repository.jdbc.initialize-schema 属性可以控制初始化行为:
spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded # 仅嵌入式数据库(默认)
spring.ai.chat.memory.repository.jdbc.initialize-schema=always # 始终初始化
spring.ai.chat.memory.repository.jdbc.initialize-schema=never # 从不初始化(适用于 Flyway/Liquibase)
若需覆盖 schema 脚本位置,可使用:
spring.ai.chat.memory.repository.jdbc.schema=classpath:/custom/path/schema-mysql.sql
扩展方言
若需支持新的数据库,实现 JdbcChatMemoryRepositoryDialect 接口,并提供查询、插入、删除消息的 SQL,然后在构建 Repository 时传入自定义方言:
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new MyCustomDbDialect())
.build();
CassandraChatMemoryRepository
CassandraChatMemoryRepository 使用 Apache Cassandra 存储消息,适合需要持久化聊天记忆的应用场景,尤其在高可用性、耐久性、可扩展性方面表现优异,同时可利用 TTL(生存时间)特性。
CassandraChatMemoryRepository 采用时间序列模式,保留所有历史聊天窗口的记录,有助于治理和审计。建议为消息设置 TTL,例如三年。
要先使用 CassandraChatMemoryRepository,请将依赖添加到你的项目中:
<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 | 存储消息的列名 | springframework |
spring.ai.chat.memory.cassandra.table | 存储消息的表名 | ai_chat_memory |
spring.ai.chat.memory.cassandra.initialize-schema | 启动时是否初始化 schema | true |
Schema 初始化
自动配置会在启动时创建 ai_chat_memory 表。
如果不希望自动初始化 schema,可以将属性设置为 false: spring.ai.chat.memory.repository.cassandra.initialize-schema=false
Neo4j ChatMemoryRepository
Neo4jChatMemoryRepository 是一个内置实现,它使用 Neo4j 将聊天消息以节点和关系的形式存储在图数据库中。它适用于希望利用 Neo4j 图数据库能力来持久化聊天记忆的应用场景。
首先,将依赖添加到项目中:
<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 | 存储工具调用节点的标签(如 Assistant Messages) | 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 不需要额外的 schema 初始化,但应确保你的 Neo4j 实例对应用可访问。
CosmosDBChatMemoryRepository
CosmosDBChatMemoryRepository 是一个内置实现,它使用 Azure Cosmos DB 的 NoSQL API 来存储消息。适用于需要全球分布、高度可扩展文档数据库来持久化聊天记忆的应用。该仓库使用会话 ID 作为分区键,以确保高效的数据分布和快速检索。
首先,将依赖添加到项目中:
<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 Identity 认证 | — |
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 Chat Memory Repository 支持两种认证方式:
- 密钥认证:提供
spring.ai.chat.memory.repository.cosmosdb.key属性,使用 Cosmos DB 主密钥或次密钥。 - Azure Identity 认证:未提供密钥时,使用 Azure Identity(DefaultAzureCredential)进行认证,可支持托管身份、服务主体或其他 Azure 凭证来源。
Schema 初始化
自动配置会在数据库和容器不存在时自动创建。容器配置使用会话 ID (/conversationId) 作为分区键,以确保聊天记忆操作的性能最优。无需手动设置 schema。
你也可以通过上述配置属性自定义数据库和容器名称。
MongoChatMemoryRepository
MongoChatMemoryRepository 是一个内置实现,使用 MongoDB 来存储消息,适用于需要灵活的文档型数据库来持久化聊天记忆的应用。
首先,在项目中添加以下依赖:
<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 实现管理对话记忆。在每次交互中,它会从记忆中检索对话历史,并以纯文本形式附加到系统提示(system prompt)中。VectorStoreChatMemoryAdvisor:使用提供的VectorStore实现管理对话记忆。在每次交互中,它会从向量存储中检索对话历史,并以纯文本形式附加到系统消息中。
例如,如果你想将 MessageWindowChatMemory 与 MessageChatMemoryAdvisor 配合使用,可以这样配置:
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
在调用 ChatClient 时,记忆将由 MessageChatMemoryAdvisor 自动管理。对话历史会根据指定的 conversation 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 用于自定义 Advisor 如何将检索到的记忆与系统消息合并。这与在 ChatClient 上配置 TemplateRenderer(使用 .templateRenderer())不同,后者是在 Advisor 运行 前 渲染初始的用户/系统提示内容。有关客户端级别模板渲染的更多信息,请参见 ChatClient Prompt Templates。自定义的 PromptTemplate 可以使用任何 TemplateRenderer 实现(默认使用基于 StringTemplate 引擎的 StPromptTemplate)。唯一重要的要求是模板必须包含以下两个占位符:
instructions占位符:用于接收原始系统消息。memory占位符:用于接收检索到的对话记忆。
VectorStoreChatMemoryAdvisor
自定义模板
VectorStoreChatMemoryAdvisor 使用默认模板将检索到的对话记忆附加到系统消息中。你可以通过在构建器中使用 .promptTemplate() 方法提供自定义 PromptTemplate 对象来修改这一行为。
PromptTemplate 用于自定义 Advisor 如何将检索到的记忆与系统消息合并。这与在 ChatClient 上配置 TemplateRenderer(使用 .templateRenderer())不同,后者是在 Advisor 运行 前 渲染初始的用户/系统提示内容。有关客户端级别模板渲染的更多信息,请参见 ChatClient Prompt Templates。自定义 PromptTemplate 可以使用任何 TemplateRenderer 实现(默认使用基于 StringTemplate 引擎的 StPromptTemplate)。唯一重要的要求是模板必须包含以下两个占位符:
- instructions 占位符:用于接收原始系统消息。
- long_term_memory 占位符:用于接收检索到的长期对话记忆。
聊天模型中的记忆
如果你直接使用 ChatModel 而不是 ChatClient,可以显式地管理记忆:
// 创建记忆实例
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = "007";
// 第一次交互
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());
// 第二次交互
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());
// 返回的响应将包含 "James Bond"