一、前言

上一节课我们讲了上下文管理的基础策略(截断、优先级排序),但这些策略有一个明显的缺陷:简单截断会丢失有用信息,而完全保留又会导致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压缩机制的优势所在。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐