一、前言
上一节课我们讲了上下文管理的基础策略(截断、优先级排序),但这些策略有一个明显的缺陷:简单截断会丢失有用信息,而完全保留又会导致Token超限。
Claude Code的核心亮点之一,就是“三层上下文压缩机制”——它不是简单的“删减”,而是“信息提纯”,在保证Agent能记住关键信息的前提下,将上下文压缩到合理长度,兼顾性能和效果。
本节课我们深入拆解这三层压缩的设计逻辑、实现细节,以及触发机制,这也是工业级Agent与普通Agent的核心区别之一。
二、三层压缩的核心设计思想
核心原则:从浅到深、从快到慢、从无损耗到有损耗,优先使用低成本、无损耗的压缩方式,只有在必要时才使用高成本、有损耗的压缩,最大限度保证信息完整性和性能。
三层压缩的触发顺序:微压缩 → 会话压缩 → 全量压缩,只有当前一层压缩无法将Token降到阈值以下时,才触发下一层。
触发阈值:Token占用达到93%(可配置),由TokenTracker监控触发。
三、第一层:微压缩(Micro Compact)—— 本地快速压缩(无API调用)
1. 核心特点
- 无API调用:纯本地字符串处理,速度最快(毫秒级);
- 无信息损耗:只截断冗余的工具结果,不修改核心信息;
- 适用场景:上下文轻度膨胀,主要是工具结果过多导致Token超限。
2. 实现逻辑(Claude Code源码解析)
微压缩的核心类是com.claudecode.core.compact.MicroCompact,其逻辑是“按时间和数量截断工具结果”:
public class MicroCompact implements Compressor { // 保留最近的工具结果数量(默认6个) private static final int MAX_TOOL_RESULTS = 6; // 时间阈值(默认10分钟),超过该时间的工具结果只保留2个 private static final long TIME_THRESHOLD = 10 * 60 * 1000; // 10分钟 @Override public CompactionResult compact(ConversationMemory memory) { List<ToolResult> toolResults = memory.getToolResults(); int originalSize = toolResults.size(); // 1. 按时间筛选:超过10分钟的工具结果,只保留最近2个 List<ToolResult> recentResults = new ArrayList<>(); long currentTime = System.currentTimeMillis(); for (ToolResult result : toolResults) { if (currentTime - result.getTimestamp() <= TIME_THRESHOLD) { recentResults.add(result); } } // 超过10分钟的结果,保留最近2个 if (recentResults.size() < 2) { recentResults.addAll(toolResults.subList(Math.max(0, toolResults.size() - 2), toolResults.size())); } // 2. 按数量筛选:保留最近6个工具结果 if (recentResults.size() > MAX_TOOL_RESULTS) { recentResults = recentResults.subList(recentResults.size() - MAX_TOOL_RESULTS, recentResults.size()); } // 3. 更新记忆中的工具结果 memory.setToolResults(recentResults); // 计算压缩效果 int compressedSize = recentResults.size(); int reducedCount = originalSize - compressedSize; return new CompactionResult("微压缩完成", reducedCount, false); } }3. 关键设计点
- 时间感知:近期的工具结果更可能被后续推理使用,所以保留更多;远期结果只保留少量核心;
- 只压缩工具结果:不触碰系统提示词、用户目标、历史对话,保证核心信息不丢失;
- 无API调用:避免因压缩增加LLM成本和延迟。
四、第二层:会话压缩(Session Memory Compact)—— AI摘要压缩(1次API调用)
1. 核心特点
- 有API调用:调用LLM对历史对话和工具结果进行摘要,中等速度(秒级);
- 低信息损耗:LLM会提炼核心信息,删除冗余描述,保留关键数据;
- 适用场景:微压缩后Token仍超限,上下文包含大量冗余的历史对话和工具结果。
2. 实现逻辑(Claude Code源码解析)
会话压缩的核心类是com.claudecode.core.compact.SessionMemoryCompact,其逻辑是“让LLM生成上下文摘要”:
public class SessionMemoryCompact implements Compressor { // 摘要后的Token范围(10K-40K) private static final int MIN_SUMMARY_TOKENS = 10000; private static final int MAX_SUMMARY_TOKENS = 40000; // LLM客户端(用于生成摘要) private final LlmClient llmClient; @Override public CompactionResult compact(ConversationMemory memory) { // 1. 提取需要压缩的内容(历史对话+工具结果) String contentToCompact = buildContentToCompact(memory); int originalTokens = tokenizer.countTokens(contentToCompact); // 2. 调用LLM生成摘要(提示词工程很关键) String prompt = buildSummaryPrompt(contentToCompact); String summary = llmClient.generateSummary(prompt); // 3. 校验摘要长度,确保在合理范围 int summaryTokens = tokenizer.countTokens(summary); if (summaryTokens < MIN_SUMMARY_TOKENS) { summary = addDetails(summary, memory); // 补充细节 } else if (summaryTokens > MAX_SUMMARY_TOKENS) { summary = truncateSummary(summary); // 进一步截断 } // 4. 更新记忆:用摘要替换原始历史对话和工具结果 memory.setHistory(List.of(summary)); memory.setToolResults(List.of()); // 工具结果已融入摘要 // 计算压缩效果 int reducedTokens = originalTokens - summaryTokens; return new CompactionResult("会话压缩完成", reducedTokens, true); } // 构建需要压缩的内容 private String buildContentToCompact(ConversationMemory memory) { StringBuilder content = new StringBuilder(); content.append("历史对话:").append(memory.getHistory()).append("\n"); content.append("工具结果:").append(memory.getToolResults()).append("\n"); return content.toString(); } // 构建摘要提示词(关键:告诉LLM如何提炼核心信息) private String buildSummaryPrompt(String content) { return "请你作为AI助手,对以下内容进行摘要,要求:" + "1. 保留用户的核心目标和所有关键工具结果数据;" + "2. 删除冗余的对话和重复的描述;" + "3. 保持逻辑连贯,让后续推理能基于摘要继续推进任务;" + "4. 摘要长度控制在10K-40K Token之间。" + "需要摘要的内容:" + content; } }3. 关键设计点
- 提示词工程:摘要的质量取决于提示词,必须明确告诉LLM“保留什么、删除什么”;
- 摘要范围控制:避免摘要过短(丢失信息)或过长(压缩无效);
- 核心信息保留:系统提示词和用户目标不参与压缩,只压缩历史对话和工具结果。
五、第三层:全量压缩(Full Compact)—— 兜底压缩(多次API调用)
1. 核心特点
- 多次API调用:对整个上下文(除系统提示词)进行全量重写,速度最慢(数十秒);
- 一定信息损耗:优先保留用户目标和核心工具结果,删除所有非必要信息;
- 适用场景:会话压缩后Token仍超限,属于极端情况(如长时间运行、大量工具调用)。
2. 实现逻辑(Claude Code源码解析)
全量压缩的核心类是com.claudecode.core.compact.FullCompact,其逻辑是“按API轮次分组,逐步丢弃非核心信息”:
public class FullCompact implements Compressor { // 熔断器:连续3次压缩失败,停止压缩(防止无限调用API) private static final int MAX_RETRY = 3; private final LlmClient llmClient; private int retryCount = 0; @Override public CompactionResult compact(ConversationMemory memory) { if (retryCount >= MAX_RETRY) { return new CompactionResult("全量压缩失败(熔断器触发)", 0, false); } try { // 1. 提取全量上下文(除系统提示词) String fullContext = buildFullContext(memory); int originalTokens = tokenizer.countTokens(fullContext); // 2. 按API轮次分组(每轮调用为一组) List<String> contextGroups = groupByApiRound(fullContext); // 3. 逐步丢弃早期非核心分组,生成全量摘要 List<String> keptGroups = new ArrayList<>(); int currentTokens = 0; // 从后往前保留分组(近期分组更重要) for (int i = contextGroups.size() - 1; i >= 0; i--) { String group = contextGroups.get(i); int groupTokens = tokenizer.countTokens(group); if (currentTokens + groupTokens <= MAX_SUMMARY_TOKENS) { keptGroups.add(0, group); // 插入到前面,保持顺序 currentTokens += groupTokens; } else { // 对当前分组进行摘要,再尝试加入 String groupSummary = llmClient.generateSummary("提炼以下内容的核心信息:" + group); if (currentTokens + tokenizer.countTokens(groupSummary) <= MAX_SUMMARY_TOKENS) { keptGroups.add(0, groupSummary); currentTokens += tokenizer.countTokens(groupSummary); } else { break; // 无法再加入,停止保留 } } } // 4. 生成最终全量摘要,更新记忆 String fullSummary = String.join("\n", keptGroups); memory.setHistory(List.of(fullSummary)); memory.setToolResults(List.of()); // 计算压缩效果 int reducedTokens = originalTokens - tokenizer.countTokens(fullSummary); retryCount = 0; // 重置重试次数 return new CompactionResult("全量压缩完成", reducedTokens, true); } catch (Exception e) { retryCount++; return new CompactionResult("全量压缩失败,重试次数:" + retryCount, 0, false); } } // 按API轮次分组(每一次LLM调用为一轮) private List<String> groupByApiRound(String fullContext) { // 解析上下文,按"API Round X"分组,逻辑略... return new ArrayList<>(); } }3. 关键设计点
- 熔断器保护:避免因LLM异常导致多次无效API调用,浪费成本;
- 分组保留:按API轮次分组,优先保留近期分组,符合Agent的推理逻辑;
- 兜底策略:只有在前面两层压缩无效时才触发,是最后的保障。
六、三层压缩的触发流程(总结)
TokenTracker监控Token数量 → 达到93%阈值 ↓ 调用AutoCompactManager.autoCompactIfNeeded() ↓ 1. 先调用MicroCompact(微压缩) ↓ 压缩后Token仍超限? 2. 再调用SessionMemoryCompact(会话压缩) ↓ 压缩后Token仍超限? 3. 最后调用FullCompact(全量压缩) ↓ 压缩后仍超限? 触发异常,提示用户“上下文过长,无法继续执行”七、实操练习:实现简单的微压缩和会话压缩
结合本节课所学,实现微压缩(本地截断)和会话压缩(简化版,用固定摘要模拟LLM调用)。
public class SimpleCompactManager { // 微压缩:保留最近3个工具结果 public List<String> microCompact(List<String> toolResults) { if (toolResults.size() <= 3) { return toolResults; } // 保留最近3个 return toolResults.subList(toolResults.size() - 3, toolResults.size()); } // 会话压缩:简化版,模拟LLM摘要 public String sessionCompact(List<String> history, List<String> toolResults) { // 模拟LLM摘要,提炼核心信息 StringBuilder summary = new StringBuilder(); summary.append("用户目标:帮我写一个字符串反转工具类\n"); summary.append("工具执行记录:\n"); for (String result : toolResults) { if (result.contains("读取文件")) { summary.append("- 读取文件成功,确认项目为Spring Boot架构\n"); } else if (result.contains("编码规范")) { summary.append("- 检查编码规范,符合要求\n"); } } summary.append("历史对话:用户要求编写工具类,Agent已完成前期准备\n"); return summary.toString(); } // 测试 public static void main(String[] args) { SimpleCompactManager manager = new SimpleCompactManager(); // 测试微压缩 List<String> toolResults = new ArrayList<>(); toolResults.add("读取文件1成功"); toolResults.add("读取文件2成功"); toolResults.add("读取文件3成功"); toolResults.add("读取文件4成功"); List<String> compactedTools = manager.microCompact(toolResults); System.out.println("微压缩后工具结果:" + compactedTools); // 测试会话压缩 List<String> history = new ArrayList<>(); history.add("用户:帮我写一个字符串反转工具类"); history.add("Agent:好的,我需要先读取你的项目结构"); history.add("Agent:已读取文件,正在检查编码规范"); String summary = manager.sessionCompact(history, compactedTools); System.out.println("\n会话压缩后摘要:" + summary); } }八、本课重点总结
1. 三层压缩的核心是“从浅到深、兼顾性能和信息完整性”,避免简单截断导致的信息丢失;
2. 微压缩(本地、无损耗)→ 会话压缩(AI摘要、低损耗)→ 全量压缩(兜底、有损耗),逐步升级;
3. 关键设计点:时间感知、提示词工程
下节课预告
第05课:工具调用系统设计(Tool Calling)—— Agent的“手脚”,如何设计可扩展的工具系统,让Agent能执行文件操作、Shell命令、网络搜索等,对应tool包下的接口和实现类源码解析。
、熔断器保护,这也是Claude Code压缩机制的优势所在。