第03课:上下文管理与Token控制(Agent的“短期记忆”)
一、前言
上一节课我们设计了Agent Loop的骨架,而上下文(Context)就是Agent的“短期记忆”——它包含了用户输入、历史对话、工具执行结果、系统提示等所有信息,是LLM推理的基础。
但实际开发中,我们会遇到三个致命问题:
1. 上下文过长:超过LLM的Token上限,导致模型报错;
2. 关键信息遗忘:上下文过长时,Agent会忘记早期的用户指令或工具结果;
3. 效率低下:上下文越长,LLM推理速度越慢、成本越高。
本节课我们拆解上下文的组成、核心痛点,以及基础的Token控制策略,为下一节课的“三层压缩”打下基础。
二、上下文的核心组成(Claude Code实战解析)
在Claude Code中,上下文由com.claudecode.context.ContextManager管理,其核心组成分为4部分,按优先级排序(越靠前越重要,不会被轻易压缩):
1. 系统提示词(System Prompt)
作用:定义Agent的角色、能力、规则,是LLM推理的“指导方针”。
示例(Claude Code中的系统提示词片段):
你是一个CLI AI编码助手,具备以下能力:
1. 能调用文件操作、Shell、网络搜索等工具,自主完成编码相关任务;
2. 严格遵循用户提供的编码规范,所有代码必须有中文注释;
3. 执行危险操作(如删除文件、执行rm命令)前,必须先获取用户确认;
4. 工具执行失败后,需尝试重试,若多次失败则提示用户。
特点:固定不变,优先级最高,不会被压缩或删除。
2. 用户核心指令(User Goal)
作用:用户的最终目标,是Agent循环的核心驱动力。
示例:“帮我在我的Spring Boot项目中,添加一个字符串反转工具类,并编写单元测试,确保符合阿里巴巴Java开发手册”。
特点:贯穿整个循环,优先级仅次于系统提示词,压缩时会优先保留。
3. 工具执行结果(Tool Results)
作用:Agent执行工具后得到的反馈,是后续推理的重要依据。
示例:“读取文件project.txt成功,文件内容:这是一个Spring Boot项目,使用MyBatis做数据持久化”。
特点:数量多、体积大,是上下文膨胀的主要原因,也是压缩的重点对象。
4. 历史对话(History)
作用:用户与Agent的历史交互记录,用于保持对话连贯性。
示例:用户:“帮我检查代码漏洞”;Agent:“已调用安全审查工具,正在分析代码...”。
特点:部分历史对话可被摘要压缩,优先级最低。
三、上下文管理的三大痛点及解决方案
痛点1:上下文过长,超过Token上限
原因:工具执行结果过多(比如多次读取大文件、执行Shell命令返回大量输出)、历史对话积累。
基础解决方案:
1. 截断历史:只保留最近N轮对话和最近M个工具结果(比如最近5轮对话、最近3个工具结果);
2. 长度限制:给上下文设置固定长度上限,超过则触发压缩。
痛点2:关键信息遗忘
原因:截断或压缩时,误删用户核心指令、关键工具结果。
基础解决方案:
1. 优先级排序:系统提示词、用户核心指令永不删除;
2. 关键信息标记:将用户指令中的核心目标、工具结果中的关键数据标记为“不可压缩”。
痛点3:效率低下,推理速度慢、成本高
原因:上下文过长,LLM需要处理大量冗余信息,推理时间和Token消耗增加。
基础解决方案:
1. 实时精简:工具执行结果只保留核心信息(比如读取文件时,只保留与任务相关的片段);
2. 按需加载:不一次性加载所有历史,只加载与当前任务相关的上下文。
四、Claude Code中的Token控制实现(源码解析)
Claude Code使用com.claudecode.core.TokenTracker类,实现Token的实时监控和控制,核心逻辑如下:
1. Token计算方式
TokenTracker通过调用LLM的Token计算接口(如OpenAI的tiktoken、Anthropic的tokenizer),实时计算上下文的Token数量:
public class TokenTracker {
// Token上限(可配置,默认200000)
private final int maxTokens;
// 当前Token数量
private int currentTokens;
// Token计算工具
private final Tokenizer tokenizer;
// 计算上下文Token数量
public void updateTokenCount(String context) {
this.currentTokens = tokenizer.countTokens(context);
}
// 检查是否超过阈值(默认93%)
public boolean isOverThreshold() {
double threshold = maxTokens * 0.93;
return currentTokens > threshold;
}
// 剩余Token数量
public int getRemainingTokens() {
return maxTokens - currentTokens;
}
}
2. Token控制策略
Claude Code的Token控制分为“预防”和“处理”两步:
1. 预防:在构建上下文时,实时计算Token数量,若接近阈值(80%),提前精简上下文;
2. 处理:若超过阈值(93%),触发上下文压缩(下一节课详解)。
五、实操练习:实现简单的上下文管理与Token控制
结合本节课所学,实现一个简单的上下文管理器,包含上下文构建、Token计算、截断逻辑。
import java.util.ArrayList;
import java.util.List;
public class SimpleContextManager {
// 系统提示词(固定)
private final String systemPrompt = "你是一个编码助手,只能调用文件工具,严格遵循编码规范。";
// 用户核心指令
private String userGoal;
// 工具结果列表
private final List<String> toolResults = new ArrayList<>();
// 历史对话列表
private final List<String> history = new ArrayList<>();
// Token上限
private static final int MAX_TOKENS = 1000;
// Token计算工具(简化版,实际需调用LLM的Tokenizer)
private Tokenizer tokenizer = new Tokenizer();
// 初始化用户目标
public void setUserGoal(String userGoal) {
this.userGoal = userGoal;
}
// 添加工具结果
public void addToolResult(String toolResult) {
toolResults.add(toolResult);
// 保留最近3个工具结果,防止上下文膨胀
if (toolResults.size() > 3) {
toolResults.remove(0);
}
}
// 添加历史对话
public void addHistory(String message) {
history.add(message);
// 保留最近5轮对话
if (history.size() > 5) {
history.remove(0);
}
}
// 构建上下文
public String buildContext() {
StringBuilder context = new StringBuilder();
// 1. 系统提示词
context.append(systemPrompt).append("\n");
// 2. 用户目标
context.append("用户目标:").append(userGoal).append("\n");
// 3. 工具结果
context.append("工具结果:").append("\n");
for (String result : toolResults) {
context.append("- ").append(result).append("\n");
}
// 4. 历史对话
context.append("历史对话:").append("\n");
for (String msg : history) {
context.append("- ").append(msg).append("\n");
}
// Token检查与截断
int currentTokens = tokenizer.countTokens(context.toString());
if (currentTokens > MAX_TOKENS) {
// 截断历史对话(优先级最低)
while (history.size() > 0 && tokenizer.countTokens(context.toString()) > MAX_TOKENS) {
history.remove(0);
// 重新构建上下文
context = rebuildContext();
}
}
return context.toString();
}
// 重新构建上下文(截断后)
private StringBuilder rebuildContext() {
// 逻辑与buildContext一致,略...
return new StringBuilder();
}
// 简化版Token计算(实际需对接LLM)
static class Tokenizer {
public int countTokens(String text) {
// 简化:按空格分割,每个单词算1个Token
return text.split(" ").length;
}
}
// 测试
public static void main(String[] args) {
SimpleContextManager manager = new SimpleContextManager();
manager.setUserGoal("帮我写一个字符串反转工具类");
manager.addToolResult("读取文件成功,文件内容:Spring Boot项目结构");
manager.addToolResult("检查编码规范,符合阿里巴巴Java开发手册");
manager.addHistory("用户:帮我写工具类;Agent:正在准备相关操作");
String context = manager.buildContext();
System.out.println("构建的上下文:" + context);
System.out.println("Token数量:" + new Tokenizer().countTokens(context));
}
}
六、本课重点总结
1. 上下文是Agent的“短期记忆”,核心组成是系统提示词、用户目标、工具结果、历史对话;
2. 上下文管理的核心是“控长、保关键、提效率”,避免Token超限、关键信息遗忘、推理缓慢;
3. 基础Token控制策略:截断冗余信息、优先级排序、实时监控Token数量。
下节课预告
第04课:三层上下文压缩机制(Claude Code核心亮点)——如何在不丢失关键信息的前提下,高效压缩上下文,解决Token超限问题,对应compact包下的三个压缩类源码解析。
更多推荐
所有评论(0)