ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

智能体上下文管理:删记录比堆窗口更能跑赢长任务

智能体上下文管理:删记录比堆窗口更能跑赢长任务 1. 从不迷信“无限上下文”说起这两年大模型厂商一个比一个卷模型上下文窗口从 32K 一路干到 1M恨不得把“全书”都塞进一次对话里。1M 上下文是什么意思直观一点说大概相当于一次性读完整套《三体》三部曲还有富余。但是做过真实业务的人都知道窗口大不等于能用好更不等于长任务就能稳定跑到底。我踩过的坑很典型把一个十几步的长链任务交给智能体开头几步一切正常步骤越多脑子越糊涂到后面要么答非所问要么干脆把前面已经确认过的结论又推翻重来。很多人第一反应是“上下文不够了换更大窗口的模型”但真正的问题往往不是窗口不够而是上下文里塞了太多不该塞的东西模型在前面的信息里被反复拉扯。这时候就算换 1M 窗口的模型该乱还是乱。说白了上下文就像一个办公室桌面。桌面再大你把所有文件、邮件、便签全部摊开找东西照样困难。真正高效的工作方式是随时把没用的垃圾扔掉只保留当前这步需要的那几张纸。智能体也一样与其追求无限大的上下文不如学会自己删记录、自己整理工作台。这就是今天要聊的核心话题给智能体装上“上下文管理”的能力让它跑长任务时始终保持头脑清醒。这篇文章适合正在用 Dify、Coze、LangChain 这类平台或框架搭智能体的开发者也适合被“上下文超长”折磨的业务人员。我会结合自己实际跑过的一个长任务案例从原理拆解到可落地的工程方案一步步说明白为什么删记录比堆窗口重要以及具体该怎么实现。2. 上下文失控才是长任务的真正瓶颈2.1 你以为的“上下文”和模型看到的“上下文”不是一回事先说个容易被忽略的问题上下文窗口到底装的是什么很多人以为就是用户聊天记录其实在智能体里上下文远比这复杂。它至少包含四类东西系统提示词里写的角色设定、任务规则、输出格式要求用户本轮以及历史轮次的输入智能体调用工具后的返回结果、查询到的外部数据智能体自己的思考过程、中间结论、决策记录。这些信息全部拼接在一起才构成一次请求里模型实际看到的内容。问题就在这里智能体每执行一个步骤就会往上下文里追加一段内容步骤多了历史累加即使每条信息都很短累积起来也会迅速逼近窗口上限。我遇到过最夸张的一次某个工作流只跑了 6 步上下文里就堆了 3.2 万 token里头一大半是中间过程的完整 JSON 输出比如某个搜索接口返回的 20 条结果原文。模型每走下一步都要重新读一遍这些内容既浪费 token又制造大量无关干扰。等到第 7 步需要真正做决策时关键信息已经被淹没了。所以判断上下文是否够用不能只看“总 Token 数 窗口大小”更要看“有效信息密度”。上下文失控的本质不是容量不够而是信噪比太低。2.2 长任务为什么会“越跑越笨”长任务跑崩通常不是突然崩的而是逐步劣化的。我总结过它的典型过程基本符合这条曲线前几步模型记忆清晰每步执行很果断工具调用准确。中间几步上下文里开始夹杂大量之前步骤的中间结果。模型需要花更多注意力去分辨哪些是有用的。后段上下文里已经堆了太多不同步骤的片段模型开始前后矛盾表现为重复询问已给过的问题、重新生成之前已经确定的表结构、输出与任务目标明显偏离。最后完全失控。要么吐出一堆无意义内容要么直接报错超过上下文限制。拿我调试一个销售线索清洗智能体来说任务本身不复杂读取原始线索表 → 去重 → 补全企业信息 → 按行业打分 → 输出分级结果。前 3 步跑得非常漂亮到第 4 步开始发疯——把已经完成去重的结论又拿出来重新判断然后怀疑原始数据有问题竟然又去调了一次查询接口。我打开调试日志一看第 4 步发起请求时携带的历史消息里包含了前面 3 步所有工具返回的完整 JSON里面有用信息不超过 20%剩下全是无关字段和过程数据。模型不是不聪明是实在被噪声淹没得没法聪明了。2.3 “大窗口”为什么救不了你有人会觉得那换个大窗口模型不就行了吗比如从 32K 换到 128K所有历史都塞得下模型总能看到完整过程了吧实际情况是模型对上下文不同位置的关注度是不均匀的。业界做过很多实验也验证了这一点距离当前时间越早的内容注意力越淡特别长的输入中段往往是模型最不擅长的区域。窗口越大这个问题越明显——不是模型不想看是注意力机制决定了它很难同等对待每一段输入。有人把这种情况叫“迷失在中间”。更现实的问题是成本。大窗口模型价格更高响应更慢。一个 10 步的长任务每一步都要重新把所有历史发给模型Token 消耗随窗口增长近乎线性上升。窗口开 128K每跑一趟完整任务光输入可能就吃掉几万 Token跑一次成本可能够小模型跑几十次了。所以我的观点一直很明确大窗口是兜底能力不是日常方案。真正支撑长任务可靠运行的核心能力是智能体是否具备根据任务阶段动态裁剪、压缩、重组上下文的能力。3. 给智能体装上“记忆管理”机制3.1 删记录的核心思想从“全量记忆”变成“按需取用”要解决上下文失控思路得先扭转过来。人类完成长任务靠的不是把所有信息背下来而是把每个阶段的关键产出记录在合适的地方到下一步时只拿出需要的那部分。智能体也应该这样。具体来说一个合格的上下文管理机制应该做三件事及时清零每一步执行完毕后把已经用过的原始数据、不再需要的中间结果从上下文里移除。精简保留只保留对后续步骤有影响的关键信息比如最终结论、筛选条件、确认过的决策。按需取回后续步骤需要某条信息时从外部存储里定向取回而不是天然带着所有历史走。做到这三点上下文里就永远只装“当前这步该做的事”和“全局必须记住的少量结论”。Token 占用会大幅下降模型注意力也能集中在当下最关键的内容上。3.2 三种记忆类型对应不同删除策略要把这套机制落地首先要对记忆做分类。我在实践中通常把智能体的记忆分成三种短期工作记忆、长期项目记忆、静态规则记忆。三类信息的生命周期完全不同处理策略也不同。短期工作记忆是任务执行过程中产生的临时数据比如上一步的工具返回结果、临时循环变量、中间计算值。这类信息的特点是“用完即弃”应该在下一步开始前主动清理。典型例子是一个查询接口返回了 50 行商品数据你把其中符合条件的产品 ID 提取出来那 50 行原始数据就没有保留价值了直接从消息列表里删掉。长期项目记忆是整个任务生命周期内必须稳定的关键信息比如用户的核心需求、已经确定的目标函数、已产出的阶段性结论。这类信息必须保留但保留的形式不是原始长文本而是提炼后的摘要或结构化字段。比如“用户要求所有价格单位按美元计算”这种全局约束就应该以一条规则的形式固定下来而不是留在某段历史对话里自然延续。静态规则记忆是角色设定、输出格式、安全限制这类内容它们从头到尾不能变。这类记忆不参与任务过程的增删管理但要注意它们占用的 Token 数规则太长的要压缩表达避免每次请求都携带一大坨无用的设定文本。3.3 没有外部记忆的“删记录”是耍流氓如果删完就彻底扔掉那后面的步骤需要前面的信息时怎么办所以实现删记录的前提是同时具备“外部记忆存储”能力。简单来说就是把关键信息写到外部存储里数据库、向量库、Redis 都行上下文里只留一个轻量引用或关键摘要。我在 Dify 里跑长任务时常用的组合是工作流内部变量保存短期关键值外部知识库或者直接建一张简单数据库表保存跨步骤结论。每执行完一个阶段把该阶段的核心产出整理成一条简洁记录存入外部存储然后把这条信息对应的完整上下文从会话消息中移除。这样做有双重好处一方面上下文变干净模型不再被噪声干扰另一方面外部存储里的记录是结构化、可检索的后续步骤可以精确取用比让模型从一大段历史里自己找要可靠得多。4. 实战在 Dify 工作流里实现长任务上下文瘦身4.1 一个典型的“上下文超长”工作流案例前段时间我帮人搭过一个内容审核工作流任务链是这样的抓取文章原文 → 正文清洗 → 敏感词检测 → 语义风险判断 → 生成审核报告。单篇文章正文平均 6000 字转换成 Token 差不多 8000 左右。如果每步都把原文带在上下文里第 1 步结束后上下文就有 9000 Token第 2 步结束时超过 1.8 万到第 4 步时已经接近 3 万。虽然有上下文 32K 的模型能硬扛但实际跑的时候经常出现检测结果前后不一致一个明明安全的词到了第 4 步突然被判为高风险。问题就出在上下文堆积上。前一步的判断依据和后一步需要的信息其实完全不同。比如敏感词检测需要的是全文文本而语义风险判断需要的是检测命中的词列表及所在段落摘要至于文章开头那些大段背景介绍后面完全用不上。改造方案很明确每个步骤执行完就把这步的输入原始内容从会话消息中移除只往下游步骤传递结构化的小体积结论。4.2 关键实现步骤消息改写与变量传递在 Dify 这类可视化工作流平台里不需要改一行代码也能实现上下文瘦身。我的做法是充分用好两个核心组件消息处理节点Message Tool和变量赋值节点。以内容审核工作流为例我的节点编排如下第一步文章原文作为用户输入传入工作流先经过文本清洗节点输出清洗后的正文。这个正文会作为后续检测节点的输入。第二步把清洗后的正文存入一个临时变量比如article_clean。紧接着用一个消息处理节点把当前会话中的用户消息替换成一句简短摘要比如“用户提交了一篇文章已完成清洗正文字数为 X”。这一步相当于直接抹掉了原文在上下文里的大段存在感。第三步敏感词检测节点从变量article_clean读取正文进行检测输出命中列表。此步结束后再次用消息处理节点把当前上下文中的这篇文章内容删除同时把命中结果写入新变量word_hits。第四步语义风险判断节点读取word_hits和对应命中词的上下文片段不再读取全文。第五步生成审核报告使用最终的word_hits、risk_level、summary等变量输出。这套流程跑下来上下文里从头到尾不会出现完整正文。每一步模型实际看到的信息量很小相当于每执行完一步就“翻篇”一次模型永远只面对当前这一张写得清清楚楚的笔记而不是背后那堆原始档案。4.3 把“删”做成智能体自己的技能上面的方案适合固定流程的工作流。但如果你的智能体是自由对话式的任务路径不可预知那单纯靠编排就不够了需要让智能体自己学会“何时删、怎么删”。我在基于 Dify Agent 节点做自由任务智能体时采用的方式是给智能体配置一套上下文管理工具类似这样extract_key_info(source_text, keep_fields)把一段长文本提炼成精简摘要或指定字段的结构化数据。drop_history(range_or_index)移除当前会话中指定的历史消息区间。write_note(key, value)把关键结论写入外部记忆存储。read_note(key)从外部记忆读取指定结论。clear_scratchpad()清空所有临时过程中产生的消息记录。然后在系统提示词里明确写入使用规则这部分规则要写得非常具体否则模型不会主动执行。我实际使用的提示词片段是在执行每个任务步骤前先检查当前会话中的历史消息。如果历史消息中存在“原始工具返回值、上一步骤的完整输出、已被提炼过的源文本”同时它们与当前步骤无关先调用 drop_history 清除。清除前确保已经把必要结论通过 write_note 保存。这套方法跑下来的效果非常明显。拿一个资料调研类智能体来说改造前它需要让它读 20 篇网页文章跑到第 8 篇时模型的记忆就开始混淆来源改造后每篇文章读完只留下一句话的核心观点笔记上下文里始终只有当前这篇文章的内容跑完全部 20 篇依然思路清晰。4.4 一个 Python 版本的极简实现如果你不是用平台搭工作流而是用代码直接调模型 API那么上下文管理的实现更直接。核心就是自己维护 message 列表在每次调模型前手动裁剪。下面我把自己在 Python 里常用的一套极简实现贴出来核心思路是用一个NoteStore保存必须跨步骤保留的信息每轮对话结束后把消息列表做一次瘦身。import json from typing import Any, Dict, List class NoteStore: 轻量外部记忆用 dict 充当存储生产环境可换数据库/向量库 def __init__(self): self._notes: Dict[str, Any] {} def write(self, key: str, value: Any): self._notes[key] value def read(self, key: str, defaultNone): return self._notes.get(key, default) def to_system_block(self, keys: List[str]) - str: 把指定 key 的笔记汇总为一条精简的系统提示片段 lines [] for k in keys: if k in self._notes: lines.append(f{k}: {json.dumps(self._notes[k], ensure_asciiFalse)}) return \n.join(lines) def compact_messages(messages: List[Dict], keep_last_n: int 4, note_block: str ) - List[Dict]: 简单的上下文压缩策略 1. 系统提示词 最近 keep_last_n 条消息 2. 额外附加 note_block 作为外部记忆摘要注入 system_msgs [m for m in messages if m[role] system] recent messages[-keep_last_n:] compacted system_msgs recent if note_block: # 把笔记摘要挂到第一条 system 消息末尾确保模型感知全局关键信息 merged compacted[0].copy() merged[content] merged[content].rstrip() \n\n[外部记忆参考]\n note_block compacted[0] merged return compacted def run_smart_agent(step_inputs: List[Dict]): store NoteStore() messages [{role: system, content: 你是一个能管理自己上下文的智能体。}] for idx, step in enumerate(step_inputs): # 每一步只追加当前步骤的输入 messages.append({role: user, content: step[prompt]}) # 模拟调用模型的返回实际场景里这里调用 LLM API # 这里以一段简单伪代码代替 assistant_reply f第{idx1}步完成产出关键值: {step.get(key)} messages.append({role: assistant, content: assistant_reply}) # 提取关键信息存入外部记忆 if step.get(note_key): store.write(step[note_key], step[note_value]) # 步骤结束后压缩消息列表仅保留最近 4 条 # 同时注入外部记忆摘要让模型拥有全局视野 messages compact_messages( messages, keep_last_n4, note_blockstore.to_system_block([rule, confirmed_result]) ) return messages # 使用示例 step_list [ {prompt: 读取原始数据文件统计总行数。, note_key: line_count, note_value: 12890}, {prompt: 根据规则过滤无效行规则statusactive。, note_key: rule, note_value: 仅保留statusactive的数据}, {prompt: 基于上一步结果进行分组统计并输出各城市数量。, note_key: confirmed_result, note_value: 城市数量已统计北京3782上海2941}, ] final_messages run_smart_agent(step_list) for m in final_messages: print(f{m[role]}: {m[content][:80]})这个极简版本的核心洞察是不要让模型通过自然对话历史去记忆重要信息而是把重要信息显式提取出来作为系统提示的一部分注入。历史消息被裁掉之后模型失去了“原文”但该记住的结论都在note_block里稳稳妥妥地带着。这就既瘦身又不失忆。5. 更系统的上下文管理三板斧5.1 清理把用完即弃的信息坚决扔出去实际落地时很多人会心软觉得“万一之后还要用呢”然后就把原始数据留在上下文里。我在自己项目中定了一个原则没有明确说明“后续第几步要用”的数据一律视为用完即弃。判断一条消息是否该删可以按这三个条件自检这条信息对当前步骤之后的任意已知步骤是否有影响如果没有删除。这条信息是否已经被提炼成更高层的结论如果是删除原始内容保留结论。这条信息是否可以随时从外部存储重新取回如果可以删除只保留引用标识。按这个标准执行下来通常一个 10 步任务只需要在上下文里保留不到 20% 的信息。5.2 摘要长资料变短结论再喂给模型清理不一定非要删除。对于某些还需要参考、但原始内容太长的中间产物最适合的方法是先摘要再让模型读摘要而不是读原文。我在内容审核案例里用的就是摘要方案敏感词检测步骤需要全文但语义判断步骤只需要“命中词列表 命中位置所在句子”。那么在执行完检测后我就把检测结果整理成命中词列表[沟通, 安排, 搞定] 命中位置第2段第3句、第5段第1句 涉及上下文……这样原本几千字的正文就被压缩成几十字的结构化摘要。模型后续判断时拿到的信息密度非常高决策质量反而比读全文更好。具体实现时可以使用单独的摘要节点也可以用模型自己来提炼。建议摘要节点的模型用高性价比型号比如更小的模型专门拿来压缩文本大模型只做最终决策成本能省不少。5.3 引用让模型学会“按需查档”而非“抱着一堆档案干活”删和摘要解决的是“过去的信息”还要解决“未来的信息”。长任务做到后面某一步会需要早期步骤的某个具体数据那这时候怎么办不能靠上下文自动携带而应该提供“查档”能力。我在智能体技能列表里加入了一个query_memory(keyword)工具。模型在执行某一步时如果发现缺少信息会主动调用这个工具从外部的记忆库中检索相关记录。这个工具的底层可以是一个简单的关键词匹配也可以直接查数据库表甚至接向量检索。这样做的好处是信息存在外部需要时才取回来取回来的只是和当前步骤相关的那一小块。上下文里不会因为某条信息“以后可能用到”就一直占着位置。我见过很多失败的长任务智能体它们的共同问题就是没有“查档”意识。模型不知道信息存哪了只能指望历史里碰运气。这个工具一加上相当于给了模型一个明确的信息获取入口它就不再被动依赖积累了。6. 常见问题与排查技巧实录6.1 删完记录后模型失忆了怎么办这是最常遇到的问题。我用“删记录”方案改造的早期版本里模型经常忘记全局约束。比如用户明确说过“所有输出要按中文列名”但是跑到第五步时模型突然开始输出英文列名。排查后发现问题出在我把用户原始约束也当成“用完即弃”信息删掉了。而全局约束显然是长期记忆不应该被清理。修正方案区分“全局固定信息”和“步骤临时信息”。全局固定信息本来就不该放在普通对话消息里而应该放进系统提示词的固定部分或者通过write_note写入外部记忆然后每一步都把它注入note_block。这样即使用户消息被删约束也不会丢。另一个失忆来源是模型需要某条信息但该信息已经被清掉并且没有写入外部记忆。例如模型在第一步自行推测出一个重要结论但你没有显式提示它“把结论保存”后续步骤就无法访问。解决办法是在系统提示词里强调每个关键结论产出后立即调用 note 相关工具保存保存后才能继续下一步。6.2 上下文明明不长但任务还是乱有朋友跑过来问我他的工作流每步输入都很短上下文总共才 2000 token怎么任务还是跑不对我看了看他的工作流设计发现问题的根源不是上下文太长而是他的节点之间传递的是“模型生成的自由文本”不是结构化数据。比如第一步让模型“提取客户名称”返回的是自然语言“有两个客户A公司和B公司”第二步又让模型“比较这两个客户的规模”它从自由文本里解析公司名一旦表达稍有变化就找不到目标。这不是上下文管理能解决的问题这是数据流设计的问题。正确做法是让工具节点输出 JSON 或结构化数据并存到变量里后续步骤直接用变量引用而不是让模型从文本中二次提取。上下文管理的目标不是把所有问题都变成“信息太长”而是让信息在恰当的结构中以恰当的形式流动。如果你发现信息很短但模型还是乱大概率不是删得不够多而是信息结构没有理清。6.3 删除后 token 降下来了但模型变“目光短浅”了还有一个副作用值得警惕上下文删得太狠模型容易变得只盯着眼前几步失去全局规划能力。比如一个数据统计任务第 1 步要读取多张表第 5 步要把这些表做 join。如果你在第 1 步结束后就把表的字段信息全删了到第 5 步模型根本不知道原表长什么样自然没法正确 join。针对这种情况我的经验是保留“元信息”删除“实体内容”。比如一张媒体表实体内容可能是几千行数据这不能放在上下文里但表的字段名、行数、主键、与其它表的关系这些元信息很小却极其关键必须长期保留。类似地前面所有步骤的关键决策结论、已经确认过的口径、统一过的术语也都属于元信息。具体做法在每步结束后除了写note额外写一个meta_note里面记录“这一步处理对象的结构信息 这一步产出的关键决策”。模型在后续步骤需要时可以直接在系统提示里看到所有历史的关键决策摘要这些摘要加起来通常不超过几百 token。我实践下来的配置是上下文窗口如果只有 8K那么历史消息只留最近 3 轮但meta_note允许容纳全部步骤的关键决策最多 1K token。这样模型既有眼前的操作信息又有全局的结构信息长任务跑起来非常稳定。6.4 快速自查表你的智能体是否需要上下文管理如果你还不确定自己的智能体该不该做这套改造可以参考下面的自查表凡是有两条以上命中就说明上下文管理已经在拖后腿了。检查项命中说明任务步骤超过 5 步时模型开始重复提问或答非所问历史噪声干扰了注意力工具返回的原始 JSON 直接暴露在后续对话中上下文信噪比低Token 消耗比预估高 30% 以上大量无效历史被反复携带同一个任务在不同时间跑结果偏差严重模型注意力在不同历史区间漂移模型会自己追溯前面步骤的中间产物并试图修改它已经分不清当前该干什么如果你的智能体命中了两条以上别急着换更大窗口的模型先做上下文管理。大概率你会发现问题解决得比想象中快。7. 最终的一点实战体会从“堆窗口”到“删记录”这个观念转变我花了挺长时间才真正想明白。刚开始做长任务智能体时我也总担心信息删了会丢拼命保留一切后来被一个大参数模型在 16K 上下文里跑崩了三次之后才下定决心彻底清理消息。结果是任务成功率反而从不到 60% 提升到了 90% 以上。再分享一个小技巧调试智能体时别只看最终答案一定要打开每次请求的消息列表逐条看看模型实际看到了什么。很多时候你以为它看到了全貌其实它只看到了一坨杂乱的历史。每当你发现一条历史消息对当前步骤毫无帮助那就是该删的记录。上下文管理不是一个高大上的算法而是一种朴实的工程习惯。你需要的不是什么复杂的 Agent 框架而是时刻问自己这一段信息模型现在真的需要吗如果答案是否定的就把它从工作台上拿开。长任务跑得起来从来不是靠超大的口袋而是靠随时腾出干净的桌面。
返回列表