PromptChatMemoryAdvisor 类用于管理对话历史(Chat Memory)的组件,它结合了 对话记忆(Chat Memory) 和 提示词工程(Prompt Engineering),帮助在多轮对话中维护上下文,并动态优化提示词(Prompt)。
其主要核心功能如下:
对话历史管理:自动保存用户与AI的交互消息,确保后续请求能携带上下文。支持限制记忆的容量,如只保留最近的N条消息。实际是通过内部的 ChatMemory 对象,以及 ChatMemory 对象内部的 ChatMemoryRepository 去完成保存操作。
动态提示词增强:在发送请求给 AI 模型前,自动将历史对话注入到当前提示词中(系统提示词),形成完整的上下文感知 Prompt。内部维护了一个默认的系统提示词模板,如下:
private static final PromptTemplate DEFAULT_SYSTEM_PROMPT_TEMPLATE = new PromptTemplate(""" {instructions} Use the conversation memory from the MEMORY section to provide accurate answers. --------------------- MEMORY: {memory} --------------------- """);
与 ChatClient 集成:作为 ChatClient 的增强层,透明地处理上下文传递,无需手动拼接历史消息。使用时,简单配置到 defaultAdvisors() 方法中就可以了。
和其他 Advisor 一样,PromptChatMemoryAdvisor 没有提供公开的构造方法,它的构造方法是私有的,源码:
private PromptChatMemoryAdvisor(ChatMemory chatMemory, String defaultConversationId, int order, Scheduler scheduler, PromptTemplate systemPromptTemplate) { Assert.notNull(chatMemory, "chatMemory cannot be null"); Assert.hasText(defaultConversationId, "defaultConversationId cannot be null or empty"); Assert.notNull(scheduler, "scheduler cannot be null"); Assert.notNull(systemPromptTemplate, "systemPromptTemplate cannot be null"); this.chatMemory = chatMemory; this.defaultConversationId = defaultConversationId; this.order = order; this.scheduler = scheduler; this.systemPromptTemplate = systemPromptTemplate; }
如果要构建 PromptChatMemoryAdvisor 的实例,可以通过静态方法 builder(ChatMemory chatMemory),源码如下:
public static Builder builder(ChatMemory chatMemory) { return new Builder(chatMemory); }
该方法返回的是 PromptChatMemoryAdvisor.Builder 类型的对象,是静态内部类。
PromptChatMemoryAdvisor.Builder 是 Spring AI 中用于构建 PromptChatMemoryAdvisor 的流式构建器,专门设计用于优化提示工程中的上下文管理。包含了多个配置成员:
public static final class Builder { // 系统提示词模板 private PromptTemplate systemPromptTemplate = DEFAULT_SYSTEM_PROMPT_TEMPLATE; // 会话ID private String conversationId = ChatMemory.DEFAULT_CONVERSATION_ID; // Advisor 序号 private int order = Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER; // 调度策略 private Scheduler scheduler = BaseAdvisor.DEFAULT_SCHEDULER; // 保存会话记录实现 private ChatMemory chatMemory; //... }
其中属性详细说明:
systemPromptTemplate 基础系统提示模板,定义系统级别的初始提示模板,控制如何将记忆注入到系统提示中。注意,还可包含变量,如 {conversation_id}。默认系统模板如下图:
conversationId 唯一会话 ID,必须保证唯一性才能正确隔离不同对话。注意,在生产环境建议使用组合ID,如 userId_sessionId。如果未设置该值时,可能导致记忆混淆。多个用户共用一个会话上下文,这不就乱套了吗。默认值为“default”。
order 控制当前 Advisor 执行顺序,数值越小优先级越高,越优先执行。
scheduler 控制异步处理线程,常用取值:
Schedulers.immediate() 同步执行(默认)
Schedulers.boundedElastic() 弹性线程池,推荐 IO 操作
Schedulers.parallel() 固定线程池,CPU 密集型
chatMemory 对话记忆存储实现,。常用实现如下图:
其中:
InMemoryChatMemoryRepository 基于内存的对话记忆存储实现,适用于开发测试环境或轻量级生产场景,提供零延迟的对话记忆访问。
JdbcChatMemoryRepository 基于 关系型数据库 的对话记忆存储实现,通过 JDBC 提供标准化的 SQL 数据库访问,支持主流关系型数据库如 MySQL、PostgreSQL、Oracle 等。
Neo4jChatMemoryRepository 为 Neo4j 图数据库设计的对话记忆存储实现,它利用图数据库的天然优势来高效存储和检索复杂的对话关系。
CassandraChatMemoryRepository 基于 Apache Cassandra 数据库的对话记忆存储实现,专为大规模、高并发的对话系统设计,利用 Cassandra 的分布式特性实现高性能的记忆存储与检索。
下面示例将通过 SimpleLoggerAdvisor 来观察 PromptChatMemoryAdvisor 对 AI 会话增强的请求信息,是否自动帮助我们将历史信息添加到会话中。
由于我们还是通过 MySQL 数据库来保存历史会话信息,配置可以参考“MessageChatMemoryAdvisor 对话历史管理”。
依赖项也参考“MessageChatMemoryAdvisor 对话历史管理”。
配置文件请参考“MessageChatMemoryAdvisor 对话历史管理”。
创建名为 AiConfig 的配置类,添加 PromptChatMemoryAdvisor 相关配置,以及自定义一个系统提示词模板,如下:
package com.hxstrive.springai.springai_openai.advisor_PromptChatMemoryAdvisor.config; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; /** * AI配置 * @author Administrator */ @Configuration public class AiConfig { // 实际存储历史会话,这里通过数据库存储 @Bean public ChatMemoryRepository chatMemoryRepository(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); return JdbcChatMemoryRepository.builder().jdbcTemplate(jdbcTemplate).build(); } // 回话记忆 @Bean public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) { return MessageWindowChatMemory.builder() .chatMemoryRepository(chatMemoryRepository) // 会话存储实现 .maxMessages(10) // 消息大小,10条,包含请求和响应,即仅仅保存5个对话数据 .build(); } @Bean public PromptChatMemoryAdvisor promptChatMemoryAdvisor(ChatMemory chatMemory) { // 自定义系统提示模板 PromptTemplate promptTemplate = new PromptTemplate(""" {instructions} 请使用 “MEMORY” 部分中的对话记忆来提供准确答案。 --------------------- MEMORY: {memory} --------------------- """); return PromptChatMemoryAdvisor.builder(chatMemory).systemPromptTemplate(promptTemplate).build(); } // 添加日志 Advisor,要显示需要设置日志级别为 DEBUG @Bean public SimpleLoggerAdvisor simpleLoggerAdvisor() { return new SimpleLoggerAdvisor(); } }
📢注意,上述配置的系统提示词模板中的 {instructions} 和 {memory} 占位符,Spring AI 会自动帮我们替换。我们还可以添加自定义的占位符,但需要使用 PromptTemplate 的 add(String name, Object value) 添加占位符具体的值。
创建名为 AIController 的 Controller,使用 @Autowired 将配置的 Advisor 注入进去,然后通过 ChatClient 的 defaultSystem() 方法进行配置,如下:
package com.hxstrive.springai.springai_openai.advisor_PromptChatMemoryAdvisor.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.PromptChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class AIController { @Autowired private ChatModel chatModel; @Autowired private PromptChatMemoryAdvisor promptChatMemoryAdvisor; @Autowired private SimpleLoggerAdvisor simpleLoggerAdvisor; @GetMapping("/ai/simple") public String completion(@RequestParam("userText") String userText) { // 构建 RAG advisor 时需要完成 vectorStore 初始化 ChatClient chatClient = ChatClient.builder(chatModel) .defaultSystem("请使用中文进行回复") .defaultAdvisors( simpleLoggerAdvisor, promptChatMemoryAdvisor ) .build(); // conversationId 区分不同对话,定位对应的历史记录 String conversationId = "sessionId-20250705220950"; return chatClient.prompt() // 运行时设置会话ID参数 .advisors(advisor -> { advisor.param(ChatMemory.CONVERSATION_ID, conversationId); }) .user(userText) .call() .content(); } }
启动应用程序,通过浏览器访问,问“成都大学是 211 吗?”,由于首次访问,你会发现打印的日志中“MEMORY”部分内容为空,如下图:
再次,问“是 985?”,此时日志中的“MEMORY”部分出现了上次对话的问题和答案。如下图:
而且,你发现了吗?“是 985?”我们没有说什么是 985,AI 根据上下文推断出你问的是“成都大学”是 985?。