Agent AtlasAI Agent 知识库
02.02提示与上下文 · Prompt & Context

上下文工程

上下文腐烂、压缩、隔离、卸载与检索:把上下文当成稀缺资源来分配,而不是把一切都塞进提示词。

上下文窗口是 Agent 的工作记忆。它的容量有限、成本线性、注意力会衰减——所以「放什么」比「放多少」重要一个数量级。

一个思想实验

假设你要做一个客服 Agent,手头有:

  • 系统提示 1.2k token
  • 工具定义 2.5k token
  • 产品手册 80k token
  • 用户历史 6k token
  • 当前问题 200 token

窗口有 200k,看起来绰绰有余。但如果每次请求都把手册全文塞进去:

  • 输入成本变成 20 倍;
  • 首 token 延迟显著增加;
  • 模型要在 80k 文本里找相关段落,关键信息被稀释;
  • 手册更新后,提示缓存全部失效。

更合理的做法是:手册放进检索库,每次只取最相关的 3–5 段(约 2k token)。窗口从「仓库」变成「工作台」。

上下文腐烂

「Context Rot」描述的是:上下文越长,模型对其中信息的利用率反而下降。常见表现:

  • 忽略放在中间的关键约束;
  • 被无关段落带偏,答非所问;
  • 前后引用不一致;
  • 对早期对话中的决定「失忆」;
  • 工具定义太多时,选错工具。

原因不神秘:注意力资源有限,长文本里噪声更多,位置也会影响权重。即使模型宣称支持 1M 窗口,有效利用仍然是一个工程问题。

不要把「窗口够大」当成设计依据

窗口是上限,不是目标。优秀的 Agent 通常在 8k–30k 的有效上下文里工作,而不是把 200k 塞满。

四种基本操作

把上下文管理拆成四个动作:

图 1 · 上下文工程的四个动作
WRITE写入把目标、计划、关键事实写到外部存储
SELECT选择检索、过滤、排序,只取当前需要的内容
COMPRESS压缩摘要、抽取、去重,降低 token 占用
ISOLATE隔离子 Agent、沙箱、分阶段,避免互相污染
四个动作配合使用,而不是只靠「压缩」一个手段。

写入:把状态放到模型之外

不要指望模型记住一切。把关键状态写进结构化存储:

json
{
  "goal": "为 ACME 生成 2025 年风险简报",
  "status": "collecting",
  "done": ["找到 10-K", "抽取利润表"],
  "open": ["核对一次性费用", "对比同行毛利率"],
  "facts": [
    { "key": "gross_margin_2025", "value": "38.2%", "source": "10-K p.42" }
  ],
  "decisions": ["不采用新闻稿口径,以审计报表为准"]
}

每一步只把这份「任务状态」的精简版放进上下文,而不是全部历史。这也是为什么有状态的图编排(LangGraph 等)比纯消息列表更好维护。

选择:不是检索到就放进去

检索结果需要二次处理:

  1. 重排:按相关性排序,而不是按向量距离;
  2. 去重:同一段落的不同切片只留一个;
  3. 截断:按 token 预算截取,保留标题与来源;
  4. 过滤:按权限、时间、版本筛选;
  5. 标注:告诉模型每段的来源、日期和可信度。

一个常见错误是把 20 个检索片段原样拼接。模型看到大量重复和弱相关内容,反而更容易忽略真正有用的那一段。

压缩:摘要要保留决策,而不只是内容

压缩不是「写一段更短的总结」。对 Agent 来说,历史摘要必须保留:

  • 目标与当前状态;
  • 已确认的事实与来源;
  • 做过的决定及理由;
  • 失败的尝试(避免重复);
  • 未解决的问题;
  • 用户的明确偏好与约束。
python
COMPACT_PROMPT = """
把下面的 Agent 历史压缩成可继续工作的状态摘要,必须保留:
1. 原始目标与当前进度
2. 已确认的事实(附来源)
3. 已做决策及理由
4. 失败的尝试与原因
5. 未完成事项与下一步
不要保留寒暄、重复内容和已经被推翻的假设。

历史:
{history}
"""

压缩本身会丢信息。所以原始记录要保留在外部,需要时可以回查;摘要只是工作副本。

隔离:让子任务拥有自己的上下文

当一个子任务需要读大量材料时,把它交给子 Agent:

text
主 Agent:目标、计划、当前状态(约 3k token)
  └─ 子 Agent:读取 40 页文档,返回 800 字结论 + 引用
主 Agent:只接收结论,不接收 40 页原文

这就是多智能体最有价值的场景:上下文隔离。它不是为了「更多智能」,而是为了不让一个子任务的噪声污染全局。

隔离的其他形式:

  • 代码在沙箱执行,只把 stdout / 错误摘要返回;
  • 长文档用工具按页读取,而不是整篇加载;
  • 不同阶段使用不同的系统提示;
  • 把「研究」和「写作」分开,避免写作时被原始材料淹没。

每一步的上下文怎么组装

一个生产级 Agent 的上下文通常按优先级组装:

text
[固定前缀]
  系统提示(角色、规则、输出契约)
  工具定义(只放当前可用的)
  少量高质量示例
[动态部分]
  任务状态摘要
  最近 N 轮对话
  检索结果(重排、截断、带来源)
  当前用户消息
[尾部]
  本轮要求(格式、不要做什么)

固定前缀放前面,能最大化提示缓存命中。动态内容放后面,避免每次请求都改变前缀。

把「当前时间」放在最后

很多团队把时间戳放在系统提示开头,导致每次请求前缀都不同,缓存全部失效。把它移到用户消息或尾部,成本可能直接减半。

预算策略:给每一类内容设上限

不要等超限了才截断。提前分配预算:

内容预算占比超限策略
系统提示 + 工具15%精简描述、按需加载工具
任务状态10%结构化摘要
对话历史20%保留最近 N 轮 + 摘要
检索结果40%重排后截断,保留来源
输出预留15%设置 max_tokens

具体比例按任务调整,但必须有一个显式的预算表,而不是让拼接逻辑随机决定。

python
def build_context(state, budget=32_000):
    ctx = []
    ctx.append(section("system", state.system_prompt, max_tokens=4_000))
    ctx.append(section("tools", render_tools(state.tools), max_tokens=2_500))
    ctx.append(section("state", state.summary, max_tokens=3_000))
    ctx.append(section("history", recent_turns(state, n=6), max_tokens=6_000))

    remaining = budget - used(ctx) - 2_000  # 给输出留余量
    docs = rerank(state.query, state.candidates)[:8]
    ctx.append(section("evidence", fit(docs, remaining), max_tokens=remaining))
    return ctx

长任务:什么时候压缩

一个实用的阈值策略:

  • 上下文使用率 < 50%:正常追加;
  • 50%–70%:开始对早期历史做摘要,保留最近 6–10 轮原文;
  • 70%–85%:触发压缩,写入外部状态,重建上下文;
  • > 85%:停止新工具调用,优先完成当前步骤或转人工。

压缩时机的选择会影响质量:太早压缩会丢细节,太晚则模型已经开始被噪声干扰。最好在「一个子任务完成」的边界压缩,而不是在步骤中间。

上下文质量的评估

上下文工程做得好不好,不能只看「回答像不像」。可以测:

  1. 关键信息召回:在给定上下文下,模型能否答出必须知道的事实;
  2. 噪声敏感度:加入无关段落,正确率下降多少;
  3. 位置稳定性:把关键信息放在开头/中间/结尾,结果是否一致;
  4. 压缩损失:压缩前后在同一测试集上的通过率差异;
  5. 成本与延迟:平均输入 token、TTFT、P95。

一个简单实验:固定 50 条问题,分别用「全量文档」「检索 top-5」「检索 top-5 + 重排」三种上下文跑,比较准确率与成本。多数团队会发现,更少的上下文反而更准。

常见失败模式

把工具返回原样塞回上下文

一个 API 返回 50KB JSON,模型真正需要的可能只有 5 个字段。在工具层做裁剪和摘要,别让原始数据污染上下文。

摘要越写越长

每次压缩都保留上一版摘要,导致摘要本身不断膨胀。压缩应该是有损的、定长的,并丢弃已被推翻的内容。

所有子任务共享一份历史

子 Agent 不需要知道主 Agent 的全部对话。给它目标、必要背景和输出要求即可,否则隔离就失去了意义。

忽略缓存友好性

动态内容放前面、每次请求都变,会让提示缓存完全失效。成本可能相差数倍。

检查清单

  • 有显式的上下文预算表,而不是能塞多少塞多少
  • 系统提示与工具定义精简,且稳定在前
  • 检索结果经过重排、去重、截断
  • 长历史有摘要机制,且保留决策与失败
  • 大型子任务用子 Agent 或工具按需读取隔离
  • 上下文使用率有监控与压缩阈值
  • 关键信息的位置经过测试(开头/中间/结尾)
  • 提示缓存命中率被记录

小结

  • 上下文是工作记忆,不是仓库。窗口大不代表有效。
  • 上下文腐烂真实存在,噪声和长度都会降低利用率。
  • 四个动作:写入外部状态、选择相关内容、压缩历史、隔离子任务。
  • 预算、位置、压缩时机和缓存友好性,决定了成本与质量。
  • 评估上下文质量要看召回、噪声敏感度和压缩损失,而不只是最终答案。

下一篇把「输出」也变成可编程的接口:结构化输出与 Schema。

读完了?进度会保存在本地浏览器。
内容会持续修订。发现错误或想补充,欢迎反馈。 遇到问题?查排查手册 →