上下文工程:不只是"把 Prompt 写好"
如果你读过 Anthropic 的 Agent 最佳实践,会看到这样一句话:
“The most important skill in building agents is context engineering — the art of getting the right information into the model’s context window at the right time.”
上下文工程(Context Engineering)比 Prompt 工程更底层,管的是整个上下文窗口的内容构成与预算分配:放什么、放多少、按什么顺序、预算不够时怎么取舍。
本篇从三个维度拆解它:
- 上下文五来源 + Token 成本剖析:上下文窗口里住着哪些东西
- 预算约束下的动态组装:Token 告急时怎么做取舍
- 溢出策略三选一:截断、摘要、检索,同一问题下答案质量的真实差异
上下文的五个来源
一个完整的 Agent 上下文由五类内容组成,每类都有不同的生命周期和成本特征:
┌─────────────────────────────────────────────────────────┐ │ Agent 上下文构成 │ ├──────────────────┬──────────────────────────────────────┤ │ ① System Prompt │ 固定载入,定义 Agent 角色和行为准则 │ │ │ 特征:稳定,适合 Prompt Caching │ ├──────────────────┼──────────────────────────────────────┤ │ ② 工具定义 │ 按需加载,当前任务相关的工具 Schema │ │ │ 特征:工具越多膨胀越快(每个 ~50-200t) │ ├──────────────────┼──────────────────────────────────────┤ │ ③ 对话历史 │ 最近 K 轮,随对话增长 │ │ │ 特征:线性增长,需要截断/摘要控制 │ ├──────────────────┼──────────────────────────────────────┤ │ ④ 检索内容 │ 动态注入,当前问题相关的知识库片段 │ │ │ 特征:质量决定生成质量,需相关度过滤 │ ├──────────────────┼──────────────────────────────────────┤ │ ⑤ 当前输入 │ 用户当前 Turn 的问题 │ │ │ 特征:永远最后载入,不可省略 │ └──────────────────┴──────────────────────────────────────┘Token 成本实测
对一个典型的客服 Agent 做 Token 剖析(128K 窗口,4K 输出预留,可用 124K):
来源 Token数 占预算% 用途 ──────────────────────────────────────────────────────────── ① System Prompt 155 0.1% 固定载入 ② Tool Definitions 174 0.1% 按需加载(当前任务相关工具) ③ Conv. History (8轮) 263 0.2% 最近 N 轮(可截断/摘要) ④ Retrieved Content 200 0.2% 动态(相关度过滤) ⑤ Current Input 22 0.0% 当前 Turn ──────────────────────────────────────────────────────────── 合计 814 0.7% 剩余 Buffer 123,186看起来 8 轮对话只用了 0.7%,问题在哪?两个增长因子:
因子 1:对话轮数增长
- 8 轮历史 = 263 tokens;100 轮历史 ≈ 3,200 tokens;1000 轮历史 ≈ 32,000 tokens
- 长期使用的 Agent(客服/助手)不加控制会在几百轮后遭遇溢出
因子 2:工具数量增长
- 4 个工具 = 174 tokens;20 个工具 ≈ 860 tokens;100 个工具 ≈ 4,300 tokens
- MCP Agent 接入大量工具时,工具定义本身就会吃掉几 K tokens
Token 计数工具(使用 tiktoken,cl100k_base 编码作为近似):
importtiktoken _enc=tiktoken.get_encoding("cl100k_base")defcount_tokens(text:str)->int:"""统计文本 Token 数(对中文是近似值,偏低估)"""returnlen(_enc.encode(text))defmsg_tokens(msg)->int:"""计算单条消息 Token 数(含 4 个 overhead tokens)"""returncount_tokens(msg.content)+4预算约束下的动态上下文组装
核心思路:按优先级加载,预算告急时高优先级内容永远完整,低优先级内容弹性裁剪。
优先级模型
P0 System Prompt — 永远完整载入(角色定义不能丢) P1 Current Input — 永远完整载入(没有问题什么都没有) P2 Recent History — 从最新轮倒序加入,直到撑不下(可压缩) P3 Retrieved Docs — 按相关度从高到低加入(可截断) P4 Tool Defs — 只加载当前任务相关工具(可按需裁剪)