Skip to content

第 8 章 Context Engineering —— Agent 系统的核心工程

Context Engineering 不是"写好 prompt"的升级版,而是 Agent 系统架构设计的物理约束层——它决定了延迟、成本、准确率的上界。

8.1 从 Prompt Engineering 到 Context Engineering

Chatbot 与 Agent 的范式断裂

过去两年,大模型应用的重心从"响应式对话(Chatbots that respond)"迁移到"行动式智能体(Agents that act)"。Agent 的运行内核是 ReAct 循环:

text
输入 → 思考(Thought) → 调用工具(Action) → 观察结果(Observation) → 再思考 → ...

这个循环带来了一个工程层面的根本性变化:模型不再只接收一次输入然后输出答案,而是在每一步都需要重新消化全部历史上下文。当循环执行 50 步,每步都要把系统指令 + 工具定义 + 全部历史消息重新发送给模型。

Prompt Engineering 关注的是内容层面——"如何写一条指令让模型表现更好"。Context Engineering 关注的是系统架构层面——"如何高效组织指令、工具、历史和外部信息,让 Agent 系统整体高效且低成本地运转"。

Context 的五个构成要素

Agent 每次调用模型时,输入的"Context"实际由五层信息拼接而成:

层级内容稳定性
System Prompt角色定义、行为规则、输出格式全局静态
Tool Definitions工具名称、参数 Schema、描述项目级静态
Conversation History历史 user/assistant 消息只追加
Tool Results工具调用返回的 stdout/stderr/数据只追加
External Knowledge文件内容、检索结果、环境变量按需注入

Context Engineering 的核心命题:在有限的上下文窗口与算力成本下,精确控制什么信息、在什么时间、以什么形式输入给模型,从而最大化模型下一步正确行动的概率。

为什么不直接微调或 RL?

当 Agent 表现不佳时,第一反应是训练专属模型。但在当前阶段这通常是陷阱:

  1. 创新周期悖论:微调和 RL 需要极长的闭环周期(收集数据 → 训练 → 评估)。在达成 PMF 之前,Agent 的工具和任务流每天在变,训练数据迅速过时。
  2. 基础模型降维打击:花费数月微调的模型,很容易被下个月新发布的基座模型在 Zero-shot 能力上直接超越。
  3. RL 环境不稳定:RL 依赖固定的动作空间和奖励函数,但真实的软件开发或网页浏览环境中,状态空间是无限且不可预测的。

结论:强大的通用基座模型 + 极致的上下文工程,是当前构建生产级 Agent 最清晰的技术路径。


8.2 Prompt Caching:Agent 系统的架构基石

为什么 KV-Cache 命中率是最关键的单一指标

LLM 推理分为两个阶段:Prefill(预填充,处理所有输入 token 生成 KV 向量)和 Decode(逐 token 生成输出)。Prefill 阶段是 Compute Bound,计算量与输入长度成正比。

Agent 的 I/O 比例极度失衡——Manus 披露的典型比例约为 100:1。每一步都要将完整的系统指令 + 工具列表 + 全部执行历史发送给模型,而模型可能只输出一条简短的 Action。如果没有缓存,每一步都要对成千上万的历史 token 重新做 Prefill,成本呈二次方爆炸。

具体数据参考:

  • Claude 缓存命中时 token 价格仅为未缓存的 1/10(如 Opus 从 $3/MTok 降至 $0.30/MTok)
  • 单个 Agent 任务(约 2M tokens),无缓存 ~$6.00,有缓存仅 ~$1.15
  • 150K+ tokens 上下文中,命中缓存的请求 TTFT(首字响应时间)提速 67%

Manus 将 KV Cache Hit Rate 视为"最重要的单一系统指标";Claude Code 团队将缓存命中率下降定义为线上生产事故(SEV)。

五条通用法则

无论是 Claude Code、Manus 还是 Codex,其底层架构都遵循以下法则:

法则 1:前缀绝对稳定

System Prompt、Tools 定义、早期对话历史永远不修改。缓存的生效条件是严格的前缀匹配——哪怕多一个空格也会导致该位置及之后的所有缓存失效。

法则 2:只追加不修改(Append-Only)

历史消息如有错误,不要回溯修改历史 JSON,而是在对话末尾追加一条新消息进行更正。永远不编辑、不删除已有消息。

法则 3:工具定义解耦

传入大模型的 tools 数组保持静止不变,通过外部参数或机制(如 Logits Masking、allowed_tools 参数)控制工具的可用范围。详见 8.4 节。

法则 4:动态信息后置

时间戳、环境变量、状态提醒等时刻变化的信息,必须放在最后的 User Message 中。典型做法是通过 <system-reminder> 标签包裹放在最新消息末尾。

法则 5:压缩操作必须 Cache-Safe

对上下文进行总结或压缩时,必须复用原有的 Cache 前缀。压缩请求携带与当前会话完全相同的 System Prompt + Tools + 对话前缀,仅在末尾追加 Compaction Prompt,从而"白嫖"父会话的缓存。

缓存破坏机制(常见踩坑点)

以下操作会导致严重的 Cache 破坏:

破坏方式具体场景后果
动态时间戳System Prompt 开头放当前时间(精确到秒)第一个 token 永远不同,整个对话 Cache 彻底失效
动态工具定义按需加载/移除工具,或工具注册顺序随机语义相同但 token 序列不同,前缀阻断
会话内切换模型从 Opus 切换到 Haiku 试图省钱Cache 是 Model-specific,100K 历史需全量 Prefill,反而更贵
非确定性序列化JSON Key 排序随机语义相同但字节序列不同,Cache 静默失效
修改/删除历史消息删除失败的 Action 记录前缀变更,后续所有 Cache 失效

确定性序列化的保障手段:

python
# 强制字典按 Key 排序,保证同一内容的哈希值永远一致
json.dumps(obj, sort_keys=True)

四层缓存架构

Claude Code 采用清晰的四层架构构建 Prompt,稳定性从高到低排列:

四层缓存架构

层级内容缓存范围示例
Layer 1全局 System Prompt + Tools Schema跨会话共享角色定义、工具列表
Layer 2项目级配置同项目内共享CLAUDE.md 内容
Layer 3会话级上下文当前会话内Git status、环境变量(仅初始化时生成)
Layer 4对话消息逐步追加user/assistant 轮次

Codex 采用类似的三层结构:Instructions → Tools → Input。核心原则一致:越稳定的内容越靠前,越动态的内容越靠后

显式与隐式缓存断点结合的 API 配置示例:

json
{
  "model": "claude-sonnet-4-20250514",
  "max_tokens": 1024,
  "cache_control": { "type": "ephemeral" },
  "system": [
    {
      "type": "text",
      "text": "You are a senior developer agent...",
      "cache_control": { "type": "ephemeral" }
    }
  ],
  "messages": [ "..." ]
}

cache_control 标记在 System Prompt 末尾设定显式断点;在对话较新位置,框架自动计算偏移量插入动态断点,让缓存随对话推进而"滚动向前"。

Prompt Caching 层级


8.3 五大上下文策略

五大上下文策略

业界将上下文工程归纳为三个核心维度:Reduce(缩减)、Isolate(隔离)、Offload(卸载)。在此基础上加入 Hierarchical Action Space 和 Context Caching,构成五大策略的完整体系。

策略一:Offload & Retrieval —— 文件系统作为外部记忆

核心思想:将大模型的"短期记忆"(上下文窗口)释放,把厚重的数据转移到"长期记忆"(文件系统)中。

文件系统作为终极上下文

优秀的 Agent 框架均将 Linux 文件系统视为 Agent 最好的记忆库。文件系统天然具备无限扩展、持久化和树状层级的优势。

当 Agent 抓取了一个 5MB 的网页,或执行了一次复杂的 SQL 查询时,不要将数据直接扔回上下文。最佳实践是引导 Agent 将结果写入本地文件,在上下文中只留下极短的一句记录:"数据已成功写入 /tmp/query_result.csv"。

即时检索(Just-in-Time)vs 传统 RAG

传统 RAG 的标配流程是文档分块 → Embedding 向量化 → 向量检索。这套系统复杂且容易产生信息碎片化。

现代 Agent 提倡回归本质的即时检索:赋予 Agent 调用 grepfindripgrepcatjq 等系统命令的能力。当 Agent 需要了解某个函数的实现时,先用 ripgrep 全局搜索函数名找到文件路径,再用读文件工具查看特定行范围的代码。

优势:免去维护向量数据库的成本,搜索结果 100% 精确匹配且永远是最新状态。

策略二:Context Reduction —— Compaction vs Summarization

当 Agent 运行轮数过多,即便是简短的对话也会逼近模型的预衰退阈值(例如在 1M 窗口下设定阈值为 128K)。此时必须进行降维缩减。

缩减分为两种境界:

Compaction(紧凑化)—— 无损可逆压缩

剥离 Payload(冗长的数据负载),保留 Metadata(元数据与指针)。这种压缩是可逆的,因为信息仍然躺在文件系统里。

json
// 原始 Tool Result —— 15000 tokens
{
  "tool_call": "write_file",
  "arguments": {"path": "app.js", "content": "/* 10000 lines of code... */"}
}
// Compaction 后 —— 30 tokens
{
  "tool_call": "write_file",
  "result": {"status": "success", "file_path": "app.js", "note": "Content omitted."}
}

Summarization(摘要化)—— 有损压缩

当 Compaction 也无法拯救逼近极限的上下文时,必须进行摘要。摘要会丢失细节,因此需要容灾闭环:

  1. 全量数据备份(Dump):触发摘要前,系统将完整的原始对话历史(JSONL 格式)持久化写入磁盘。
  2. 结构化摘要生成:调用 LLM 对前半部分对话生成结构化摘要,要求保留:已完成事项、当前状态、关键决策。
  3. 注入查找线索:在摘要末尾附上系统提示——"如需详细信息(如精确的代码片段、错误信息),请读取完整 transcript 文件"。
  4. 保留近期记忆:绝对不要把最后几轮的工具调用也摘要掉。必须保留最近 2-3 轮的完整对话,确保模型能无缝衔接当前语境。

工程策略优先级:Offload 优先 → Compaction 其次 → Summarization 万不得已

策略三:Context Isolation —— Subagent 模式

如果主 Agent 承载了太多中间试错步骤(反复编译、看报错、再编译),主上下文很快会被"污染"。解决方案:将复杂任务委托给拥有干净上下文的子智能体。

子智能体的分类维度

  • 按权限划分:主 Agent 全能型,子 Agent 受限。例如 Explore 子 Agent 只能调用只读工具(Glob, Grep, Read),确保系统安全。
  • 按模型划分:简单搜索任务用低成本小模型(如 Haiku),汇总决策时主 Agent 使用顶级模型(如 Sonnet)。

两种并发模式

借鉴 Go 语言的并发哲学,多 Agent 协同有两种流派:

模式 A:"通过通信共享内存"(Share memory by communicating)—— 强烈推荐

主 Agent 将任务写成一段明确的 Prompt,启动一个全新的子 Agent 进程。子 Agent 的上下文完全空白,通过工具自己探索环境,完成后返回最终答案。

  • 优点:主从上下文绝对隔离,缓存利用率极高
  • 子 Agent 使用与主 Agent 相同的 Prompt Prefix,实现 Cache 复用
  • Claude Code 数据:Explore 子代理 Cache 复用率 92%,Plan 子代理 93%,Execution 阶段 97%

模式 B:"共享内存以通信"(Fork 模式)

遇到极度复杂的"深度研究"或"跨多文件重构"任务时,一两句 Prompt 无法交代清楚背景。主 Agent 将当前完整上下文(或部分状态)直接 Fork 给子 Agent。

  • 缺点:消耗极高 Token,且每次 Fork 的历史不同,KV Cache 命中率极低
  • 仅在万不得已的深度推理场景使用

隔离污染原则:子 Agent 执行完毕后,不要将冗长的思考过程和原始日志返回主干 Agent。子 Agent 应返回经过压缩的 Summary,避免主 Context 被低价值信息污染。

策略四:Hierarchical Action Space —— 三层动作空间

随着 Agent 接入的工具越来越多(尤其是大量 MCP 服务),直接将几十上百个 API Schema 塞入 Prompt 会导致**工具混淆(Context Confusion)**和严重的性能下降。

生产级 Agent 必须构建三层抽象的动作空间:

Level 1:核心原子函数(Function Calling)

只保留最核心的系统级能力:read_filewrite_fileexecute_shellsearch_web。数量极少(10 个左右),Schema 定义雷打不动。这保证了 System Prompt 极其稳定,能够 100% 命中 KV Cache。

Level 2:沙盒实用工具(Sandbox Utilities)

不把"PDF 转文本"、"计算器"、"专用系统查询"注册为 LLM 原生 Tool。把所有扩展能力打包成 CLI 预装在沙盒中,模型只需通过 execute_shell 这个原子工具去运行命令。

收益:无限扩展 Agent 能力,却不增加主 Prompt 的哪怕 1 个 token。

Level 3:脚本与动态 API 代理(Packages & APIs)

假设 Agent 需要调用一个返回 50MB JSON 数据的金融行情 API。直接调用会瞬间让系统崩溃。高阶做法:引导 Agent 编写一段 Python 脚本,在脚本内部完成 API 调用、数据清洗和计算,最后把精简结论 print 出来。Agent 看到的只是短短几句标准输出。

mermaid
graph TD
    A[Agent 需要执行动作] --> B{动作复杂度}
    B -->|简单| C["Level 1: 原子函数<br/>read_file, write_file, bash"]
    B -->|中等| D["Level 2: 沙盒 CLI<br/>通过 bash 调用预装工具"]
    B -->|复杂| E["Level 3: 脚本<br/>编写 Python 脚本处理"]
    C --> F[直接返回结果]
    D --> F
    E --> G[脚本内部处理数据]
    G --> F
    F --> H[精简输出进入 Context]

策略五:Context Caching —— KV-Cache 优化

详见 8.2 节的完整论述。此策略与前四个策略不是并列关系,而是底层约束——前四个策略的所有设计决策,都必须在 Cache 友好的前提下进行。

Context Engineering 总览


8.4 工具管理的三种策略

Agent 可能拥有 30+ 个工具,但特定阶段只需用到几个。如果为了避免模型乱调用而"按需向 Prompt 注入工具",就会破坏 Cache(违反前缀不变原则)。同时,如果上下文中包含对某工具的调用记录,但该工具突然从定义中消失,模型会产生格式错误或幻觉动作。

各家大厂的解决本质一致:传入大模型的 Tool 定义数组永远不变(保 Cache),通过外挂机制限制当前可用的工具范围。

缓存架构设计

Claude Code 方案:Stub 加载 + ToolSearch(按需发现)

两个核心机制:

工具即状态(Tool as State Transition):将进入规划模式(EnterPlanMode)本身定义为一个工具。模型自主调用该工具进入规划状态,此时工具列表并未发生物理变更——状态切换是逻辑层面的,而非 Prompt 层面的修改。

延迟加载(Defer Loading):Prompt 中只存放大量工具的极简存根(Stub),例如只有工具名和一句话描述,不包含完整的参数 Schema。当模型需要确切知道某个工具的用法时,通过调用 ToolSearch 工具获取完整 Schema。

python
# Stub 形式:只有名字和描述,不占用 token
deferred_tools = [
    {"name": "NotebookEdit", "description": "Edit Jupyter notebook cells"},
    {"name": "WebFetch", "description": "Fetch content from a URL"},
    # ... 20+ tools, each ~10 tokens
]

# 按需获取完整 Schema
def tool_search(query: str) -> list:
    """模型调用 ToolSearch 时,返回匹配工具的完整定义"""
    matched = fuzzy_match(query, deferred_tools)
    return [load_full_schema(t) for t in matched]

优势:初始前缀极度稳定(所有 Stub 加起来可能只有几百 token),按需加载不影响前缀。

Manus 方案:Logits Masking(解码层拦截)

全量保留所有工具定义(如 browser_clickshell_execfile_write)始终完整存在于 Prompt 前缀中。

限制手段在解码阶段实施:系统根据当前状态(Auto / Required / Specified),直接将不该使用的工具名称对应的 token Logits 强制置为负无穷,从物理层面禁止模型生成不合规的工具调用。

更精细的控制可通过 Response Prefilling 实现:

text
# 通过预填充 assistant 响应的开头,将模型限制在浏览器工具组内
<|im_start|>assistant<tool_call>{"name": "browser_

这样模型只能补全以 browser_ 开头的工具名,而无需修改底层的工具定义。

限制:此方案强依赖私有化部署或底层 API 支持,不适用于纯 API 调用场景。

OpenAI 方案:allowed_tools 参数

最简洁的 API 级方案:Prompt 中的 tools 数组保持完整且静止。在每次 API 请求时,通过额外的 allowed_tools 参数传入当前允许调用的工具子集。

json
{
  "model": "gpt-4.1",
  "tools": ["read_file", "write_file", "bash", "browser", "search", "..."],
  "allowed_tools": ["read_file", "bash"],
  "messages": ["..."]
}

API 服务端处理时,既能复用整体的 Prefix Cache,又能限制输出。开发者不需要关心底层的 Logits 操作。

三种方案对比

维度Claude CodeManusOpenAI
前缀稳定性Stub 极简,按需加载全量保留全量保留
限制机制应用层(ToolSearch)推理层(Logits Masking)API 层(allowed_tools)
部署要求无特殊要求需底层模型访问权限需 API 支持
Token 效率高(Stub 极短)中(全量定义占 token)中(全量定义占 token)

8.5 上下文腐化与压缩

Context Rot:注意力被长历史稀释

虽然现代模型标称支持 128K 甚至 1M 的超长上下文,但在 Agent 场景中千万不能把上下文"塞满"

当 Agent 执行了大量工具调用,返回了成千上万行的 Shell 日志或代码片段后,模型会出现"上下文腐化(Context Rot)"。具体表现:

  • 开始重复调用刚才已经报错的工具
  • 忘记 System Prompt 规定的输出格式(如突然不输出 JSON 而是纯文本)
  • 推理速度极度变慢,甚至产生严重幻觉

底层原因:

  1. 注意力稀释(O(n2) 复杂度):Transformer 中每个 token 都要与之前所有 token 计算注意力权重。当上下文达到 200K 时,注意力被海量无效日志摊薄,无法聚焦到真正重要的指令上。
  2. 训练数据分布偏差:预训练阶段的高质量数据绝大多数是短文本。模型对超长距离的逻辑依赖处理经验先天不足。
  3. Lost in the Middle:模型对上下文中间位置的信息检索能力显著弱于开头和结尾。长历史中的关键信息如果恰好落在中间,极易被忽略。

三层压缩策略

mermaid
flowchart TD
    A["每一轮工具调用结果"] --> B["Layer 1: micro_compact<br/>(静默, 每轮执行)"]
    B --> C{"token 数 > 阈值?"}
    C -->|否| D[继续执行]
    C -->|是| E["Layer 2: auto_compact<br/>(保存 transcript + LLM 摘要)"]
    E --> D
    F["模型主动调用 compact 工具"] --> G["Layer 3: manual compact<br/>(与 auto_compact 相同流程)"]
    G --> D

    style B fill:#e1f5fe
    style E fill:#fff3e0
    style G fill:#fce4ec

Layer 1:micro_compact —— 静默替换旧工具结果

每次 LLM 调用前自动执行。将超过最近 3 轮的工具返回结果替换为占位符:

python
def micro_compact(messages: list) -> list:
    tool_results = collect_all_tool_results(messages)
    if len(tool_results) <= KEEP_RECENT:
        return messages
    # 只保留最近 3 个工具结果的完整内容
    for result in tool_results[:-KEEP_RECENT]:
        if len(result.get("content", "")) > 100:
            tool_name = lookup_tool_name(result["tool_use_id"])
            result["content"] = f"[Previous: used {tool_name}]"
    return messages

这一层是无损的——文件系统中的实际数据未被删除,Agent 随时可以重新读取。

Layer 2:auto_compact —— Token 阈值触发的 LLM 摘要

当 token 估算超过阈值(如 50K)时自动触发:

  1. 将完整的原始对话历史序列化为 JSONL,写入 .transcripts/ 目录
  2. 调用 LLM 生成结构化摘要,要求保留:已完成事项、当前状态、关键决策
  3. 用摘要消息替换全部历史,附上 transcript 文件路径以供恢复
python
def auto_compact(messages: list) -> list:
    # 1. 保存完整 transcript 到磁盘
    transcript_path = save_transcript(messages)
    # 2. LLM 生成摘要
    summary = llm_summarize(messages)
    # 3. 替换为压缩后的消息对
    return [
        {"role": "user",
         "content": f"[Conversation compressed. Transcript: {transcript_path}]\n\n{summary}"},
        {"role": "assistant",
         "content": "Understood. I have the context from the summary. Continuing."},
    ]

Layer 3:manual compact —— 模型主动触发

compact 注册为一个工具。当 Agent 自己感知到上下文过长或注意力下降时,主动调用 compact 工具触发与 auto_compact 相同的流程。

关键洞察:"Agent 可以策略性地遗忘,从而无限工作下去。" Transcript 文件保证了没有信息真正丢失——只是从活跃上下文移到了磁盘。

Transcript Archive 支持恢复

压缩的核心容灾机制:当 Agent 发现摘要中信息不足时,能够通过系统留下的线索,主动使用 grepread_file 去查阅自己过去的完整原始日志。

摘要末尾的系统提示模板:

"If you need specific details from before compaction (like exact code snippets, error messages), read the full transcript at: [transcript_path]"

这使得压缩从"不可逆的信息丢失"变成了"可恢复的信息归档"。

Cache-Safe Compaction 的工程实现

压缩动作本身绝对不能破坏现有 Cache。两种主流实现:

  • Claude Code 做法:压缩请求携带与当前会话完全相同的 System Prompt + Tools + 对话前缀,仅在末尾追加 Compaction Prompt。由于前缀完全一致,直接复用父会话的 Cache。通常在 Context 满之前预留 Compaction Buffer 提前触发。
  • Codex 做法:提供专门的 /responses/compact 端点。超过 auto_compact_limit 时触发,返回压缩后的 Item 列表,并生成加密的 Compaction 项目,将总结内容安全地放回上下文。

8.6 六条操作原则(Manus 视角)

以下六条原则来自 Manus 团队的生产实战经验总结,与前文的策略体系形成从"理论框架"到"操作手册"的闭环。

原则一:KV-Cache 命中率是关键指标

对生产环境的 Agent 而言,KV-Cache 命中率直接决定延迟(TTFT)和成本。必须做到:

  • 保持 Prompt Prefix 绝对稳定,不在开头放动态时间戳
  • 实行 Append-Only 的上下文策略,确保数据序列化的确定性
  • 在推理框架不支持自动增量缓存时,手动标记缓存断点

原则二:Masking 而非移除工具(保留前缀)

面对庞大工具库,直觉做法是用类似 RAG 的方式动态加载/移除工具定义。这有致命缺陷:

  1. 破坏缓存:工具定义位于上下文最前端,动态修改瞬间清空后续所有 KV 缓存
  2. 引发幻觉:上下文中存在对某工具的调用记录,但该工具突然从定义中消失,模型会产生格式错误或幻觉

正确做法:保持工具定义静态不变,通过 Logits Masking 或 Response Prefilling 在解码阶段限制可用工具范围。

原则三:文件系统作为外部记忆(让压缩可恢复)

尽管模型支持 128K+ 上下文窗口,但直接截断或激进压缩会导致不可逆信息丢失。你永远无法准确预测,当前步骤的一条微小观察结果是否会在十步之后成为关键线索。

最佳实践:

  • 将文件系统设计为 Agent 的终极外部记忆
  • 让模型学会按需读写文件
  • 所有上下文压缩必须是可恢复的——在上下文中仅保留文件路径或 URL,Agent 随时可通过读取文件找回信息

原则四:Recitation 锚定注意力

在长达几十步的复杂任务循环中,Agent 极易出现"迷失在中间(Lost in the Middle)"现象,偏离主题或遗忘全局目标。

解决方案:利用自然语言在上下文中手动操控模型的注意力。让 Agent 维护一个结构化的待办事项(如 todo.md),在任务推进过程中不断更新并在当前上下文末尾处复述核心目标和已完成进度。这能将全局计划强行推入模型"最近期的注意力视野"中,有效对抗目标偏移。

注意力引导的演进路线:

阶段方案问题/改进
Manus 早期文本 todo 列表Agent 浪费 ~1/3 的 Action 次数维护 todo
Manus 后期专职 Planner Agent独立负责任务拆解,模型自主推进
Claude CodePlan Mode 工具模型自主调用 EnterPlanMode,需用户审批
Codexupdate_plan 工具执行流的一环,无需审批,更轻量

原则五:保留错误信息

在多步任务中,环境报错、API 异常、模型幻觉是常态。开发者往往想把报错痕迹从上下文中"擦除"。

绝对不要隐藏错误。 将失败的 Action、报错信息(如 Stack Trace)原封不动保留在上下文中。

理由有二:

  1. 架构层面:删除历史记录会破坏前缀,导致 Cache 失效
  2. 智能层面:LLM 需要"证据"更新内部置信度。当模型在上下文中清晰看到某条路径走不通时,会调整策略,极大降低重复犯相同错误的概率。从错误中恢复的能力,才是真正具备智能的 Agent 行为

原则六:避免 Few-Shot 陷阱

Few-shot prompting 通常能提升输出质量,但在长序列的 Agent 上下文中可能适得其反。

LLM 是极强的模式模仿者。如果上下文中堆积了大量极其相似的"动作-观察"循环(例如连续翻了 20 页相同格式的文档),模型会陷入无意识的"复读机"节奏——仅因为"之前都是这么做的"而盲目重复,最终导致过度泛化或动作偏离。

解决方案:在上下文的序列化过程中,有意注入可控的结构化噪音。对相似的动作和观察结果使用稍微不同的描述模板、变换措辞或微调格式,打断模型的机械模仿惯性,强制它重新聚焦于当前任务的真实状态。

mermaid
graph LR
    subgraph "Token 预算分配建议"
        A["System Prompt<br/>~5%"] --> B["Tool Definitions<br/>~10%"]
        B --> C["Project Config<br/>~5%"]
        C --> D["Session Context<br/>~5%"]
        D --> E["Conversation History<br/>~60%"]
        E --> F["Compaction Buffer<br/>~15%"]
    end
    style A fill:#4caf50,color:#fff
    style B fill:#4caf50,color:#fff
    style C fill:#8bc34a,color:#fff
    style D fill:#cddc39
    style E fill:#ff9800,color:#fff
    style F fill:#f44336,color:#fff

上图展示了典型 Agent 系统的 token 预算分配。Compaction Buffer(预留 ~15%)是关键——它确保在触发压缩时仍有足够空间执行摘要请求,而不会因 Context 溢出导致请求失败。Layer 1-3 的稳定前缀(~25%)是 Cache 命中的基础,Conversation History(~60%)是实际工作的载体。这个比例不是固定数字,而是一个设计思维:永远为压缩留出余量,永远让前缀保持稳定