Agent六大核心之——上下文工程:大脑供氧系统

45 阅读8分钟

很多人认为 Agent 开发最重要的是接模型、调工具。错。业内共识是:

Context Engineering(上下文工程)是 Agent 开发最重要的工程能力,没有之一。

本文讲清楚它的方法论:五个维度、提示词怎么设计、上下文怎么压缩、缓存怎么做。


一、为什么说它最重要?

理由很简单:模型大家都用同一批(GPT、Deepseek...),工具系统也大同小异。真正拉开差距的是上下文管理——它直接决定了你的 Agent 在第 50 轮之后还能不能做出正确决策。

我们无法决定模型的能力,只能决定喂给模型的上下文长什么样。

还记得"上下文腐烂"吗——上下文越长,模型对每一条信息的注意力越弱。而 Agent 恰恰是个"跑 50 轮、读 50 个文件"的长跑选手——上下文工程就是它的供氧系统。

以下两种方式,确实也可以提升Agent的能力,但是:

  • "Agent 不够聪明,我训一个自己的模型吧"——微调(Fine-tuning)解决不了上下文管理问题,别动不动就想训模型
  • "等产品稳定了我用 RL 优化 Agent 行为"——方向没错,但不要试图重建基础模型,工程手段优先

二、上下文工程的五个维度

整个上下文工程可以归纳为五个字:卸、缓、压、隔、检。

维度解决什么问题手段
Offload(卸载)上下文太挤把信息搬到上下文之外(数据库、文件系统),只留引用
Cache(缓存)重复计算复用已有计算结果(KV Cache、Prompt Cache)
Reduce(压缩)上下文太长压缩成更短的形式(摘要、紧凑化)
Isolate(隔离)一个上下文装不下所有任务拆成多个独立上下文(Multi-Agent)
Retrieve(检索)需要的信息不在上下文中从外部源检索(RAG、文件读取)

五个维度有优先级(重要!):

Offload > Cache > Reduce > Isolate > Retrieve

先卸载(文件系统是廉价的,上下文窗口是昂贵的);能缓存就缓存(别重复算);再压缩(先温和后暴力);实在不行拆上下文(多 Agent);最后才是检索。

2.1 Offload(卸载)

上下文窗口是昂贵的,但文件系统是廉价的。所以可以把信息卸载到上下文之外,仅在上下文中保留一个引用(比如文件路径)。需要时再读回来。

2.2 Cache(缓存)

缓存的方式有很多种,KV Cache、Prompt Cache,这两种都是大模型厂商内置的,我们的Agent中无需再做,但还有一种 Context Collapse(上下文折叠):把上下文中的重复信息折叠起来,只保留一次

2.3 Reduce(压缩)的两个层次

  • Compaction(紧凑化):不改变对话结构,把老旧的工具结果替换成占位符 [old_tool result cleared],或缩短工具参数。信息损失小,回收空间也小。
  • Summarization(摘要化):用一段 LLM 生成的摘要替换整段对话。消息结构被破坏,信息损失大,但 token 回收多。

2.4 Isolate(隔离)的两种方式

  • By communicating(消息传递):主 Agent 给子 Agent 发一段任务消息,子 Agent 处理完返回结果。主 Agent 的上下文里只多出"发出的消息 + 返回的结果",完全不知道子 Agent 的内部细节。
  • By sharing context(共享上下文):子 Agent 直接复制主 Agent 的完整上下文,在此基础上继续工作。主 Agent 上下文只多出"返回的结果",但子 Agent 的 token 消耗更大。

Claude Code 两种都支持,按需选择。

2.5 Retrieve(检索)

需要的信息不在上下文里,就从外部源检索——RAG 是其中的代表。


三、提示词工程:不只是"写得好"

很多人以为提示词就是打磨一句话。但 Agent 的提示词是一套行为控制系统,追求的不是单轮回答质量,而是稳定性——模型可能连跑 50 轮,每一轮都要做出正确决策。

  • Chat prompt 追求高质量:一问一答,答得漂亮就行
  • Agent prompt 追求稳定:连续 50 轮不出昏招

Agent 的提示词系统要回答四个问题。

问题一:Prompt 怎么分模块?

把一大段话拆成职责清晰的模块:

const systemPrompt = [
  identitySection(),       // 身份:"你是 XX,负责 YY"
  systemRulesSection(),    // 系统规则:环境约束
  taskGuidelinesSection(), // 做事方式
  riskGuidelinesSection(), // 行动准则:危险命令要确认
  toolUsageGuide(tools),   // 工具指南:根据实际工具列表动态生成
  outputStyle()            // 输出风格:简洁,不要……
]

模块化的好处:

  1. 独立修改:改输出风格不会碰坏身份和规则
  2. 条件组装:不同场景选择性拼装不同模块
  3. 缓存友好:稳定的前缀可以在多次请求间复用缓存,显著降低延迟和成本

问题二:哪些能够缓存?

System prompt 有静态/动态分界线:

const systemPrompt = [
  // ---- 静态部分:全局可缓存,所有用户共享 ----
  identitySection(),
  systemRulesSection(),
  taskGuidelines(),
  riskGuidelines(),
  toolUsageGuide(tools),
  outputStyle(),

  // ============ 分界线 ============

  // ---- 动态部分:每个会话都不同 ----
  envInfo(cwd, gitStatus),   // 工作目录、Git 状态
  userConfig(claudeMd),      // 用户自定义规则
  languagePref(lang),        // 语言偏好
  memoryContext(memories),   // Memory 内容
]

静态在前,动态在后——因为缓存是按前缀匹配的,前缀越稳定,缓存命中越多。

问题三:设置了系统提示词后,用户还怎么自定义 Agent 角色?

两条规则:

  1. 越通用的配置,优先级越低:全局系统提示词的默认规则会被用户自定义规则覆盖
  2. 低优先级的先加载,高优先级的后加载:因为transformer的注意力机制,模型对 prompt 末尾的内容更敏感

问题四:高频变化的信息放哪?

放对话消息里,别放 system prompt 里。

比如用户在 IDE 里打开了一个文件,这个信息每小时都在变。正确做法是在用户消息前注入:

<system-context>
当前 IDE 打开的文件:src/auth.ts (第 42 行)
相关 Skill:@security-review(安全审查最佳实践)
今天日期:2026-04-07
</system-context>

帮我看看 auth.ts 有什么问题

这样 system prompt 保持稳定、缓存不被击穿。


四、压缩:三层策略

不管提示词管理做得多好,Agent 跑完 50 轮后,上下文大概率会爆。Claude Code 的策略是三层处理,先试轻手段,不行再上重手段:

第 1 层:Microcompact(微压缩)

不删消息、不改对话结构,只把老旧的工具结果缩小。比如第 3 轮读文件得到 3000 token 的结果,到第 30 轮时这个结果再也用不上了,就替换成一句话 [Old tool result content cleared]。省了 token,对话结构完好。

第 2 层:Snip(裁剪)

直接砍掉最老的消息。从历史对话头部开始删,删了就永久丢失。

第 3 层:Auto-compact(自动摘要)

用 LLM 给整个对话生成摘要。流程:

  1. 剥离图片:上下文中的图片替换成 [Image] 标记
  2. Fork 一个子 Agent 专门生成摘要(绝对不能在主Agent干)
  3. 生成严格九段结构的摘要:用户意图、技术概念、文件改动、错误修复、问题解决、用户消息、待办任务、当前工作、下一步
  4. 恢复关键文件:压缩后附加最近读过的 5 个文件内容(防止丢失当前任务的关键信息)
  5. 替换消息:旧消息 → [压缩边界标记] + [摘要] + [恢复的文件] + [最近对话]

什么时候触发压缩?

触发阈值 = 有效上下文窗口 - 13K(留出安全余量)


五、缓存:三级缓存体系

5.1 KV Cache(模型推理层,不受 Agent 管控)

模型推理时会把已计算的 K/V 缓存起来,避免重复计算。它按 prompt 前缀 匹配——前缀一变,缓存全失效。这也是为什么提示词要"静态在前、动态在后"、工具要延迟加载。

5.2 Prompt Cache(API 层,开发者可控制)

模型厂商提供的 API 级缓存:相同的 prompt 前缀直接复用,计费打折。用法是在请求里标记缓存断点:

{
  "model": "claude-sonnet-4-6",
  "max_tokens": 1024,
  "system": [
    {
      "type": "text",
      "text": "你是一个代码助手。以下是行为规则……(几千 token 的规则)",
      "cache_control": { "type": "ephemeral" }
    }
  ],
  "messages": [
    { "role": "user", "content": "帮我看看 auth.ts" }
  ]
}

那个 cache_control 标记就是关键:它告诉服务端"这一段帮我缓存住,下次别重算"。

5.3 Context Collapse(上下文折叠,开发者控制)

与其把老消息删掉(信息永久丢失),不如把它们折叠——老消息存入外部存储,上下文里只保留一个折叠标记。需要时可以再展开恢复。

附:模型路由——变相的"缓存"

省钱和省上下文的另一个思路:让不同任务用不同模型。

  • 静态路由:按任务类型,XX 类任务固定交给 XX 模型
  • 动态路由:用一个分类器分析当前 prompt 的难度——简单任务给便宜模型,复杂任务给贵模型

小结

本文是上下文工程的"方法论"部分:

  • 五个维度:卸(Offload)> 缓(Cache)> 压(Reduce)> 隔(Isolate)> 检(Retrieve),优先级依次递减
  • 提示词是行为控制系统:模块化、静态前缀缓存、动态信息注入到消息里
  • 压缩三十六计:先 Microcompact(温和替换),再 Snip(直接删),最后 Auto-compact(LLM 摘要 + 恢复关键文件)
  • 三级缓存:KV Cache(推理层)→ Prompt Cache(API 层)→ Context Collapse(应用层)

方法论之外,它还有两个明星应用:RAG(让模型开卷考试)和记忆系统(让 Agent 记住你)——它们共同回答一个关键问题:信息到底应该在什么时候进入上下文?