大型语言模型(LLM)是无状态的,这意味着它们不保留有关以前交互的信息。当您想在多个交互中维护上下文或状态时,这可能是一个限制。为了解决这个问题,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是一个内置的实现,它使用JDBC将消息存储在关系数据库中。它支持开箱即用的多个数据库,适用于需要持久存储聊天内存的应用程序。
首先,将以下依赖项添加到您的项目中:
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 PostgresChatMemoryDialect())
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
Spring AI通过方言抽象支持多个关系数据库。开箱即用支持以下数据库:
PostgreSQL
MySQL/MariaDB
SQL Server
嵌入式数据库
使用JdbcChatMemoryRepositoryAlect.from(DataSource)时,可以从JDBC URL自动检测到正确的方言。您可以通过实现JdbcChatMemoryRepositoryDialect接口来扩展对其他数据库的支持。
Property |
Description |
Default Value |
|
Controls when to initialize the schema. Values: |
|
|
Location of the schema script to use for initialization. Supports |
|
|
Platform to use in initialization scripts if the @@platform@@ placeholder is used. |
auto-detected |
自动配置将在启动时使用特定于供应商的数据库SQL脚本自动创建SPRING_AI_CHAT_MEMORY表。默认情况下,模式初始化仅对嵌入式数据库(H2、HSQL、Derby等)运行。
您可以使用spring.ai.chat.memory.repository.jdbc.initialize-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
要添加对新数据库的支持,请实现JdbcChatMemoryRepositoryAlect接口,并提供用于选择、插入和删除消息的SQL。然后,您可以将自定义方言传递给存储库构建器。
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new MyCustomDbDialect())
.build();
CassandraChatMemoryRepository使用Apache Cassandra来存储消息。它适用于需要持久存储聊天内存的应用程序,特别是在可用性、耐用性、可扩展性以及利用生存时间(TTL)功能时。
CassandraChatMemoryRepository有一个时间序列模式,记录了所有过去的聊天窗口,对治理和审计很有价值。建议将寿命设定为一定值,例如三年。
要先使用CassandraChatMemoryRepository,请将依赖项添加到您的项目中:
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,可以通过提供一个CassandraChat MemoryRepositoryConfig实例来实现:
ChatMemoryRepository chatMemoryRepository = CassandraChatMemoryRepository
.create(CassandraChatMemoryConfig.builder().withCqlSession(cqlSession));
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
Property |
Description |
Default Value |
|
Host(s) to initiate cluster discovery |
|
|
Cassandra native protocol port to connect to |
|
|
Cassandra datacenter to connect to |
|
|
Time to live (TTL) for messages written in Cassandra |
|
|
Cassandra keyspace |
|
|
Cassandra column name for messages |
|
|
Cassandra table |
|
|
Whether to initialize the schema on startup. |
|
自动配置将自动创建ai_chat_memory表。
您可以通过将属性spring.ai.chat.memory.repository.cassandra.initialize-schema设置为false来禁用架构初始化。
Neo4jChatMemoryRepository是一个内置的实现,它使用Neo4j将聊天消息作为节点和关系存储在属性图数据库中。它适用于希望利用Neo4j的图形功能实现聊天内存持久性的应用程序。
首先,将以下依赖项添加到您的项目中:
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();
Property |
Description |
Default Value |
|
The label for the nodes that store conversation sessions |
|
|
The label for the nodes that store messages |
|
|
The label for nodes that store tool calls (e.g. in Assistant Messages) |
|
|
The label for nodes that store message metadata |
|
|
The label for the nodes that store tool responses |
|
|
The label for the nodes that store media associated with a message |
|
Neo4j存储库将自动确保为会话ID和消息索引创建索引,以优化性能。如果使用自定义标签,也将为这些标签创建索引。不需要模式初始化,但您应该确保您的应用程序可以访问您的Neo4j实例。
当使用ChatClient API时,您可以提供ChatMemory实现来维护多个交互中的会话上下文。
Spring AI提供了一些内置的顾问,您可以根据需要使用它们来配置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使用默认模板用检索到的对话内存来增强系统消息。您可以通过.PromptTemplate()构建器方法提供自己的PromptTemplate对象来自定义此行为。
自定义PromptTemplate可以使用任何TemplateRenderer实现(默认情况下,它使用基于StringTemplate引擎的StPromptTemplate)。重要的要求是模板必须包含以下两个占位符:
用于接收原始系统消息的指令占位符。
存储器占位符,用于接收检索到的对话存储器。
VectorStoreChatMemoryAdvisor使用默认模板用检索到的对话内存来增强系统消息。您可以通过.PromptTemplate()构建器方法提供自己的PromptTemplate对象来自定义此行为。
此处提供的PromptTemplate可自定义顾问如何将检索到的内存与系统消息合并。这与在ChatClient本身上配置TemplateRenderer(使用.templateRender())不同,后者会影响顾问运行前初始用户/系统提示内容的呈现。有关客户端级模板呈现的更多详细信息,请参阅ChatClient提示模板。
自定义PromptTemplate可以使用任何TemplateRenderer实现(默认情况下,它使用基于StringTemplate引擎的StPromptTemplate)。重要的要求是模板必须包含以下两个占位符:
用于接收原始系统消息的指令占位符。
长时记忆占位符,用于接收检索到的对话记忆。
如果你直接使用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"