
长程 Agent 的上下文工程截断、摘要压缩与检索回放系列导航00 系列导航 · 01 单 Agent 总论 · 02 ReAct 原理 · 03 手写内核 · 04 ACI 工具设计 ·05 上下文工程· 06 两个增强变体 · 07 上线前清单小提一句 由于平台每日发布笔记数量限制目前系列笔记还没全部上传。后续内容会持续更新大家可以先点个关注、收藏以免错过~引子上下文是唯一不可扩容的资源算力可以加钱买模型可以换更强的工具可以慢慢补。只有上下文窗口是硬的它是单 Agent 的物理天花板且 Agent 的历史只增不减。第 1 篇给过测算公式可支撑步数 N ≈ (窗口 W × 安全系数 α − 固定开销 F) / 单步增量 k这篇讲的就是三个变量怎么调F怎么压、k怎么压、W怎么用满而不失效。先明确一个反直觉的事实长上下文不等于长记忆。把 200k token 塞进窗口模型并不会平等地对待每一个 tokenLost in the MiddleLiu et al., TACL 2024等研究表明模型对开头和结尾的内容关注度高对中间的利用率显著下降。这意味着上下文不是储物间是工作台。堆在台面上的东西越多每件东西被用到的概率越低。所以上下文工程的目标不是塞更多信息而是提高单位 token 的决策价值。一、上下文的四类内容与预算分配先把上下文拆开。一次 Agent 调用的 prompt 由四部分组成它们的性质完全不同类别内容性质典型占比优化方向① 固定区system prompt、工具定义每步完全相同5~15%精简工具集第 4 篇、放前缀最前面吃缓存② 任务区任务描述、约束、输出格式每次任务固定1~5%与①一起构成稳定前缀③ 压缩区远期轨迹的摘要 / 检索回放的片段低频变化10~30%本文主题只在压缩点更新④ 热区最近 N 步的完整T/A/O每步增长50~80%截断 Observation、控制 N这四类的排列顺序比内容本身更要紧第 6 节前缀缓存会解释为什么稳定前缀 · 可命中 prompt caching① 固定区system prompt · 工具定义② 任务区任务描述 · 约束 · 输出格式③ 压缩区远期摘要 / 检索回放片段只在压缩点更新④ 热区最近 N 步完整 T/A/O每步增长 50~80%二、三种压缩策略按保真度从低到高排列策略做法保真实现成本主要风险截断 / 滑动窗口丢弃最早的T/A/O只留最近 N 步低极低早期关键事实永久丢失且模型不知道自己忘了摘要压缩用 LLM 把历史压成结构化笔记中中多一次调用摘要引入二次幻觉摘要本身会累积漂移检索回放轨迹全量落盘按需检索注入高中高需存储与检索检索质量决定成败模型不知道该检索什么下面逐个讲清楚实现与坑。2.1 截断 / 滑动窗口defsliding_window(steps:list[Step],n:int6)-list[Step]:returnsteps[-n:]简单到不值一提但它是所有策略的兜底。任何系统最终都需要一个实在装不下就丢最旧的的保底机制。它的真实问题不在丢东西而在模型不知道自己丢了什么。它会基于我从没查过 X来做决策而实际上它查过只是被丢了。一个便宜的缓解丢弃时插入一条系统提示[系统] 为控制上下文长度step 1-8 的详细内容已被移除。 这些步骤中确认过的事实摘要摘要。 如果你需要某一步的完整 Observation调用 recall(关键词)。这就把它从截断升级成了截断 检索回放代价极小。2.2 摘要压缩关键设计增量式 结构化 带溯源。SUMMARY_PROMPT请把以下 Agent 轨迹片段压缩成结构化笔记。 已有笔记若为空则忽略 {previous} 新增轨迹 {turns} 输出格式严格遵守 ## 已确认事实 - [step {i}] {事实} ← 每条必须标注来自哪一步 ## 已排除/已证伪 - [step {i}] {假设} → 已证伪理由{...} ## 待办与未决 - {还差什么} ## 关键句柄 - {handle} → {内容简述} 规则 - 只记录 Observation 中出现过的事实不要补充任何推理或背景知识 - 无法确定的写未确认不要猜测 - 与已有笔记冲突时保留更晚的 step并在已证伪中记录被推翻的旧结论。 asyncdefsummarize(turns:list[Step],llm,previous:str)-str:body\n.join(render_brief(s)forsinturns)returnawaitllm(SUMMARY_PROMPT.format(previouspreviousor无,turnsbody))四个设计要点每一个都对应一个真实的坑已证伪是显式字段。这是治理上下文污染的核心手段第 4 节。普通摘要会把错误结论和正确结论混在一起模型无法区分。每条事实带 step 编号。既便于人工审计也让模型能说根据 step 3……。增量式不是全量重算。全量重算每步都要把所有历史喂给 LLM成本是平方级增量式只喂新增部分 已有笔记。禁止补充背景知识。摘要器最容易犯的错就是顺手补全这是二次幻觉的主要来源。提示里必须明令禁止。⚠️摘要的最大风险二次幻觉。摘要本身是一次 LLM 生成它可能把疑似写成确认。在事实敏感的任务故障排查、合规上我建议摘要只压缩 Thought 和 ActionObservation 中的关键事实原文保留Observation 是环境给的它是唯一不该被模型重写的内容。2.3 检索回放思路轨迹全量落盘便宜磁盘不值钱上下文里只放一个recall(query)工具模型需要时自己取。classTrajectoryStore:全量轨迹落盘 关键词检索。生产可换成向量库或 BM25。def__init__(self):self.items:list[dict][]defadd(self,step:Step):self.items.append({i:step.i,text:f{step.thought}\n{step.call.name}{step.call.args}\n{step.observation},})defsearch(self,query:str,k:int3)-str:# 演示用词频打分生产建议 BM25 / embeddingscoredsorted(self.items,keylambdait:-sum(it[text].count(w)forwinquery.split()iflen(w)1),)[:k]ifnotscored:return未命中任何历史步骤。可换用更具体的关键词服务名、错误码、文件名。return\n---\n.join(f[step{it[i]}]\n{it[text][:1500]}foritinscored)把它做成工具tools.register(namerecall,description从本次任务的完整历史中检索之前的步骤内容含已移出上下文的早期步骤。,params{type:object,properties:{query:{type:string}},required:[query]},returns命中的历史步骤原文最多 3 条,)asyncdefrecall(args):returnstore.search(args[query])保真度最高因为回放的是原文。但它有一个结构性弱点检索的前提是知道自己要找什么。模型不会去检索它压根不记得自己见过的东西。所以检索回放不能单独使用必须与摘要搭配——摘要负责告诉模型你见过什么元知识检索负责在需要时提供原文细节。这是下一节组合架构的核心思路。三、组合架构热 / 温 / 冷三层生产实践中三种策略几乎总是组合使用上下文窗口有限每步写入每 4~6 步压缩需要细节时检索固定区system · 工具定义任务区task温区 · 摘要已确认 / 已证伪 / 待办每 4~6 步更新一次热区 · 最近 6 步原文T/A/O 完整保留每步追加冷区 · 全量轨迹落盘磁盘 / 对象存储通过 recall(query) 按需取回原文三层各管一件事热区给细节温区给元知识冷区给保真备份。实现dataclassclassCtxConfig:window:int200_000reserve:int8_000# 给生成留的空间hot_turns:int6# 热区步数compress_every:int4# 每新增多少步做一次压缩obs_limit:int1_200# 单条 Observation 的 token 上限classContextManager:def__init__(self,task:str,tools:ToolRegistry,llm,cfg:CtxConfigCtxConfig()):self.task,self.tools,self.llm,self.cfgtask,tools,llm,cfg self.turns:list[Step][]# 全量内存 落盘self.summary:str# 温区self.storeTrajectoryStore()# 冷区self._since_compress0defadd(self,step:Step):self.turns.append(step)self.store.add(step)self._since_compress1asyncdefmaybe_compress(self):只在达到阈值时压缩——这是为前缀缓存考虑的关键设计ifself._since_compressself.cfg.compress_every:returncoldself.turns[:-self.cfg.hot_turns]ifnotcold:returnself.summaryawaitsummarize(cold,self.llm,self.summary)self._since_compress0defrender(self)-str:hotself.turns[-self.cfg.hot_turns:]parts[SYSTEM,# ① 固定区# 可用工具\nself.tools.render(),f# 任务\n{self.task},# ② 任务区]ifself.summary:parts.append(f# 早期步骤摘要\n{self.summary})# ③ 温区parts.append(# 近期轨迹)forsinhot:# ④ 热区parts.append(f[step{s.i}])ifs.thought:parts.append(fthink{s.thought}/think)parts.append(Action: json.dumps({action:s.call.name,args:s.call.args},ensure_asciiFalse))ifs.observation:parts.append(fObservation:{s.observation})parts.append(f[step{self.turns[-1].i1}])return\n.join(parts)defusage_ratio(self)-float:returnapprox_tokens(self.render())/(self.cfg.window-self.cfg.reserve)usage_ratio()应该被监控起来超过 0.7 就该告警说明你的任务正在逼近窗口需要调hot_turns或obs_limit了。四、上下文污染与 checkpoint 回滚第 2 篇推导过Observation 是推理链上的重置点只重置后续不清除既有。一条早期错误事实一旦进入上下文会被后续 Thought 反复引用、自我强化。这就是上下文污染。三种治理手段按代价从小到大① 显式标注证伪。在摘要里用已证伪字段记录同时在热区插入一条系统更正defmark_refuted(ctx:ContextManager,step_i:int,reason:str):ctx.summary(f\n## 已证伪\n- [step{step_i}] 该步的结论已被推翻{reason}。f后续推理不得再引用该步的事实。)② Checkpoint 回滚。在关键决策点打点出错时回退classContextManager:# ... 接上文defcheckpoint(self)-int:returnlen(self.turns)defrollback(self,mark:int,reason:str):回退到 mark。注意不是简单丢弃而是留一条已作废记录discardedself.turns[mark:]self.turnsself.turns[:mark]self.summary(f\n## 已作废的尝试\nf- 曾执行{len(discarded)}步step{mark1}~{marklen(discarded)}f因「{reason}」被作废。结论不要重复该路径。)注意第 ② 条里那个容易被忽略的细节回滚不能干净地丢弃。如果完全不留痕迹模型不记得自己走过这条路很可能原样再走一遍。你付出了回滚的代价却没买到任何信息。留一条简短的已作废记录几十 token既省空间又保留了负反馈。③ 结构性隔离。对高污染风险的来源如 LLM 生成的中间结论不直接放进主上下文而是先写入 scratchpad 由代码校验后再注入。这条成本最高只在事实敏感场景使用。五、把单步增量 k 压下来最便宜的优化回到公式N ≈ (W×α − F) / k。降低k是三个变量里投入产出比最高的因为它不需要任何压缩机制。k的构成len(Thought) len(Action) len(Observation)其中 Observation 通常占 70% 以上。所以压k就是压 Observation手段效果实现位置结构化摘要而非原文2000 → 200 token工具内部第 4 篇军规 7句柄 按需读取20000 → 150 token工具返回 read_range工具强制limit/ 分页直接封顶工具参数poka-yoke截断标注不改变大小但避免误判truncate()第 3 篇Thought 三段式模板抑制废话省 30~50%system prompt第 2 篇一个真实量级的对比优化前单步 k ≈ 2,800 token日志原文 2000 思考 500 动作 300 → 200k 窗口能撑 ≈ 67 步 优化后单步 k ≈ 900 token结构化摘要 200 句柄 100 思考 400 动作 200 → 同样窗口能撑 ≈ 210 步且每步的注意力更集中同样的模型、同样的窗口、同样的工具只因为返回形状不同可用步数翻了三倍。第 4 篇的返回设计是整个系列投入产出比最高的部分。六、前缀缓存一个影响架构顺序的硬约束几乎所有主流厂商都提供 prompt caching前缀缓存如果两次请求的前缀完全相同命中部分按折扣价计费通常 0.1~0.5×且延迟更低。这对上下文策略有两个直接推论推论 1把稳定内容放在最前面且顺序固定。system prompt → 工具定义 → 任务描述 → [摘要] → 热区轨迹 └─────────── 稳定前缀跨步可缓存 ──────────┘任何放在前缀里的东西发生变化从变化点开始全部缓存失效。所以工具定义不要动态增删别做每步只加载相关工具那会破坏缓存除非你按任务类型一次性加载并保持稳定不要把时间戳、随机数、请求 ID 等易变内容放进前缀。推论 2摘要不要每步重算。如果摘要每步都更新那么摘要及其之后的所有内容每步都失效缓存命中率归零。所以第 3 节的compress_every不只是为了省调用——它是为了让摘要在压缩周期内保持稳定从而让前缀可缓存。每步重算摘要 前缀 [system tools task] → 缓存只有固定区 每 N 步重算 前缀 [system tools task summary] → 在 N 步内前缀完全稳定缓存命中率大幅提升 ✅这笔账很好算N4 时热区4 步 × 900 3600 token走全价前缀假设 6000 token走折扣价。步数越多、固定区越大这个优化的收益越显著。七、怎么衡量上下文做得好不好四个指标指标定义测法健康阈值⚠️ 经验值有效信息密度被后续 Thought 实际引用过的上下文 token / 总上下文 token回溯 trace对每个 Observation 片段检查后续 Thought 是否提及其内容可用关键词/embedding 匹配近似≥ 40%低于 25% 说明上下文里堆了大量无用内容压缩失真率因压缩导致关键事实丢失进而使任务失败的比例构造一组答案依赖早期步骤的任务分别在无压缩 / 压缩下跑比较成功率差≤ 3%上下文水位 P95approx_tokens(prompt) / 窗口的 P95trace 统计≤ 0.7 0.85 说明任务已超界该分层或分治了污染事件率出现引用已被证伪事实的步数占比标注已证伪条目后检测后续 Thought 是否仍引用≤ 2%其中有效信息密度是我最推荐的指标因为它直接回答了我塞进去的东西有没有用。测起来也简单近似版definfo_density(trace:list[Step])-float:totalsum(approx_tokens(s.observationor)forsintrace)cited0fori,sinenumerate(trace):later .join((t.thoughtor)fortintrace[i1:])# 近似抽取 observation 中的关键 token看是否出现在后续 thought 中keys{wforwinre.findall(r[A-Za-z_]{4,}|[\u4e00-\u9fff]{2,},s.observationor)}hitssum(1forwinlist(keys)[:20]ifwinlater)ifhits2:citedapprox_tokens(s.observationor)returncited/totaliftotalelse0.0跑一批 trace如果这个数字是 20%那你的第一优先级不是换压缩策略而是少往上下文里放东西。八、反思记忆也是一种上下文第 6 篇的 Reflexion 会把历史反思注入下一次尝试。请注意反思记忆同样服从本文的全部约束。反思会随尝试次数线性增长 → 必须限制条数K3~5并做相关性检索不能全量注入一条错误的反思就是一条高浓度的上下文污染它是经验模型会格外信任它→ 必须允许人工清洗反思应放在哪个区温区与摘要并列作为跨尝试的元知识而不是热区。把反思库当作跨会话的温区来管理第 6 篇的三条约束限条数、相关性注入、允许清洗就有了统一解释。九、小结上下文工程从头到尾都在跟一个硬约束打交道窗口有限历史只增不减而长上下文并不等于长记忆。所有招式都指向同一个目标——提高单位 token 的决策价值。四区按稳定性分层、三种压缩策略组合成热温冷三层、把单步增量k压到最低、为前缀缓存把稳定内容前置、摘要按阈值而不是每步更新。这几件事看着各自独立其实是同一条思路的不同侧面。其中性价比最高的是压k。同样的模型、窗口和工具只改工具的返回形状可用步数就能翻几倍而且不需要上任何压缩机制。把反思也当作受约束的上下文温区来管理前面几篇看起来零散的约束就有了统一解释。做得好不好用四个数字衡量有效信息密度 ≥40%、压缩失真率 ≤3%、上下文水位 P95 ≤0.7、污染事件率 ≤2%。其中信息密度最能直接回答那句话——塞进去的东西到底有没有用。⚠️ 本系列明确存疑项三种压缩策略在 100 步任务上没有公开定量对比基准上表阈值均为工程经验值请按自己的任务集校准。上一篇04 ACI工具设计方法论下一篇06 Plan-and-Execute 与 Reflexion给 ReAct 装上全局锚和经验回放参考Liu et al.,Lost in the Middle: How Language Models Use Long Contexts, TACL 2024.Yao et al.,ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023, arXiv:2210.03629上下文只增不减的原始设定。Shinn et al.,Reflexion: Language Agents with Verbal Reinforcement Learning, NeurIPS 2023, arXiv:2303.11366情景记忆作为上下文。Anthropic,Building Effective Agents, 2024-12。