聊天记忆

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

快速入门

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 会自动配置一个类型为 InMemoryChatMemoryRepositoryChatMemoryRepository bean,您可以直接在应用程序中使用它。

@Autowired
ChatMemoryRepository chatMemoryRepository;

如果您更愿意手动创建 InMemoryChatMemoryRepository,可以按照以下方式进行

ChatMemoryRepository repository = new InMemoryChatMemoryRepository();

JDBC 存储库

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

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

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-jdbc</artifactId>
</dependency>
dependencies {
    implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-jdbc'
}

Spring AI 为 JdbcChatMemoryRepository 提供了自动配置,您可以直接在应用程序中使用它。

@Autowired
JdbcChatMemoryRepository chatMemoryRepository;

ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .chatMemoryRepository(chatMemoryRepository)
    .maxMessages(10)
    .build();

如果您更愿意手动创建 JdbcChatMemoryRepository,可以通过提供一个 JdbcTemplate 实例来完成

ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
    .jdbcTemplate(jdbcTemplate)
    .build();

ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .chatMemoryRepository(chatMemoryRepository)
    .maxMessages(10)
    .build();

配置属性

属性

描述

默认值

spring.ai.chat.memory.repository.jdbc.initialize-schema

是否在启动时初始化数据库 schema。

true

Schema 初始化

自动配置将使用 JDBC 驱动程序自动创建 ai_chat_memory 表。目前,仅支持 PostgreSQL 和 MariaDB。

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

如果您的项目使用 Flyway 或 Liquibase 等工具来管理数据库 schema,您可以禁用 schema 初始化,并参考 这些 SQL 脚本 来配置这些工具以创建 ai_chat_memory 表。

在聊天客户端中使用记忆

使用 ChatClient API 时,您可以提供一个 ChatMemory 实现,以便在多次交互中保持对话上下文。

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

当前,与大型语言模型进行工具调用时交换的中间消息不会存储在记忆中。这是当前实现的一个限制,将在未来的版本中解决。如果您需要存储这些消息,请参考 用户控制的工具执行 的说明。
  • MessageChatMemoryAdvisor. 此 Advisor 使用提供的 ChatMemory 实现来管理对话记忆。在每次交互时,它会从记忆中检索对话历史,并将其作为消息集合包含在 prompt 中。

  • PromptChatMemoryAdvisor. 此 Advisor 使用提供的 ChatMemory 实现来管理对话记忆。在每次交互时,它会从记忆中检索对话历史,并将其作为纯文本附加到系统 prompt 中。

  • VectorStoreChatMemoryAdvisor. 此 Advisor 使用提供的 VectorStore 实现来管理对话记忆。在每次交互时,它会从向量存储中检索对话历史,并将其作为纯文本附加到系统消息中。

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

ChatMemory chatMemory = MessageChatMemoryAdvisor.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(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, conversationId))
    .call()
    .content();

结构化输出 工具调用

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