轻量 Agent 的内存管理:上下文窗口满载时的智能压缩与淘汰策略
轻量 Agent 的内存管理上下文窗口满载时的智能压缩与淘汰策略一、当上下文窗口触底Agent 记忆溢出的真实场景一个代码审查 Agent 在分析一个 2 万行的 PR 时上下文窗口从初始的 3K tokens 迅速膨胀至 120K tokens——GPT-4 的硬上限。Agent 开始丢失对话前期的分析结果重新请求同一段代码时回答前后矛盾。最严重的情况是当窗口满载后继续追加新的信息模型直接丢弃了最早的对话内容导致 Agent 完全忘记了当前审查的是哪个文件。这个问题的本质是Agent 的上下文是线性增长的而模型的上下文窗口是固定的。在达到窗口上限之前必须有策略地压缩或淘汰旧信息否则 Agent 的行为质量会断崖式下降。二、上下文窗口管理的三层策略flowchart TD A[新消息进入] -- B{窗口使用率?} B --| 60%| C[直接追加无需处理] B --|60% - 85%| D[层1: 无损压缩] B --| 85%| E[层2: 有损淘汰] D -- D1[合并连续同类消息] D -- D2[去除对话中的重复内容] D -- D3[工具调用结果摘要化] D1 -- F{压缩后使用率?} D2 -- F D3 -- F F --| 75%| C F --| 75%| E E -- E1[按时间衰减淘汰早期消息] E -- E2[按语义重要性保留关键信息] E -- E3[生成压缩摘要替代原始对话] E1 -- G[更新上下文] E2 -- G E3 -- G2.1 无损压缩工具调用结果的摘要化工具调用Tool Call是上下文膨胀的头号元凶。一次文件读取可能返回 2000 tokens 的内容但 Agent 实际需要的可能只是其中 50 tokens 的信息。在工具结果进入上下文窗口之前做过滤// 工具调用结果的智能摘要器 type ToolResultCompressor struct { llm LLMClient maxResultLen int // 工具结果的最大 token 数 smallModel LLMClient // 用更便宜的模型做摘要 } func (c *ToolResultCompressor) Compress( ctx context.Context, toolName string, rawResult string, ) string { tokenCount : estimateTokens(rawResult) // 小结果不压缩直接返回 if tokenCount c.maxResultLen { return rawResult } // 对大结果用轻量模型做智能摘要 prompt : fmt.Sprintf( 对以下工具%s的返回结果做信息提取。只保留与当前任务相关的关键信息省略冗余内容。 原始结果%d tokens: %s 请用不超过 %d tokens 输出关键信息摘要, toolName, tokenCount, rawResult, c.maxResultLen, ) summary, err : c.smallModel.Generate(ctx, prompt) if err ! nil { // 摘要失败时做暴力截断取前 N 个 token return truncateByTokens(rawResult, c.maxResultLen) } return summary }工具结果压缩的关键决策是用什么模型做摘要。使用 GPT-4 做摘要的成本可能比直接传给 GPT-4 更贵。推荐用 GPT-3.5 或 Claude Haiku 这类轻量模型做摘要——成本是完整传递的 1/10。2.2 有损淘汰基于重要性的消息排名当窗口使用率超过 85% 时需要主动淘汰不重要的历史消息// 上下文消息的重要性评分器 type MessageScorer struct { /* 评分权重由场景决定 */ } type ScoredMessage struct { Message ChatMessage Score float64 Position int // 在对话中的原始位置 } func (s *MessageScorer) ScoreMessages(messages []ChatMessage) []ScoredMessage { scored : make([]ScoredMessage, len(messages)) for i, msg : range messages { score : 1.0 // 基础分 // 系统消息最重要永不淘汰 if msg.Role system { score 100.0 } // 用户消息 AI 消息 工具结果 switch msg.Role { case user: score * 3.0 case assistant: score * 2.0 case tool: score * 0.5 // 工具结果可淘汰性最高 } // 近期消息权重高反衰减因子 recency : float64(i) / float64(len(messages)) score * (0.5 0.5*recency) // 包含关键决策的消息加分 if containsKeywords(msg.Content, 决策, 选择, 结论, 最终, 确认) { score * 1.5 } scored[i] ScoredMessage{msg, score, i} } return scored } // 按重要性排序后保留 top-K 条消息 func (s *MessageScorer) KeepTopK(messages []ChatMessage, k int) []ChatMessage { scored : s.ScoreMessages(messages) // 系统消息永远保留 sort.Slice(scored, func(i, j int) bool { return scored[i].Score scored[j].Score }) kept : make([]ChatMessage, 0, k) for i : 0; i k i len(scored); i { kept append(kept, scored[i].Message) } // 按原始位置排序以保持对话流的时序 sort.Slice(kept, func(i, j int) bool { return scored[i].Position scored[j].Position }) return kept }三、滑动窗口 摘要的混合策略单纯的时间滑动窗口只保留最近 N 条消息会丢失早期的关键上下文单纯的摘要策略会丢失细节。混合方案取长补短// 混合上下文管理滑动窗口 滚动摘要 type HybridContextManager struct { windowSize int // 保留最近 N 条原始消息 summarizer *ContextSummarizer summary string // 滚动摘要覆盖超出窗口的历史消息 } func (m *HybridContextManager) Manage(messages []ChatMessage) []ChatMessage { if len(messages) m.windowSize { return messages } // 超出窗口的历史消息生成摘要 overflow : messages[:len(messages)-m.windowSize] newSummary : m.summarizer.Update(m.summary, overflow) // 最终上下文 摘要作为系统消息注入 最近 N 条原始消息 result : []ChatMessage{{ Role: system, Content: fmt.Sprintf(历史对话摘要: %s, newSummary), }} result append(result, messages[len(messages)-m.windowSize:]...) m.summary newSummary return result }窗口大小是一个关键参数。过小 5 条会让 Agent 丢失即时上下文过大 50 条则压缩效果有限。经验值GPT-4 的滑动窗口设为 20-30 条消息GPT-3.5 设为 10-15 条。每轮对话结束时检查窗口是否触发压缩阈值。四、边界与权衡压缩带来的精度损失工具结果的摘要化可能丢失关键细节——特别是包含精确数字、代码片段或 JSON 结构的结果。对于精确性要求高的工具调用如数据库查询不应压缩其返回结果。淘汰策略的误伤风险基于重要性的淘汰是启发式的可能错误淘汰了后续推理需要的信息。建议先做软淘汰——将被淘汰消息的内容摘要保留为一行注释而非完全删除。滚动摘要的质量衰减摘要→再摘要→再摘要……链条过长后信息会发生传话游戏效应。建议在摘要模型中对不确定的内容标注置信度低置信度内容在新摘要中主动丢弃而非二次模糊。Token 计数的误差不同模型的 tokenizer 不同。GPT-4 的 token 计数与 Claude 的不同估算可能偏差 10-20%。建议在上下文中保留 10% 的缓冲空间不要压着窗口上限设计。五、总结Agent 上下文管理的核心是在信息完整性和窗口利用率之间找到动态平衡。三层策略的分工60% 以下不作处理、60-85% 做无损压缩合并重复、摘要化工具结果、85% 以上做有损淘汰时间衰减 重要性排序。落地优先级先实现工具调用结果的摘要化——这是上下文膨胀的最大贡献者投入产出比最高。然后逐步加入滚动摘要和消息评分。最后加入自适应的窗口大小调整——根据任务复杂度简单问答用大窗口、复杂推理用小窗口保留更多上下文动态配置。所有的淘汰和压缩策略都需要在真实使用场景中验证——A/B 测试淘汰方案前后的回复质量不要凭直觉决定哪些该淘汰。