news 2026/11/4 2:16:35

AI Agent上下文管理实战:删记录机制让长任务不再越跑越慢

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent上下文管理实战:删记录机制让长任务不再越跑越慢

我们做AI Agent的都遇到过这个场景:任务指令发下去,Agent前期跑得又快又稳,越往后越慢,慢到怀疑是不是死循环了,最后干脆报个超时错误。翻日志一看——好家伙,光是上下文里堆积的历史记录就占了几万token,模型每走一步都要把所有历史"重新读"一遍。很多人第一反应是"换个上下文更大的模型不就解决了?",但真正跑过长任务的都知道,无限上下文是个美丽的陷阱,学会删记录的Agent才具备长跑能力。

这篇文章不聊理论,我把自己的项目里踩过的坑、做过的实验以及最终落地的一套"上下文删减机制"完整拆出来。项目背景是一个面向客服场景的多轮长任务Agent,单次任务可能持续几小时,涉及几十个子步骤和上百次工具调用。这类任务对上下文管理的要求极其苛刻,也是最能暴露"上下文堆积"问题的场景。适合正在做Agent开发、或者在跑自动化长流程时被token成本逼疯的工程师参考。

1. 无限上下文为什么是伪命题

先破个题。模型窗口从几万token做到几十万甚至上百万,很多人觉得"窗口大了就不用管历史记录了",实际跑下来根本不是这么回事。窗口大只代表能塞进去的内容上限变高,不代表塞进去之后模型还能高效使用它。

1.1 窗口大不等于检索准

把100万token的内容全部塞进上下文,模型在生成时理论上能看到全部内容,但注意力分配是有限的。业界做过不少长上下文测试,结论高度一致:模型对输入中间位置的召回能力显著弱于开头和结尾,英文语境里有个专门的词叫"lost in the middle"(迷失在中间)。

套到长任务场景就很致命了。Agent执行到第40步时,被历史记录淹没的往往是"任务目标是什么"、"前面已经完成了哪些步骤"这类最关键的信息,因为它们早被排在了上下文的中间位置。因为剩下的位置被更多的新日志挤占,关键信息不断往后挪,模型对它们的关注度越来越低。

我自己做过一个简单的对照实验:用同一份客服工单,一份上下文控制在5k token以内,一份塞到60k token,让模型在中间位置提取用户的原始诉求。前者的提取准确率在95%以上,后者直接掉到70%以下。这个差距不是模型能力问题,是上下文结构问题。

1.2 算完成本账之后,钱包先扛不住

即便模型窗口足够大,成本也按实际输入的token数计算。窗口大不意味着只按窗口大小计费,而是按你实际喂进去的token数计费。长任务跑下来,每一步都在重复发送完整历史,消耗按任务步数复利增长。

算一笔具体的账:假设一个任务需要50步,初始上下文4k token,每一步新增1k token。如果全程不删记录,第50步时的单次请求输入约54k token。仅输入部分累计消耗约140万token;如果每一步都把上下文压缩回4k,总输入消耗可以控制在约25万token,成本相差接近6倍。这还是按输入单价算的,如果接的是输出计费更高的模型,差距会更大。

1.3 延迟曲线里的隐性损耗

做过流式输出的都有体感:上下文翻倍,首字延迟肉眼可见地上涨。模型需要对全部输入做一次预填充,历史越长,这个阶段耗时越久。长任务经常涉及几十次串行工具调用,每一步都被附加延迟拖慢,整条链路跑下来就变得不可忍受。

我测试过一组数据:上下文10k时首字延迟约1.2秒,40k时约2.8秒,到了100k能到6秒以上。对实时客服场景来说,几秒的延迟已经非常影响体验了。更难受的是Agent的每一步都要等待模型完整解析一遍增量的历史,累积起来任务的完成耗时可能翻三到五倍。

2. 长任务跑挂的第一现场:先看看上下文里堆积了什么

要解决问题,先得知道历史记录里到底堆了哪些东西。我把自己项目的Agent日志扒了一遍,发现上下文膨胀主要是三类内容在叠加,每一类都需要不同的处理策略。

2.1 工具调用的完整输入输出日志

Agent每调用一次工具,模型就会把工具名、入参、返回结果全部存进上下文。单看一次调用可能也就几百token,但一个长任务里往往有几十次调用。尤其是那些返回大对象的工具——比如查一次数据库返回几百行记录,或调一次搜索接口返回一堆网页摘要——单次调用就能吃掉好几千token。

我记得有一次排查Agent中间步骤为什么变慢,拉出来看上下文,发现光是某一次订单查询工具的返回结果就占了7k token,而且这个结果在后来的步骤里压根没被再次引用。像这种一次性数据留在上下文里,唯一的贡献就是拉高成本、拖慢速度。

2.2 多轮对话的中间产物

长任务不是一次成型的,Agent经常会在中途让用户确认信息、询问偏好,或者根据中间结果重新规划任务。这些多轮对话内容会原封不动地留在上下文里。问题在于,对话里有很多寒暄、情绪词、重复表达,信息密度很低。5轮对话可能只有2k的实质信息,但前后包裹的修饰语占了另一半空间。

更隐蔽的是中间产物的"过期问题"。比如用户最早说"我要红色外壳",后来改成了"还是蓝色吧"。如果不删记录,模型每一步都能看到旧的诉求,就可能陷入"按哪个执行"的摇摆。这类过时信息比噪音更危险,它会直接影响任务方向。

2.3 系统指令和少量示例的重复复制

很多Agent框架会在每一轮请求里都重新拼接完整的系统提示词和示例片段。系统提示可能包含一堆工具使用说明、返回格式要求、风格约束,加上几条few-shot示例,这部分内容就占了2-3k token。由于每轮都会重复发送,这部分看似固定不变的内容,实际全程都在消耗成本。

既然系统指令每轮都一样,删除策略自然要把它们排除在可裁剪范围之外——合理的设计应该是固定前置放在上下文头部,但不计入可管理区域。后面细说。

3. 帮模型学会"断舍离":删记录的四个层面

删记录不是粗暴地"全删了,只留最近几轮"。我在项目里迭代了两个月,最终沉淀出一套分层处理方案,拆成四个层面:按时间过期、按内容去噪、按关键信息抽取、按语义压缩。四个层面配合起来使用,才能既压得住体积,又不丢核心语义。

3.1 时间维度:超过N轮的历史直接归档

最基础也是最容易实现的一层,是给历史记录设定一个"保质期"。比如任务执行过程中,超过最近10轮对话或工具调用的历史,不再逐字保留,而是进入摘要流程。

这一层的逻辑基于一个很朴素的经验:Agent在处理当前步骤时,依赖的信息大多在近期几步内。早期步骤的中途细节(比如某一次参数验证失败、某一次网络重试)对当前动作已经失去了直接价值。保留太久反而会干扰当前决策。

不过时间维度要小心"窗口过短导致失忆"的问题。我在早期版本里试过只保留最近5轮,结果Agent经常忘记用户初期提过的核心要求。后来加上了第三层的关键信息抽取,这个问题才真正解决。时间维度负责"短期视野",关键信息负责"长期锚点",两者缺一不可。

3.2 内容维度:识别并剔除一次性噪音

第二步是识别哪些内容属于"用过即丢"。典型包括:

  • 工具返回的大段原始数据,如果当前步骤已经加工整理完毕,原始数据就不再需要保留。
  • 中间过程的失败信息、重试日志、错误堆栈,一旦该步骤成功执行,这些信息对后续任务稳定性的价值几乎为零。
  • 用户的无关补充对话,比如寒暄、确认语气词,这类内容不承载任务语义。

甄别一次性噪音可以走两条路。一条是靠规则判断:工具返回结果在下一步的请求里是否仍被引用,如果不被引用就标记为可删除。另一条是让模型自己判断:在每次工具调用后加一个轻量判断步骤,"当前返回中哪些信息后续步骤可能还需要?"再按结果决定保留还是丢弃。

我在项目里是两条路结合,规则兜底、模型精判,准确率能到90%以上,误删率控制在2%以内。

3.3 关键信息维度:让核心状态始终游离在删除线之外

有些内容无论隔了多少轮都不能删,我把它们归为"任务锚点":

  • 用户的初始目标与所有约束条件。
  • 当前已经完成的里程碑(步骤完成情况)。
  • 待办事项清单及下一步计划。
  • 用户中途做出的变更决定(比如换色、改时间),这些变更必须覆盖旧信息。

我的做法是把这些锚点单独抽离出来,存成一个结构化的任务状态对象。每次模型调用前,将任务状态对象放在上下文中最靠前的位置,再把近期对话放在后面。这样即便中段历史被大量删减,模型的注意力也能长期盯住这个任务主心骨。

3.4 语义压缩维度:把旧历史"浓缩"成摘要

最后一步是对"过期但仍有价值"的历史做摘要。比如用户在第3轮解释过自己的使用习惯,这个信息本身不是任务锚点,但可能影响后续推荐逻辑。直接把这段对话删掉会丢失用户画像,所以需要压缩成一句话:"用户偏好夜间使用,对响应速度敏感。"

不少Agent框架会把"对话摘要"放到系统提示词里,每轮更新。我也试过这种方案,但发现摘要越积越长,最终同样会膨胀。现在我的策略是摘要只保留二级摘要——即仅针对已过期、且不进入锚点的历史做摘要,且摘要有上限。超过上限之后,更早的摘要会被新摘要覆盖。通过这种方式确保这部分占用空间的规模是可控的。

4. 落地:我从项目里扒出来的一套删记录工程实现

前面讲的都是原则,这节分享一套可跑通的工程实现。我基于Python写了一个轻量的ContextReducer模块,核心思路是"固定段+工作段+历史段"三段式结构管理上下文。固定段放系统指令和任务锚点,工作段放当前步骤正在用的信息,历史段放经过压缩的往事。三段按固定比例分配token预算,从机制上保证上下文不会失控。

4.1 三段式上下文的token预算分配

假设模型窗口是128k,我通常只规划到80k以内,给输出留足余量。三段分配大致如下:

区域预算占比内容更新策略
固定段15%(约12k)系统提示、工具说明、任务锚点任务锚点按里程碑更新
工作段35%(约28k)当前步骤的对话、工具结果每步滚动覆盖
历史段50%(约40k)过历史摘要、早期关键信息定期压缩

工作段的淘汰标准最核心:一旦当前步骤执行完成,工作段里除了要进锚点的内容,其余全部视为可回收空间。历史段则定期把"段内已有摘要+新增过期内容"合并成更紧凑的新摘要。

4.2 关键代码:一个精简的上下文管理器

下面这段代码是我从项目里抽出来的核心逻辑,去掉了业务部分,保留了最关键的删减流程。可以直接参考实现。

class ContextReducer: def __init__(self, max_tokens: int = 80000, anchor_headroom: int = 12000): self.max_tokens = max_tokens self.anchor_headroom = anchor_headroom self.system_prompt = "" self.anchors = [] # 任务锚点,永不删除 self.work_seg = [] # 工作段:当前步骤的对话与工具结果 self.history_seg = [] # 历史段:压缩后摘要 self.summary_llm = None # 用于摘要的LLM客户端(按需注入) def add_turn(self, role: str, content: str): self.work_seg.append({"role": role, "content": content}) self._maybe_trim() def add_tool_result(self, tool_name: str, result: str, keep: bool = False): entry = f"[{tool_name}] {result}" if keep: self.anchors.append(entry) else: self.work_seg.append({"role": "tool", "content": entry}) self._maybe_trim() def _estimate_tokens(self, messages) -> int: # 生产环境建议用tiktoken或对应模型的分词器,这里简化处理 return sum(len(m["content"]) // 2 for m in messages) def _maybe_trim(self): total = ( self._estimate_tokens([{"role": "system", "content": self.system_prompt}]) + self._estimate_tokens(self.anchors) + self._estimate_tokens(self.work_seg) + self._estimate_tokens(self.history_seg) ) if total <= self.max_tokens: return # 1. 先把工作段里最久远的一半压进历史摘要 overflow = total - self.max_tokens + self.anchor_headroom removed = self.work_seg[: len(self.work_seg) // 2] if removed: self.history_seg.append( self._summarize(removed) ) self.work_seg = self.work_seg[len(self.work_seg) // 2:] # 2. 历史段还需要继续压缩就做二次摘要 while self._estimate_tokens(self.history_seg) > 20000: self.history_seg = [ self._summarize(self.history_seg[: len(self.history_seg) // 2]) ] + self.history_seg[len(self.history_seg) // 2:] def _summarize(self, messages) -> dict: text = "\n".join(m["content"] for m in messages) prompt = f"请将以下对话/日志压缩成一段不超过100字的摘要,保留与任务目标、用户偏好、已有结论相关的信息:\n{text}" summary = self.summary_llm.complete(prompt) if self.summary_llm else text[:100] return {"role": "system", "content": f"[历史摘要] {summary}"} def build_messages(self) -> list: return ( [{"role": "system", "content": self.system_prompt}] + self.anchors + self.history_seg + self.work_seg )

这个实现的几个细节值得说明:

  • _maybe_trim判断触发删除条件是总量超限而非"每步都删",避免过于频繁地调用摘要接口。
  • 工作段触发裁剪时,只裁一半、保留最近的一半,保证当前步骤的上下文连续不割裂。
  • 历史段设了硬上限(示例代码里是20000 token),超过后做二次摘要,保证这个区域不会再次膨胀。
  • 任务锚点追加通过add_tool_result(keep=True)传入,实际操作时还可以单独留一个update_anchor方法做结构化更新。

4.3 实际跑下来的效果数据

应用这套方案后,我在相同任务集上做了对比测试,结果如下:

指标优化前(不删记录)优化后(删记录)
平均单任务输入token消耗约138万约24.5万
平均任务完成时间约46分钟约19分钟
任务成功率78%94%
超出上下文导致任务中断率12%0%

最让我意外的是任务成功率的变化。优化的收益远远不止省成本,还直接降低了任务失败率。原因不复杂:删掉噪音之后,模型每一步决策时关注的信息更集中,生成结果的稳定性大幅提升。

5. 不该省的和不能删的:三条红线经验

删记录如果删过头,比不删更糟。项目上线后我遇到过几次"删出事故",总结出三条务必遵守的红线。

5.1 安全类信息永远不能被压缩

涉及账号、金额、身份、授权凭证等信息,任何情况下都不能被摘要压缩。摘要本质上是一种有损压缩,它擅长提取语义主干,但不擅长精确保留数值和标识符。一次摘要操作可能把"用户ID: 1003421"压缩成"用户账号",后面所有依赖这个ID的操作都会崩掉。

在实际落地时,我所有安全类字段强制走锚点通道,且一律禁止进入摘要流程。这条没有例外。

5.2 删除操作后必须验证可回溯性

无论删除策略设计得多周全,无法排除某次摘要失败了、或者某段信息被误判为噪音的情况。所以每一次裁剪,都把原始内容做持久化存档。这个存档不仅能用于排查问题,还能在任务出错时做"上下文回放"——把Agent每一步看到的历史原样恢复出来,分析决策链路。

我在项目里把每次裁剪前的完整上下文和裁剪后的摘要都存进了日志表,字段大概是task_id、step、before_messages、after_messages、created_at。一次排障时,这套回放日志帮我直接定位了一个"中间步骤结论被压缩丢失"的隐性bug,节省了大概一整天的排查时间。

5.3 异常恢复期不要轻易删记录

如果Agent当前处于异常分支——比如某次工具调用失败、用户表达含糊、模型输出格式不对——此时不要触发大面积删除。异常期往往需要更多历史来理解"为什么走到这一步",贸然压缩会丢掉因果链,导致Agent在错误方向上越走越远。

我的做法是给ContextReducer加了一个"异常锁":检测到最近一次工具调用失败或模型输出异常时,暂时禁用工作段裁剪,只允许锚点更新和历史段二次摘要这类保守操作。等到任务恢复稳定后再重新启用完整删除策略。

6. 从删记录到记忆分层:长任务Agent的下一步演进

删记录只是解决了"历史太冗余"这个单一问题。顺着"Agent怎么管理自己的记忆"这个思路往深走,你会发现真正能扛长任务的Agent,需要的是一套完整的分层记忆机制。删记录是其中最重要、也最容易落地的一环,但它不是终点。

以我现在的项目为例,删记录做了之后,下一步我在试的是给Agent增加主动记忆意识——让它不只是在超限时才被动裁剪,而是在每个里程碑结束时主动总结、主动提炼锚点、主动丢弃噪音。这套机制比"按token阈值触发删减"更接近人类的工作习惯:普通人做长任务,不会事无巨细地把每个操作都刻在脑子里,他们会做笔记(锚点)、会遗忘细节(摘要)、会专注当前(工作段)。

从实施路径看,删记录是一次性低成本改造,当天就能看到token成本和延迟的改善;记忆分层是持续性的结构调整,需要更精细的设计,但收益也更大。目前我已经在客服Agent上试了主动里程碑总结的版本,上下文体积进一步下降了40%,用户长对话场景的体验提升很明显。

如果你正在做的Agent任务经常跑几十分钟以上,或者明明没有超窗口但越跑越慢,先别急着换大窗口模型。把历史扒开看看,该删的删掉、该收的收好,长任务自然就跑起来了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 18:36:23

让Agent自己选模型和工具:五件自制件实战解析

上个月&#xff0c;我把手头一个客服类Agent项目重构了一遍&#xff0c;起因特别简单&#xff1a;老板嫌贵、客服嫌笨。系统里的模型、工具全是我在代码里写死的——简单问题也去调大模型&#xff0c;复杂问题又经常被小模型的上下文长度卡住&#xff1b;工具倒是接了一堆&…

作者头像 李华
网站建设 2026/11/1 16:21:12

Ansys SIwave串扰仿真十步详解:从PCB导入到结果分析

做高速PCB的工程师&#xff0c;基本都绕不开串扰仿真。前几天一个项目里&#xff0c;DDR4的数据线在表层走了快60mm的平行段&#xff0c;时序裕量硬是掉了一截&#xff0c;最后用Ansys SIwave拉出来一看&#xff0c;近端串扰快3%了。这种问题如果在Layout阶段就能提前发现&…

作者头像 李华
网站建设 2026/11/4 9:10:35

ICM42688-P焊接与调试全攻略:从板级装配到数据精准校准

焊坏第一片ICM42688-P那天&#xff0c;我就知道这个项目值得单独写一篇。焊台上温度还没降下来&#xff0c;隔壁工位同事凑过来问&#xff1a;“这芯片做姿态检测、动作追踪不是挺热门吗&#xff0c;嵌入式系统里经常能看到它&#xff0c;怎么你焊了十分钟还在对引脚&#xff1…

作者头像 李华
网站建设 2026/11/2 13:49:23

如何用Markdown仓库打造个人技能管理系统

标题只有“skills”一个词&#xff0c;乍看有点抽象。如果你翻过GitHub上那些命名很随意的仓库&#xff0c;大概率见过有人把自己的技能清单、学习路径、项目履历全部塞进一个叫skills的目录里&#xff0c;时间久了变成一座越堆越高的信息废墟。我这篇想聊的&#xff0c;就是怎…

作者头像 李华
网站建设 2026/11/2 11:10:45

Java Agent流程引擎实战:状态轮转替代if-else与流式输出

1. 从if-else泥潭到Agent工作流引擎&#xff1a;我为什么重写流程调度最近在做一个Java Agent项目&#xff0c;接了意图识别、工具调用、RAG检索、人工确认好几套逻辑&#xff0c;一开始图省事&#xff0c;所有流程调度全用if-else硬写。等需求改到第三版的时候&#xff0c;我终…

作者头像 李华
网站建设 2026/10/23 17:57:41

雅思听力语料库5.1:从高频场景词到同义替换的提分方案

先说个现象&#xff1a;我这些年带雅思听力&#xff0c;发现一个特别扎心的规律——大部分卡在6分以下的学生&#xff0c;不是听不懂句子&#xff0c;而是“反应不过来”。听一遍录音&#xff0c;跟读的时候每个词都认识&#xff0c;合上书再看原文也觉得不难&#xff0c;但考试…

作者头像 李华