【斯坦福 AI 编程课 04】上下文管理——Agent 的”记忆宫殿”

【斯坦福 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"]

工作流程

  1. 用户提问:"测试覆盖率是多少?"
  2. 检索相关记忆:找到之前讨论测试的对话
  3. 注入到上下文:"之前我们讨论过测试,你提到要用 Jest..."
  4. 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 会:

  1. 保留最近 10 条消息(完整)
  2. 把更早的消息压缩成摘要
  3. 保留关键决策和修改记录

上下文管理的最佳实践

实践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