聊天记忆
大型语言模型 (LLM) 是无状态的,这意味着它们不会保留有关以前交互的信息。当您希望在多个交互中维护上下文或状态时,这可能是一个限制。为了解决这个问题,Spring AI 提供了聊天内存功能,允许您在与 LLM 的多次交互中存储和检索信息。
这ChatMemory
抽象允许您实现各种类型的内存以支持不同的使用案例。消息的底层存储由ChatMemoryRepository
,其唯一职责是存储和检索消息。这取决于ChatMemory
implementation 来决定要保留哪些消息以及何时删除它们。策略示例可能包括保留最后 N 条消息、将消息保留一段时间或将消息保持在某个令牌限制内。
在选择内存类型之前,必须了解聊天内存和聊天记录之间的区别。
-
聊天记录。大型语言模型保留并用于在整个对话中保持上下文感知的信息。
-
聊天记录。整个会话历史记录,包括用户与模型之间交换的所有消息。
这ChatMemory
abstraction 旨在管理聊天内存。它允许您存储和检索与当前对话上下文相关的消息。但是,它并不是存储聊天记录的最佳选择。如果您需要维护所有交换消息的完整记录,您应该考虑使用不同的方法,例如依靠 Spring Data 来有效存储和检索完整的聊天历史记录。
快速开始
Spring AI 会自动配置ChatMemory
可以直接在应用程序中使用的 bean。默认情况下,它使用内存中存储库来存储消息 (InMemoryChatMemoryRepository
) 和MessageWindowChatMemory
implementation 来管理对话历史记录。如果已经配置了不同的存储库(例如,Cassandra、JDBC 或 Neo4j),Spring AI 将改用该存储库。
@Autowired
ChatMemory chatMemory;
以下部分将进一步描述 Spring AI 中可用的不同内存类型和存储库。
内存存储
Spring AI 提供了ChatMemoryRepository
用于存储聊天内存的抽象。本节描述了 Spring AI 提供的内置存储库及其使用方法,但如果需要,您也可以实现自己的存储库。
内存存储库
InMemoryChatMemoryRepository
使用ConcurrentHashMap
.
默认情况下,如果尚未配置其他存储库,则 Spring AI 会自动配置ChatMemoryRepository
类型的 beanInMemoryChatMemoryRepository
可以直接在应用程序中使用。
@Autowired
ChatMemoryRepository chatMemoryRepository;
如果您更愿意创建InMemoryChatMemoryRepository
手动,您可以按如下方式执行此作:
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();
JdbcChatMemoryRepository
JdbcChatMemoryRepository
是使用 JDBC 在关系数据库中存储消息的内置实现。它支持多个开箱即用的数据库,适用于需要持久存储聊天内存的应用程序。
首先,将以下依赖项添加到您的项目中:
-
Maven
-
Gradle
<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
instance 和JdbcChatMemoryRepositoryDialect
:
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new PostgresChatMemoryDialect())
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
支持的数据库和 Dialect 抽象
Spring AI 通过 dialect 抽象支持多个关系数据库。以下数据库是开箱即用的:
-
PostgreSQL 数据库
-
MySQL / MariaDB
-
SQL 服务器
-
HSQLDB 数据库
使用 JDBC URL 时,可以从 JDBC URL 中自动检测正确的方言JdbcChatMemoryRepositoryDialect.from(DataSource)
.您可以通过实现JdbcChatMemoryRepositoryDialect
接口。
配置属性
属性 |
描述 |
默认值 |
|
控制何时初始化架构。值: |
|
|
用于初始化的架构脚本的位置。支持 |
|
|
在初始化脚本中使用的平台(如果使用@@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 # 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
CassandraChatMemoryRepository
CassandraChatMemoryRepository
使用 Apache Cassandra 存储消息。它适用于需要持久存储聊天内存的应用程序,尤其是可用性、持久性、规模以及利用生存时间 (TTL) 功能时。
CassandraChatMemoryRepository
具有时间序列架构,保留所有过去聊天窗口的记录,对于治理和审计很有价值。建议将 time to live 设置为某个值,例如三年。
要使用CassandraChatMemoryRepository
首先,将依赖项添加到您的项目中:
-
Maven
-
Gradle
<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(CassandraChatMemoryConfig.builder().withCqlSession(cqlSession));
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
配置属性
属性 |
描述 |
默认值 |
|
用于启动集群发现的主机 |
|
|
要连接的 Cassandra 本机协议端口 |
|
|
要连接到的 Cassandra 数据中心 |
|
|
在 Cassandra 中写入的消息的生存时间 (TTL) |
|
|
Cassandra 密钥空间 |
|
|
消息的 Cassandra 列名称 |
|
|
Cassandra 表 |
|
|
是否在启动时初始化 Schema。 |
|
Neo4j ChatMemory仓库
Neo4jChatMemoryRepository
是一个内置实现,它使用 Neo4j 将聊天消息作为节点和关系存储在属性图数据库中。它适用于希望利用 Neo4j 的图形功能实现聊天内存持久性的应用程序。
首先,将以下依赖项添加到您的项目中:
-
Maven
-
Gradle
<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();
配置属性
属性 |
描述 |
默认值 |
|
存储会话的节点的标签 |
|
|
存储消息的节点的标签 |
|
|
存储工具调用的节点的标签(例如,在 Assistant Messages 中) |
|
|
存储消息元数据的节点的标签 |
|
|
存储工具响应的节点的标签 |
|
|
存储与消息关联的媒体的节点的标签 |
|
聊天客户端中的内存
使用 ChatClient API 时,您可以提供ChatMemory
实现以在多个交互中维护对话上下文。
Spring AI 提供了一些内置的 Advisor,你可以使用它们来配置ChatClient
,具体取决于您的需求。
目前,在执行工具调用时与大型语言模型交换的中间消息不存储在内存中。这是当前实施的限制,将在将来的版本中得到解决。如果需要存储这些消息,请参阅 User Controlled Tool Execution 的说明。 |
-
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();
提示ChatMemoryAdvisor
自定义模板
这PromptChatMemoryAdvisor
使用默认模板通过检索到的对话内存来扩充系统消息。您可以通过提供自己的PromptTemplate
对象通过.promptTemplate()
builder 方法。
这PromptTemplate 此处提供自定义 advisor 如何将检索到的内存与系统消息合并。这与配置TemplateRenderer 在ChatClient 本身(使用.templateRenderer() ),这会影响 advisor 运行之前初始用户/系统提示内容的呈现。请参阅 ChatClient 提示模板 有关客户端级模板渲染的更多详细信息。 |
自定义PromptTemplate
可以使用任何TemplateRenderer
实现(默认情况下,它使用StPromptTemplate
基于 StringTemplate 引擎)。重要的要求是模板必须包含以下两个占位符:
-
一
instructions
placeholder 接收原始系统消息。 -
一个
memory
placeholder 接收检索到的对话内存。
VectorStoreChatMemoryAdvisor
自定义模板
这VectorStoreChatMemoryAdvisor
使用默认模板通过检索到的对话内存来扩充系统消息。您可以通过提供自己的PromptTemplate
对象通过.promptTemplate()
builder 方法。
这PromptTemplate 此处提供自定义 advisor 如何将检索到的内存与系统消息合并。这与配置TemplateRenderer 在ChatClient 本身(使用.templateRenderer() ),这会影响 advisor 运行之前初始用户/系统提示内容的呈现。请参阅 ChatClient 提示模板 有关客户端级模板渲染的更多详细信息。 |
自定义PromptTemplate
可以使用任何TemplateRenderer
实现(默认情况下,它使用StPromptTemplate
基于 StringTemplate 引擎)。重要的要求是模板必须包含以下两个占位符:
-
一
instructions
placeholder 接收原始系统消息。 -
一个
long_term_memory
placeholder 接收检索到的对话内存。
聊天模型中的内存
如果您直接使用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"