【从0到1构建一个ClaudeAgent】内存管理-上下文压缩

[复制链接]
发表于 2026-4-14 08:05:45 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
对话一长,Token 烧得肉疼。那怎么办,做压缩
Java实当代码
  1. public class ContextCompactSystem {
  2.     // --- 配置 ---
  3.     private static final Path WORKDIR = Paths.get(System.getProperty("user.dir"));
  4.     private static final Path TRANSCRIPT_DIR = WORKDIR.resolve(".transcripts");  // 新增:对话存档目录
  5.     private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
  6.    
  7.     // 压缩参数
  8.     private static final int THRESHOLD_TOKENS = 50000;  // 触发自动压缩的 token 阈值
  9.     private static final int KEEP_RECENT = 3;           // 保留的最近工具结果数量
  10.    
  11.     // --- 工具枚举 ---
  12.     public enum ToolType {
  13.         BASH("bash", "Run a shell command."),
  14.         READ_FILE("read_file", "Read file contents."),
  15.         WRITE_FILE("write_file", "Write content to file."),
  16.         EDIT_FILE("edit_file", "Replace exact text in file."),
  17.         COMPACT("compact", "Trigger manual conversation compression.");  // 新增:手动压缩工具
  18.         public final String name;
  19.         public final String description;
  20.         ToolType(String name, String description) { this.name = name; this.description = description; }
  21.     }
  22.     // ... 省略相同的 ToolExecutor 接口和基础工具实现
  23.    
  24.     // --- 三层次压缩系统 ---
  25.    
  26.     /**
  27.      * Layer 1: 微观压缩 - 静默替换旧的工具结果
  28.      */
  29.     private static List<Map<String, Object>> microCompact(List<Map<String, Object>> messages) {
  30.         // 收集所有的 tool_result 条目
  31.         List<ToolResultInfo> toolResults = new ArrayList<>();
  32.         
  33.         for (int msgIdx = 0; msgIdx < messages.size(); msgIdx++) {
  34.             Map<String, Object> msg = messages.get(msgIdx);
  35.             if ("user".equals(msg.get("role"))) {
  36.                 Object content = msg.get("content");
  37.                 if (content instanceof List) {
  38.                     @SuppressWarnings("unchecked")
  39.                     List<Map<String, Object>> contentList = (List<Map<String, Object>>) content;
  40.                     
  41.                     for (int partIdx = 0; partIdx < contentList.size(); partIdx++) {
  42.                         Map<String, Object> part = contentList.get(partIdx);
  43.                         if ("tool_result".equals(part.get("type"))) {
  44.                             toolResults.add(new ToolResultInfo(msgIdx, partIdx, part));
  45.                         }
  46.                     }
  47.                 }
  48.             }
  49.         }
  50.         
  51.         if (toolResults.size() <= KEEP_RECENT) {
  52.             return messages;
  53.         }
  54.         
  55.         // 从先前的 assistant 消息中映射 tool_use_id 到 tool_name
  56.         Map<String, String> toolNameMap = new HashMap<>();
  57.         for (Map<String, Object> msg : messages) {
  58.             if ("assistant".equals(msg.get("role"))) {
  59.                 Object content = msg.get("content");
  60.                 if (content instanceof List) {
  61.                     @SuppressWarnings("unchecked")
  62.                     List<Map<String, Object>> contentList = (List<Map<String, Object>>) content;
  63.                     
  64.                     for (Map<String, Object> block : contentList) {
  65.                         if ("tool_use".equals(block.get("type"))) {
  66.                             String toolId = (String) block.get("id");
  67.                             String toolName = (String) block.get("name");
  68.                             toolNameMap.put(toolId, toolName);
  69.                         }
  70.                     }
  71.                 }
  72.             }
  73.         }
  74.         
  75.         // 清除旧的结果(保留最近的 KEEP_RECENT 个)
  76.         List<ToolResultInfo> toClear = toolResults.subList(0, toolResults.size() - KEEP_RECENT);
  77.         
  78.         for (ToolResultInfo info : toClear) {
  79.             Map<String, Object> result = info.result;
  80.             Object content = result.get("content");
  81.             
  82.             if (content instanceof String && ((String) content).length() > 100) {
  83.                 String toolId = (String) result.get("tool_use_id");
  84.                 String toolName = toolNameMap.getOrDefault(toolId, "unknown");
  85.                 result.put("content", "[Previous: used " + toolName + "]");  // 静默替换
  86.             }
  87.         }
  88.         
  89.         return messages;
  90.     }
  91.    
  92.     /**
  93.      * Layer 2: 自动压缩 - 保存完整对话并生成摘要
  94.      */
  95.     private static List<Map<String, Object>> autoCompact(List<Map<String, Object>> messages) throws IOException {
  96.         // 保存完整对话到磁盘
  97.         Files.createDirectories(TRANSCRIPT_DIR);
  98.         Path transcriptPath = TRANSCRIPT_DIR.resolve("transcript_" + System.currentTimeMillis() + ".jsonl");
  99.         
  100.         try (BufferedWriter writer = Files.newBufferedWriter(transcriptPath)) {
  101.             for (Map<String, Object> msg : messages) {
  102.                 writer.write(gson.toJson(msg));
  103.                 writer.newLine();
  104.             }
  105.         }
  106.         
  107.         System.out.println("[transcript saved: " + transcriptPath + "]");
  108.         
  109.         // 调用 LLM 生成摘要
  110.         String conversationText = gson.toJson(messages);
  111.         if (conversationText.length() > 80000) {
  112.             conversationText = conversationText.substring(0, 80000);
  113.         }
  114.         
  115.         String summary = simulateLLMSummary(conversationText);
  116.         
  117.         // 用摘要替换整个对话历史
  118.         List<Map<String, Object>> compressedMessages = new ArrayList<>();
  119.         
  120.         compressedMessages.add(Map.of(
  121.             "role", "user",
  122.             "content", "[Conversation compressed. Transcript: " + transcriptPath + "]\n\n" + summary
  123.         ));
  124.         
  125.         compressedMessages.add(Map.of(
  126.             "role", "assistant",
  127.             "content", "Understood. I have the context from the summary. Continuing."
  128.         ));
  129.         
  130.         return compressedMessages;
  131.     }
  132.    
  133.     /**
  134.      * Layer 3: 手动压缩工具
  135.      * 当 Agent 主动调用 compact 工具时触发
  136.      */
  137.     private static String handleCompactTool(Map<String, Object> args) {
  138.         String focus = (String) args.get("focus");
  139.         String focusMsg = focus != null ? " Focus: " + focus : "";
  140.         return "Manual compression requested." + focusMsg;
  141.     }
  142.    
  143.     /**
  144.      * 估算 token 数量
  145.      * 简单实现:约 4 个字符对应 1 个 token
  146.      */
  147.     private static int estimateTokens(List<Map<String, Object>> messages) {
  148.         String messagesStr = gson.toJson(messages);
  149.         return messagesStr.length() / 4;
  150.     }
  151.    
  152.     // --- 工具处理器映射 ---
  153.     private static final Map<String, ToolExecutor> TOOL_HANDLERS = new HashMap<>();
  154.    
  155.     static {
  156.         // ... 省略基础工具注册
  157.         
  158.         TOOL_HANDLERS.put(ToolType.COMPACT.name, ContextCompactSystem::handleCompactTool);
  159.     }
  160.    
  161.     // --- Agent 主循环(集成了三层压缩)---
  162.     public static void agentLoop(List<Map<String, Object>> messages) {
  163.         while (true) {
  164.             try {
  165.                 // Layer 1: 每次调用前进行微观压缩
  166.                 messages = microCompact(messages);
  167.                
  168.                 // Layer 2: 如果 token 数超过阈值,自动压缩
  169.                 if (estimateTokens(messages) > THRESHOLD_TOKENS) {
  170.                     System.out.println("[auto_compact triggered]");
  171.                     messages = autoCompact(messages);
  172.                 }
  173.                
  174.                 // ... 省略相同的 LLM 调用逻辑
  175.                
  176.                 boolean manualCompact = false;
  177.                 for (Map<String, Object> block : content) {
  178.                     if ("tool_use".equals(block.get("type"))) {
  179.                         String toolName = (String) block.get("name");
  180.                         
  181.                         // 检查是否是 compact 工具
  182.                         if (ToolType.COMPACT.name.equals(toolName)) {
  183.                             manualCompact = true;  // 标记手动压缩
  184.                         }
  185.                         
  186.                         // ... 执行工具
  187.                     }
  188.                 }
  189.                
  190.                 // Layer 3: 如果调用了 compact 工具,执行手动压缩
  191.                 if (manualCompact) {
  192.                     System.out.println("[manual compact]");
  193.                     messages = autoCompact(messages);
  194.                 }
  195.                
  196.             } catch (Exception e) {
  197.                 System.err.println("Error in agent loop: " + e.getMessage());
  198.                 e.printStackTrace();
  199.                 return;
  200.             }
  201.         }
  202.     }
  203.    
  204.     // --- 辅助类和方法 ---
  205.     private static class ToolResultInfo {
  206.         int msgIndex;
  207.         int partIndex;
  208.         Map<String, Object> result;
  209.         
  210.         ToolResultInfo(int msgIndex, int partIndex, Map<String, Object> result) {
  211.             this.msgIndex = msgIndex;
  212.             this.partIndex = partIndex;
  213.             this.result = result;
  214.         }
  215.     }
  216. }
复制代码
三条理压缩体系架构

办理恒久对话中的上下文长度限定题目,通过三层渐进式压缩计谋,在不丢失关键信息的条件下大幅度缩减上下文长度,实现无穷长对话的本领。
  1. // 压缩流程
  2. while (true) {
  3.     // Layer 1: 每次调用前进行微观压缩
  4.     messages = microCompact(messages);
  5.    
  6.     // Layer 2: 如果 token 数超过阈值,自动压缩
  7.     if (estimateTokens(messages) > THRESHOLD_TOKENS) {
  8.         messages = autoCompact(messages);
  9.     }
  10.    
  11.     // Layer 3: 如果调用了 compact 工具,执行手动压缩
  12.     if (manualCompact) {
  13.         messages = autoCompact(messages);
  14.     }
  15. }
复制代码

  • 分层压缩:微观、主动、手动三层计谋,粒度从细到粗
  • 智能触发:基于token估算主动判定压缩机遇
  • 渐进生存:生存迩来的关键信息,确保一连性
  • 可规复性:压缩前生存完备对话,克制信息丢失
微观压缩:无感地举行轻量级压缩
  1. private static List<Map<String, Object>> microCompact(List<Map<String, Object>> messages) {
  2.     // 收集所有的 tool_result
  3.     List<ToolResultInfo> toolResults = new ArrayList<>();
  4.    
  5.     // 保留最近的 KEEP_RECENT 个完整结果
  6.     if (toolResults.size() <= KEEP_RECENT) {
  7.         return messages;
  8.     }
  9.    
  10.     // 将旧的结果替换为占位符
  11.     for (ToolResultInfo info : toClear) {
  12.         result.put("content", "[Previous: used " + toolName + "]");
  13.     }
  14. }
复制代码

  • 存档优先:压缩前先完备生存,克制信息丢失
  • 智能择要:用LLM天生高质量的对话择要
  • 上下文重置:大幅缩减上下文,但生存焦点信息
  • 路径嵌入:在消息中包罗存档路径,便于调试
  • 结构完备:保持user-assistant对话结构
手动压缩:给予 Agent 主动控制权
  1. /**
  2. * Layer 2: 自动压缩 - 保存完整对话并生成摘要
  3. */
  4. private static List<Map<String, Object>> autoCompact(List<Map<String, Object>> messages) throws IOException {
  5.     // 1. 保存完整对话到磁盘
  6.     Files.createDirectories(TRANSCRIPT_DIR);
  7.     Path transcriptPath = TRANSCRIPT_DIR.resolve("transcript_" + System.currentTimeMillis() + ".jsonl");
  8.    
  9.     try (BufferedWriter writer = Files.newBufferedWriter(transcriptPath)) {
  10.         for (Map<String, Object> msg : messages) {
  11.             writer.write(gson.toJson(msg));
  12.             writer.newLine();
  13.         }
  14.     }
  15.     // 存档保护:完整对话保存到文件,随时可查
  16.     // JSONL格式:每行一个消息,便于处理和加载
  17.    
  18.     // 2. 调用 LLM 生成摘要
  19.     String conversationText = gson.toJson(messages);
  20.     if (conversationText.length() > 80000) {
  21.         conversationText = conversationText.substring(0, 80000);
  22.     }
  23.     String summary = simulateLLMSummary(conversationText);
  24.    
  25.     // 3. 用摘要替换整个对话历史
  26.     List<Map<String, Object>> compressedMessages = new ArrayList<>();
  27.    
  28.     compressedMessages.add(Map.of(
  29.         "role", "user",
  30.         "content", "[Conversation compressed. Transcript: " + transcriptPath + "]\n\n" + summary
  31.     ));
  32.     // 上下文重置:用单条消息包含存档位置和摘要
  33.     // 完整可追溯:存档路径包含在上下文中
  34.    
  35.     compressedMessages.add(Map.of(
  36.         "role", "assistant",
  37.         "content", "Understood. I have the context from the summary. Continuing."
  38.     ));
  39.     // 连续性保持:添加assistant确认,维持对话结构
  40.    
  41.     return compressedMessages;
  42. }
复制代码
  1. /**
  2. * Layer 3: 手动压缩工具
  3. * 当 Agent 主动调用 compact 工具时触发
  4. */
  5. private static String handleCompactTool(Map<String, Object> args) {
  6.     String focus = (String) args.get("focus");
  7.     String focusMsg = focus != null ? " Focus: " + focus : "";
  8.     return "Manual compression requested." + focusMsg;
  9.     // Agent控制:Agent可以根据需要主动压缩
  10.     // 参数化:可以指定摘要焦点,指导LLM关注特定方面
  11. }
复制代码

  • Agent自主控制:Agent可以主动管理上下文长度
  • 使命驱动压缩:在符合的时间点(如使命切换时)触发压缩
  • 聚焦择要:可以指定择要重点,优化信息生存
  • 无缝集成:与主动压缩共享底层机制
架构演进与代价

从 TaskSystem 到 ContextCompactSystem 的升级
维度TaskSystemContextCompactSystem对话长度受上下文限定支持无穷长对话信息生存全量存储智能择要+存档控制方式被动限定主动+主动压缩恒久影象使命文件对话存档+择要上下文优化无三层智能压缩
免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表