适用读者:做过"加提示词没效果"的调优、要把长会话 Agent 做稳、要压 token 成本的人。
前置:S21-J08(Agent 定义)、J06(编排);S20-J02(token 预算)。
覆盖旧文:A-4568(上下文工程,第 2 章,62,503 字符)→ 43 小节 / 约 6.3 万字符。
扩展点:E-S21-029(上下文三要素:代码/流程/环境)、E-S21-030(静态前缀 + 轨迹:为什么"前面不能动")、E-S21-031(压缩前必须保留的四类内容)。
上一篇:S21-J08 · 现代 Agent 的定义:LLM + 上下文 + 工具
下一篇:S21-J10 · 用户记忆与知识库。
1. 一句话结论
Agent 的请求结构永远是"静态前缀 + 动态轨迹"。 静态前缀(system prompt + 工具定义)在多轮间不变,轨迹(用户消息、模型回复、工具结果)不断增长。这一条结构决定了:前缀可以缓存,轨迹必须压缩——也就是"前面不能动、后面可以压"。
出处:《AI Agent 深入理解》第 2 章(
A-4568)。
messages = [system] + tools_schema + [user, assistant(tool_calls), tool, …, status]
└────── 轨迹(每轮增长)──────┘
2. 为什么"上下文质量"比"模型大小"更常是瓶颈(E-S21-029)
新工程师能力再强,不了解产品架构、业务规则、技术债,也发挥不出来。Agent 是永远的新员工。以一个 Coding Agent 为例,最低信息需求有三类:
| 类 | 内容 | 缺了会怎样 |
|---|---|---|
| 代码信息 | 目录结构、模块职责、数据结构、代码规范 | 语法对、风格错,甚至架构冲突 |
| 流程规范 | 分支策略、提交规范、Review 流程、CI 要求 | 直接往主分支提未测代码 |
| 环境信息 | 配置、测试库地址、部署方式、密钥管理 | 本地过、测试环境崩 |
判据:"给足背景它能干得很好,什么都不告诉它再聪明也白搭"。 而且这首先是组织问题——知识若只存在人的记忆和私聊里,Agent 无从获取。
对远程友好 = 对 Agent 友好:Linux 内核三十年的公开邮件列表、决策全记录,天然构成"信息可检索、结构化"的环境。
形式化定义(ReAct 原文):
策略 π(a_t | c_t),其中 c_t = (o_1, a_1, …, o_{t-1}, a_{t-1}, o_t)
要点:下一步动作依赖截至当前的完整交互上下文,不是眼前这一条输入。
3. 上下文在 API 层的构成(E-S21-030)
四类角色,缺一不可:
| role | 谁产生 | 作用 |
|---|---|---|
system |
开发者 | 静态前缀:身份、规则、输出约束 |
user |
用户/上游 | 任务输入 |
assistant |
模型 | 文本或 tool_calls |
tool |
框架 | 按 tool_call_id 回填执行结果 |
第 1 次调用前: [system, user]
模型返回工具调用后: + assistant(tool_calls)
框架执行完: + tool(call_abc), tool(call_def)
第 2 次调用后: + assistant(最终回复) → 循环结束
这条轨迹的观察:Agent 框架的工作就是往列表里"按时追加"——追加错了(顺序、id、配对)模型就会出现"工具结果对不上调用"的幻觉。
3.1 为什么"前面不能动"
| 位置 | 变化频率 | 能否缓存 |
|---|---|---|
| system prompt | 一次会话内不变 | 可缓存 |
| 工具定义 | 会话内不变(除非动态工具) | 可缓存 |
| 轨迹前半 | 已定稿 | 可缓存(只要不改写) |
| 轨迹后半 | 每轮追加 | 不能缓存 |
判据:任何"改写历史"的操作都会让缓存前缀失效。 压缩要尽量从"轨迹尾部往前"做,且压缩后保持前缀稳定(J22 的 KV 命中同理)。
4. 每轮请求前,其实只做一次"上下文构造决策"(E-S21-031)
stable_prefix = system_message
stable_tools = core_tool_schemas
trajectory = load_message_history(session)
status = make_status_message(derive_current_state(trajectory))
if estimated_tokens(stable_prefix, trajectory, status) > budget:
trajectory = compress_old_evidence(
trajectory,
preserve=[decisions, constraints, failures, citations],
)
request.messages = [stable_prefix] + trajectory + [status]
压缩时必须保留的四类:
| 保留 | 为什么 |
|---|---|
| 决策(已定的方案) | 否则模型会反复推翻自己 |
| 约束(规则、边界) | 否则越界操作 |
| 失败(试过且失败的路) | 否则重复踩坑,烧 token |
| 引用(文件路径、行号、来源) | 否则无法复查 |
可以丢的:重复的原始输出、已被结论替代的中间读数、寒暄。
判据:压缩的目标不是"变短",是"保住可继续工作的最小状态"。 把"失败记录"压掉,是最常见的自伤。
5. 预算与触发时机
| 策略 | 触发 | 风险 |
|---|---|---|
| 固定阈值(如 70% 预算) | 简单 | 阈值太高 → 贴近上限抖动 |
| 每轮增量预算 | 平滑 | 实现复杂 |
| 按任务阶段压缩 | 阶段边界清晰 | 阶段识别错 → 压早了 |
预算 = 上下文窗口 − 预留输出 token − 安全余量
判据:先扣掉"输出预留"再谈压缩。把窗口塞满,模型没有空间生成,会截断或报错。
6. 反例与排障
| 现象 | 根因 | 处理 |
|---|---|---|
| 长会话后"忘记约定" | 约束被压掉 | 约束进静态前缀或状态消息 |
| 反复试同一条死路 | 失败记录被删 | preserve 加 failures |
| 成本忽高忽低 | 前缀被改写 → 缓存失效 | 固定前缀,压缩只动尾部 |
| 工具结果错位 | tool_call_id 配对错 |
严格按 id 回填 |
| 输出被截断 | 没留输出预算 | 预算公式先扣预留 |
7. 常见误区
| 误区 | 纠正 |
|---|---|
| "上下文就是提示词工程" | 它是信息供给系统(含工具结果、状态、历史) |
| "塞更多信息总没坏处" | 噪声会挤掉关键约束,且烧钱 |
| "压缩就是摘要" | 摘要会丢"失败/引用",要按四类保留 |
| "历史可以随便改" | 改历史 = 缓存全失效 |
| "团队文档与 Agent 无关" | 文档化是 Agent 的前置条件 |
8. 自测
- 上下文在 API 层的两个部分是什么?哪部分可缓存?
- 压缩时必须保留哪四类内容?举例说明丢"失败"的后果。
- 为什么把窗口塞满会出问题?
- ReAct 的
c_t包含什么?为什么强调"完整交互上下文"?
答案
- 静态前缀(system + 工具定义,可缓存)与动态轨迹(不可缓存)。
- 决策、约束、失败、引用;丢失败 → 重复踩坑、烧 token。
- 没给输出留 token,模型无法生成完整答案。
(o_1,a_1,…,o_t);因为下一步动作依赖全部历史观察与行动,而非单条输入。
9. 下一篇
S21-J10 · 用户记忆与知识库 —— 记忆的层次结构、四种存储格式、可执行代码作为知识表示,以及压缩与整理机制(待写)。
本篇属于系列《S21 · Agent 工程与 MCP》。旧文来源:A-4568(《AI Agent 深入理解》第 2 章);扩展点 E-S21-029/030/031。ReAct 定义引自原文引用的论文。