【斯坦福 AI 编程课 04】上下文管理——Agent 的"记忆宫殿"
CS146S: 现代软件开发学习笔记系列第四篇
上一篇我们聊了 MCP 协议——AI Agent 的"万能接口"。有了工具,Agent 就能"动手"做事了。
但还有一个关键问题:Agent 怎么记住之前做的事?
如果 Agent 没有记忆,就会像金鱼一样,7秒就忘。你说"帮我修复登录bug",它改完代码;你说"测试一下",它又忘了刚才改过什么,重新读一遍代码。
这不仅浪费 token,还可能导致错误决策。
今天我们来聊聊 Agent 的"记忆宫殿"——上下文管理。
上下文管理的三大挑战
挑战1:对话会越来越长
用户:帮我优化这个函数
Agent:(读取代码,分析,优化)
用户:再优化一下性能
Agent:(又要读取一遍代码...)
用户:加个单元测试
Agent:(又要读取一遍代码...)
每次对话,上下文都在增长。10轮对话后,上下文可能已经 50,000 tokens 了。
挑战2:模型上下文窗口有限
不同模型的上下文窗口:
- GPT-3.5: 4K tokens
- GPT-4: 8K tokens
- Claude 2: 100K tokens
- Claude 3: 200K tokens
- Gemini 1.5 Pro: 1M tokens
即使是最新的 Gemini 1.5 Pro(1M tokens),也只能容纳约 750,000 个英文单词。一个中型项目可能就超了。
挑战3:成本和速度
上下文越长:
- API 调用成本越高(按 token 计费)
- 响应速度越慢(模型要处理更多内容)
- 推理质量可能下降(噪音太多,难以聚焦)
解决方案1:滑动窗口(Sliding Window)
最简单的策略:只保留最近的 N 条消息。
def sliding_window(messages, max_tokens=4000):
"""只保留最近的 max_tokens 个 token"""
total_tokens = count_tokens(messages)
while total_tokens > max_tokens:
# 删除最早的消息
messages.pop(0)
total_tokens = count_tokens(messages)
return messages
优点:
- 简单直接
- 保证上下文不超限
缺点:
- 丢失早期重要信息(如项目结构、需求说明)
- 可能导致 Agent 重复提问
解决方案2:摘要压缩(Summarization)
更智能的策略:把旧对话压缩成摘要。
def summarize_old_messages(messages):
"""把旧消息压缩成摘要"""
# 保留关键信息
summary = {
"project_structure": "Next.js + PostgreSQL",
"main_changes": [
"修复了登录bug(auth.ts)",
"优化了查询性能(query.ts)"
],
"current_task": "添加单元测试"
}
# 返回摘要 + 最近的消息
return [
{"role": "system", "content": f"项目摘要: {summary}"}
] + messages[-5:] # 保留最近5条
优点:
- 保留关键信息
- 大幅减少 token 消耗
缺点:
- 摘要可能丢失细节
- 需要额外的 LLM 调用生成摘要
解决方案3:向量数据库(Vector Database)
最先进的策略:把所有对话存入向量数据库,按需检索。
import chromadb
# 初始化向量数据库
client = chromadb.Client()
collection = client.create_collection("agent_memory")
# 存储对话
def store_message(message):
collection.add(
documents=[message["content"]],
metadatas=[{"role": message["role"], "timestamp": time.time()}],
ids=[str(uuid.uuid4())]
)
# 检索相关对话
def retrieve_relevant_messages(query, top_k=5):
results = collection.query(
query_texts=[query],
n_results=top_k
)
return results["documents"]
工作流程:
- 用户提问:"测试覆盖率是多少?"
- 检索相关记忆:找到之前讨论测试的对话
- 注入到上下文:"之前我们讨论过测试,你提到要用 Jest..."
- Agent 基于检索到的记忆回答
优点:
- 无限存储(理论上)
- 精准检索相关信息
- 不会遗忘重要信息
缺点:
- 需要额外的向量数据库
- 检索可能不精准
- 增加系统复杂度
实战:Claude Code 的上下文管理
Claude Code 使用了混合策略:
1. 项目结构缓存
第一次扫描项目后,缓存目录结构:
{
"project_root": "/app",
"structure": {
"src/": ["main.ts", "utils.ts"],
"tests/": ["main.test.ts"],
"package.json": {...}
},
"last_updated": "2026-03-19T09:00:00Z"
}
下次对话直接用缓存,不用重新扫描。
2. 文件修改追踪
记住哪些文件被修改过:
{
"modified_files": [
{
"path": "src/auth.ts",
"changes": "修复登录bug",
"timestamp": "2026-03-19T08:30:00Z"
}
]
}
用户问"刚才改了什么",Agent 能立即回答。
3. 智能压缩
当上下文快满时,Claude Code 会:
- 保留最近 10 条消息(完整)
- 把更早的消息压缩成摘要
- 保留关键决策和修改记录
上下文管理的最佳实践
实践1:分层存储
┌─────────────────┐
│ 工作记忆(当前对话) │ ← 滑动窗口(最近 20 条)
└─────────────────┘
│
┌─────────────────┐
│ 短期记忆(项目上下文) │ ← 结构缓存、修改记录
└─────────────────┘
│
┌─────────────────┐
│ 长期记忆(历史对话) │ ← 向量数据库(可选)
└─────────────────┘
实践2:主动压缩
不要等上下文满了才压缩,主动压缩:
if token_count > threshold * 0.7: # 70% 时就开始压缩
messages = summarize_old_messages(messages)
实践3:关键信息优先
有些信息必须一直保留:
- 项目结构
- 核心需求
- 关键决策
- 最近的修改
这些信息即使压缩也要保留在摘要中。
实践4:用户可控
让用户控制上下文:
用户:/compact
Agent: 已压缩旧对话,当前上下文: 2,345 tokens
用户:/clear
Agent: 已清空对话历史,开始新会话
用户:/remember 我之前说过什么关于测试的?
Agent: 检索中... 你提到要用 Jest,覆盖率目标 80%
成本对比
假设 10 轮对话,每轮 1000 tokens:
| 策略 | 总 Tokens | 成本(GPT-4) | 响应时间 |
|---|---|---|---|
| 不管理 | 10,000 | $0.30 | 8s |
| 滑动窗口(4K) | 4,000 | $0.12 | 3s |
| 摘要压缩 | 2,500 | $0.075 | 2s |
| 向量检索 | 1,500 | $0.045 | 1.5s |
节省 85% 的成本,速度提升 5 倍!
代码实战:实现一个简单的上下文管理器
class ContextManager:
def __init__(self, max_tokens=4000):
self.max_tokens = max_tokens
self.messages = []
self.project_summary = {}
self.modifications = []
def add_message(self, role, content):
"""添加新消息"""
self.messages.append({"role": role, "content": content})
self._check_and_compress()
def _check_and_compress(self):
"""检查并压缩上下文"""
token_count = self._count_tokens()
if token_count > self.max_tokens * 0.8:
# 保留最近 5 条完整消息
recent = self.messages[-5:]
# 压缩更早的消息
old = self.messages[:-5]
summary = self._summarize(old)
self.messages = [
{"role": "system", "content": f"历史摘要:\n{summary}"}
] + recent
def _summarize(self, messages):
"""压缩旧消息(简化版)"""
# 实际应用中应该用 LLM 生成摘要
summary_points = []
for msg in messages:
if "修复" in msg["content"]:
summary_points.append("修复了bug")
elif "优化" in msg["content"]:
summary_points.append("优化了性能")
return "\n".join(summary_points)
def _count_tokens(self):
"""估算 token 数量"""
# 简化版:1 token ≈ 4 字符
total_chars = sum(len(msg["content"]) for msg in self.messages)
return total_chars // 4
# 使用示例
manager = ContextManager(max_tokens=1000)
manager.add_message("user", "帮我优化这个函数")
manager.add_message("assistant", "我来看看代码...")
# ... 更多对话
下期预告
下一篇我们来聊聊 多 Agent 协作——当任务太复杂,单个 Agent 搞不定时,如何让多个 Agent 分工合作。
参考资料:
这是斯坦福 CS146S 课程学习笔记系列的第四篇。上一篇:MCP 协议——AI Agent 的"万能接口"
Views: 11
