Skip to content

上下文工程

本页速览 从 prompt engineering 到 context engineering 的范式升级:为什么上下文是稀缺资源,写入/选择/压缩/隔离四大策略如何落地,以及 Manus、Claude Code 在生产环境验证过的会话压缩、工具掩码、KV cache 优化技术。

上下文工程 ​

2025 年 6 月,Andrej Karpathy 在 X 上发帖,主张用「context engineering」取代「prompt engineering」来描述构建 LLM 应用的真正工作。Tobi Lutke(Shopify CEO)、Simon Willison 等人迅速跟进,这个词在一个月内成为行业共识。到 2025 年下半年,Anthropic、LangChain、Manus 先后发表了各自生产实践中的 context engineering 长文——三家独立得出几乎相同的结论:Agent 的失败大多是上下文失败,而不是模型失败。

本页讲清楚三件事:上下文为什么是必须精打细算的稀缺资源;业界收敛出的四大管理策略;以及那些只有在生产环境里踩过坑才能学到的实战技术。

一、定义:从 Prompt Engineering 到 Context Engineering ​

范式升级 ​

Prompt engineering 回答的问题是:「这句指令怎么写效果最好?」它隐含一个前提——任务是一次性的、输入是可控的,你要打磨的是那一段 system prompt。

Agent 打破了这个前提。一个 Agent 在 Agent Loop 里跑几十上百轮,每一轮模型看到的东西都在变:system prompt、工具定义、工具返回结果、检索到的文档、用户插入的新消息、之前的对话历史。Cognition(Devin 的团队)把话说的最直接:context engineering 「实际上是构建 AI Agent 的工程师的第一要务」。

Anthropic 在其工程博客文章 Effective Context Engineering for AI Agents(2025 年 9 月)中给出了一个被广泛引用的定义:

Context engineering 是在 LLM 推理过程中,策划和维护最优 token 集合(信息)的一系列策略——包括 prompt 之外所有可能落入上下文的信息。

Karpathy 的表述更有传播力,他把 LLM 类比为一种新型操作系统:LLM 是 CPU,context window 是 RAM。就像操作系统精心调度什么数据进入内存一样,context engineering 是「在每一步,把恰到好处的信息填入上下文窗口的精妙艺术与科学」。

一张图看清上下文里到底有什么 ​

┌─────────────────── Context Window(有限、稀缺)───────────────────┐
│                                                                    │
│  ┌──────────────┐  ┌──────────────┐  ┌─────────────────────────┐  │
│  │ System Prompt │  │  工具定义     │  │  Few-shot 示例           │  │
│  │ (角色/规则)  │  │ (schema)   │  │  (canonical examples)  │  │
│  └──────────────┘  └──────────────┘  └─────────────────────────┘  │
│                                                                    │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │ 消息历史(user / assistant / tool_result 交替累积)            │  │
│  │   —— 这是唯一「只涨不跌」的部分,也是最需要管理的部分           │  │
│  └─────────────────────────────────────────────────────────────┘  │
│                                                                    │
│  ┌──────────────┐  ┌──────────────┐  ┌─────────────────────────┐  │
│  │ RAG 检索结果  │  │ 记忆注入      │  │ 子代理返回的摘要         │  │
│  └──────────────┘  └──────────────┘  └─────────────────────────┘  │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘
   每一步 Agent Loop 迭代前,都要重新决定:上面哪些 token 该进、该留、该压、该扔

LangChain 在 Context Engineering for Agents 一文中把要管理的上下文归为三类:指令(prompt、示例、工具描述)、知识(事实、记忆)、工具反馈(tool call 的返回)。这三类各有不同的生命周期和更新频率,管理手段也不同。

和 prompt engineering 的关系

Context engineering 不是取代 prompt engineering,而是它的超集。System prompt 依然要写好(见 提示词工程),但只占总工作量的一小部分。一个判断标准:如果你的调试方式是反复改写 prompt 却从不检查「第 14 步时模型实际看到了什么 token」,那你还在用 prompt engineering 的思维做 Agent。

二、上下文窗口的经济学:注意力是稀缺资源 ​

为什么不等 context window 再大点,把所有东西都塞进去?因为三个层面的证据都表明:上下文越长,模型表现越差,而且这个退化是连续的、从第一个 token 就开始的。

注意力预算与 context rot ​

Transformer 的注意力机制让 n 个 token 两两关联,产生 n² 的 pairwise 关系。上下文变长时,模型捕捉这些关系的能力被摊薄——Anthropic 称之为有限的 attention budget(注意力预算),每加入一个新 token 都在消耗预算。再叠加一个事实:训练数据里短序列远比长序列常见,模型对超长上下文的「经验」天然不足。结果是性能梯度式下滑,而不是到达窗口上限才悬崖式失效。

Chroma Research 在 2025 年发布的 Context Rot 技术报告把这个现象量化成了硬数据:他们测试了 18 个模型(含 GPT-4.1、Claude 4 系列、Gemini 2.5、Qwen3 等),控制任务难度不变、只增加输入长度,发现:

  • 即使在「复制一串重复单词」这种近乎 trivial 的任务上,所有模型的表现都随输入变长而退化,且退化方式非均匀、难以预测;
  • 一个干扰项(distractor)就会拉低正确率,四个干扰项叠加退化更严重——长上下文里不相关但「看起来相关」的内容是有毒的;
  • 在 LongMemEval 对话记忆基准上,给模型「聚焦的 ~300 token 相关上下文」和「完整的 ~113k token 原始历史」,前者表现显著更好——无关上下文强迫模型先检索再推理,检索这一步本身就在掉分。

「迷失中间」:位置也在收费 ​

更早的奠基性研究是 Liu 等人 2023 年的论文 Lost in the Middle: How Language Models Use Long Contexts(arXiv:2307.03172,后发表于 TACL 2024)。核心发现:模型对上下文开头和结尾的信息利用最好,对中间部分的信息召回明显下降,呈 U 型曲线。当关键文档被放在长上下文中间时,部分模型的表现甚至低于完全不给文档的闭卷基线。

对工程实践的直接推论:

  • 最重要的指令放开头,最关键的最新信息放结尾——中间的「腰部」是注意力的低地;
  • 不要把 20 份检索文档一股脑塞进去,排序、截断、压缩比召回数量重要(详见 RAG);
  • 「先扔进去再说,反正模型自己会找」在长上下文下不成立。

长上下文的四种失败模式 ​

Drew Breunig 在 How Long Contexts Fail(2025 年 6 月)中给出了一套已被业界广泛采用的失败分类,LangChain 的官方总结也引用了它:

失败模式含义典型场景
Context Poisoning幻觉或错误信息进入上下文,被后续步骤反复引用Agent 误判了文件结构,之后所有决策都基于这个错误认知
Context Distraction上下文过长,模型过度关注历史而忽视训练中学到的能力重复的错误解法在历史里出现多次后,模型不再尝试新路径
Context Confusion无关内容影响了输出工具集过大,模型调用了名字相似但用途错误的工具
Context Clash上下文内部信息自相矛盾早期假设的结论与后期工具的返回冲突,模型在两者之间摇摆

注意一个反直觉的推论:很多「模型变笨了」的体感,其实是上下文管理的债务在滚利。 模型没变,是它每一步看到的 token 变脏了。这也意味着大部分问题可以在不升级模型的情况下,通过工程手段解决——这正是 context engineering 存在的理由。

大窗口不是免死金牌

Gemini 等模型提供百万级 token 窗口,消除了「放不下」的问题,但没有消除「注意力稀释」「context rot」和成本问题——成本和延迟大致随上下文长度线性增长。百万窗口是容量升级,不是质量豁免。上下文质量差时,更大的窗口只是让你更贵地犯错。

三、四大策略:写入、选择、压缩、隔离 ​

LangChain 的 Harrison Chase 团队在 2025 年中发表的 Context Engineering for Agents 给出了目前最通用的分类框架。它从信息流向的角度问一个问题:每个 token 相对 context window,是被写出去、选进来、压缩了,还是隔离在别处?答案对应四大策略:

策略一句话定义典型手段
Write 写入把信息存到上下文窗口之外,需要时再取scratchpad、文件笔记、跨会话记忆
Select 选择从外部来源只把当前需要的 token 拉进来RAG、工具按需加载、记忆检索
Compress 压缩保留完成任务所需的 token,丢弃其余会话总结、历史截断、工具结果清理
Isolate 隔离把上下文拆到不同Agent/线程里,互不污染子代理、多线程状态、环境隔离

Write:写出去 ​

窗口内的东西会被冲走,窗口外的东西不会。Agent 边干活边记笔记(scratchpad),是人类工作方式的直接模仿:Claude Code 维护 todo list,Anthropic 的多代理研究系统让 LeadResearcher 把计划写入 Memory 以防 200k token 截断后丢失方向。

实现可以很轻:一次写文件的工具调用,或运行时状态对象里的一个持久字段。关键不在载体,在于把「必须跨压缩存活的信息」显式外置,而不是指望它永远留在消息历史里。跨会话的记忆写入则属于另一个话题,见 记忆系统。

Select:选进来 ​

Select 回答「这一步需要什么」。两个方向:

  • 知识选择:RAG 检索相关片段而非全量灌入,记忆按相关性召回;
  • 工具选择:不给 Agent 挂 50 个工具,而是按需加载。工具定义本身吃 token 还制造 Context Confusion——Anthropic 的判断很直白:如果人类工程师都说不清某个场景该用哪个工具,Agent 也不可能做得更好。

一个越来越主流的取向是 just-in-time 上下文:不预先检索所有数据,而是让 Agent 持有轻量引用(文件路径、查询语句、URL),在运行时通过工具自主探索、渐进式地把数据拉进上下文。Claude Code 就是混合策略:CLAUDE.md 启动时直接注入,其余靠 glob/grep 按需导航——既避免了索引过期,又把探索的决定权交给了模型。

Compress:压小它 ​

详情见下一节的实战部分。核心动作:总结(summarization)、截断(trimming)、工具结果清理(tool result clearing)。压缩是长任务的第一杠杆,也是最容易做过头的——过度压缩会丢掉当时看似不重要、后面才关键的信息。

Isolate:隔离开 ​

让一个主 Agent 保持干净的高层计划上下文,把脏活(大量搜索、读几十个文件、跑命令)分包给子代理。每个子代理用自己的干净窗口探索数万 token,最后只返回 1000-2000 token 的蒸馏摘要。Anthropic 的多代理研究系统正是靠这个模式在复杂研究任务上显著超过单代理。架构上的展开见 多智能体系统。

四策略不是互斥选项

生产系统几乎全部四者混用。Claude Code:写入(todo list)、选择(just-in-time 文件导航)、压缩(compaction)、隔离(Task 子代理)一样不少。框架的价值在于帮你建立分类直觉——遇到上下文问题时,先归类,再选杠杆。

四、实战技术 ​

这一节的技术都来自一线团队公开的生产实践,而非博客推测。出处集中在三处:Anthropic 工程博客、Manus 的 Context Engineering for AI Agents: Lessons from Building Manus(2025 年 7 月)、LangChain 的 how_to_fix_your_context 示例仓库。

会话压缩(Compaction) ​

长会话逼近窗口上限时,把消息历史交给模型总结,用摘要开启新窗口。Claude Code 的实现是:保留架构决策、未解决的 bug、实现细节,丢弃冗余的工具输出,压缩后的上下文加上最近访问的五个文件继续工作。

Anthropic 给出的调优方法论值得照抄:先最大化召回,再迭代提升精度。即先确保压缩 prompt 能抓住 trace 里每一条可能相关的信息(宁可多留),再逐步删掉被证明多余的类目。最安全的低垂果实是清理历史深处的工具调用和结果——工具早就执行完了,模型不需要再看一遍原始输出。Anthropic 后来把这个能力做成了平台级的 tool result clearing 功能。

一个最小可用的压缩循环:

python
# 框架无关的 compaction 骨架:真实项目里请配合你所用框架的状态管理
def should_compact(messages, token_counter, threshold=0.8, window=200_000):
    """当已用 token 超过窗口的 80% 时触发压缩"""
    return token_counter(messages) > threshold * window


def compact(messages, llm, keep_recent=6):
    """
    压缩策略:
    1. 保留最近 keep_recent 条消息原样不动(保持对话连贯性)
    2. 更早的历史交给模型做高召回摘要
    3. 摘要 + 最近消息 = 新上下文
    """
    head, tail = messages[:-keep_recent], messages[-keep_recent:]

    summary = llm.complete(
        "你是会话压缩器。请总结以下 Agent 工作历史,必须保留:"
        "(1)已确认的架构/设计决策;(2)未解决的问题与错误信息;"
        "(3)关键的文件路径、变量名、接口约定;"
        "(4)用户明确表达过的偏好和约束。"
        "可以丢弃:已执行完毕的工具原始输出、重复的探索过程。\n\n"
        f"历史消息:\n{format_messages(head)}"
    )

    # 用一条 system 消息承载摘要,后接未压缩的最近消息
    return [{"role": "system", "content": f"[此前会话摘要]\n{summary}"}, *tail]

要点不在代码而在取舍:压缩触发阈值、保留多少原文、摘要 prompt 里点名要保的类目——这三个旋钮决定了压缩是无损续命还是渐进失忆。摘要模板应该用真实失败 trace 来调,不要凭空写。

工具结果掩码与「不要动态增删工具」 ​

Manus 在其文章里分享了一个反直觉的经验:不要在 Agent 运行中途动态增删工具定义。 原因有二:一是工具定义通常位于上下文前部,改动它会让此前所有轮次的 KV cache 全部失效(详见下一条);二是消息历史里引用了已不存在的工具,会让模型困惑。

他们的替代方案是掩码(masking):工具全集保持稳定,通过约束解码(在 logits 层面屏蔽不可用的工具 token)来控制当前可选动作。上下文不变、cache 不失效,但模型的实际可选范围被精确收窄。同理,历史深处的大体积工具结果(比如读入的整个日志文件)可以在确认不再需要后替换为占位符——上下文长度不变,但信息密度回升了。

文件系统作为外部上下文 ​

Manus 和 Anthropic 独立收敛到同一个模式:把文件系统当作无限容量的外部上下文。Agent 不把整个数据塞进窗口,而是持有引用——文件路径、存储的查询、URL——需要时用工具按需加载。Claude Code 能用 head/tail 分析超出窗口容量的数据库和日志,全程不把完整数据对象读入上下文。

这个模式有一个常被低估的红利:文件系统的元数据本身就是上下文。tests/test_utils.py 和 src/core_logic/test_utils.py 同名不同义,目录层级、命名约定、时间戳都在帮 Agent 判断信息的相关性——这些信号免费,不用白不用。Manus 的文章甚至给出了一条可量化的心智基准:他们倾向于让模型把可恢复的、体积大的内容卸载到文件系统,因为「只要 URL 还在,网页内容就可以从上下文里丢弃」。

KV cache 命中优化 ​

这是 Manus 文章里最具工程含金量的一节。KV cache 让相同前缀的 token 不必重复计算,cache 命中与未命中的推理成本可以相差一个数量级(Manus 当时报告以 Claude Sonnet 为例,缓存与未缓存输入 token 的价差约 10 倍)。Agent 的每一轮请求都是「前缀 + 新增」,cache 命中率就是成本的生命线。

三个实操原则:

  1. 保持前缀稳定。System prompt、工具定义任何一字节的变动都会让其后所有 cache 失效。哪怕时间戳都别放进前缀——在 system prompt 开头放精确到秒的时间戳,等于主动清零 cache。
  2. 只追加,不改写。消息历史 append-only。要确保序列化是确定性的:同样的 JSON 内容如果 key 顺序变了,cache 也会静默失效。
  3. 手动标记 cache 断点。有些平台要求显式标记 cache breakpoint,标记位置要考虑前缀复用。

先想清楚再压缩

KV cache 视角给了「何时压缩」一个更精确的判断标准:压缩是一次彻底的前缀重写,旧 cache 全部作废。所以压缩时机应该偏晚(在性能和成本还剩余量时不动它),但一旦压缩就要压到位——频繁的小压缩是最差策略,每压一次付一次全量前缀的钱。

复述目标(Recitation) ​

Manus 发现的另一个廉价技巧:让 Agent 把 todo list 或当前计划复述进上下文尾部。因为模型天然更关注上下文末尾(回忆 Lost in the Middle 的 U 型曲线),把全局目标定期「重述」到最近的位置,能显著减少长任务中的目标漂移。Claude Code 的 todo list 工具起到的正是这个作用——它不只是项目管理工具,更是注意力操纵工具。

长任务三选一的速查 ​

Anthropic 对三种长时程(long-horizon)手段的适用边界给了明确划分,直接可用作选型依据:

  • Compaction 适合需要大量来回交互、对话连贯性优先的任务——压缩保的是「聊到哪里了」;
  • 结构化笔记 适合有清晰里程碑的迭代式开发——笔记保的是「做到哪一步了」;
  • 子代理隔离 适合可以并行探索的复杂研究分析——隔离保的是「主线不被支线淹没」。

实际系统往往串联使用:子代理各自做笔记,返回的摘要进入主上下文,主上下文快满时再 compaction。理解每个手段「保的是什么」,比记住手段本身重要。

五、反面模式 ​

以下模式在生产 trace 里反复出现,每一个都有真实代价:

1. 上下文膨胀(Context Bloat)。 把「可能有用」的东西都塞进去:完整聊天记录、整个文件、几十页检索结果。代价是三重的——成本线性上涨、注意力稀释、context rot 加速。判据很简单:如果删掉某段上下文后输出质量没变化,它一开始就不该在。

2. 矛盾指令堆积(Instruction Accretion)。 每次用户投诉就往 system prompt 里加一条规则,半年后 prompt 变成一部内部冲突的补丁法典:「要详细」与「要简洁」并存,「主动澄清」与「不要多问」打架。Anthropic 的建议是用 canonical examples(精心挑选的多样化范例)替代穷举规则清单——范例是「一图胜千言」,规则堆叠只会制造 Context Clash。System prompt 要追求「足以完整定义行为的最小信息集」,注意是最小而非最短。

3. 脏数据中毒(Context Poisoning)。 一次幻觉、一个错误的工具返回、一份过期的检索结果进入历史后,会被后续几十轮当作事实引用。防御手段:关键事实写入 scratchpad 前先验证;发现历史里有错误认知时,显式在上下文里纠正(而不是默默指望模型自己注意到矛盾);对外部检索内容保持来源标注。

4. Few-shot 模式锁定。 Manus 称之为「don't get few-shotted」:LLM 是绝佳的模仿者,如果上下文里堆满相似的历史动作—观察对,模型会无脑延续这个模式,哪怕它已非最优。比如批量评审 20 份简历的任务,处理到第 15 份时输出格式和思路会被前 14 份锁死,丧失个案判断。解法不是更多示例,而是引入变化:调整表述结构、轮换顺序、控制同类样本的密度。

5. 错误擦除(Error Erasure)。 直觉上应该把工作流中的失败尝试从历史里删掉,保持上下文「干净」。Manus 的实践经验恰恰相反:保留错误和恢复过程是 Agent 最有效的学习信号之一。看到「这个命令失败了→改用那个方法成功了」的完整轨迹,模型才会在相似场景下主动规避。把错误擦掉的 Agent 会反复踩同一个坑。

别把上下文工程做成过度设计

反方向也要警惕。Anthropic 的忠告是「做能用的最简单方案」:短会话、低工具量、单轮任务不需要 scratchpad、子代理和压缩管线。四策略是工具箱不是 checklist。为 10 轮以内的任务搭建 compaction 系统,本身就是工程资源的 context bloat。

六、与记忆系统的边界 ​

Context engineering 和 memory 经常被混为一谈,边界值得划清:

  • Context engineering 管「这次推理」:窗口内放什么、怎么放、放多少。时间尺度是单次任务、单个会话。
  • Memory 管「跨会话」:什么信息值得在任务结束后沉淀,下次启动时如何召回。时间尺度是数天、数月、用户的整个生命周期。

两者的接口在「写入」和「选择」两个动作上交汇:scratchpad 里记下的东西,哪些在会话结束时升格为长期记忆?长期记忆里存着的东西,这一步该召回哪几条进窗口?前者是 memory 系统的写入策略问题,后者是 context engineering 的选择策略问题。

换句话说:记忆系统决定「仓库里有什么」,上下文工程决定「工作台上摆什么」。 仓库再大,工作台摆错了照样干不好活;反过来,工作台管理得再好,仓库是空的也无法跨任务积累。两个系统要分别设计、协同调优,深入的展开见 记忆系统。

参考资料 ​