第7章:上下文压缩与优化技术

160 阅读16分钟

前面三章——第4章的采集与预处理、第5章的智能检索、第6章的层次化记忆——解决的都是同一个问题:如何把信息高效地"放进"上下文。但一个被长期忽视的真相是:放进去只是第一步。更关键的问题是,放进去了之后呢?如果上下文不断膨胀、从不收缩,那么所有精心收集和组织的知识终将被淹没在噪声之中。本章直面上下文工程中最具挑战性的命题——不是越多越好,而是越精越好——系统阐述压缩、优化和缓存这三大使上下文保持"精瘦且有力"的技术支柱。


7.1 为什么压缩是必须的:成本、延迟与上下文腐烂

在讨论"怎么压缩"之前,必须先回答"为什么要费这个劲压缩"。答案是三重的,每一重都足以单独成立。

7.1.1 成本维度的不可持续性

2026年,一个典型的Agent交互场景可能是这样的:一个中等复杂度的编码Agent,在一个会话中可能消耗50K至200K的输入token——包括系统指令、工具定义、对话历史、检索结果、文件内容。以Claude Sonnet 4的定价(3.00/百万输入token)计算,一次会话的输入成本就是3.00/百万输入token)计算,一次会话的输入成本就是0.15至0.60。一个日均处理1000次Agent请求的企业,月输入成本是0.60。一个日均处理1000次Agent请求的企业,月输入成本是4,500至$18,000。而如果通过压缩将输入token减少50%,月成本直接减半。

但成本只是最浅层的理由。更深层的问题是延迟。

7.1.2 延迟维度的非线性惩罚

每增加1000个输入token,模型的prefill(预填充)阶段就多处理1000个token的注意力计算。而Transformer的注意力复杂度是O(n²)的——输入长度翻倍,注意力计算量翻四倍。这个"无声的延迟税"在长上下文场景下尤其显著:

  • 1K token输入 → prefill延迟约0.1秒
  • 100K token输入 → prefill延迟约2-5秒
  • 1M token输入 → prefill延迟可达30-60秒

在一个ChatBot场景下,2秒的延迟可能是"有点慢但可以接受",而30秒的延迟意味着用户体验的彻底崩溃。上下文压缩在这里的意义不仅仅是"省钱",更是"让系统在人类耐心耗尽之前完成任务"。

7.1.3 上下文腐烂:ChromaDB 18个SOTA模型的定量判决

然而,成本可以承受,延迟可以优化——真正让压缩从"可选项"变成"必修课"的,是2025年7月ChromaDB研究团队发布的里程碑式报告《Context Rot: How Increasing Input Tokens Impacts LLM Performance》。

什么是上下文腐烂(Context Rot)? ChromaDB给出的定义是:LLM的性能并非随上下文长度均匀分布——模型对第10,000个token的处理并不可靠到与第100个token同等水平。 随着输入token的增加,模型性能以非均匀、常令人惊讶的方式退化。甚至在最极端的情况下,更多的上下文反而导致更差的回答——不是因为模型"忘记"了什么,而是因为它被无关信息"淹没"了。

实验设计与规模。 ChromaDB的研究共测试了18个SOTA模型——包括GPT-4.1系列、Claude 4系列(Sonnet和Opus)、Gemini 2.5系列(Pro和Flash)、Qwen3系列(8B、32B、72B)、Llama 4 70B、Llama 3.1 70B、Mistral Large 2、Yi 34B、Grok-2等——总共执行了194,480次LLM调用,拒绝了仅0.035%(69次)。每个模型在8个输入长度×11个needle位置上接受测试。这是迄今为止对长上下文性能退化最系统、最大规模的实证研究。

核心发现一:越长越差,且呈非线性加速。

所有18个模型的召回准确率都随输入token的增加而单调衰减——这已经不是"某些模型"的问题,而是Transformer架构的固有属性:

输入长度高相似度Needle准确率低相似度Needle准确率
1K tokens约95%约82%
32K tokens约88%约65%
64K tokens约82%约55%
128K+ tokens约70-75%约40-50%

这里最值得警惕的是低相似度场景——现实世界的查询极少与知识库中的原文一模一样,用户在Agent交互中的提问往往是"用自己的话说"的,而非精确的关键词匹配。这意味着现实场景中的上下文腐烂效应比Needle-in-a-Haystack基准(以精确词汇匹配为核心)所展示的更严重。

核心发现二:干扰项放大腐烂。

实验在Needle之外额外加入了1个或4个"干扰项"(语义相似但不包含答案的信息),结果令人不安:4个干扰项的存在,使模型在128K+输入下的准确率再降12-18%。而且不同模型的反应模式不同——Claude家族在不确定时倾向于弃权(保守策略),GPT家族在不确定时倾向于"自信地给出错误答案"(幻觉策略)。这意味着在真实的多文档检索场景中——几乎必然存在多个语义相似但非答案的文档——上下文腐烂效应会比实验室条件下严重得多。

核心发现三:反直觉的结构性发现。

实验对比了两种Haystack的组织方式——一种是原始文章(有逻辑连贯性),一种是随机打乱的句子(无逻辑连贯性)。结果出乎意料:打乱句子的Haystack反而让模型表现更好,准确率提升了5-8%。原因可能是:连贯的文本让Needle更容易"融入背景",模型对它的注意力权重降低;而打乱的文本让Needle作为"异常点"更突出。这揭示了一个深层问题:模型对"连贯文本中的关键信息"的检测能力,在长上下文下是系统性地不足的。

核心发现四:LongMemEval的最终判决。

LongMemEval是一个对话问答基准,测试模型在完整113K token对话历史中定位和提取关键信息的能力。所有模型在完整历史设置下的表现比"只保留相关部分的聚焦设置"下降至少25%。其中Claude模型下降幅度高达35%。这意味着:即使给你一个百万token的上下文窗口,直接塞进去"越多越好"的信息也在系统性损害模型的核心能力。

7.1.4 压缩的不可逃避性

ChromaDB的研究给出了一个清晰的结论:上下文腐烂不是一个可以通过"买更大的上下文窗口"来解决的问题——1M token窗口只是让你能塞更多信息进去,但那些信息中的很大一部分会互相干扰、彼此淹没。唯一的解方是:系统地移除不必要的信息,让剩下的每一个token都携带尽可能高的信号。这就是上下文压缩的根本使命。


7.2 Drew Breunig五大上下文失效模式

在ChromaDB从定量角度揭示了"上下文越长越差"的同时,Overture Maps Foundation策略师、O'Reilly《Context Engineering Handbook》作者Drew Breunig从定性角度完成了一项同样重要的工作——他在2025年6月发表的两篇博文《How Long Contexts Fail》和《How to Fix Your Context》中,系统归纳了五大上下文失效模式,为上下文工程提供了一个诊断框架。

7.2.1 上下文中毒(Context Poisoning)

定义: 幻觉或错误信息进入上下文后,被模型反复引用和放大,最终围绕一个错误的前提进行推理——像个"谣言"一样在上下文中传播。

经典案例: Google DeepMind团队在Gemini 2.5的Pokemon实验中发现了一个令人警醒的现象——Agent如果在游戏过程中产生了一个幻觉(例如错误地认为某个位置有稀有道具),这个幻觉会被写入Agent的"目标摘要"中。此后,Agent会发展出荒谬的策略并重复行为去追逐这个不可能实现的目标。即使后来Agent发现了矛盾的信息,最初的"中毒"信息已经在上下文中根深蒂固,需要大量的交互才能"解毒"。

DeepMind技术报告的原文精准地描述了这一现象:"上下文中毒是一种特别恶劣的失败形式……上下文的多个部分(目标、摘要)被关于游戏状态的错误信息'污染',而纠正这种污染往往需要非常长的时间。"

现实影响: 在企业Agent场景中,上下文中毒的后果更加危险。一个被错误信息"中毒"的技术支持Agent可能围绕一个错误的原因("用户的数据库崩溃了")进行一长串的诊断和决策——而实际上用户的问题只是一个简单的权限配置错误。这不仅是效率问题,更是可信度问题。

诊断信号:

  • Agent的输出中出现越来越多的矛盾信息
  • Agent执着于一个在早期对话中出现的、但可能不正确的前提
  • 用户开始质疑"你刚才说的和之前不一样"

7.2.2 上下文分心(Context Distraction)

定义: 过长的上下文导致模型过度依赖上下文中的零碎信息,而忽略了自身训练时获得的结构化知识和判断力。通俗地说,模型变得"只见树木,不见森林"。

数据证据:

  • Google Gemini团队发现,上下文超过100K token时,模型开始出现显著的"模式锁定"行为——倾向于重复历史中的行为模式,而非综合全局信息制定新计划。
  • Databricks的独立研究发现,Llama 3.1 405B在约32K token处正确率就开始下降,更小的模型下降点更早。
  • Meta在Galactic基准测试中的发现:上下文长度达到128K后,模型开始系统性地优先选择上下文中出现过的答案——即使那个答案是错误的。这种"上下文偏见"在事实性问答任务上尤其明显。

现实影响: 上下文分心最阴险的地方在于:模型看起来"没问题"——它仍然在输出流畅、自信的文本——但它已经在系统性地忽视自己的推理能力,转而依赖上下文中的碎片。在代码审查Agent中,这可能表现为Agent因为看了太多行代码而忽略了明显的逻辑错误;在医疗Agent中,这可能表现为Agent因为读了太多病历而忽略了一个关键的诊断线索。

7.2.3 上下文混淆(Context Confusion)

定义: 上下文中的无关或冗余信息(尤其是过多的工具定义和文档片段)相互干扰,导致模型调用不该调用的工具、读取不该读取的文件、或者将不同文档之间的信息张冠李戴。

数据证据:

  • DeepSeek-v3团队的内部测试发现:工具数量超过30个时,工具描述开始重叠混淆;超过100个时,模型在测试中几乎必然失败。
  • Berkeley Function-Calling Leaderboard的系统性测试显示:所有模型在提供多于1个工具时性能都有不同程度的下降——没有哪个模型能免疫。
  • 论文《Less is More》中的精辟案例:Llama 3.1 8B在46个工具时调用失败,当工具数量减少到19个时(通过删除与该任务无关的工具),同一模型在同一任务上成功了。

现实影响: 上下文混淆是一种"杀死于无形"的失效——它不像中毒那样有明显的错误传播,也不像分心那样有研究基准可以量化,但它让Agent的行为变得不可预测。用户问的是A,Agent调用了B工具,返回了C信息,然后基于C给出了一个似是而非的回答——整个过程看起来"合理",但实际上从一开始就走错了方向。

7.2.4 上下文冲突(Context Clash)

定义: 上下文中的新旧信息产生矛盾——旧的工具结果说数据库是正常的,新的错误日志显示数据库连接超时;旧的用户偏好说"我不喜欢长篇回答",但当前任务明显需要详细的技术说明——模型面对这些矛盾时,无法确定应该相信哪一方。

数据证据: Microsoft Research与Salesforce的联合研究发现,将一个benchmark中的关键信息分散到多轮对话中(模拟真实的多步Agent场景)后,模型性能平均下降39%。更令人震惊的是OpenAI o3的表现:从原始的98.1分直降至64.1分。研究者分析发现,核心原因是模型在信息不完整时就过早尝试回答,错误的中间答案留在上下文中,干扰了最终答案的推理。

现实影响: 上下文冲突在"长期运行"的Agent中尤其常见——一个跨越数百次交互的Agent可能积累了来自不同时间段的、相互冲突的信息。用户两个月前说"我们用Python 3.10",但最近的项目实际上已经升级到了Python 3.12;如果不主动管理这种冲突,Agent会基于过时的假设进行推理。

7.2.5 性能与成本的隐性衰减

定义: 这不是单一的"事件",而是一种系统性的退化过程——随着上下文的增长,Agent的每次响应都变得更慢、更贵、更不准确。这三个维度的退化同时发生且互相加剧。

量化视角:

上下文翻倍 → 
  延迟:prefill时间约增加 3-4x(O(n²)注意力)
  成本:输入token费用翻倍
  质量:根据ChromaDB数据,准确率下降 10-20%

这三条曲线不是平行的——它们在最坏的情况下是相互强化的。更慢的响应意味着更长的对话(用户需要更多轮次来纠正或澄清),更长的对话意味着更多的上下文,更多的上下文意味着更慢的响应……这是一个螺旋下降循环。

Breunig的六大解决方案:

针对这五种失效模式,Breunig在后续文章中给出了六条解决方案:

  1. RAG(选择性注入):正如第5章所述,不把整个知识库塞进上下文,而是按需检索相关片段。
  2. 工具选配(Tool Loadout):只将当前任务相关的工具定义加入上下文,而非全量加载。"RAG MCP"论文显示,工具选择准确率可因此提升3倍。
  3. 上下文隔离(Context Quarantine):在不同线程/子Agent中隔离不同任务的上下文,防止交叉污染——这是第9章将要深入讨论的多智能体上下文管理的前奏。
  4. 上下文修剪(Context Pruning):使用自动化工具(如Provence,1.75GB的工具包)去除上下文中95%以上的无关内容,只保留核心段落。
  5. 上下文摘要(Context Summarization):将累积的上下文提炼为精简的摘要——这正是7.3节要展开的内容。
  6. 上下文卸载(Context Offloading):通过文件系统和工具将信息存储到上下文窗口之外——这是7.4.2节的主题。

7.3 2026年两种主流压缩范式

面对上下文腐烂和五大失效模式,工程界形成了两种互补的压缩范式:紧凑化(可逆压缩)和摘要化(不可逆压缩)。它们是上下文的"减肥手术"——前者是抽脂(移除多余的,保留原样),后者是塑形(改变结构,提炼精华)。

7.3.1 紧凑化:可逆压缩——保留引用,零信息丢失

核心思想: 紧凑化不是"忘记"信息,而是将信息"折叠"起来——用更少的token表达同样的信息,同时保留必要时"展开"的能力。

为什么紧凑化必须是"可逆的"?

想象一个Agent在处理一个跨越多天的复杂任务。在第一天,Agent遇到了一个bug,花了30轮交互来诊断和修复。到了第三天,这个bug的完整推理过程可能已经不重要了——但为什么选择了这个修复方案(而非其他方案)、这个修复是否引入了已知的副作用、相关的代码审查意见——这些都可能在未来成为关键信息。

如果只是生成一句摘要"第一天修复了一个bug",第三天当Agent发现这个修复导致了新的问题时,它无法回溯到原始的推理过程来理解为什么当初做了这个决定。紧凑化的可逆性保证:任何时候,只要有需要,都可以通过"展开"引用恢复到原始信息的完整精度。

紧凑化的五种实用技术:

技术一:工具结果的结构化缩减。

当一个工具的执行结果长达数千token(例如一次数据库查询返回了2000行数据),Agent通常只需要其中的关键信息和"继续深入"的入口。紧凑化不是说"结果太长,我删掉",而是说"结果太长,我保留关键摘要和查询引用,需要时可以重新执行"。

# 原始工具结果 (8,000 tokens)
┌─────────────────────────────────────────┐
│ SELECT * FROM orders WHERE ...          │
│ [2000 rows × 15 columns]               │
│ ...                                     │
└─────────────────────────────────────────┘

# 紧凑化后 (约200 tokens)
┌─────────────────────────────────────────┐
│ [查询结果摘要]                           │
│ 查询: SELECT * FROM orders WHERE date   │
│       > '2025-01-01'                    │
│ 返回: 2,000行, 15列                     │
│ 关键发现:                               │
│   - 总订单金额: ¥3,247,890              │
│   - 平均订单金额: ¥1,623                │
│   - TOP 5 客户占据 34% 的订单量          │
│ [引用: query_execution_id=QK-8873]     │
│ → 需要展开时重新执行查询或查看完整结果     │
└─────────────────────────────────────────┘

当一个工具结果被紧凑化后,Agent仍然知道"这个查询是怎么做的、返回了什么关键结果"——足以支持继续推理。如果需要精确的某一行数据,Agent可以通过引用ID重新执行查询或展开完整结果。

技术二:文件系统引用的路径化。

这与第6章讨论的"上下文卸载"是同一策略的不同侧面。对于文档内容(代码文件、配置、日志),紧凑化的做法是在上下文中保留路径引用+简短摘要,而非完整文件内容。Agent知道"哪些文件是相关的"和"每个文件大概包含什么",需要时可随时读取。

# 紧凑化的文件上下文
def compact_file_context(file_paths: list[str]) -> str:
    """将文件列表紧凑化为路径引用 + 摘要"""
    lines = []
    for path in file_paths:
        size = os.path.getsize(path)
        # 仅保留元数据,不读取内容
        summary = infer_summary_from_path_and_size(path, size)
        lines.append(f"- `{path}` ({format_size(size)}): {summary}")
    return "\n".join(lines)

# 紧凑化前的上下文 (~50,000 tokens):
# "src/auth/login.py 文件内容:\n{class LoginView: ...}\n...\n...\n"
# 
# 紧凑化后的上下文 (~500 tokens):
# - `src/auth/login.py` (12 KB): 登录视图,处理用户名/密码认证
# - `src/auth/middleware.py` (8 KB): 会话验证中间件
# - `src/api/endpoints.py` (45 KB): REST API端点定义,较大
# - `tests/test_auth.py` (22 KB): 认证模块的集成测试

技术三:对话轮次的层级折叠。

对于多轮对话,紧凑化的做法是:保留最近N轮的完整内容(逐字逐句),对于更早的内容,用一层折叠摘要替代("在第3-5轮中,用户和Agent讨论了数据库连接池的配置策略")。如果需要精确还原早期对话,Agent可以从持久化存储中展开折叠的摘要。

技术四:中间推理步骤的外化存储。

Agent在多步推理中产生的中间结果(如"我先验证了A假设,发现不成立,然后转向B假设"),如果完整保留在上下文中会迅速膨胀。紧凑化的做法是:将完整的中间推理存储到文件(如REASONING_LOG.md),在上下文中只保留当前正在进行的推理步骤和已得出结论的引用。

技术五:代码块的结构化折叠。

对于代码生成Agent,经常需要在上下文中展示大段代码。紧凑化的做法是:保留关键代码段(当前正在修改的文件、被引用的函数签名),对于"仅供参考"的代码块,只保留文件路径和行号引用。

7.3.2 摘要化:不可逆压缩——用LLM提炼精华

核心思想: 当紧凑化的收益递减时——即上下文中的"可折叠"信息已经被折叠到极限,剩下的"不可折叠但也不完全必要"的信息需要被更激进地处理——此时摘要化登场。它不像紧凑化那样保留恢复能力,但可以更大幅度地减少token。

摘要化的两个黄金原则:

原则一:只在紧凑化之后使用。 如果一个信息可以通过紧凑化的方式保留引用,就不要生成摘要。摘要化是"最后的手段",而非"首选方案"。因为摘要一旦生成,原始信息的内容就永久丢失了(除非另存了完整记录,但那又回到了紧凑化的思路)。

原则二:摘要的粒度和焦点由"未来需求"决定。 生成摘要时不要问"这段对话的核心是什么?"而要问"未来Agent需要从这段对话中得到什么信息?"——这两个问题产生的摘要有本质区别。前者生成的是叙事性摘要("用户和Agent讨论了数据库优化"),后者生成的是行动导向的摘要("用户决定采用连接池方案,Pool Size=20,Timeout=30s,需要在下一次部署时配置")。

摘要化的三种实用策略:

策略一:滚动摘要(Rolling Summaries)。

这是Anthropic推荐并在Claude Code中验证过的方案。核心做法是:保留最近N轮交互的完整内容(精确的消息历史),对N轮之前的所有内容生成一个"滚动摘要"——一个不断更新、不断压缩的叙事性摘要,每当有新内容需要合入时重新生成。

# 滚动摘要的更新逻辑
async def update_rolling_summary(
    existing_summary: str,
    new_messages: list[dict],
    max_summary_tokens: int = 1000
) -> str:
    """用新消息更新滚动摘要"""
    
    if not existing_summary:
        return await generate_summary(new_messages)
    
    updated = await llm.generate(f"""
    你正在维护一个Agent会话的滚动摘要。
    下面是当前的摘要和新的消息。
    
    当前摘要:
    {existing_summary}
    
    新消息:
    {format_messages(new_messages)}
    
    请生成一个更新后的摘要,重点保留:
    1. 已做出的关键决策和决策理由
    2. 已完成的任务和结果
    3. 未解决的bug和待办事项
    4. 需要跨会话保留的上下文(路径、配置、状态等)
    5. 重要的技术发现或经验教训
    
    摘要应控制在 {max_summary_tokens} tokens 以内。
    如果现有摘要中的某些信息已经过时(被新信息替代),可以删除。
    """)
    return updated.strip()

滚动摘要的关键参数是"N"——保留多少轮完整历史。N越大,Agent能看到越详细的近期对话,但token消耗也越大。2026年的经验值是N=10至20,取决于任务的复杂度和每轮对话的平均长度。

策略二:聚焦摘要(Focused Summaries)。

与滚动摘要的"全量压缩"不同,聚焦摘要只关注特定信息类型。在Anthropic的实践中,这意味着生成不同类型的摘要来服务不同的检索需求:

  • 决策摘要:只提取决策和决策理由
  • 状态摘要:只记录任务的当前状态(进度、阻塞、依赖)
  • 知识摘要:只提取新的技术发现和经验教训
  • 错误摘要:只记录遇到的错误和修复方案

每种聚焦摘要都是轻量的(200-500 tokens),这让Agent在后续会话中能够精准检索——如果Agent想知道"上次那个数据库连接错误是怎么修的",它不需要在滚动摘要中大海捞针,直接检索错误摘要即可。

策略三:条件触发摘要(Conditional Summarization)。

不是每个对话都需要被摘要化。条件触发摘要的核心思想是:只在上下文接近容量阈值时才触发摘要,平时尽可能保留完整的对话历史。

class ConditionalSummarizer:
    def __init__(self, trigger_threshold: int = 0.85):
        """
        trigger_threshold: 上下文窗口使用率,超过此值触发压缩
        例如 0.85 表示使用率达到 85% 时触发
        """
        self.threshold = trigger_threshold
        self.keep_recent = 20  # 保留最近20条消息不变
    
    def should_summarize(self, current_tokens: int, 
                         max_tokens: int) -> bool:
        return current_tokens / max_tokens > self.threshold
    
    async def summarize_if_needed(self, messages: list, 
                                   model_context_limit: int):
        current_tokens = count_tokens(messages)
        
        if not self.should_summarize(current_tokens, model_context_limit):
            return messages  # 不需要摘要,原样返回
        
        # 保留最近N条消息不动
        recent = messages[-self.keep_recent:]
        older = messages[:-self.keep_recent]
        
        # 将更早的消息压缩为摘要
        summary = await self.generate_summary(older)
        
        # 返回:摘要 + 最近消息
        return [
            {"role": "system", "content": f"[历史摘要]\n{summary}"}
        ] + recent

这是LangChain的SummarizationMiddleware采用的设计模式——以GPT-4o Mini作为摘要模型(而非完整版GPT-4o),在上下文超过4000 tokens时触发,保留最近20条消息不变。

7.3.3 紧凑化与摘要化的互补决策树

需要从上下文中移除信息?
    │
    ├── 这些信息"可能"在未来需要精确还原?
    │       └── 是 → 紧凑化:保留引用,可逆
    │
    ├── 这些信息"几乎不可能"在未来需要精确还原?
    │       └── 是 → 摘要化:生成摘要,不可逆
    │
    ├── 不太确定?
    │       └── 紧凑化优先 → 如果紧凑化后仍然超过容量 → 摘要化
    │
    └── 关键变量:
            ├── 信息的"可重建性":原始信息是否可以通过工具/文件重新获取?
            │       高可重建性 → 摘要化更合适(丢了也可以找回来)
            │       低可重建性 → 紧凑化更合适(必须保留引用)
            │
            └── 信息的"可能复用频率":
                    高频复用 → 紧凑化(经常需要展开)
                    低频复用 → 摘要化(很少需要,摘要就够了)

7.4 上下文压缩的最佳实践

理论清楚之后,实践就是关键。本节聚焦三个在2026年产线中被反复验证的上下文压缩技术:Anthropic的Tool Result Clearing、上下文卸载、和滚动摘要的工程实践。

7.4.1 Anthropic的Tool Result Clearing:最安全、最轻量的压缩

在Anthropic的上下文工程实践体系中,Tool Result Clearing被明确标注为"最安全、最轻量的Compaction形式"。它的核心逻辑非常直觉化:一旦工具在消息历史的深处被调用过,且Agent已经消费(读取并推理过)了该工具的结果,为什么还需要在上下文中保留原始的工具返回数据?

工作机制:

消息历史(压缩前,~150K tokens):
┌─────────────────────────────────────────┐
│ [System] 你是一个代码审查Agent            │
│ [User] 审查我的项目                       │
│ [Assistant] => 调用 tool: list_files()   │
│ [Tool Result] src/auth/...              │  ← 7K tokens
│ [Tool Result] src/api/...               │  ← 12K tokens
│ [Assistant] 我发现项目包含...             │
│ [Assistant] => 调用 tool: read_file()   │
│ [Tool Result] 完整文件内容...             │  ← 35K tokens  ← 已被Agent消费
│ [Assistant] 这个文件存在以下问题...        │
│ ...                                     │
└─────────────────────────────────────────┘

消息历史(压缩后,~95K tokens):
┌─────────────────────────────────────────┐
│ [System] 你是一个代码审查Agent            │
│ [User] 审查我的项目                       │
│ [Assistant] => 调用 tool: list_files()   │
│ [Tool Result] src/auth/...              │  ← 保留:最近使用
│ [Tool Result] src/api/...               │  ← 保留:最近使用
│ [Assistant] 我发现项目包含...             │
│ [Assistant] => 调用 tool: read_file()   │
│ [Tool Result] [已清除,Agent已消费]       │  ← 移除
│ [Assistant] 这个文件存在以下问题...        │
│ ...                                     │
└─────────────────────────────────────────┘

为什么这个策略是"安全的"? 因为Agent已经将工具结果的"信息"内化到了自己的推理中——它的分析文本已经包含了它对工具结果的理解和提炼。保留原始的工具返回数据提供的增量价值非常有限,但消耗的token成本非常大。

适用范围:

  • 适用:一次性查询的结果(如数据库查询、文件读取、API调用)
  • 部分适用:需要反复参考的结果(如配置文件、参考文档)——这种情况下应该用紧凑化而非直接清除
  • 不适用:用户明确要求"保留完整信息"的场景、需要在后续进行精确引用或验证的场景

实现要点:

class ToolResultManager:
    """管理工具结果的自动清除"""
    
    def __init__(self, max_recent_results: int = 3):
        self.max_recent = max_recent_results
    
    def clean_tool_results(self, messages: list) -> list:
        """清除已经被Agent消费过的旧工具结果"""
        cleaned = []
        tool_result_count = 0
        recent_boundary = self._find_recent_boundary(messages)
        
        for i, msg in enumerate(messages):
            if msg["role"] == "tool":
                # 判断该工具结果是否在"最近N个"范围内
                tool_result_count += 1
                if i > recent_boundary and tool_result_count > self.max_recent:
                    continue  # 清除旧的结果
            cleaned.append(msg)
        
        return cleaned
    
    def _find_recent_boundary(self, messages: list) -> int:
        """找到'最近'的边界——最近一次用户消息之后"""
        for i in range(len(messages) - 1, -1, -1):
            if messages[i]["role"] == "user":
                return i
        return max(0, len(messages) - 20)

7.4.2 上下文卸载:文件系统是"廉价硬盘"

核心思想: 如果说上下文窗口是Agent的"RAM"(昂贵、快速、容量有限),那么文件系统就是Agent的"硬盘"(廉价、相对慢速、容量巨大)。上下文卸载的核心思想是将"不需要在RAM中的信息"——完整的文件内容、长对话历史、未消费的工具结果——写到文件系统中,在上下文中只保留路径引用和简短摘要。

Anthropic的"think工具"——卸载的典范。

Anthropic在2025年底推出的Claude Think Tool是上下文卸载的代表性实践。它为模型提供了一个独立的空间(名为think的工具),让模型在推理过程中将"草稿"、"笔记"、"中间推理"写入这个空间,而不是堆积在主上下文窗口中。

关键设计:

  • think工具的写入不污染主对话流——它存储在侧通道中,Agent可以在后续随时读取
  • 它模拟的是人类在解决问题时的"草稿纸"——写下思路、列清单、做计算,但这些草稿不会出现在最终的报告里
  • Anthropic的研究表明,think工具配合领域提示词可以在专业Agent基准测试中实现高达54%的性能提升

文件系统卸载的工程模式:

# 上下文卸载的简化实现
class ContextOffloader:
    """将上下文信息卸载到文件系统"""
    
    def __init__(self, workspace_dir: str):
        self.workspace = workspace_dir
    
    async def offload_conversation_history(self, 
                                            old_messages: list,
                                            session_id: str) -> dict:
        """将旧对话历史卸载到文件,返回引用"""
        
        # 生成摘要(不可逆压缩)
        summary = await self._summarize(old_messages)
        
        # 将完整历史保存到文件(可逆——需要时可以展开)
        history_file = f"{self.workspace}/.context/{session_id}/history.json"
        os.makedirs(os.path.dirname(history_file), exist_ok=True)
        with open(history_file, 'w') as f:
            json.dump(old_messages, f, ensure_ascii=False, indent=2)
        
        # 返回仅包含摘要和文件引用的上下文片段(~300 tokens)
        return {
            "summary": summary,
            "full_history_ref": history_file,
            "message_count": len(old_messages),
            "date_range": f"{old_messages[0]['timestamp']} - {old_messages[-1]['timestamp']}"
        }
    
    async def offload_large_file_content(self, 
                                          file_path: str) -> dict:
        """将大文件的完整内容卸载,只保留元数据和摘要"""
        size = os.path.getsize(file_path)
        
        if size < 50_000:  # 50KB以下,不太大,保留在上下文
            return {"content": read_file(file_path), "path": file_path}
        
        # 50KB以上,卸载到文件系统
        summary = await self._summarize_file(file_path)
        
        return {
            "path": file_path,
            "size": format_size(size),
            "summary": summary,
            "note": "[文件较大,内容已卸载。可使用 read_file 工具展开完整内容]"
        }

上下文卸载的设计原则:

  1. 引用必须精确可追踪:被卸载的信息必须有明确的文件路径或引用ID,Agent随时可以恢复
  2. 摘要必须足以为决策提供依据:Agent在只看摘要的情况下,应该能够判断是否需要展开完整信息
  3. 卸载 ≠ 丢弃:卸载的信息必须持久化存储,不能是临时的
  4. 卸载的触发应该是自动的:不应该由用户手动触发,"上下文接近溢出"就是触发信号

7.4.3 滚动摘要:保留最近N轮,更早的压缩为摘要

滚动摘要是7.3.2节中讨论的"摘要化"策略的具体实现形式,但它本身已经发展出了一套完整的工程最佳实践。

滚动摘要的黄金参数配置:

参数推荐值理由
保留完整历史的最近N轮10-20轮太少的轮数导致Agent"健忘",太多浪费token
摘要的最大token数500-1000 tokens摘要是"辅助记忆",不是"百科全书"
摘要更新频率每增加5-10轮新消息后不需要每轮都更新摘要,批量更新更高效
摘要模型使用更小/更便宜的模型GPT-4o Mini、Claude Sonnet、Qwen 7B等

滚动摘要的内容优先级:

不是所有信息都应该进入滚动摘要。以下是经过验证的优先级排序:

  1. 最高优先级:决策记录。任何"做了决定"的信息——选择了什么方案、为什么、有什么约束。
  2. 高优先级:未完成事项。任何"还没做完"、"需要follow up"的信息。
  3. 中优先级:关键发现。技术洞察、错误模式、被验证有效的解决方案。
  4. 低优先级:上下文背景。项目结构、当前状态、环境配置。
  5. 不保留:可重建信息。可以通过重新执行工具或查询获得的信息——这些信息的摘要价值极低。

7.5 KV缓存优化

前面的三节讨论的都是"上下文内"的优化——如何让上下文更小、更精、更有信号。但上下文的成本不仅取决于"多少token被发送给模型",还取决于"有多少token被重复计算"。KV缓存优化要解决的就是这个"重复计算"的问题——让模型在处理"基本相同的前缀"时,不需要每次从头计算注意力。

7.5.1 缓存命中率:优化Agent成本和延迟的头号指标

Manus团队的一位工程师在一次技术分享中直言:"Cache hit rate is the single most important metric for optimizing agent costs and latency."(缓存命中率是优化Agent成本和延迟的最重要指标,没有之一。)

这句话不夸张。来看一个真实的成本分析:

场景:一个Agent会话,System Prompt + 工具定义 = 10K tokens

无缓存:
  每次推理都从第零个token开始计算注意力 → 每次推理都需要处理这10K + 新增的N个token
  10K tokens × $3.00/MTok (Claude Sonnet 4 输入价格) = $0.03/次
  1000次推理/天 × 30天 = $900/月(仅前缀部分)

有缓存(92%命中率,Claude Code的真实数据):
  10K tokens × 92% 命中 × $0.30/MTok (缓存读取价格) 
  + 10K tokens × 8% 未命中 × $3.00/MTok 
  + 写入成本(首次建立缓存)
  = 约 $170/月(仅前缀部分)

节省:$900 → $170 = 81% 成本降低

这是Claude Code的真实数据:92%缓存命中率 → 81%成本节省。这不仅仅是省钱——更低的延迟(跳过10K token的prefill计算)带来了更好的用户体验。

缓存命中率的工程定义:

缓存命中率 = cache_read_input_tokens / (cache_read_input_tokens + cache_creation_input_tokens)

如果这个值低于70%,说明缓存策略存在可修复的问题——可能是前缀不够稳定、可能是工具定义在频繁变化、可能是模型切换导致了缓存失效。

7.5.2 稳定前缀策略:不要在System Prompt开头放时间戳

KV缓存的匹配机制是逐字节的精确哈希匹配。这意味着:缓存只对"从第一个token开始,一直到某个断点为止,完全逐字节相同的输入"生效。任何一点差异——哪怕是一个空格——都会导致缓存失效。

最常见的缓存杀手:时间戳。

# ❌ 缓存杀手——每次推理的前缀都不同
system_prompt = f"""
当前时间:{datetime.now().isoformat()}
你是一个代码助手...
"""

# ✅ 缓存友好——时间戳放在结尾或使用相对时间
system_prompt = """
你是一个代码助手...
"""
# 动态时间信息追加在消息列表末尾,而不是前缀
messages = [
    {"role": "system", "content": system_prompt},  # ← 永远不变 → 缓存命中
    {"role": "user", "content": f"当前时间是 {datetime.now().isoformat()}。请帮我..."}
]

稳定前缀的工程原则:

  1. System Prompt固定不变。角色定义、行为规则、工具描述——这些是整个会话生命周期甚至跨会话都不变的内容,是缓存的"锚点"。
  2. 用户ID、会话ID、时间戳等动态信息不要出现在前缀中。如果必须包含这类信息,放在第一条用户消息(动态后缀)中。
  3. 工具定义(JSON schema)保持稳定。序列化时开启sort_keys=True确保键顺序一致。
  4. 跨会话复用同一个System Prompt。如果每次新会话都修改System Prompt的开头几个词,缓存就会完全失效。

7.5.3 追加式修改策略:只追加不修改已有消息

KV缓存的另一个关键特性是:可以增量延续。如果前10K token已经缓存了,而你在末尾追加了1K token,那么缓存的10K可以继续命中,只有新增的1K需要重新计算。

这意味着:在Agent的多轮交互中,绝对不要修改已经发送过的消息。

# ❌ 破坏缓存——修改历史消息
messages[3]["content"] = "更新后的内容"  # 缓存从第3条消息开始失效

# ✅ 保护缓存——只在末尾追加
messages.append({"role": "assistant", "content": response})
messages.append({"role": "user", "content": new_query})

Append-only不仅保护了缓存,也维护了对话历史的完整性——Agent可以回溯完整的推理链,不会因为历史被修改而产生上下文冲突(7.2.4节)。

7.5.4 确定性序列化:JSON key顺序固定,保护缓存命中

即使两条消息的语义完全相同,如果它们在序列化成文本后的字节表示不同,缓存仍然不会命中。这在工具定义的JSON序列化中尤其常见——Python的json.dumps()在不同调用中可能产生不同的键顺序。

修复方案:

import json

# ❌ 不确定的序列化——键顺序随机,缓存可能不命中
tool_schema_str = json.dumps(tool_schema)

# ✅ 确定性的序列化——键顺序固定,缓存稳定命中
tool_schema_str = json.dumps(tool_schema, sort_keys=True, ensure_ascii=False)

三条硬约束:

  1. JSON序列化必须开启sort_keys=True
  2. 不在会话中途修改工具定义(加一个参数→整个缓存前缀失效)
  3. 不切换模型版本(缓存是模型绑定的——Claude Sonnet 4和Claude Opus 4的KV缓存不能共享)

7.5.5 "Mask Don't Remove":工具定义保持稳定,通过API层限制调用

这是上下文混淆(7.2.3节)与缓存优化之间的交汇点。我们面临一对矛盾:

  • 为了避免上下文混淆,应该只给模型提供当前任务相关的工具定义(减少噪音)
  • 为了保护KV缓存,工具定义必须在整个会话中保持不变(稳定前缀)

"Mask Don't Remove"就是解决这个矛盾的方案:

# "Mask Don't Remove" 模式
ALL_TOOLS = [
    tool_search, tool_read, tool_write, tool_execute,
    tool_database, tool_api_call, tool_email, ...
]  # 所有工具定义 — 永不改变,缓存锚点

# 每次推理时,通过 API 参数限制可用工具,但不修改工具列表本身
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    system=SYSTEM_PROMPT,           # 不变 → 缓存命中
    tools=ALL_TOOLS,                # 不变 → 缓存命中
    tool_choice={"type": "auto"},   # 自动选择
    # 通过 allowed_tools 做硬约束(API层面),不改变 tools 定义
    # 注:Claude API 目前使用 tool_choice 的 "any" + "tool" 子类型限制
    #     或通过 system prompt 软约束;OpenAI 有原生 allowed_tools 参数
    messages=messages,              # append-only → 增量缓存
)

在OpenAI的实现中,allowed_tools是一个原生参数——工具定义保持不变(缓存命中),但模型在推理时被API层面硬性禁止调用不在allowed_tools列表中的工具:

# OpenAI 的 "Mask Don't Remove" 实现
response = client.responses.create(
    model="gpt-4.1",
    tools=ALL_TOOLS,                          # 不变 → 缓存命中
    allowed_tools=["search", "read"],         # 硬约束:只能用这两个
    input=messages,
)

三层约束强度对比:

约束方式强度缓存影响适用场景
Prompt软约束("请只使用A和B工具")弱无简单任务,容错率高
allowed_tools参数硬约束无OpenAI等商业API
物理移除工具定义硬约束高不推荐——除非不考虑缓存

7.5.6 OpenAI Prompt Caching与Anthropic Prompt Caching对比

核心差异一览:

特性OpenAIAnthropic (Claude)
触发方式全自动,零配置自动 + 显式断点(最多4个)
写入成本免费1.25x 基础价格(5min TTL)
读取折扣50%-90%(按模型)90% 折扣(0.1x基础价格)
默认TTL5-10分钟5分钟(每次命中刷新)
最小缓存长度1024 tokens1024 tokens
缓存粒度128 tokens/单元Block级别
断点控制无(全自动)最多4个显式断点

OpenAI的优势:零配置、零写入成本。 开发者不需要做任何额外工作,也不需要关心缓存策略——API层面自动处理。对于小型Agent和快速原型开发,这种"无感知缓存"体验非常好。而且OpenAI还提供了prompt_cache_key参数——允许开发者手动指定缓存的"分组键",将同一端点的不同场景(如不同租户使用同一个System Prompt)导向同一个缓存空间,提升命中率。在OpenAI的公开案例中,prompt_cache_key可以将缓存命中率从60%提升到87%。

Anthropic的优势:细粒度控制、更高的读取折扣。 虽然Anthropic对缓存写入收费(1.25x基础价格),但读取折扣更高(90% vs OpenAI的50%-90%)。更重要的是,Anthropic允许开发者设置最多4个显式缓存断点——可以精确指定"缓存前N条消息""缓存System Prompt"等。对于高频使用和长对话场景,这种精确控制可以显著提升缓存命中率。

Anthropic的"system_and_3"黄金策略:

这是社区总结的最优断点配置:

messages = [
    # 断点1:System Prompt + 全部工具定义 ← 跨会话稳定
    {"role": "system", "content": SYSTEM_PROMPT},
    
    # ... 对话历史 ...
    
    # 断点2:倒数第3条非system消息
    {"role": "user", "content": "..."},
    # 断点3:倒数第2条
    {"role": "assistant", "content": "..."},
    # 断点4:最后一条
    {"role": "user", "content": "..."},
]

# 设置 cache_control 断点
messages[-3]["cache_control"] = {"type": "ephemeral"}
messages[-2]["cache_control"] = {"type": "ephemeral"}
messages[-1]["cache_control"] = {"type": "ephemeral"}

关键陷阱:

使用Anthropic Cache时有一个非常容易中招的坑:当使用OpenAI兼容端点(/v1/chat/completions)访问Claude模型时,cache_control参数会被静默丢弃——不报错、不警告、缓存不生效。必须使用Anthropic原生端点(/v1/messages)才能激活显式缓存控制。


7.6 动态上下文窗口:根据任务复杂度自动调整上下文大小

前面五节讨论的都是"被动"优化——在上下文已经形成之后,通过压缩、卸载、缓存来降低成本和延迟。但最理想的策略是在上下文"形成之前"就做出判断:这个任务需要多少上下文? 动态上下文窗口回答的就是这个问题。

7.6.1 问题的重新定义

传统思维:上下文窗口 = 模型的最大支持长度。有多少就用多少。

现代思维:上下文窗口 = 当前任务恰好需要的长度。多一分浪费,少一分不够。

这是一个从"固定分配"到"按需调度"的范式转变。Karpathy的经典类比在这里再次适用:LLM像CPU,上下文窗口像RAM,上下文工程就是决定哪些信息放入RAM的"调度器"。动态上下文窗口就是调度器的"内存管理单元"——不是给每个进程分配相同的内存,而是根据进程的需求动态分配。

7.6.2 动态上下文窗口的三种实现模式

模式一:渐进披露(Progressive Disclosure)。

这是7.5.5节"Mask Don't Remove"策略的逻辑延伸——不仅是工具定义可以"按需披露",上下文中的所有信息都可以分层加载。

Layer 1: 发现层(Discovery)—— ~200 tokens
├── 轻量级元数据
├── 知识库索引(可用的文档/数据源列表)
└── 工具目录(名称 + 一句话描述)

Layer 2: 激活层(Activation)—— ~500 tokens
├── 被选中工具的完整指令和参数
└── 被选中数据源的上下文片段

Layer 3: 执行层(Execution)—— ~1,500 tokens
├── Few-shot 示例
├── 参考文档全文
└── 历史相关案例

Claude Code的Skills系统是渐进披露的典范:Skills以文件形式存在.claude/skills/目录中,Agent通过ls发现可用Skill列表(仅名称,~50 tokens),只有在任务需要时才通过cat读取完整指令(500-1500 tokens)。这意味着一个拥有50个Skills的Agent,它的上下文并不是50×1500=75000 tokens起步,而是从200 tokens起步,按需增长。

模式二:Manus的层次化行动空间。

Manus团队在设计Agent的工具系统时,提出了一个三层行动空间模型:

  • Layer 1:核心原子工具(~20个)。始终在上下文中。这些是"任何Agent任务都需要的"基本工具——文件操作、搜索、代码执行。数量固定且精简——约20个——确保不会触发上下文混淆(7.2.3节中提到的30个工具阈值)。
  • Layer 2:沙箱工具。 通过bash CLI暴露,不占用工具定义token。例如,数据库操作、网络请求、数据分析——这些通过一个统一的execute_command工具来间接调用,而非每个都有独立的工具定义。
  • Layer 3:复杂逻辑通过代码实现。 Agent不是调用一个"分析数据"的工具,而是自己编写Python脚本来分析数据,然后通过execute_command执行。这大幅减少了工具定义的数量,同时保持了Agent的能力灵活性。

这种设计的结果是:无论Agent支持多少功能,上下文的Token开销保持固定——不会因功能的扩展而线性膨胀。

模式三:基于任务复杂度的上下文预算分配。

一个更激进的方案是:在任务开始时,让系统评估任务的复杂度,然后预算分配上下文token。简单任务(查询天气、翻译句子)分配少量上下文预算;复杂任务(代码重构、多文档分析)分配更多预算。

class DynamicContextAllocator:
    """基于任务复杂度的上下文预算分配"""
    
    def __init__(self, model_limit: int = 200_000):
        self.model_limit = model_limit
        # 复杂度→预算映射
        self.budgets = {
            "simple": 20_000,     # 简单问答、翻译
            "moderate": 60_000,   # 代码审查、单文档分析
            "complex": 120_000,   # 多文档分析、代码重构
            "multi_step": 180_000 # 长周期多步Agent任务
        }
    
    async def allocate_budget(self, user_query: str) -> int:
        """根据用户查询评估任务复杂度并分配预算"""
        complexity = await self._assess_complexity(user_query)
        return self.budgets.get(complexity, self.model_limit)
    
    async def _assess_complexity(self, query: str) -> str:
        """快速评估任务复杂度"""
        result = await self.fast_llm.generate(f"""
        评估以下任务的复杂度,只输出一个词:simple、moderate、complex 或 multi_step。
        
        评估维度:
        - simple: 单步骤、不需要外部知识、不需要工具调用
        - moderate: 需要少量检索或单工具调用
        - complex: 需要多步推理、多工具调用、或多文档分析
        - multi_step: 长周期任务,可能需要多次迭代和中间状态保存
        
        用户查询:{query}
        """)
        return result.strip().lower()

模式四:LangChain的SummarizationMiddleware(自适应压缩)。

LangChain在2025年推出的SummarizationMiddleware是一个"监测→触发→压缩"的自动化循环。它在每次LLM调用后检查当前token数,如果超过阈值,自动对历史消息进行摘要压缩:

from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    model="gpt-4.1",
    tools=[...],
    middleware=[
        SummarizationMiddleware(
            model="gpt-4.1-mini",       # 小模型做摘要,省钱
            trigger={"tokens": 4000},    # 超过4000 tokens触发
            keep={"messages": 20},       # 保留最近20条消息
        ),
    ],
)

这种模式本质上是7.4.3节滚动摘要的自动化版本——不需要开发者手动管理压缩时机,中间件自动监测和处理。但代价是增加了一次小模型调用(摘要生成)的延迟和成本。

7.6.3 动态上下文窗口的设计原则

原则一:按需增长,而非预先填满。 上下文的初始状态应该是最精简的——只包含系统指令和最基本的信息。Agent在推理过程中"发现"自己需要更多信息时,主动检索和加载。这不是"懒加载",而是"智能加载"——由Agent的能力驱动,而非由开发者的预设驱动。

原则二:预算意识是Agent的核心能力。 一个好的上下文工程系统应该让Agent对自己的"注意力预算"有清晰的认识。就像人类知道"我现在脑子有点乱,需要整理一下思路",Agent应该能够感知到"上下文已经很长了,信号开始衰减,我需要压缩"。

原则三:动态窗口不等于"无限弹性"。 动态分配不意味着可以无限增长。对于多步Agent任务,动态窗口策略必须与滚动摘要或上下文卸载(7.4节)配合使用——当窗口增长到一定阈值后,不是继续"动态"增长,而是"压缩"、"卸载"、"重置"。


本章小结

本章从"为什么必须压缩"开始,系统性地构建了上下文压缩与优化的完整工程方法论。

7.1节 给出了压缩的三重动机:成本(输入token的线性费用)、延迟(Transformer O(n²)注意力带来的prefill非线性惩罚)和上下文腐烂(ChromaDB对18个SOTA模型的194,480次调用实验给出的定量判决——所有模型的准确率都随上下文增长而单调衰减,低相似度场景下衰减幅度可达40-50%)。

7.2节 详细阐述了Drew Breunig归纳的五大上下文失效模式——上下文中毒(错误信息像谣言一样传播)、上下文分心(过长的上下文让模型丧失全局判断力)、上下文混淆(无关信息导致工具调用错误)、上下文冲突(新旧矛盾导致推理脱轨)和性能与成本的隐性衰减——为上下文问题提供了诊断框架和六条解决方案。

7.3节 建立了2026年两种主流压缩范式——紧凑化(可逆压缩,保留引用,零信息丢失)和摘要化(不可逆压缩,用LLM提炼精华)——并给出了互补决策树:高可重建性信息优先摘要化,低可重建性信息优先紧凑化。

7.4节 聚焦三大工程实践——Anthropic的Tool Result Clearing(最安全最轻量的压缩)、上下文卸载(文件系统是Agent的"硬盘")和滚动摘要(保留最近N轮完整,更早的压缩为摘要)。

7.5节 深入KV缓存优化的完整体系——以缓存命中率为"头号指标"(Claude Code 92%命中率→81%成本节省),围绕稳定前缀、追加式修改、确定性序列化和"Mask Don't Remove"四条原则构建缓存友好型上下文架构,并详细对比了OpenAI(零配置、零写入成本)和Anthropic(90%读取折扣、显式断点控制)的Prompt Caching方案。

7.6节 探讨了动态上下文窗口——从"固定分配"到"按需调度"的范式转变,介绍了渐进披露、Manus层次化行动空间、基于任务复杂度的预算分配和LangChain SummarizationMiddleware四种实现模式。

上下文压缩与优化是上下文工程中与信噪比最直接相关的技术领域——如果说采集(第4章)决定了上下文的"原料"质量、检索(第5章)决定了上下文的"广度"、记忆(第6章)决定了上下文的"深度",那么压缩与优化决定了上下文的"纯度"。在下一章,我们将转向另一个维度——当上下文需要与外部世界交互时,工具调用如何扩展上下文的边界,又能避免让边界本身变成漏洞。