Agent 上下文窗口管理长篇对话里如何高效压缩历史一、用户说了 50 句话后Agent 开始胡说八道不是因为模型变笨了是上下文窗口被塞满了。GPT-4 有 128K token 容量但长上下文有两个致命问题迷失在中间Lost in the Middle模型对窗口中间部分的内容注意力衰减重要信息如果在第 15-30 轮之间大概率被忽略成本非线性prompt token 越多推理计算越大延迟也越高。一个 60K token 的请求比 10K token 慢 3 倍不止核心矛盾Agent 需要记得越久越好但上下文越大性能越差。解决这个矛盾的唯一办法是压缩——在信息损失最小的前提下把历史对话映射到紧凑表征。flowchart TD A[原始对话历史br/50 轮] -- B{压缩策略选择} B -- C[窗口截断] B -- D[摘要压缩] B -- E[关键帧提取] B -- F[分层缓存] C -- C1[最近 N 轮保留] C -- C2[早期历史丢弃] C -- C3[简单但损失大] D -- D1[LLM 生成摘要] D -- D2[保留语义骨架] D -- D3[丢失细节] E -- E1[只保留决策节点] E -- E2[跳过中间推理] E -- E3[恢复需推断] F -- F1[全量存向量库] F -- F2[检索 摘要分层] F -- F3[成本可控最优]二、压缩策略的梯级选择Level 1朴素截断Window Truncation最简单只保留最近 N 轮对话其余全部丢弃。优点实现零成本token 精确可控缺点丢失所有远距离上下文Level 2LLM 摘要压缩让 GPT-4 把历史对话总结成 500 词的摘要替代原始对话。优点语义保留度高token 压缩比可达 20:1缺点摘要本身消耗 token生成摘要的 prompt 也要花钱且有信息损耗Level 3关键帧提取 向量检索不是逐轮存对话而是识别决策点用户修改需求、Agent 选定方案、工具返回关键结果。这些是关键帧。非关键帧的聊天内容丢弃需要时从向量库检索。Level 4分层缓存最优方案混合策略L0 热缓存最近 10 轮完整保留注入 promptL1 温缓存10-30 轮的 LLM 摘要注入 promptL2 冷缓存30 轮存入向量库按需检索三、分层上下文管理器实现import asyncio import json import time from dataclasses import dataclass, field from typing import Optional from openai import AsyncOpenAI dataclass class ConversationTurn: role: str # user / assistant / tool content: str timestamp: float field(default_factorytime.time) is_decision_point: bool False # 是否为关键决策节点 metadata: dict field(default_factorydict) class LayeredContextManager: 分层上下文管理器。 三层缓存设计 L0 (热): 最近 N 轮直接注入 prompt —— 零损耗 L1 (温): 中间轮次的 LLM 摘要 —— 高压缩比 L2 (冷): 早期历史的向量检索 —— 按需提取 设计决策 - L0 的 N 是动态的基于 token 预算而非固定轮次 - 每轮对话都可能触发层间数据迁移 - 摘要异步生成不阻塞对话响应 def __init__( self, hot_window_tokens: int 4000, warm_window_turns: int 30, max_summary_tokens: int 500, ): self._client AsyncOpenAI() self._hot_limit hot_window_tokens self._warm_limit warm_window_turns self._max_summary max_summary_tokens self._turns: list[ConversationTurn] [] self._l1_summary: str # 温层摘要 self._l2_store: list[ConversationTurn] [] # 冷层向量库 def add_turn(self, turn: ConversationTurn) - None: 添加一轮对话并触发分层迁移。 self._turns.append(turn) # 超过温层上限的轮次迁移到冷层 while len(self._turns) self._warm_limit: oldest self._turns.pop(0) # 先进先出 if oldest.is_decision_point: self._l2_store.append(oldest) # 异步触发温层摘要更新 asyncio.create_task(self._update_summary()) async def build_prompt_context(self, max_tokens: int) - str: 构建注入 LLM 的上下文。 预算分配策略 - L0 热缓存优先保证质量 - 剩余预算分给 L1 摘要 - L2 冷缓存在需要时按 tool call 检索 # 等待温层摘要更新完成 if self._turns: await self._update_summary() parts [] # L1 摘要如果有的话先放 if self._l1_summary: parts.append(f[对话历史摘要]\n{self._l1_summary}) # L0 热缓存从最近开始倒序添加直到达到 token 预算 hot_turns [] token_count 0 for turn in reversed(self._turns): turn_tokens len(turn.content) // 4 # 粗略估算 if token_count turn_tokens self._hot_limit: break hot_turns.insert(0, turn) # 头部插入保持时间顺序 token_count turn_tokens for turn in hot_turns: parts.append(f{turn.role}: {turn.content}) context \n---\n.join(parts) # 最后一道防线整体截断 if len(context) max_tokens * 4: context context[-(max_tokens * 4):] return context async def search_l2(self, query: str) - list[ConversationTurn]: 检索冷层中有用的历史信息。 在实际实现中这里用向量检索。 这里用简单的关键词匹配做占位。 if not self._l2_store: return [] # 简单关键词匹配——实际应用需替换为 embedding 向量检索 query_words set(query.lower().split()) scored [] for turn in self._l2_store: content_words set(turn.content.lower().split()) overlap len(query_words content_words) if overlap 0: scored.append((overlap, turn)) scored.sort(keylambda x: x[0], reverseTrue) return [turn for _, turn in scored[:3]] async def _update_summary(self) - None: 异步更新温层摘要。 设计决策 - 只对温层范围内的轮次做摘要 - 热层不参与摘要它本身就是完整上下文 - 冷层已经被压缩存储不再参与 if len(self._turns) 10: return # 内容太少不需要摘要 # 排除热层最近几轮 summary_turns self._turns[: -min(len(self._turns), 10)] if not summary_turns: return # 构建摘要 prompt dialog_text \n.join( f{t.role}: {t.content} for t in summary_turns ) response await self._client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是一个对话摘要助手。将以下对话压缩为简洁的结构化摘要。保留所有关键决策、用户偏好和重要事实。不要遗漏任何需要后续引用的信息。}, {role: user, content: f请摘要以下对话{len(summary_turns)} 轮保留所有关键信息\n\n{dialog_text}}, ], max_tokensself._max_summary, temperature0.1, ) self._l1_summary response.choices[0].message.content or def get_stats(self) - dict: 获取分层统计 hot_tokens sum(len(t.content) // 4 for t in self._turns[-10:]) warm_tokens sum(len(t.content) // 4 for t in self._turns[:-10]) cold_tokens sum(len(t.content) // 4 for t in self._l2_store) return { hot_turns: min(len(self._turns), 10), warm_turns: max(0, len(self._turns) - 10), cold_turns: len(self._l2_store), hot_tokens: hot_tokens, warm_tokens: warm_tokens, cold_tokens: cold_tokens, l1_summary_tokens: len(self._l1_summary) // 4, }四、压缩的代价与适用边界信息损失评估摘要压缩信息损失率大约 30-50%。理解类任务总结、分析影响较小精确任务数字计算、代码生成影响大关键帧压缩损失率 60-70%但保留的都是高价值节点截断损失率 100%丢弃部分信息彻底消失Token 成本对比策略30 轮对话后 Token保存率全量保留15000 tokens100%分层缓存~3000 tokens~80%摘要压缩~800 tokens~60%朴素截断~2000 tokens~30%不适合的场景需要精确引用历史原文法律、合规审查对话长度 10 轮不需要压缩对延迟极敏感的场景摘要生成增加 1-2 秒五、总结上下文窗口管理的核心不是怎么装更多而是怎么丢掉不重要的信息。分层缓存策略给出了一条可配置的谱线预算充足时多保留成本紧张时多压缩。关键帧标记是最有价值但最被忽视的技术——标记了决策点意味着压缩时可以放心丢弃中间推理过程。