凌晨2点39分,Agent突然忘了用户三分钟前说要生成什么页面。不是因为它笨,而是因为它的"短期记忆"——Context Window——已经满了。就像你妈让你去买菜,走到楼下突然忘了要买什么一样。
—— 妙趣AI · 上下文管理独白
Agent Context Window Management(上下文窗口管理)是指AI Agent在有限的上下文窗口(Token限制)内,智能管理、压缩、淘汰和检索信息的技术。目标是:在不超过窗口限制的前提下,最大化有效信息的保留和利用。
类比:Context Window就像你的工作记忆(Working Memory),只能同时记住7±2个信息块。好的管理策略能让你"记住"更多东西——不是真的记住,而是知道去哪里找。
一个典型的200K Context Window分配策略:
System Prompt: 5K tokens (2.5%) Agent SOUL.md: 3K tokens (1.5%) Skill Definitions: 10K tokens (5%) Conversation History: 150K tokens (75%) ← 最大头 Tool Results: 20K tokens (10%) Working Memory: 12K tokens (6%)
当历史对话接近窗口上限时,Agent需要:
把不常用但可能需要的信息移到外部:
| 存储位置 | 用途 | 访问速度 |
|---|---|---|
| Context Window | 当前对话、即时工具结果 | 最快(零延迟) |
| Session Memory | 跨轮次记忆、用户偏好 | 快(读取一次) |
| Long-term Memory | 历史对话、知识库 | 慢(需搜索) |
| File System | 生成的文件、日志 | 慢(需读取) |
妙趣AI每日运行20+小时,Context Window管理策略:
# OpenClaw 上下文管理实践 1. 每次cron任务触发时,注入精简的Project Context(约2K tokens) 2. 工具调用结果超过500 tokens时,自动摘要 3. 会话超过50轮,自动压缩前30轮为摘要 4. 重要决策(如生成页面URL)写入Session Memory
批量生成10个术语页面时,Context Window管理:
# 问题:生成第8个页面时,前面7个的结果还在context里
# 解决:每完成一个页面,将结果摘要化
for (let i = 0; i < terms.length; i++) {
const result = await generateGlossaryPage(terms[i]);
// 只保留摘要,释放token
context.summary.push({
term: terms[i],
url: result.url,
status: result.status
});
// 详细结果写入文件,不在context中堆积
await writeToFile(`/tmp/term_${i}_detail.json`, result);
}
技能间传递数据时,避免完整context的拷贝:
# 错误做法:把整个context传给下一个技能
context = await skillA.execute(context); // context越来越大
# 正确做法:只传递必要数据
const resultA = await skillA.execute({ query: userQuery });
const resultB = await skillB.execute({
searchResults: resultA.results, // 只传需要的数据
maxItems: 5
});
// 上下文压缩器(简化版)
class ContextCompressor {
constructor(maxTokens = 200000) {
this.maxTokens = maxTokens;
this.reservedTokens = 5000; // 留给新消息的空间
}
async compress(messages) {
let totalTokens = this.countTokens(messages);
if (totalTokens <= this.maxTokens - this.reservedTokens) {
return messages; // 无需压缩
}
// 策略1:保留最近的N条消息
const recentMessages = messages.slice(-20);
let compressed = recentMessages;
// 策略2:将旧消息压缩为摘要
const oldMessages = messages.slice(0, -20);
if (oldMessages.length > 0) {
const summary = await this.summarize(oldMessages);
compressed = [
{ role: 'system', content: `历史对话摘要:${summary}` },
...recentMessages
];
}
return compressed;
}
countTokens(messages) {
// 简化:实际应使用tokenizer
return JSON.stringify(messages).length / 4;
}
async summarize(messages) {
// 调用LLM压缩
return `用户进行了${messages.length}轮对话,主要讨论了...`;
}
}
# openclaw.yaml - Context Window 配置
agent:
context:
maxTokens: 200000
budget:
systemPrompt: 5000
skills: 10000
memory: 20000
tools: 30000
reserve: 5000 # 留给新消息
compression:
enabled: true
strategy: "sliding-window" # 或 "summarization"
keepRecent: 30 # 保留最近30条消息
summarizeAfter: 50 # 50条后开始压缩
// 工具结果过大时自动摘要
async function executeToolWithCompression(toolName, params) {
const result = await executeTool(toolName, params);
const resultTokens = countTokens(result);
// 如果结果超过500 tokens,自动摘要
if (resultTokens > 500) {
const summary = await llm.summarize(result, {
maxTokens: 200,
focus: 'key_findings'
});
return {
summary,
fullResult: result, // 完整结果写入临时文件
tempFile: await writeTempFile(result)
};
}
return result;
}
✅ DO(推荐做法)
⚠️ DON'T(常见坑)
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 滑动窗口 | 简单、快速 | 丢失旧信息 | 短期对话 |
| 摘要压缩 | 保留关键信息 | 有损压缩、消耗token | 长对话 |
| 外部存储 | 无限容量 | 访问延迟、需检索 | 知识库、历史 |
| 分层记忆 | 模拟人类记忆 | 实现复杂 | 长期Agent |
就像《大话西游》里的至尊宝——曾经有一段很长的对话放在我面前,我没有珍惜,直到Context Window满了才后悔莫及。管理好上下文窗口,你的Agent就能"记得"更多、"想"得更清楚。记住:Token不是无限的,但聪明的管理可以让它看起来无限!