7 Chat Memory

75 阅读12分钟

Chat Memory(聊天记忆)

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

ChatMemory 抽象允许您实现各种类型的记忆以支持不同的用例。消息的底层存储由 ChatMemoryRepository 处理,其唯一职责是存储和检索消息。由 ChatMemory 实现来决定保留哪些消息以及何时删除它们。策略示例可以包括保留最后 N 条消息、保留消息特定时间段,或保留消息到特定令牌限制。

在选择记忆类型之前,了解聊天记忆和聊天历史之间的区别至关重要。

  • 聊天记忆(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 提供的内置记忆类型及其特性。

Message Window Chat Memory(消息窗口聊天记忆)

MessageWindowChatMemory 维护一个消息窗口,直到指定的最大大小。当消息数量超过最大值时,会删除旧消息,同时保留系统消息。默认窗口大小为 20 条消息。

MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
    .maxMessages(10)
    .build();

这是 Spring AI 用于自动配置 ChatMemory bean 的默认消息类型。

记忆存储

Spring AI 提供了 ChatMemoryRepository 抽象来存储聊天记忆。本节描述了 Spring AI 提供的内置存储库以及如何使用它们,但如果需要,您也可以实现自己的存储库。

In-Memory Repository(内存存储库)

InMemoryChatMemoryRepository 使用 ConcurrentHashMap 在内存中存储消息。

默认情况下,如果没有配置其他存储库,Spring AI 会自动配置一个类型为 InMemoryChatMemoryRepositoryChatMemoryRepository bean,您可以直接在应用程序中使用。

@Autowired
ChatMemoryRepository chatMemoryRepository;

如果您想手动创建 InMemoryChatMemoryRepository,可以按以下方式进行:

ChatMemoryRepository repository = new InMemoryChatMemoryRepository();

JdbcChatMemoryRepository

JdbcChatMemoryRepository 是一个内置实现,使用 JDBC 在关系数据库中存储消息。它支持多种数据库,适用于需要聊天记忆持久化存储的应用程序。

首先,向您的项目添加以下依赖:

Maven
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
Gradle
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

当使用 JdbcChatMemoryRepositoryDialect.from(DataSource) 时,可以从 JDBC URL 自动检测正确的方言。您可以通过实现 JdbcChatMemoryRepositoryDialect 接口来扩展对其他数据库的支持。

配置属性
属性描述默认值
spring.ai.chat.memory.repository.jdbc.initialize-schema控制何时初始化架构。值:embedded(默认)、alwaysneverembedded
spring.ai.chat.memory.repository.jdbc.schema用于初始化的架构脚本位置。支持 classpath: URL 和平台占位符。classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-@@platform@@.sql
spring.ai.chat.memory.repository.jdbc.platform如果使用 @@platform@@ 占位符,在初始化脚本中使用的平台。自动检测
架构初始化

自动配置将在启动时自动创建 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)

要覆盖架构脚本位置,请使用:

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 具有时间序列架构,保留所有过去聊天窗口的记录,对于治理和审计很有价值。建议将生存时间设置为某个值,例如三年。

要使用 CassandraChatMemoryRepository,首先向您的项目添加依赖:

Maven
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
</dependency>
Gradle
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.keyspaceCassandra 键空间springframework
spring.ai.chat.memory.cassandra.messages-columnCassandra 消息列名springframework
spring.ai.chat.memory.cassandra.tableCassandra 表ai_chat_memory
spring.ai.chat.memory.cassandra.initialize-schema是否在启动时初始化架构true
架构初始化

自动配置将自动创建 ai_chat_memory 表。

您可以通过将属性 spring.ai.chat.memory.repository.cassandra.initialize-schema 设置为 false 来禁用架构初始化。

Neo4j ChatMemoryRepository

Neo4jChatMemoryRepository 是一个内置实现,使用 Neo4j 将聊天消息作为节点和关系存储在属性图数据库中。它适用于希望利用 Neo4j 的图功能进行聊天记忆持久化的应用程序。

首先,向您的项目添加以下依赖:

Maven
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-neo4j</artifactId>
</dependency>
Gradle
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 实例可被应用程序访问。

Chat Client 中的记忆

当使用 ChatClient API 时,您可以提供 ChatMemory 实现来在多个交互中维护对话上下文。

Spring AI 提供了一些内置的 Advisors,您可以使用它们根据需要配置 ChatClient 的记忆行为。

警告:目前,执行工具调用时与大型语言模型交换的中间消息不会存储在记忆中。这是当前实现的限制,将在未来版本中解决。如果您需要存储这些消息,请参阅用户控制的工具执行的说明。

  • MessageChatMemoryAdvisor:此顾问使用提供的 ChatMemory 实现管理对话记忆。在每次交互时,它从记忆中检索对话历史并将其作为消息集合包含在提示中。
  • PromptChatMemoryAdvisor:此顾问使用提供的 ChatMemory 实现管理对话记忆。在每次交互时,它从记忆中检索对话历史并将其作为纯文本附加到系统提示。
  • VectorStoreChatMemoryAdvisor:此顾问使用提供的 VectorStore 实现管理对话记忆。在每次交互时,它从向量存储中检索对话历史并将其作为纯文本附加到系统消息。

例如,如果您想将 MessageWindowChatMemoryMessageChatMemoryAdvisor 一起使用,可以按以下方式配置:

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 自定义了顾问如何将检索到的记忆与系统消息合并。这与在 ChatClient 本身上配置 TemplateRenderer(使用 .templateRenderer())不同,后者影响顾问运行之前初始用户/系统提示内容的渲染。有关客户端级别模板渲染的更多详细信息,请参阅 ChatClient 提示模板

自定义 PromptTemplate 可以使用任何 TemplateRenderer 实现(默认情况下,它使用基于 StringTemplate 引擎的 StPromptTemplate)。重要要求是模板必须包含以下两个占位符:

  • 一个 instructions 占位符来接收原始系统消息。
  • 一个 long_term_memory 占位符来接收检索到的对话记忆。

Chat Model 中的记忆

如果您直接使用 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"

核心概念总结

1. 架构设计

  • ChatMemory 抽象:定义记忆管理的核心接口
  • ChatMemoryRepository:负责消息的持久化存储
  • 分离关注点:记忆策略与存储实现分离

2. 记忆类型

  • MessageWindowChatMemory:基于消息数量的滑动窗口
  • 可配置大小:支持自定义最大消息数量
  • 系统消息保护:确保系统消息不会被删除

3. 存储选项

存储类型适用场景特点
In-Memory开发测试、临时会话快速、简单、易失性
JDBC生产环境、关系数据库事务支持、SQL 查询
Cassandra大规模、高可用分布式、TTL 支持
Neo4j复杂关系、图查询图关系、灵活查询

4. 集成方式

  • ChatClient Advisors:声明式记忆管理
  • 直接 ChatModel:程序化记忆控制
  • 自动配置:Spring Boot 简化集成

5. 配置灵活性

  • 多数据库支持:通过方言抽象扩展
  • 自定义模板:定制记忆注入方式
  • 架构初始化:自动或手动控制

最佳实践

1. 选择合适的存储

  • 开发环境:使用 In-Memory 存储库
  • 生产环境:根据数据量选择 JDBC、Cassandra 或 Neo4j
  • 高并发场景:考虑 Cassandra 的分布式特性

2. 记忆策略

  • 消息窗口大小:平衡上下文质量和性能
  • 会话隔离:使用唯一的对话 ID
  • 记忆清理:定期清理过期数据

3. 性能优化

  • 批量操作:减少数据库往返
  • 索引优化:确保查询性能
  • 连接池:合理配置数据库连接

4. 监控和维护

  • 存储监控:监控存储使用情况
  • 性能指标:跟踪记忆操作性能
  • 错误处理:优雅处理存储故障

术语对照表

英文术语中文翻译说明
Chat Memory聊天记忆LLM 在对话中维持的上下文信息
Chat History聊天历史完整的对话记录
Message Window消息窗口基于消息数量的记忆策略
In-Memory Repository内存存储库基于内存的消息存储
JDBC RepositoryJDBC 存储库基于关系数据库的存储
Cassandra RepositoryCassandra 存储库基于 Cassandra 的分布式存储
Neo4j RepositoryNeo4j 存储库基于图数据库的存储
Advisor顾问ChatClient 的功能增强器
Conversation ID对话 ID会话的唯一标识符

核心要点总结

  1. 状态管理:解决 LLM 无状态特性的关键机制
  2. 架构灵活:支持多种记忆类型和存储选项
  3. 易于集成:通过 Spring Boot 自动配置简化使用
  4. 生产就绪:提供多种持久化存储选项
  5. 可扩展性:支持自定义记忆策略和存储实现
  6. 性能优化:通过窗口策略控制上下文大小
  7. 多种集成:支持 ChatClient 和直接 ChatModel 两种使用方式

通过 Spring AI 的聊天记忆功能,开发者可以轻松构建具有上下文感知能力的智能对话系统。