Lzh on GitHub

聊天记忆

大型语言模型(LLM)是无状态的,这意味着它们不会保留之前交互的信息。当你希望在多次交互中保持上下文或状态时,这可能会成为一个限制。为了解决这一问题,Spring AI 提供了聊天记忆功能,使你能够在多次与 LLM 的交互中存储和检索信息。

大型语言模型(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 会自动配置一个类型为 InMemoryChatMemoryRepositoryChatMemoryRepository 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>

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(默认)、alwaysneverembedded
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>

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.portCassandra 原生协议端口9042
spring.cassandra.localDatacenterCassandra 数据中心datacenter1
spring.ai.chat.memory.cassandra.time-to-live写入 Cassandra 消息的 TTL
spring.ai.chat.memory.cassandra.keyspaceCassandra keyspacespringframework
spring.ai.chat.memory.cassandra.messages-column存储消息的列名springframework
spring.ai.chat.memory.cassandra.table存储消息的表名ai_chat_memory
spring.ai.chat.memory.cassandra.initialize-schema启动时是否初始化 schematrue

Schema 初始化

自动配置会在启动时创建 ai_chat_memory 表。

如果不希望自动初始化 schema,可以将属性设置为 falsespring.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>

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>

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.endpointAzure Cosmos DB 端点 URI,自动配置必需
spring.ai.chat.memory.repository.cosmosdb.keyAzure Cosmos DB 主密钥或次密钥。如果未提供,将使用 Azure Identity 认证
spring.ai.chat.memory.repository.cosmosdb.connection-modeCosmos DB 客户端连接模式(direct 或 gateway)gateway
spring.ai.chat.memory.repository.cosmosdb.database-nameCosmos DB 数据库名称SpringAIChatMemory
spring.ai.chat.memory.repository.cosmosdb.container-nameCosmos DB 容器名称ChatMemory
spring.ai.chat.memory.repository.cosmosdb.partition-key-path容器分区键路径/conversationId

认证方式

Cosmos DB Chat Memory Repository 支持两种认证方式:

  1. 密钥认证:提供 spring.ai.chat.memory.repository.cosmosdb.key 属性,使用 Cosmos DB 主密钥或次密钥。
  2. 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>

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 的记忆行为。

目前,当执行工具调用(Tool Call)时,与大型语言模型交换的中间消息不会被存储在记忆中。这是当前实现的一个限制,未来版本会改进。如果你需要存储这些消息,可以参考 “用户控制的工具执行(User Controlled Tool Execution)” 的相关说明。
  • MessageChatMemoryAdvisor:使用提供的 ChatMemory 实现管理对话记忆。在每次交互中,它会从记忆中检索对话历史,并将其作为消息集合包含在提示中。
  • PromptChatMemoryAdvisor:使用提供的 ChatMemory 实现管理对话记忆。在每次交互中,它会从记忆中检索对话历史,并以纯文本形式附加到系统提示(system prompt)中。
  • VectorStoreChatMemoryAdvisor:使用提供的 VectorStore 实现管理对话记忆。在每次交互中,它会从向量存储中检索对话历史,并以纯文本形式附加到系统消息中。

例如,如果你想将 MessageWindowChatMemoryMessageChatMemoryAdvisor 配合使用,可以这样配置:

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"