《SpringAI 入门教程》13. 对话记忆 ChatMemory 处理

79 阅读3分钟

大模型本身是没有记忆能力的,本身并不会存储我们跟它的对话上下文信息。

要实现大模型的记忆功能,我们需要自己实现。

1. 聊天记忆(Chat Memory)

大型语言模型保留的信息,用于在整个对话过程中维持上下文意识。

2. 聊天历史(Chat History)

完整的对话记录,包括用户与模型之间交换的所有消息。

3. 自己实现的版本

import lombok.AllArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AllArgsConstructor
@RestController
public class ChatClientChatMemoryController {

    private final DeepSeekChatModel chatModel;

    @RequestMapping(value = "/chatMemory", produces = "text/stream;charset=UTF-8")
    public void chatMemory() {
        var chatClient = ChatClient.builder(chatModel)
                //.defaultSystem(prompt)
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
        String content = chatClient.prompt().user("我叫五毛").call().content();
        System.out.println(content);

        String content1 = chatClient.prompt().user("我叫什么?").call().content();
        System.out.println(content1);
    }

}

第二次调用,大模型并不知道我的名字。

4. 进阶版本


@RequestMapping(value = "/chatMemory2", produces = "text/stream;charset=UTF-8")
public void chatMemory2() {
    MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder().build();
    // 当前对话的唯一标识
    var conversationId = "001";

    // 第一次会话
    UserMessage userMessage1 = new UserMessage("my name is upfive");
    chatMemory.add(conversationId, userMessage1);
    ChatResponse response1 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
    chatMemory.add(conversationId, response1.getResult().getOutput());
    System.out.println(response1.getResult().getOutput().getText());
    System.out.println("========================================================");

    // 第二次会话
    UserMessage userMessage2 = new UserMessage("what's my name?");
    chatMemory.add(conversationId, userMessage2);
    ChatResponse response2 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
    chatMemory.add(conversationId, response2.getResult().getOutput());

    System.out.println(response2.getResult().getOutput().getText());

}

根据会话ID取出所有会话历史数据,发送给 大模型 ,然后把大模型响应的数据放到ChatMemory中

依然很麻烦,需要手动去维护ChatMemory

5. 最佳版本

结合会话拦截器 Advisors实现


@RequestMapping(value = "/chatMemory3", produces = "text/stream;charset=UTF-8")
public void chatMemory3() {
    MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
            //.chatMemoryRepository()
            .maxMessages(100)
            .build();
    // 当前对话的唯一标识

    ChatClient chatClient = ChatClient.builder(chatModel)
            .defaultAdvisors(
                    PromptChatMemoryAdvisor.builder(chatMemory)
                    .build()
            )
            .build();

    // 第一次会话
    String content = chatClient.prompt().user("my name is upfive")
            .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "001"))
            .call().content();
    System.out.println(content);
    System.out.println("========================================================");

    // 第二次会话
    String content2 = chatClient.prompt().user("what is my name?")
            .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "001"))
            .call().content();
    System.out.println(content2);

    System.out.println("================================================================");
    // 第三次会话
    String content3 = chatClient.prompt().user("what is my name?")
            .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, "002"))
            .call().content();
    System.out.println(content3);

}

SpringAI提供了 org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration 类

需要引入如下依赖包,这样就不需要手动去构建ChatMemoryBean了

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId>
</dependency>

6. 其他存储对话记忆的方式

InMemory

Jdbc

Redis

Neo4j图数据库

Elasticsearch

介绍一下jdbc的方式:

  1. 引入依赖包
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
  1. 引入 chatMemoryRepository,构建ChatMemory
@Autowired
JdbcChatMemoryRepository chatMemoryRepository;

ChatMemory chatMemory = MessageWindowChatMemory.builder()
    .chatMemoryRepository(chatMemoryRepository)
    .maxMessages(10)
    .build();
# 配置是否初始化
spring.ai.chat.memory.repository.jdbc.initialize-schema=always
# 数据库初始化脚本
spring.ai.chat.memory.repository.jdbc.schema=classpath:/sql/schema-mysql.sql
  1. 配置mysql链接数据源
spring:
  datasource:
     username: root
     password: 123456
     url: jdbc:mysql://localhost:3306/springai?characterEncoding=utf8
     driver-class-name: com.mysql.cj.jdbc.Driver

schema-mysql.sql (1.0版本需要自己定义)

CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
 `conversation_id` VARCHAR(36) NOT NULL,
 `content` TEXT NOT NULL,
 `type` VARCHAR(10) NOT NULL,
 `timestamp` TIMESTAMP NOT NULL,
 INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`)
)
  1. 配置 ChatMemory

@Bean
ChatMemory chatMemory(JdbcChatMemoryRepository chatMemoryRepository) {
  return MessageWindowChatMemory.builder()
          .maxMessages(10)
          .chatMemoryRepository(chatMemoryRepository)
          .build();
}

7. 多层记忆架构通点

记忆多 = 聪明,记忆多会触发 token上限

要知道,无论我们用什么存储对话,也只能保证服务端的存储性能。

但是一旦聊天记录多了依然会超过token上限,但是有时候我们依然希望存储更多的聊天记录,这样才能保证整个对话更像“人”。

8. 多层次记忆架构(模仿人类)

近期记忆

保留在上下文窗口中的最近几轮对话,每轮对话完成后立即存储(可通过ChatMemory);

中期记忆

通过RAG检索的相关历史对话(每轮对话完成后,异步将对话内容转换为向量并存入向量数据库)

长期记忆

关键信息的固话总结。

方式一:定时批处理

  • 通过定时任务(如每天或每周)对积累的对话进行总结和提炼
  • 提取关键信息、用户偏好、重要事实等
  • 批处理方式降低计算成本,适合大规模处理

方式二:关键点实时处理。

  • 在对话中识别出关键信息点时立即提取并存储
  • 例如,当用户明确表达偏好、提供个人信息或设置持久性指令时
  • 采用“写入触发器”机制,在特定条件下自动更新长期记忆