AI项目博客
记录 AI 工程实践与大模型应用

Spring AI Alibaba 18|Memory 短期记忆 · 概述

记忆可以让 Agent 记住之前的会话内容。对于 AI Agent,记忆至关重要,因为它让它们能够记住先前的交互、从反馈中学习并适应用户偏好。随着 Agent 处理更复杂的任务和大量用户交互,这种能力对于效率和用户满意度都变得至关重要。

短期记忆让你的应用程序能够在单个线程或会话中记住先前的交互。

注意:会话可以隔离同一个 Agent 实例中的多个不同交互,类似于电子邮件在单个对话中分组消息的方式。

Spring AI Alibaba 将短期记忆作为 Agent 状态的一部分进行管理。

通过将这些存储在 Graph 的状态中,Agent 可以访问给定对话的完整上下文,同时保持不同对话之间的分离。状态使用 checkpointer 持久化到数据库(或内存),以便可以随时恢复线程。短期记忆在调用 Agent 或完成步骤(如工具调用)时更新,并在每个步骤开始时读取状态。

保留所有对话历史是实现短期记忆最常见的形式。但较长的对话对历史可能会导致大模型 LLM 上下文窗口超限,导致上下文丢失或报错。

即使你在使用的大模型上下文长度足够大,大多数模型在处理较长上下文时的表现仍然很差。因为很多模型会被过时或偏离主题的内容”分散注意力”。同时,过长的上下文,还会带来响应时间变长、Token 成本增加等问题。

在 Spring AI ALibaba 中,ReactAgent 使用 messages 记录和传递上下文,其中包括指令(SystemMessage)和输入(UserMessage)。在 ReactAgent 中,消息(Message)在用户输入和模型响应之间交替,导致消息列表随着时间的推移变得越来越长。由于上下文窗口有限,许多应用程序可以从使用技术来移除或”忘记”过时信息中受益,即 “上下文工程”。

在 Spring AI Alibaba 中,要向 Agent 添加短期记忆(会话级持久化),你需要在创建 Agent 时指定 checkpointer。

import com.alibaba.cloud.ai.graph.agent.ReactAgent;

import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.alibaba.cloud.ai.graph.RunnableConfig;

// 配置 checkpointer
ReactAgent agent = ReactAgent.builder()
  .name("my_agent")
  .model(chatModel)
  .tools(getUserInfoTool)
  .saver(new MemorySaver())
  .build();

// 使用 thread_id 维护对话上下文
RunnableConfig config = RunnableConfig.builder()
  .threadId("1") // threadId 指定会话 ID
  .build();

agent.call("你好!我叫 Bob。", config);

在生产环境中

在生产环境中,使用数据库支持的 checkpointer:

示例:使用 Redis Checkpointer:

import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver;
import org.redisson.api.RedissonClient;

// 配置 Redis checkpointer
RedisSaver redisSaver = new RedisSaver(redissonClient);

ReactAgent agent = ReactAgent.builder()
  .name("my_agent")
  .model(chatModel)
  .tools(getUserInfoTool)
  .saver(redisSaver)
  .build();

默认情况下,Agent 使用状态通过 messages 键管理短期记忆,特别是对话历史。

你可以通过在工具或 Hook 中访问和修改状态来扩展记忆功能。

import com.alibaba.cloud.ai.graph.agent.hook.ModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import org.springframework.ai.chat.messages.Message;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

// 在 Hook 中访问和修改状态
public class CustomMemoryHook extends ModelHook {

  @Override
  public String getName() {
      return "custom_memory";
  }

  @Override
  public HookPosition[] getHookPositions() {
      return new HookPosition[]{HookPosition.BEFORE_MODEL};
  }

  @Override
  public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
      // 访问消息历史
      Optional<Object> messagesOpt = state.value("messages");
      if (messagesOpt.isPresent()) {
          List<Message> messages = (List<Message>) messagesOpt.get();
          // 处理消息...
      }

      // 添加自定义状态
      return CompletableFuture.completedFuture(Map.of(
          "user_id", "user_123",
          "preferences", Map.of("theme", "dark")
      ));
  }

  @Override
  public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
      return CompletableFuture.completedFuture(Map.of());
  }
}

启用短期记忆后,长对话可能超过 LLM 的上下文窗口。常见的解决方案包括:

  • 修剪消息。在调用 LLM 之前移除前 N 条或后 N 条消息
  • 删除消息。从 Graph 状态中永久删除消息
  • 总结消息。总结历史中较早的消息并用摘要替换它们
  • 自定义策略。自定义策略(例如消息过滤等)

这允许 Agent 在 reasoning-acting 循环中持续跟踪对话而不超过 LLM 的上下文窗口。

大多数 LLM 都有最大支持的上下文窗口(以 token 计)。

决定何时截断消息的一种方法是计算消息历史中的 token 数量,并在接近该限制时进行截断。

要在 Agent 中修剪消息历史,请使用 MessagesModelHook:

import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import org.springframework.ai.chat.messages.Message;
import java.util.ArrayList;
import java.util.List;

@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageTrimmingHook extends MessagesModelHook {

  private static final int MAX_MESSAGES = 3;

  @Override
  public String getName() {
      return "message_trimming";
  }

  @Override
  public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
      if (previousMessages.size() <= MAX_MESSAGES) {
          return new AgentCommand(previousMessages); // 无需更改
      }

      // 保留第一条消息和最后几条消息
      Message firstMsg = previousMessages.get(0);
      int keepCount = previousMessages.size() % 2 == 0 ? 3 : 4;
      List<Message> recentMessages = previousMessages.subList(
          previousMessages.size() - keepCount,
          previousMessages.size()
      );

      List<Message> trimmedMessages = new ArrayList<>();
      trimmedMessages.add(firstMsg);
      trimmedMessages.addAll(recentMessages);

      // 使用 REPLACE 策略替换消息列表,只保留需要的消息
      return new AgentCommand(trimmedMessages, UpdatePolicy.REPLACE);
  }
}

// 使用
ReactAgent agent = ReactAgent.builder()
  .name("my_agent")
  .model(chatModel)
  .tools(tools)
  .hooks(new MessageTrimmingHook())
  .saver(new MemorySaver())
  .build();

RunnableConfig config = RunnableConfig.builder()
  .threadId("1")
  .build();

agent.call("你好,我叫 bob", config);
agent.call("写一首关于猫的短诗", config);
agent.call("现在对狗做同样的事情", config);
AssistantMessage finalResponse = agent.call("我叫什么名字?", config);

System.out.println(finalResponse.getText());
// 输出:你的名字是 Bob。你之前告诉我的。

本文整理自 Spring AI Alibaba 官方文档《Memory 短期记忆》:https://java2ai.com/docs/frameworks/agent-framework/tutorials/memory

赞(0)
未经允许不得转载:AI项目博客 » Spring AI Alibaba 18|Memory 短期记忆 · 概述

评论 抢沙发

AI项目博客

专注 AI 工程化落地 —— 覆盖智能体、大模型、Java AI 与 Python 的学习笔记,以及 AI 平台的项目实战记录。

关于本站文章归档

登录

找回密码

注册