Agent 实战:构建第一个 Agent 与记忆系统设计

导读:当你的 AI 应用从"一问一答"升级为"自主完成任务",Agent 就登场了。但 Agent 不只是能调工具的聊天机器人------它还需要"记忆"。本文将从零开始,带你用 Spring AI 构建一个具备工具调用能力的个人助理 Agent,再逐步为它装上三层记忆系统(工作记忆、历史压缩、长期记忆),让它真正"记住"用户、理解上下文。全文涵盖设计思路、核心代码与踩坑经验,适合有 Spring Boot 基础的开发者阅读。


一、用 Spring AI 构建第一个 Agent

1.1 Agent 与普通聊天的区别

在普通聊天场景中,大模型只做一件事:根据用户输入生成回答。而 Agent 则多了三个核心能力:

  • 工具调用:能主动调用外部 API 获取实时数据(天气、汇率、日历等);
  • 自主推理:基于 ReAct(Reasoning + Acting)框架,循环执行"思考 -> 行动 -> 观察"直到任务完成;
  • 多轮编排:一次用户请求可能触发多次工具调用,框架自动处理循环流程。

因此,Agent 天然比普通聊天慢------它不是 Bug,而是每一轮都包含了"大模型推理 + 工具执行"的开销。

1.2 最小可用 Agent:个人助理

我们的目标是构建一个能 查天气、查日期、查汇率、创建提醒 的个人助理。Spring AI 没有一个叫 Agent 的类,但它提供了工具注册 + 自动多轮调用的机制,框架会帮我们完成之前需要手写 for 循环才能实现的 ReAct 流程。

工具类:AssistantTools

首先定义一个工具组件 com.jichi.agent.tools.AssistantTools,把所有助理能力集中在一个类里:

复制代码
@Component
public class AssistantTools {

    @Tool(description = """
            查询指定城市的实时天气。
            适用于:用户询问某城市当前天气、今天天气、是否需要带伞等问题。
            返回:城市、天气状况、温度范围、湿度、风力。
            注意:只查当前天气,不查天气预报。
            """)
    public String getWeather(
            @ToolParam(description = "城市名,中文,例如:北京、上海、深圳") String city) {
        // 演示用模拟数据,真实项目对接天气 API(高德、心知天气等)
        return String.format("""
                城市:%s
                天气:晴转多云
                温度:12~20°C
                湿度:55%%
                风力:东南风 2 级
                查询时间:%s
                """, city, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
    }

    @Tool(description = "获取当前日期和时间。适用于用户询问今天几号、现在几点、这周星期几等时间相关问题。")
    public String getCurrentDateTime() {
        LocalDateTime now = LocalDateTime.now();
        return String.format("""
                当前日期:%s
                当前时间:%s
                星期:%s
                """,
                now.toLocalDate(),
                now.format(DateTimeFormatter.ofPattern("HH:mm")),
                getDayOfWeekChinese(now));
    }

    @Tool(description = """
            查询两种货币之间的汇率。
            适用于:用户询问人民币兑换外币的比率、外汇换算、出国换多少钱等问题。
            返回:汇率数值和换算说明。
            """)
    public String getExchangeRate(
            @ToolParam(description = "源货币,3位代码,例如:CNY、USD、EUR、JPY") String from,
            @ToolParam(description = "目标货币,3位代码") String to) {
        // 演示用模拟汇率,真实项目对接汇率 API
        if ("CNY".equals(from) && "USD".equals(to)) {
            return "1 CNY = 0.138 USD(1 美元 ≈ 7.24 人民币)";
        }
        if ("USD".equals(from) && "CNY".equals(to)) {
            return "1 USD = 7.24 CNY";
        }
        return from + " 兑 " + to + " 的汇率:暂无实时数据,建议查询银行官网";
    }

    @Tool(description = """
            创建一个定时提醒事项。
            适用于:用户说"提醒我..."、"帮我记一下..."、"x点提醒我"等需要设置提醒的场景。
            返回:提醒创建结果(成功或失败)。
            """)
    public String createReminder(
            @ToolParam(description = "提醒内容描述") String content,
            @ToolParam(description = "提醒时间,格式:yyyy-MM-dd HH:mm,例如:2024-03-20 09:00") String remindAt) {
        // 真实项目里这里存数据库并触发推送通知(微信模板消息、短信等)
        return String.format("提醒已创建:「%s」,将在 %s 提醒你", content, remindAt);
    }
}

关键点@Tool 注解的 description 决定了大模型是否能正确选择工具。注意我们用了多行描述,包含"适用于"和"返回"说明,让模型更精准地匹配用户意图。@ToolParam 注解则为每个参数提供了详细格式示例。

Agent 核心代码:PersonalAssistantAgent
复制代码
@Service
public class PersonalAssistantAgent {

    private final ChatClient chatClient;

    public PersonalAssistantAgent(@Qualifier("dashScopeChatModel") ChatModel chatModel,
                                   AssistantTools assistantTools) {
        this.chatClient = ChatClient.builder(chatModel)
                .defaultSystem("""
                        你是一个聪明的个人助理,名字叫小智。

                        你可以:
                        - 查询任意城市的实时天气
                        - 告知当前日期、时间和星期
                        - 查询外汇汇率
                        - 帮用户创建提醒事项

                        工作原则:
                        - 需要数据时主动调用工具,不要猜测或编造任何数据
                        - 回答简洁,重点突出,不要废话
                        - 用户一次问多个问题时,把所有相关工具都调完再统一回答
                        - 工具调用失败时,如实告知用户并说明原因
                        """)
                .defaultTools(assistantTools)
                .build();
    }

    public String chat(String message) {
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }
}

就这么简单------把系统提示词写好、工具注册进去,Spring AI 会自动完成多轮工具调用和结果整合。注意这里通过 @Qualifier("dashScopeChatModel") 指定了具体的模型实现,AssistantTools 直接作为整个对象传入 defaultTools(),Spring AI 会自动扫描其中所有 @Tool 方法。

1.3 系统提示词的 Agent 化

普通聊天的系统提示往往只是"你是一个友善的助手",但 Agent 的系统提示需要额外做好三件事:

维度 说明 示例
能力声明 明确列出 Agent 能做什么 "你可以查询天气、汇率..."
工作原则 告诉模型何时该调工具 "需要数据时主动调用工具,不要猜测"
边界限制 防止 Agent 做超出预期的事 "只回答与上述能力相关的问题"

把能力显式列出来后,模型会更积极地调用工具而非"瞎编",幻觉问题会明显减少。


二、为 Agent 加上会话记忆

2.1 问题:无记忆的 Agent

当前 Agent 有一个明显缺陷:每次请求都是全新的。用户问"北京天气怎么样"后再问"上海呢",Agent 不知道"上海呢"是在问天气,因为它没有上下文。

2.2 解决方案:ChatMemory + 会话隔离

Spring AI 提供了 MessageChatMemoryAdvisor,只需在构建 ChatClient 时加一个 Advisor。来看 PersonalAssistantAgentWithMemory 的完整实现:

复制代码
@Service
public class PersonalAssistantAgentWithMemory {

    private final ChatClient chatClient;

    public PersonalAssistantAgentWithMemory(@Qualifier("dashScopeChatModel") ChatModel chatModel,
                                             AssistantTools assistantTools) {
        this.chatClient = ChatClient.builder(chatModel)
                .defaultSystem("""
                        你是一个聪明的个人助理,名字叫小智。
                        能查天气、告知时间、查汇率、创建提醒。
                        需要数据时调工具,不要猜测和编造。
                        """)
                .defaultTools(assistantTools)
                .defaultAdvisors(
                        MessageChatMemoryAdvisor.builder(
                                MessageWindowChatMemory.builder()
                                        .chatMemoryRepository(new InMemoryChatMemoryRepository())
                                        .build())
                                .build())
                .build();
    }

    public String chat(String message, String sessionId) {
        return chatClient.prompt()
                .user(message)
                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, sessionId))
                .call()
                .content();
    }
}

几个关键细节:

  • MessageWindowChatMemory 搭配 InMemoryChatMemoryRepository 作为存储后端;
  • 调用时通过 ChatMemory.CONVERSATION_ID 常量传入 sessionId,实现会话隔离;
  • 同一个 sessionId:多轮对话共享上下文,问"上海呢"时 Agent 知道是在聊天气;
  • 不同 sessionId:记忆完全隔离,新会话不受旧会话影响。

生产环境提示InMemoryChatMemoryRepository 仅适合开发调试,生产环境应替换为 Redis 或数据库实现,接口不变,只需换实现类。


三、Agent 记忆系统的三层架构

框架级别的 ChatMemory被动存取 ------它不知道什么消息重要、什么消息是噪声,只是机械地存和取。但真正的 Agent 需要主动管理记忆:决定记什么、怎么压缩、什么时候回忆。

这就引出了 Agent 记忆的三层结构:

复制代码
┌─────────────────────────────────────────┐
│           长期记忆 (Long-term)           │
│   跨会话的用户偏好、身份、重要事件        │
│   存储:向量数据库 (PGVector / Milvus)   │
├─────────────────────────────────────────┤
│           工作记忆 (Working Memory)      │
│   当前任务的中间结果、已知事实            │
│   存储:内存 Map (任务结束即清除)         │
├─────────────────────────────────────────┤
│           语义记忆 (Semantic Memory)     │
│   历史对话的压缩摘要                     │
│   存储:向量数据库 / Redis               │
└─────────────────────────────────────────┘

类比来说:长期记忆 像你的人生经历------你是谁、喜欢什么;工作记忆 像你桌上的便签纸------当前任务的要点;语义记忆像你的日记本------过去发生的事的概要。


四、工作记忆:给 Agent 一块"白板"

4.1 问题场景

假设用户要求 Agent 规划一次五天的日本行程:查航班、查酒店、查景点、汇总方案。Agent 执行到第六步汇总时,突然说"航班信息还没查到"------但实际上第二步就查过了。

根因:对话历史里夹杂着工具调用的原始响应、模型的推理过程、用户闲聊等大量噪声,Agent 缺少一个干净的地方存放"已知事实"。

4.2 设计思路:把工作记忆做成工具

核心思想:创建一组工具方法,让 Agent 自己决定何时写入、何时读取工作记忆。来看 WorkingMemoryTools 的实现:

复制代码
/**
 * Agent 工作记忆------任务执行过程中的临时状态板
 * 每次任务开始时清空,任务结束后可以选择性提炼到长期记忆
 */
@Component
public class WorkingMemoryTools {

    // 结构化的工作记忆:key 是记忆标签,value 是内容
    private final Map<String, String> workingMemory = new LinkedHashMap<>();
    // 已完成的步骤列表
    private final List<String> completedSteps = new ArrayList<>();

    @Tool(description = """
            在工作记忆中保存一条信息,用于后续步骤使用。
            适用于:保存工具调用结果、中间推断结论、需要跨步骤传递的数据。
            示例:key="上海天气",value="晴,26°C,适合户外活动"
            """)
    public String memorize(
            @ToolParam(description = "记忆标签,用于之后检索") String key,
            @ToolParam(description = "要记忆的内容") String value) {
        workingMemory.put(key, value);
        return "已记住:" + key;
    }

    @Tool(description = """
            从工作记忆中检索之前保存的信息。
            当需要用到之前步骤保存的数据时调用。
            """)
    public String recall(
            @ToolParam(description = "要检索的记忆标签") String key) {
        String value = workingMemory.get(key);
        return value != null ? key + ":" + value : "工作记忆中没有关于 " + key + " 的记录";
    }

    @Tool(description = "列出当前工作记忆中所有已保存的信息,用于整理当前任务状态")
    public String listMemory() {
        if (workingMemory.isEmpty()) return "工作记忆为空";
        StringBuilder sb = new StringBuilder("当前工作记忆:\n");
        workingMemory.forEach((k, v) -> sb.append("- ").append(k).append(":").append(v).append("\n"));
        return sb.toString();
    }

    @Tool(description = "标记一个步骤已完成,并记录执行结果摘要")
    public String markStepDone(
            @ToolParam(description = "步骤描述") String step,
            @ToolParam(description = "执行结果摘要") String result) {
        completedSteps.add(step + " → " + result);
        return "已标记完成:" + step;
    }

    // 任务结束时,由外部调用清空状态,准备下一个任务
    public void reset() {
        workingMemory.clear();
        completedSteps.clear();
    }

    public Map<String, String> snapshot() {
        return Map.copyOf(workingMemory);
    }
}

把这个工具注册到 Agent 后,Agent 在执行多步任务时会自主调用 memorize 保存中间结果,需要回顾时调用 recalllistMemory。第六步汇总时再也不会"忘记"第二步查过的航班信息了。注意 reset()snapshot() 是普通方法而非 @Tool,供外部代码在任务边界调用。


五、历史压缩:解决上下文爆满问题

5.1 截断 vs 压缩

当 Agent 执行轮次较多时,消息历史会迅速膨胀,撑爆上下文窗口。常见做法有两种:

策略 优点 缺点 适用场景
截断 实现简单,零额外成本 早期关键信息(如任务约束"预算3万")会丢失 简单问答型 Agent
压缩摘要 关键信息保留,长度可控 多一次模型调用,摘要质量影响后续推理 任务型 Agent(超过10轮必上)

经验之谈:如果你的 Agent 单次任务超过 10 轮对话,一定要上压缩器,否则任务约束极易丢失。

5.2 滚动摘要压缩器:RollingMemoryCompressor

设计两个关键参数:

  • COMPRESS_THRESHOLD = 20:消息超过 20 条触发压缩;

  • KEEP_RECENT = 6:保留最近 6 条原始消息。

    /**

    • 滚动摘要压缩器:消息超过阈值时,把旧消息压缩成一段摘要,

    • 摘要 + 最近 N 条消息继续往下跑,不丢信息
      */
      @Component
      public class RollingMemoryCompressor {

      private static final int COMPRESS_THRESHOLD = 20; // 超过 20 条时触发压缩
      private static final int KEEP_RECENT = 6; // 压缩后保留最近 6 条原始消息

      private final ChatClient summaryClient;

      public RollingMemoryCompressor(@Qualifier("dashScopeChatModel") ChatModel chatModel) {
      // 专门用于摘要的轻量 ChatClient,不挂工具,职责单一
      this.summaryClient = ChatClient.builder(chatModel).build();
      }

      /**

      • 检查消息列表是否需要压缩,需要则执行,返回压缩后的列表
        */
        public List<Message> maybeCompress(List<Message> messages) {
        if (messages.size() < COMPRESS_THRESHOLD) {
        return messages;
        }

        // 把前面的旧消息压缩,保留最近几条
        List<Message> toCompress = messages.subList(0, messages.size() - KEEP_RECENT);
        List<Message> toKeep = messages.subList(messages.size() - KEEP_RECENT, messages.size());

        String summary = summarize(toCompress);

        // 用摘要替换旧消息,放在消息历史的最前面
        List<Message> compressed = new ArrayList<>();
        compressed.add(new SystemMessage(
        "以下是此前对话的摘要(部分历史已被压缩):\n" + summary));
        compressed.addAll(toKeep);

        return compressed;
        }

      private String summarize(List<Message> messages) {
      StringBuilder history = new StringBuilder();
      for (Message msg : messages) {
      String role = msg instanceof UserMessage ? "用户" :
      msg instanceof AssistantMessage ? "助手" : "系统";
      history.append(role).append(": ").append(msg.getText()).append("\n");
      }

      复制代码
       return summaryClient.prompt()
               .system("""
                       请把以下对话历史压缩成一段简洁的摘要。
                       保留:任务目标、关键决策、重要数据、用户明确表达的偏好和约束。
                       丢弃:闲聊、重复内容、过程中的细节推理。
                       摘要用第三人称,限 200 字以内。
                       """)
               .user(history.toString())
               .call()
               .content();

      }
      }

关键细节 :摘要专用一个独立的 ChatClient(不挂任何工具),通过 system 提示明确要求保留"任务目标"和"用户偏好"。在摘要的 Prompt 中,任务目标应列为最高优先级保留项。有时摘要模型会认为目标是"废话"而丢弃,导致后续推理偏离方向。


六、长期记忆:让 Agent 真正"认识"用户

6.1 什么该存入长期记忆

不是所有信息都值得长期记忆。三类信息值得存:

  1. 用户偏好:喜欢简短回答、常用 Python、坐标上海...
  2. 任务经验:用户查天气通常是为了出行决策,对价格敏感...
  3. 重要事件:用户提到的重要时间节点、里程碑...

判断标准:跨会话有价值,且不会很快过时。临时性内容(如"今天中午吃什么")不存。

6.2 用大模型做提炼:MemoryExtractionService

不要用关键词匹配(如检测到"记住"就存入)------"记住这个错误别犯了"可能把错误日志当成用户偏好。正确做法是让大模型来判断:

复制代码
@Service
public class MemoryExtractionService {

    private final ChatClient extractorClient;
    private final VectorStore vectorStore;

    public MemoryExtractionService(
            @Qualifier("dashScopeChatModel") ChatModel chatModel,
            VectorStore vectorStore) {
        this.extractorClient = ChatClient.builder(chatModel).build();
        this.vectorStore = vectorStore;
    }

    /**
     * 任务结束后调用:从对话记录中提炼有长期价值的信息
     *
     * @param userId      用户 ID(记忆按用户隔离)
     * @param taskSummary 任务对话的摘要或关键消息
     */
    public void extractAndStore(String userId, String taskSummary) {
        String extraction = extractorClient.prompt()
                .system("""
                        分析以下对话内容,提炼出值得长期记住的信息。

                        值得记的(提取出来):
                        - 用户明确告知的个人偏好、职业、技术栈、公司信息
                        - 用户的决策模式(关注什么因素)
                        - 重要的时间节点或计划

                        不值得记的(忽略):
                        - 临时性内容(打招呼、确认理解)
                        - 可以从公开信息查到的内容
                        - 模型自己的推理过程

                        如果没有值得记的内容,直接输出:无
                        如果有,每条单独一行,格式:[类型] 内容
                        类型可选:偏好 / 背景 / 计划 / 经验
                        """)
                .user(taskSummary)
                .call()
                .content();

        if (extraction.isBlank() || extraction.strip().equals("无")) {
            return; // 没有值得记的内容,不写入
        }

        // 逐条存入向量库
        for (String line : extraction.split("\n")) {
            line = line.strip();
            if (line.isEmpty()) continue;
            vectorStore.add(List.of(new Document(
                    line,
                    Map.of("userId", userId,
                           "timestamp", System.currentTimeMillis(),
                           "source", "memory_extraction"))));
        }
    }
}

注意几个设计要点:提炼结果为"无"时直接返回不写入;每条记忆附带 userIdtimestampsource 三个元数据,方便后续按用户过滤和按时间排序。

6.3 异步提炼,不阻塞主流程

长期记忆的提炼需要调用大模型 + 写入向量库,耗时较长。最佳实践是在 Agent 返回回答后,异步 执行提炼。在 FullMemoryAgent 中是这样做的:

复制代码
// 5. 异步提炼本轮对话,写入长期记忆
CompletableFuture.runAsync(() ->
        extractor.extractAndStore(userId,
                "用户:" + message + "\n助手:" + finalAnswer));

Q:异步提炼时下一次对话来得很快,记忆还没写入怎么办?

大多数场景下这是可以接受的------用户偏好本身就是渐进积累的,偶尔少一条不影响体验。如果要求更严格,可以用 Redis 队列做写入缓冲,再异步刷入向量库。


七、三层记忆的完整链路:FullMemoryAgent

把三层记忆组装到一起,来看 FullMemoryAgent 的完整实现:

复制代码
@Service
public class FullMemoryAgent {

    private final ChatClient chatClient;
    private final WorkingMemoryTools workingMemory;
    private final RollingMemoryCompressor compressor;
    private final MemoryExtractionService extractor;
    private final VectorStore vectorStore;

    // 按 userId 隔离的对话历史(实际生产用 Redis,这里简化)
    private final ConcurrentHashMap<String, List<Message>> sessionHistoryMap = new ConcurrentHashMap<>();

    public FullMemoryAgent(
            @Qualifier("dashScopeChatModel") ChatModel chatModel,
            WorkingMemoryTools workingMemory,
            RollingMemoryCompressor compressor,
            MemoryExtractionService extractor,
            VectorStore vectorStore) {
        this.chatClient = ChatClient.builder(chatModel)
                .defaultTools(workingMemory)  // 挂上工作记忆工具
                .build();
        this.workingMemory = workingMemory;
        this.compressor = compressor;
        this.extractor = extractor;
        this.vectorStore = vectorStore;
    }

    public String chat(String userId, String message, int maxIterations) {
        // 1. 检索语义记忆(长期记忆),注入系统提示
        String longTermContext = recallLongTerm(userId, message);

        // 2. 加入当前消息(按 userId 隔离)
        List<Message> sessionHistory = sessionHistoryMap
                .computeIfAbsent(userId, k -> new ArrayList<>());
        sessionHistory.add(new UserMessage(message));

        // 3. 超长时压缩历史(不截断,保留摘要)
        List<Message> messages = compressor.maybeCompress(sessionHistory);

        // 4. 执行 Agent 循环(Agent 会自己用 WorkingMemory 工具管中间状态)
        String finalAnswer = runAgentLoop(messages, longTermContext, maxIterations);

        // 5. 异步提炼本轮对话,写入长期记忆
        CompletableFuture.runAsync(() ->
                extractor.extractAndStore(userId,
                        "用户:" + message + "\n助手:" + finalAnswer));

        return finalAnswer;
    }

    private String recallLongTerm(String userId, String query) {
        List<Document> memories = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(query)
                        .topK(3)
                        .similarityThreshold(0.65)
                        .filterExpression("userId == '" + userId + "'")
                        .build());

        if (memories.isEmpty()) return "";
        return "关于这个用户的背景信息:\n" +
                memories.stream()
                        .map(Document::getText)
                        .reduce("", (a, b) -> a + "\n- " + b).strip();
    }

    private String runAgentLoop(List<Message> messages, String longTermContext, int maxIter) {
        return chatClient.prompt()
                .system(buildSystem(longTermContext))
                .messages(messages)
                .call()
                .content();
    }

    private String buildSystem(String longTermContext) {
        String base = """
                你是一个智能助理。你有工作记忆工具可以使用:
                - memorize:保存当前任务的中间结果
                - recall:检索之前保存的信息
                - listMemory:查看当前记忆中所有内容

                执行多步任务时,主动用记忆工具管理中间状态。
                """;
        return longTermContext.isBlank() ? base :
                base + "\n\n用户背景(来自长期记忆):\n" + longTermContext;
    }
}

Agent 每次处理请求的完整流程:

复制代码
用户发送消息
    │
    ▼
① 检索长期记忆:根据 userId + 当前消息,从向量库相似度搜索(topK=3, threshold=0.65)
    │
    ▼
② 加载对话历史:从 ConcurrentHashMap 获取当前用户的消息列表,加入本次消息
    │
    ▼
③ 历史压缩:调用 compressor.maybeCompress(),超过 20 条则压缩旧消息为摘要
    │
    ▼
④ Agent 执行:带上长期记忆 + 压缩后的历史 + 工作记忆工具,调用大模型
    │
    ▼
⑤ 返回回答给用户
    │
    ▼
⑥ 异步提炼长期记忆:CompletableFuture.runAsync 调用 extractor.extractAndStore()

最妙的地方在于 :每次对话结束后,Agent 都在悄悄往长期记忆里写东西。用户可能没感知到,但下次来的时候,Agent 已经"认识"他了。这和框架层面的 ChatMemory 有本质区别------前者是"真正的记忆",后者只是"假装有记忆"。


八、调试技巧

在开发 Agent 时,观察工具调用过程非常重要,推荐两种方式:

  1. SimpleLogAdvisor :在 defaultAdvisors 中加入,配合 application.yml 设置日志级别为 DEBUG,可以看到完整的请求和响应日志;
  2. 工具方法内打日志 :在每个 @Tool 方法中加 log.info(),精准追踪每次工具调用的入参和出参。

实践建议:开发阶段两种都开,上线后精简为仅保留工具内日志,DEBUG 级别仅在排查问题时临时开启。


总结

本文从零构建了一个具备完整记忆能力的 Spring AI Agent,核心要点回顾:

  1. 构建 Agent :Spring AI 通过 @Tool + ChatClient 自动处理 ReAct 循环,开发者只需注册工具、写好系统提示词(声明能力、原则、边界);
  2. 会话记忆 :通过 MessageChatMemoryAdvisor + MessageWindowChatMemory + sessionId 实现多轮对话和会话隔离;
  3. 工作记忆WorkingMemoryTools 将记忆操作封装为 memorize/recall/listMemory 三个工具,Agent 自主决定存取,解决多步任务中中间结果丢失的问题;
  4. 历史压缩RollingMemoryCompressor 超过 20 条消息时用独立 ChatClient 生成摘要替换旧消息,既省 Token 又保留关键约束;
  5. 长期记忆MemoryExtractionService 用大模型从对话中提炼有价值的用户信息,存入向量库,FullMemoryAgent 在每次请求时检索并注入系统提示。

记住两个实践原则:任务型 Agent 超过 10 轮必上压缩器长期记忆用模型判断而非硬编码。掌握了这套三层记忆架构,你的 Agent 就不再是"金鱼记忆",而是一个真正能积累经验、越用越懂你的智能助手。

相关推荐
程序猿_极客1 小时前
SpringBoot 三大参数注解详解:@RequestParam @RequestBody @PathVariable 区别及常用开发注解
java·spring boot·后端·面试八股文·springboot注释
小胖java1 小时前
校园通衢公告枢纽系统
java·spring boot
Crazy________1 小时前
docker4.8
java·开发语言·eureka
cch89182 小时前
Laravel 2.x:早期框架的奠基之路
java·开发语言
李小枫2 小时前
webflux接收application/x-www-form-urlencoded参数
android·java·开发语言
派大星酷2 小时前
Cookie、Session、Token、JWT 原理 + 流程 + 区别 + 实战
java·网络
身如柳絮随风扬2 小时前
MyBatis 插件原理详解:从拦截器到动态代理,手写一个分页插件
java·mybatis
sg_knight2 小时前
如何实现“秒传”与“断点续传”?MinIO + Java 实战进阶篇
java·开发语言·文件管理·minio·ftp·oss·文件传输