学习手册 / 把上下文当作有限预算课程官网 ↗

CHAPTER 03 / Agent 工程

把上下文当作有限预算

决定每轮模型真正需要看什么,并在压缩后保住目标、约束和证据。

进阶建议阅读 30 分钟,练习另计自编配套教程
读完这一章,你应该能
  • 为指令、工具、历史、证据和输出分别分配预算
  • 实现按优先级组装上下文的简单策略
  • 检验摘要压缩是否丢失了关键约束与来源

上下文窗口是容量,不是阅读质量保证

Repo 助手开始查一个文件时很轻松,搜索一整个仓库后却可能混淆环境、引用旧配置。原因未必是模型不知道 timeout 的含义,而是输入塞入了太多相似片段。即使消息总量还未到容量上限,关键证据也可能被忽略。研究中的长上下文检索结果不能保证任意模型对任意仓库都同样表现,需要在自己的任务上测量。Lost in the Middle 原论文提供了相关实验。

上下文包括程序规则、用户请求、工具定义、历史消息、检索证据和输出预算。一个常见错误是只统计文件内容,忘记每轮都会重复发送的工具说明。另一个错误是用“每四个字符一个 token”计算中文,实际分词依赖模型与文本,应使用对应 tokenizer 或服务提供的计数接口。

先预留,再选择,最后精确核算

假设教学用模型允许输入与输出合计 8192 tokens,可以先给程序规则 600、工具定义 900、用户任务 200、预期输出 1200 和余量 292,剩下 5000 留给证据与历史。这个数字只是演算示例;真实模型还可能有独立的最大输出限制,接入时要按实际规则核算。

把不可丢失的信息列为固定项:当前任务、用户限定的生产环境、允许访问的目录、回答必须带来源。其余候选片段按相关性、证据强度与新鲜度排序。在发送前重新计算实际序列长度,超出预算就缩减候选或分轮读取,而不是让 API 报错后随意删前半段。

检索原文、摘要和引用各有用途

搜索先给文件名和短片段,读到候选后再按行获取原文。这种按需读取减少一次性载入整个仓库的成本,但增加工具往返。对于频繁出现的小型静态资料,预先检索会更快;对于经常变化的代码,实时读取更容易获得当前状态。两种方式可以组合。

摘要适合保留“已经排除哪些文件”和“下一步查覆盖规则”,原文适合支撑具体数值,引用则让被移出窗口的内容可以再次读取。给每个片段保存路径、版本或内容哈希、行号。若两份配置互相冲突,保留冲突与适用环境,不能通过摘要擅自合并成一个确定结论。

动手:一个能解释取舍的上下文装配器

以下代码无需 API。为避免假装精确计算 token,演示明确使用字符预算,只验证选择与保留逻辑。生产接入应把 len(text) 替换成实际模型的计数方法,同时把完整消息包装开销计入。

def pack_context(pinned, candidates, char_budget):
    used = sum(len(item["text"]) for item in pinned)
    if used > char_budget:
        raise ValueError("固定约束已超预算,不能静默删除")
    chosen = list(pinned)
    skipped = []
    for item in sorted(candidates, key=lambda x: x["priority"], reverse=True):
        size = len(item["text"])
        if used + size <= char_budget:
            chosen.append(item)
            used += size
        else:
            skipped.append(item["id"])
    return {"chosen": chosen, "used_chars": used, "skipped": skipped}

pinned = [{"id": "task", "text": "只回答生产环境,并附文件行号。"}]
candidates = [
    {"id": "prod", "priority": 10, "text": "prod.cfg:2 timeout=30"},
    {"id": "old", "priority": 1, "text": "过期实验日志。" * 20},
    {"id": "test", "priority": 3, "text": "test.cfg:2 timeout=5"},
]
result = pack_context(pinned, candidates, 65)
print([item["id"] for item in result["chosen"]])
print("used:", result["used_chars"], "skipped:", result["skipped"])
assert result["chosen"][0]["id"] == "task"
assert result["used_chars"] <= 65

结果会保留 task、prod 和 test,跳过冗长的旧日志。简单优先级排序并非全局最优:一个很长的高分片段可能挤掉多个有用短片段。可继续实验按段截取、相关性与长度联合打分,关键是保留每次删减的原因以便检查。

压缩历史时留下可继续工作的状态

好的交接摘要至少写清目标、硬约束、已经确认的事实及来源、尚未解决的问题、已失败的路线和下一步。比如“生产超时待确认;README 写 30 秒,但尚未检查环境变量覆盖;已读 config.py 第 10–30 行”。这种摘要能指导继续取证,单写“讨论了超时配置”做不到。

对工具调用与结果成对处理,避免保留孤立结果。压缩后安排一次恢复检查:让新上下文回答原任务、复述硬约束,并指出仍未确认的事项。摘要生成本身也可能引入错误,涉及最终结论的数字最好重读原始来源。Anthropic 的上下文工程文章讨论了按需读取和压缩策略。

可以做一个专门的压缩回归例子:用户最初要求只读生产配置,中间几十轮都是测试环境日志,最后再询问超时。压缩后若只保留最近日志,就会丢掉最初的限定。把“生产环境”设置为必须保留的断言,再检查最终引用,能让这类跨轮约束丢失变成可重复发现的问题。约束检查应在程序状态上进行,不能仅指望摘要写作者记得它。

如何判断“更短”是否真的更好

准备短文件、长日志、同名变量、环境冲突和跨文件覆盖等任务。对比整段输入、检索片段、检索加压缩三种方式,同时记录准确率、来源正确率、输入量、调用次数和总延迟。压缩节省的 token 可能被额外检索抵消,不能只报告输入长度。

保留一组故意放在开头、中间和末尾的关键证据,观察系统是否依赖位置。不要靠把所有内容重复两次来修复问题;那会增加成本,也可能放大噪声。先检查检索是否拿到证据,再检查装配是否留下证据,最后才分析模型是否正确使用它。

LEARN BY DOING

做一次上下文丢失实验

  1. 给装配器加入 10 条无关日志和一条生产环境覆盖配置,设置不同预算。
  2. 记录每个预算下保留和丢弃的片段,手工检查能否得出正确答案。
  3. 写一份不超过 200 字的交接摘要,明确未解决冲突,再用原文核对。

验收标准

  • 任何预算下都不静默删除任务与硬约束;固定项超预算时明确报错。
  • 每次选择均提供已选 id、跳过 id 和长度统计。
  • 摘要保留生产环境限定与证据出处;未核验的覆盖规则仍标为待确认。
下载配套离线实验包 ↓ · 脚本使用模拟模型与数据,运行无需 API。

合上书,检查一下

先作答,再看解释。答错的地方,正好是值得再读一遍的地方。

1. 把字符数直接除以四,就能准确得到中文输入的 token 数吗?
2. 压缩摘要最应保留哪一组内容?

配合本章看视频

下面按本站章节主题选配。为什么没有采用原课录播 →

YouTube · 英语官网推荐 · 非课堂录像

How I use LLMs ↗

CS2680 官网 9 月 9 日课表直接推荐。用模型交互的实际示范建立使用直觉,适合入门或进入上下文管理前观看;它不是 Agent 循环的逐行实现教程。

带着问题看 · 观察怎样向模型提供任务背景、资料与约束,再把一次对话和本站的多步工具循环作比较。

查看来源与观看说明 →
YouTube · 英语同主题参考 · 非 CS2680 原课

Context Engineering for Agents ↗

用写入、选择、压缩、隔离四种操作组织上下文策略,能把本站的预算、持久记忆和子 Agent 上下文隔离串起来。

带着问题看 · 每一步保留什么信息;何时把信息移到外部;摘要丢失细节的代价;用子 Agent 隔离上下文的收益和额外成本。

查看来源与观看说明 →
Bilibili · 中文同主题参考 · 非 CS2680 原课

🗃️ 上下文工程:优化 Agent 效能的关键技术 🔑 ↗

作者以 LangChain 官方上下文工程文章为资料入口,适合配合本平台的上下文预算与记忆章节学习;其身份是中文讲解,非 LangChain 官方授课或 CS2680 课堂录像。

带着问题看 · 结合作者所引官方文章,比较写入外部状态、按需选择、压缩与隔离四种处理方式;把同一段工具返回分别放入上下文、摘要和外部存储,观察信息损失。

查看来源与观看说明 →

继续读原始资料

完成状态与自测成绩独立记录。