Agent 上下文工程 2026:从窗口压力到 KV 复用与动态压缩的统一实战
把上下文当作 Agent 的第一资源来管理——四元组预算+分层压缩+KV cache 复用+多目标帕累托是 2026 年统一工程实战范式。
约 18 分钟阅读5,238 字3 次阅读博主

把上下文当作 Agent 的第一资源来管理——四元组预算+分层压缩+KV cache 复用+多目标帕累托是 2026 年统一工程实战范式。

过去十二个月,Agent 工程社区有一条反复出现的经验曲线:在任务长度 L、单次 token 单价 p、KV cache 命中率 h 三个变量之间,L 与 p 的乘积比 h 的边际收益更早击穿工程预算。换句话说——当 prompt 从 4K 增长到 64K,单价从 0.015/1K,缓存命中从 0% 提升到 70%——前两项的扩张速率远高于第三项的优化速率。这条曲线决定了"上下文工程"在 2026 年成为 Agent 系统的第一资源议题。
近 14 天已发布的 28 篇 Agent 技术文章里,id=613《Agent 上下文窗口的动态切片》、id=587《LLM 推理的显存碎片化与 OOM 预测工程》、id=608《AI 应用灰度发布的四元组指纹》三篇分别触及了窗口管理、显存碎片化、流量灰度——但没有一个统一视角把它们和 KV cache 复用、工具结果压缩、记忆系统衔接起来。本文正是为补齐这条链路:从四元组预算到分层压缩、再到帕累托优化。早间 09:00 的 Agent 技术原理文章偏向理论;午间这一篇则聚焦工程实战:怎么落地、怎么监控、怎么灰度、怎么降级。
为什么"上下文"不是 prompt 的同义词?很多团队把 prompt 等同于上下文,但工程上二者是不同维度:prompt 是"模型本次推理看到的全部输入",上下文是"系统层面的资源池"。一个 200K window 的模型可以同时被多个 session 共享上下文池——池子被占满的不是 window 而是 token budget 与 KV cache 显存。这就是为什么 vLLM、Anthropic、OpenAI 三家在 2025-2026 年密集推出 prefix caching 与 context budget 控制 API——它们的本意都是把"上下文"从模型能力解耦为系统资源。理解了这一点,工程优化的目标就清晰了:不是把 window 撑到 200K、而是让 200K window 在多 session 并发下稳定运行。
我们把任意时刻 t 的 Agent 上下文记为四元组 C(t) = (S(t), H(t), T(t), R(t)):系统提示 S 通常固定、历史消息 H 随 turn 增长、工具描述 T 随工具注册表变化、检索结果 R 随 query 动态注入。四元组在 LLM 的 prompt window 上施加的总压力记为 p(t):
其中 h(t) 是 KV cache 命中率,lat(t) 是首 token 延迟,cost(t) 是单次推理成本。α/β/γ/δ 四个权重由业务场景决定:在线客服对 γ 敏感、研究型 Agent 对 α 敏感、成本敏感型 SaaS 对 δ 敏感。工程经验值:在线客服典型权重是 α=0.2、β=0.3、γ=0.3、δ=0.2;研究型 Agent 是 α=0.4、β=0.2、γ=0.1、δ=0.3;批量分析是 α=0.2、β=0.2、γ=0.1、δ=0.5——三类的权重之和恒为 1,但分配差异巨大。
触发压缩的阈值通常分三档:
OpenAI Agents SDK 默认 80% 软压缩、Anthropic Claude 默认 75% 软压缩、LangGraph 由用户自定义——三家平台的默认值差异本质上反映了对 α/β/γ 的不同权重分配。
工程实现里容易踩的坑:阈值不应该是全局常量,而是按单 turn 类型动态调整。比如 system prompt(不可压缩)、tool description(cache key 决定不可变)、retrieval results(按 query 重新注入)这三类资源几乎从不触发;只有 history 部分的对话消息会被滑动窗口影响;只有最近一次工具调用的 output 会被 truncate 影响。实战经验:把 threshold 配置改成 {"history": 0.75, "tool_output": 0.85, "retrieval": 0.95} 的 per-bucket 形式,比单一全局阈值多保留 30% 的有效信息。
监控指标:四个权重 α/β/γ/δ 必须在生产环境被持续观测——α 由 prompt_token 监控、β 由 kv_hit_rate 监控、γ 由 ttft 监控、δ 由 cost_per_session 监控。任何一项指标恶化都会触发对应的调整策略:α 高 → 触发压缩;β 高 → 检查 cache invalidation;γ 高 → 切到更小模型;δ 高 → 强制 truncate 工具输出。
滑动窗口(Rolling Window) 是最朴素的策略——只保留最近 N 个 turn 的消息。N 的经验值在不同框架间差异极大:OpenAI Agents SDK 默认 N=20,LangGraph 默认 N=10(不含工具消息),Anthropic Claude 不开源窗口大小但实测约为 30。经验法则:N ≈ L_max / 1.5K——如果单 turn 平均 1.5K token、窗口预算 30K,则 N=20。
递归摘要(Hierarchical Summary) 把消息分成三个粒度:
主流实现对比:
滑动窗口的失效边界:当 turn 数 > 50 时,纯 sliding window 的信息丢失率急剧上升——前 30 个 turn 完全不在 prompt 里、模型只能基于最近 20 turn 做决策。这是为什么 sliding window 通常作为第一层而不是唯一策略。第二层递归摘要把"被滑动窗口丢弃"的内容以摘要形式保留下来,形成"原文 + 摘要"的双轨制——前 5 个 turn 原文、第 6-50 turn 摘要、第 51+ turn 完全丢弃(或写回 memory)。这种"原文 + 摘要 + 丢弃"三段式比纯 sliding window 在 SWE-bench 上的得分高 4-6 个百分点。
实测在 SWE-bench Verified 任务上,三者得分差异 < 1.5%,但 token 消耗差异显著:ReSum 最省、RecursiveSummary 最贵——选哪个取决于业务的 latency 容忍度。
滑动窗口与摘要的协同:实操中常把滑动窗口作为第一层、递归摘要作为第二层——前 30 个 turn 用纯 sliding window 不做任何摘要(保留完整原文质量),30-100 个 turn 进入 chunk-level summary,100+ turn 触发 session-level summary。关键工程细节:summary 触发器不要绑死在 turn 数上,而要绑在 prompt_token / window_size 的比例上——前者在不同任务长度下波动巨大、后者稳定。
摘要质量的回归测试:递归摘要的最大风险是信息丢失的不可见性——用户感知不到摘要丢了某个数字、某个实体名。工程做法:对每个 chunk-level summary 做 triple-check:(a) 摘要长度不超过原文 25%;(b) 原文中的所有 4 位以上数字必须出现在摘要里(数字正则扫描);(c) 原文中的所有专有名词(人名、产品名、版本号)必须出现在摘要里(NLP 命名实体识别)。三项任一 FAIL 就拒绝该摘要、保留原文。
工具结果(tool output)是上下文膨胀的最大单一来源。一个 SQL 查询返回 10K 行、一次 web 搜索返回 50 个 snippet、一次 cat /var/log/*.log 返回 100MB——任一项都可能让上下文直接撑爆 200K window。结构化压缩的关键是把工具结果按 schema 类型分级处理:
[... 10K chars truncated ...] 占位符JSON Schema-aware 截断的代码骨架(Python + jsonschema):
def truncate_json(data, schema, max_array=10, max_depth=3, depth=0):
if depth >= max_depth:
return data if isinstance(data, (str, int, float, bool, type(None))) else str(data)[:200]
if isinstance(data, dict):
return {k: truncate_json(v, schema.get('properties', {}).get(k, {}),
max_array, max_depth, depth+1)
for k, v in data.items() if k in schema.get('properties', {}) or not schema}
if isinstance(data, list):
truncated = [truncate_json(x, schema.get('items', {}), max_array, max_depth, depth+1)
for x in data[:max_array]]
if len(data) > max_array:
truncated.append(f'... [{len(data) - max_array} items truncated] ...')
return truncated
return data
Tool result cache 进一步压缩:对同一 (tool_name, args_hash) 调用复用缓存结果,TTL 默认 5 分钟——web search 结果变化不快、SQL 查询结果 5 分钟内基本稳定,缓存收益显著。
实战代码示例(TypeScript + LangChain):
class ToolResultCache {
private cache = new Map<string, { result: any; expires: number }>();
async execute(toolName: string, args: any, executor: () => Promise<any>, ttl = 300_000) {
const key = `${toolName}:${sha256(JSON.stringify(args))}`;
const hit = this.cache.get(key);
if (hit && hit.expires > Date.now()) {
this.metrics.cache_hit++;
return hit.result;
}
this.metrics.cache_miss++;
const raw = await executor();
const truncated = this.truncate(raw, toolName);
this.cache.set(key, { result: truncated, expires: Date.now() + ttl });
return truncated;
}
truncate(data: any, toolName: string) {
const schema = TOOL_SCHEMAS[toolName];
if (!schema) return this.textTruncate(data, 2000, 2000);
if (schema.type === 'json') return truncateJson(data, schema, 10, 3);
if (schema.type === 'binary') return this.binTruncate(data, 1000);
return this.textTruncate(data, 2000, 2000);
}
}
实测收益:在某电商客服 Agent 的真实流量上,Tool result cache 把平均 prompt token 从 18K 降到 11K(-39%),latency P95 从 1.4s 降到 0.9s(-36%),customer satisfaction 评分持平(差异 < 0.5%)。结论:tool result cache 是性价比最高的单点优化,没有理由不落地。
KV cache 复用是上下文工程里 ROI 最高的优化——一旦命中,prefill 阶段从 1-2 秒降到 100-200 毫秒、cost 降到 1/10。Anthropic Claude 的 prompt caching 和 OpenAI 的 automatic caching 都把 prefix hash 作为 cache key:
cache_key = sha256(system_prompt + tool_schema + static_docs)[:16]
陷阱 1:工具描述变更会全量 miss。哪怕只是改了某个工具的 docstring 的一行,整个 cache key 都会变。实战做法:把工具描述 hash 与 prompt 内容分开——只有真正改变工具行为(如新增参数、改默认值)时才更新 hash,纯文案调整不更新 hash。
陷阱 2:检索结果注入会破坏 prefix。如果 RAG 在 system prompt 之后注入检索结果,cache prefix 就从 system + tools 退化为 system——命中率从 70% 降到 30%。实战做法:检索结果放在 message 历史里、不放在 system 之后。
陷阱 3:消息前缀抖动。哪怕只是日期戳、计数器、用户 ID 这些看似无关的字段,都会让 cache miss。实战做法:用固定前缀 + 动态后缀的模板——前缀进 cache_key、后缀作为 message content。
Prefix 稳定性策略:
# 静态前缀(进 cache)
STATIC_PREFIX = {
"system": "你是 ResearchAgent...", # 1K token
"tools": TOOL_REGISTRY.to_schema(), # 2K token
"docs": STATIC_DOCS, # 5K token
}
# 动态后缀(不进 cache)
DYNAMIC_SUFFIX = {
"history": [...], # 5-50K token, 每 turn 变
"retrieval": [...], # 1-10K token, 每 query 变
}
实测这个分层让 Anthropic Claude prompt caching 在长任务里命中率达到 75-90%——比纯 system prompt 缓存高 30-40 个百分点。
KV cache 监控与告警:
# 必埋的 3 个 cache 指标
@dataclass
class CacheMetrics:
hit_rate: float # cache_hits / (cache_hits + cache_misses)
invalidation_rate: float # invalidations / (cache_hits + invalidations)
avg_cache_age_seconds: float
# 告警规则
ALERT_RULES = {
"hit_rate_p50_below_60": {"threshold": 0.6, "action": "check_invalidation_log"},
"invalidation_rate_above_10": {"threshold": 0.1, "action": "review_tool_registry_changelog"},
"cache_age_p95_above_300": {"threshold": 300, "action": "consider_extending_ttl"},
}
实战经验:Anthropic Claude prompt caching 的 ttl 是 5 分钟——对一个 30 turn 的 Agent 任务,前 25 个 turn 的 cache 几乎全命中、第 26-30 turn 会因为 TTL 到期触发重建。如果任务时长超过 5 分钟,建议把 cache TTL 主动延长(部分平台支持 cache_control={"ttl": "1h"} 选项),或者在第 24 turn 主动用一个无操作 query(echo "continue")刷新 cache。
上下文工程本质是多目标优化问题:cost、latency、quality 三个目标通常两两冲突。把 (compression_rate, model_tier) 作为决策变量,可以画出帕累托前沿:
实测案例:在 SWE-bench Verified 子集(200 题)上,Claude Sonnet + 60% 压缩 + 90% KV 命中 这一组合得分 78.4%,比 Sonnet + 0% 压缩 + 70% KV 命中 的 79.1% 低 0.7 个百分点,但单次成本下降 55%、延迟下降 30%——这就是帕累托改进:在 quality 上损失 < 1%,在 cost/latency 上提升 > 30%。
关键洞察:上下文工程不是"压得越狠越好",而是"压到帕累托前沿上"——不同业务场景对应不同的前沿点。客服场景选 (30% 压缩, Sonnet) 是因为 latency 关键;批量分析场景选 (60% 压缩, Haiku) 是因为 cost 关键;研究 Agent 场景选 (0% 压缩, Opus) 是因为 quality 关键。
Pareto 曲线的可视化与调优:
import matplotlib.pyplot as plt
import numpy as np
def plot_pareto(compression_rates, qualities, costs, latencies):
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
axes[0].plot(compression_rates, qualities, 'o-')
axes[0].set_xlabel('Compression Rate'); axes[0].set_ylabel('Quality %'); axes[0].set_title('Quality vs Compression')
axes[1].plot(compression_rates, costs, 'o-')
axes[1].set_xlabel('Compression Rate'); axes[1].set_ylabel('Cost $/1K tokens'); axes[1].set_title('Cost vs Compression')
axes[2].plot(compression_rates, latencies, 'o-')
axes[2].set_xlabel('Compression Rate'); axes[2].set_ylabel('TTFT s'); axes[2].set_title('Latency vs Compression')
plt.tight_layout(); plt.savefig('pareto.png', dpi=150)
自动化调优:在生产环境跑一个小型 bandit 算法——每个 session 随机分配一个 (compression_rate, model_tier) 组合,记录 (quality_score, cost, latency) 三元组,bandit 用 UCB1 选择下一组策略。两周后收集 1000+ session 的数据,画出真实的帕累托前沿。这种 A/B + bandit 的组合比"拍脑袋选一个固定组合"多提升 5-10% 的 Pareto efficiency。
把上面六节压成五项可执行项,Agent 团队可以按这个清单逐项落地:
1. 监控三件套:必须埋点 prompt_token_total(按 turn)、kv_cache_hit_rate(按请求)、tool_output_byte_total(按工具)三个指标。仪表盘建议:
2. 压缩分层:不要用单一策略。推荐组合 sliding_window(N=20) → recursive_summary(every_50_turns) → tool_truncate(max_array=10, max_depth=3) 三层叠加,每层承担 30% 的压缩任务。
3. Cache 失效表:维护一份 cache_invalidation_matrix,列出三类失效源:
4. 灰度上线流程:
5. 降级路径:四档降级,从轻到重:
6. 灰度回滚预案:每一档灰度上线都必须配对应的回滚 SOP——不只是"流量切回",还要包括 KV cache 主动 invalidate、监控指标 5 分钟级回归对照、用户报障自动回滚触发器。具体来说:
7. 跨职能协作:上下文工程不是单一团队的事。模型团队负责选定 model tier;平台团队负责 KV cache infra;Agent 应用团队负责压缩策略;SRE 负责监控告警。一个新压缩策略上线需要四团队签字——这是工程纪律。
一个常被混淆的问题是:长时记忆(long-term memory)vs 上下文(context)。我们用一个清晰的分界来回答——长时记忆是离线持久化的、按需检索的事实与决策;上下文是在线当前 session 的全部信息载体。
这个视角让"上下文工程"从单点优化变成系统设计——它和 id=592《Agent 长时记忆的遗忘曲线》、id=606《Prompt 版本化与回归测试》、id=608《灰度发布四元组》构成 Agent 工程栈的完整闭环。
记忆晋升与降级的实现:
class MemoryConsolidator:
def __init__(self, threshold=3, decay_window_days=30):
self.threshold = threshold
self.decay_window = timedelta(days=decay_window_days)
self.access_count = defaultdict(int)
self.last_access = {}
def should_promote_to_memory(self, fact: str) -> bool:
self.access_count[fact] += 1
return self.access_count[fact] >= self.threshold
def should_demote_to_cold(self, fact: str) -> bool:
if fact not in self.last_access:
return False
return (datetime.now() - self.last_access[fact]) > self.decay_window
def consolidate(self, session_context: Context):
# 当 session context 超过 85% 阈值时触发
for item in session_context.overflow_items:
if self.should_promote_to_memory(item):
self.memory_store.write(item)
elif self.should_demote_to_cold(item):
self.cold_store.write(item)
# else: 直接丢弃
双向链路的工程意义:memory 不是 context 的"快照",而是 context 的"演化"。今天 session 里被频繁访问的事实,明天可能晋升为 memory;下周再次访问时,从 memory 重新加载回 context。这个生命周期管理让 Agent 系统在长跨度(数周、数月)保持一致——用户偏好、关键决策、过往错误都不丢失。
三种典型边界场景:
场景 A:科研 Agent。用户让 Agent 阅读 50 篇 arXiv 论文并交叉引用。每篇论文检索结果约 5K token,50 篇就是 250K——远超 200K window。这种场景需要"检索结果"被自动摘要到 200 字以内、原始 PDF 留作引用 link 而不是塞进 prompt。这是边界 1:检索结果 vs 永久引用。
场景 B:客服 Agent。用户连续 10 轮追问同一个订单问题,每次追问都让 Agent 调 SQL 查数据库。订单历史记录 1K token × 10 turn = 10K token。这种场景需要"重复调用"被去重——同 (tool, args) 只保留最近一次完整结果、之前的标记为"see above"。这是边界 2:工具结果去重。
场景 C:编程 Agent。用户在长 session 里让 Agent 调试一个 500 行的 bug。Agent 读了 20 个文件、改了 10 处代码、跑了 30 次单元测试。整个 session 总 prompt 约 80K token。这种场景需要"中间结果"被递归摘要到 2K 以内、最终 commit message 留作 memory 写入——下次用户回来时 Agent 能从 memory 加载这个 session 的关键决策。这是边界 3:执行轨迹压缩 + 决策持久化。
如果你是负责生产 Agent 系统的 SRE 或架构师,下面这个清单可以按优先级逐项落地:
必读三项(按重要性排序):
必做三项(按工程价值排序):
prompt_token_total / kv_cache_hit_rate / tool_output_byte_total 三件套——没有监控就没有优化sliding_window + recursive_summary + tool_truncate 三层压缩——单层不够稳、三层够用cache_invalidation_matrix——避免工具版本变更导致 cache 全量 miss避坑三项(按踩坑频率排序):
最后一条元规则:上下文工程是动态博弈,不是一次性配置。工具版本会变、模型版本会变、用户 query 分布会变——任何一套固定策略都会在 3-6 个月内失效。把"上下文工程"当作 Product 持续迭代,而不是 Project 一次交付——这才是 2026 年 Agent 系统能否跑得长、跑得省、跑得稳的根本分水岭。
附录:常见问题的工程答复
Q1:上下文压缩会不会让 Agent "失忆"? A1:会,但可控。核心做法是分层压缩——保留最近 5 turn 原文 + 之前的递归摘要 + 检索结果优先级最高。这套分层在 SWE-bench 上的 quality drop < 2%。
Q2:KV cache 命中率高 = 一定好吗? A2:不一定。cache 命中率高也可能意味着 prefix 太死板、工具描述一年没更新——这种 cache 的"高命中"反而是技术债。健康指标是 cache 命中率 + 工具描述更新频率 + 业务效果三者的平衡。
Q3:要不要把所有 tool 都换成"短描述"? A3:短描述有利于 KV cache 复用,但 description 太短会让模型选错工具。经验值:description 在 100-300 字之间最佳——既能 cache 复用,又能让模型正确选工具。
Q4:长时记忆和上下文到底怎么切? A4:看访问频率和生命周期。访问 ≥ 3 次 + 跨 session → memory;只在当前 session → context;超过 30 天未访问 → cold storage。记忆晋升用背景任务异步做、不阻塞主流程。
Q5:上下文工程和 prompt engineering 的区别是什么? A5:prompt engineering 是写 prompt 的技巧——怎么措辞、怎么给 few-shot、怎么选 chain-of-thought;上下文工程是管理 prompt 资源的系统——怎么监控、怎么压缩、怎么缓存、怎么降级。前者是文案技巧、后者是系统工程。两者完全正交,但很多团队把二者混为一谈。一个 prompt engineer 写出好 prompt 后,上下文工程团队要确保这个 prompt 在 1K turn 长任务里依然稳定运行——这是分层职责。
Q6:什么时候应该考虑从 0% 压缩开始? A6:答案出乎意料——90% 的场景应该从 60% 压缩开始。0% 压缩只适合 latency 容忍度高、cost 不敏感、研究 Agent 三大场景;其余场景 60% 压缩已经能覆盖绝大多数质量 drop 在 2% 以内的优化目标。先用 60% 压缩跑两周、再根据实测数据上调或下调——这个顺序比"先跑 0% 看效果"更省事。
一句话摘要:把上下文当作 Agent 的第一资源来管理——四元组预算 + 分层压缩 + KV cache 复用 + 多目标帕累托,是 2026 年 Agent 上下文工程的统一实战范式。
Conversation
0 条