ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent落地三要素:记忆、目标与执行力的工程闭环

Agent落地三要素:记忆、目标与执行力的工程闭环

连着做了几个Agent项目之后,我越来越确定一件事:决定Agent上限的是模型本身,但决定它下限的,是记忆、目标与执行力这三块骨架。之前在做一个内部知识库问答项目时,最开始的版本就是把大模型接上对话接口,结果出现了三个特别典型的症状:用户前两天刚问过的问题,再问一次它像第一次听说;复杂一点的任务做到一半,它开始答非所问;需要调用查询接口的时候,它宁可一本正经地编一个答案,也不肯去查一下真数据。这三个症状,对应的恰好就是标题里说的记忆、目标与执行力。这篇东西就围绕这三件事展开,不讲花哨的Agent概念,只讲实际项目里验证过、踩过坑的落地思路。适合正在搭Agent、或者想让现有对话系统更“懂事”的工程师和产品同学看。

1. 失忆不是模型的锅:先把记忆层从上下文窗口里拆出来

1.1 上下文窗口是工作台,不是仓库

聊Agent记忆,最先被想到的方案肯定是“扩大上下文窗口,把什么都往里塞”。这个思路不能说错,但很偷懒。我见过不少团队把一年内的对话记录、几百页文档的要点全部塞进系统提示,prompt膨胀到几十K,效果反而更差。原因有三个:一是贵,每次请求都要重新处理一遍所有token;二是噪声,模型被大量无关信息干扰,抓不住重点;三是长文退化,内容越长,模型对靠后部分的注意力越容易衰减,经常出现“记住了前面忘了后面”的情况。

上下文窗口其实只负责工作记忆,好比办公桌上的空间——手头正在处理的文件放桌上没问题,但整个书柜的文件也全堆桌面上,桌面就彻底没法用了。真正该做的,是把那些低频但重要的信息放到长期记忆里,要用的时候再临时拿到桌面上。这种分层思路,是让Agent“记住事”的第一步。

1.2 长期记忆的两条主流路线:向量检索与情景轨迹

长期记忆我分成两类,处理和落地方式不太一样。

第一类是语义记忆,偏事实和知识。典型做法是把知识文本切块,用嵌入模型转成向量存进向量库,Agent需要回忆时,拿当前目标或问题去检索最相关的几块,再塞进上下文。这其实就是RAG的底层思路,只不过检索来源不限于文档,也可以是历史对话里提炼出的知识点。实践里我比较喜欢给每个记忆块打一个类型标签,比如“用户偏好”“领域知识”“历史结论”,检索时可以按类型过滤。

第二类是情景记忆,偏事件和过程。它记录的是Agent过去完成任务的轨迹:当时的意图、采取的动作、得到的结果、用户的反馈。这类信息适合按时间线存,并且要定期做结构化清洗。举个例子,之前某次任务里我先试方案A失败,后来换方案B成功,这个“A失败、B成功”的轨迹如果不沉淀,下次遇到相似场景还会傻乎乎再走一次老路。把轨迹抽象成一行摘要写进记忆库,下次遇到相似问题,检索出来的结果会直接提示“上次B方案可行”。

除了这两类,还要做定期摘要。记忆库无限膨胀是必然的,如果只往里写从来不整理,过两个月全是噪音。让模型每隔一定轮次把旧对话浓缩成摘要,本质上是替Agent写日记。摘要可以作为更上层的记忆,新开任务时先看摘要,再按需检索具体细节。

1.3 记忆读写的几个实操经验

记忆不是越能记越好,写不对会变成负担。先说写入。不是所有对话都配叫记忆,我自己的标准是“新、真、有用”三个词——对后续任务有增量信息、经过验证为真的结论、会影响未来行为决策的内容才写。用户随口说的牢骚、执行中的临时状态、尚未验证的假设,都只放在短期记录里。举个例子:用户说“这个报告风格我不喜欢”,这不算记忆;用户看着A方案说“以后都用B方案这种布局”,这才是值得写入的偏好。

再说读取。检索出的记忆不是越多越好,top-k我控制在3到5条,相似度低于阈值的宁可不给。原因很简单,噪声比缺失更致命——一条不相关的记忆混进上下文,模型就可能被带偏到完全无关的方向。还有一个细节是时间衰减:太久远的记忆默认降权,除非当前查询主动命中,否则不优先展示。Agent和人的记忆规律其实挺像,最近用的、经常用的更容易被想起,这套机制就是在模拟这个过程。

2. 目标不能只是一句提示词:把模糊意图变成任务档案

2.1 目标字段拆解:意图、验收标准、约束、优先级

很多Agent项目没把目标当回事,觉得把用户的话扔给模型就行。“帮我调研一下竞品”,这句话看着没啥问题,但一旦任务跨多轮、需要调多个工具,模型就开始歪。我对比过人开工做项目时的任务书:目标、交付物、质量标准、时间节点,写得很清楚。Agent也应该有对应的目标档案,至少四个字段。

  • 意图:最终要达成的状态,要具体且贴合工具能力。
  • 验收标准:可判定的完成条件,这是最重要的字段。模型靠它判断任务是否真正完成,没有它,模型经常在“干了一半”的时候就说“已完成”。
  • 约束:可用的工具范围、格式要求、截止时间、风格偏好等。
  • 优先级:有多个目标时谁先谁后,单任务Agent可以简单设一个P0/P1。

这样写目标档案就很实用了:

目标:调研三家竞品的核心定价策略
验收标准:输出一篇不少于1000字的对比文档,包含三家官网页面上可查证的定价信息,所有引用标注来源;文档保存为markdown文件;文档用中文撰写。
约束:只允许使用信息收集工具和网页浏览工具,禁止进入用户后台;总耗时不超过30分钟。
优先级:P0。

这样的目标档案,大模型才能当一个真正的人来使唤。验收标准尤其管用,它让模型知道“停下来”的条件,而不是以它的自我感觉为准。

2.2 规划路线:一口气拆解,还是边走边看

目标变成档案之后,下一步是把档案转成任务清单。这里有两条主流路线,项目里要分清哪个场景用哪个。

第一种是一口气拆解,把大目标拆成若干子任务,按顺序执行。比如写报告:收集资料、列大纲、写初稿、排版润色。这个模式优点是全局性好、资源开销稳定,每步子任务边界清晰;缺点是遇到意外容易卡壳,要么僵在原处,要么整个计划作废。

第二种是边走边看,模型每执行一步都基于观察决定下一步。典型代表就是“思考-行动-观察”循环,它灵活、适合探索型任务,比如排查一个未知报错,可以在途中随时调整;缺点是容易走偏,而且每步都做决策,token开销明显更大。

维度一口气拆解边走边看
全局方向性强,方案先行弱,容易偏航
对意外适应力差,计划易失效强,随时调整
成本可控性高,规划一次低,每步都要决策
典型适用场景报告写作、数据导出调试报错、开放探索

实际项目里我倾向做两层:高层用一口气拆解,把任务分到“子任务”粒度;每个子任务内部再用边走边看的方式走。既保住全局方向,又保留单步灵活性。只给模型一个纯长链条任务,本质上是把决策负担全压给它,不靠谱。

2.3 目标记忆:让模型每轮都知道自己在干什么

目标漂移是长任务里最常见的问题:一开始的目标是A,做到第10步变成和A相似的B,最后糊里糊涂交了B的成果。根因是目标没有作为一种显式记忆持续出现在决策上下文里,被中间过程产生的信息淹没了。

解决思路也很朴素:把目标档案做成一个常驻记忆块,每次构造上下文时放在最前面。同时,每轮决策前强制模型做一次轻量“目标复述”——用一句话说出当前目标是什么、下一步动作要服务哪个目标。这多花一点token,但能让模型的注意力一直系在目标上。我实测过,加上这一小步之后,目标漂移的概率下降得很明显。

还有个细节:完成一个子任务后,要在目标档案里把它标记为done,避免模型重复做,也避免它误以为整个大任务已完成。目标档案因此也是任务状态记忆,它不是一个静态文本,而是和状态机联动更新的活文档。

3. 执行力藏在行动循环里:工具协议、结果校验与状态机

3.1 把外部行为变成结构化工具协议

执行力不是让模型“自由发挥”,而是让它的每个动作都有明确协议可遵循。外部世界对Agent来说就是工具集:查数据库、读写文件、调API、发消息都算。一个可靠的工具协议至少包含四件事。

  • 名称与描述:描述最重要的对象是给模型看的,要说清楚干什么、什么时候用。
  • 参数Schema:类型、必填、枚举、默认值都定义明确。
  • 返回值结构:区分成功返回和错误返回,错误返回里带上错误类型和建议。
  • 权限与副作用标记:哪些是只读、哪些会产生修改,方便Agent判断风险。

现代模型普遍原生支持函数调用,把工具Schema交给模型,它会直接返回结构化调用指令。这比早期“让模型输出一段JSON再去解析”稳得多——参数会被框架严格校验,少了必填字段当场就能发现。工具调用的边界定义得越清晰,Agent的“手”就越稳。

3.2 行动后必须闭环:观察、校验、重试或放弃

很多Agent Demo看起来能跑,一拿到真实环境就废,最大原因就是没有结果校验。它调了一个查询接口,拿到一个非空返回就当作成功,结果返回体里装的是一个错误提示,它照样往下推理,最后产出一份建立在错误基础上的报告。

闭环的最小步骤是:行动完成后先校验结果——对照成功返回的Schema,看关键字段是否有效、是否真的符合期望。这一步看似多余,但能拦住一大半事故。校验之后分情况处理:

  • 能重试的错误(网络超时、临时限流):做退避重试,次数设上限,我一般设3次。
  • 不能重试的错误(参数错、权限不足、目标资源不存在):别在原地打转,把错误信息完整记录,退回给规划层换一条路径。

最难的一点是让模型自己区分错误类型,如果协议里已经给错误打了标签,这一步就简单得多。错误分类做得好,Agent才不会像个无头苍蝇一样反复撞同一个坑。

3.3 子任务状态机:让进度可追踪、可恢复

Agent执行多步骤任务,应该维护一张任务状态表,而不是让状态散落在对话流里。我习惯用五个状态:待执行、执行中、已完成、已失败、已放弃。每个子任务旁边记着:对应目标中的哪一条、当前执行到哪一步、最后一次结果摘要。

有这张表有几个好处。其一,避免重复执行——机器上如果已经有一份结果,下一个动作就不该是再生成一遍。其二,能支持中间检查——用户问“现在卡在哪”,直接把状态表丢给它。其三,支持中断恢复——一次较长的任务如果因为超时或服务重启中断了,状态表加情景记忆,能让Agent从断点继续,而不是白干一遍。

我把状态表当成目标和执行器之间的“缓存层”。目标档案描述这个世界想要的样子,状态表描述当前任务走到哪一步,执行器只负责攥着当前子任务往前跑,看到已失败就往回想原因。这套结构一上,Agent的“会议感”会立刻变得清晰很多。

4. 把记忆、目标、执行力拼成一个闭环:一个可复用的Agent内核

4.1 数据流:从输入到沉淀,六步一个循环

前面三板斧拆开讲了,但它们真正发挥作用是在一个闭环里。我搭过的Agent内核,循环大概长这样:

  1. 接收输入,把用户需求解析成目标档案。
  2. 加载目标档案和状态表,同时从长期记忆里检索相关记忆块。
  3. 规划器决定下一步做什么,是拆新任务还是选一个待执行子任务。
  4. 执行器把子任务转成一次工具调用。
  5. 校验结果,把“做了什么-得到了什么-是否成功”写入情景记忆,并更新状态表。
  6. 回到第二步,直到验收标准达成。

这六步里,记忆不只是被读取,也在不断写入。长期记忆像一个不断变厚的笔记本,每个任务的轨迹都被压缩成条目,供未来任务使用。目标档案在循环里始终是决策的头号输入,执行力则保证每一步都是可验证的增量,而不是模型的一张口说。

4.2 一个足够小的内核骨架

代码我不喜欢依赖重框架,核心逻辑两块:一块是记忆检索与外部状态管理,另一块是决策循环。下面的骨架直接能看出三件事怎么咬合:

class Tool: def __init__(self, name, description, parameters): self.name = name self.description = description self.parameters = parameters def run(self, **kwargs): raise NotImplementedError class AgentCore: def __init__(self, llm, long_term_memory, tools): self.llm = llm self.memory = long_term_memory self.tools = {t.name: t for t in tools} self.goal = None self.state_table = {} def receive_task(self, user_input): self.goal = self.llm.parse_goal(user_input) self.plan = self.llm.build_plan(self.goal, tools_desc=self.tools) def build_context(self): # 1. 目标档案永远放在上下文最前面 goal_block = format_goal_block(self.goal, self.state_table) # 2. 只检索和当前意图最相关的少量记忆 related = self.memory.retrieve(self.goal["intent"], top_k=3) # 3. 最近一轮轨迹用于衔接 recent_trace = format_recent_trace(self.trace[-2:]) return assemble(goal_block, recent_trace, related) def step(self): ctx = self.build_context() action = self.llm.choose_action(ctx, self.tools) if action["type"] == "call_tool": tool = self.tools[action["name"]] result, error = safe_run(tool, action["arguments"]) # 4. 结果回到记忆,更新状态表 self.record_trace(action, result, error) self.update_state_table(action, result, error) return action, result

build_context体现的是“记忆检索+目标常驻”,step把决策收敛成一个可校验动作,record_trace和update_state_table负责闭环。真正跑起来的时候,你只需要在这个骨架上填具体的工具实现、记忆存储介质,以及模型接口。

4.3 为什么这个结构能稳住Agent的行为

单独看每个模块都能找到更花哨的替代方案,但真正稳定一个系统的,往往不是哪个模块,而是它们之间的约束关系。记忆层把历史经验变成决策依据,防止Agent从零开始瞎试;目标层把决策范围收束到当前任务的验收标准上,防止发散;执行层把“说了”和“做了”之间的空白填上,让每一步都有结果可沉淀。

换句话说,记忆管“过去”,目标管“现在”,执行管“未来”。过去经验不用每次都交学费,现在方向不跑偏,未来结果可积累。你可能听过“智能体循环”之类的说法,本质上就是这三件事在转。先把这三件事做成硬编码的机制,模型只是机制内部那个最聪明的“思考者”,会比完全依赖模型的自觉稳定得多。

5. 实战中绕不开的坑:记忆污染、规划权限与成本控制

5.1 记忆污染:记错的比记对的更可怕

加了记忆之后,一个新问题出现了:模型记住了不该记的,或者记住了错误结论,后续任务一直被带偏。我在某客服项目里亲眼见过:Agent第一次尝试某个方案失败,它把失败原因当成了“永久结论”写入记忆,之后每次规划都绕着那条路走,理由是“之前试过,不可行”。但其实那次失败只是因为当时某个临时状态不对。

应对办法我总结成三条:

  • 写入前校验:只有执行成功且没有被用户否定的结果,才允许沉淀为长期记忆。
  • 时间戳优先:用户后来的纠正必须覆盖早期记忆,同类条目冲突时新的赢。
  • 定期重排:把重复条目合并,冲突条目标记出来,交给人工或模型重新确认。

记忆系统的价值不在于存了多少条,而在于存下来的条目的可信度。宁可记忆少一点,也不要让错误的记忆反复毒害后面的任务。

5.2 规划器和执行器,到底谁说了算

两层结构里最常见的内耗是:执行器遇到一点小问题就去找规划器重新规划,结果每次重规划都产生一套新方案,反而让进度更乱。反过来也有问题:执行器太顽固,明明外部环境已经变了,还守着旧方案硬刚。

我的分工原则是:执行器在单个动作粒度上有充分自主权,比如换个检索关键词、换一种写法;遇到连续失败或者发现外部条件变化时,才把控制权交回规划器。触发重规划的阈值要提前定义,我一般用“连续两次失败”或者“出现未登记的错误类型”作为标准。还有一个隐蔽的坑是重规划后状态表要同步更新——如果不更新,新方案执行到一半又会被旧的已完成记录扰乱,导致Agent反复横跳。

5.3 评估驱动改进:不只是看能不能跑

迭代Agent不能只靠感觉。我习惯在项目里定义几个硬指标:

指标作用
任务完成率按验收标准判断整体任务是否成功,反映骨架整体有效性
平均交互轮数衡量效率,轮数太多说明上下文或检索有问题
有效工具调用占比成功且有意义的工具调用除以总调用次数,反映工具协议质量
用户修正次数用户主动纠正Agent的次数,最能反映真实体验
单次任务Token开销控制成本的重要指标,防止方案“太贵跑不起”

成本控制我有三个固定动作。检索top-k限制在较小值;上下文只放“目标档案+最近两轮对话+检索到的关键记忆”,缓存层和状态表不作为完整文本进上下文;模型分级,常规动作、简单执行用便宜的小模型,规划、总结、冲突处理这种需要全局视野的环节才用能力更强的模型。很多项目一上来就把所有环节都用最强模型,成本翻倍收益却并不明显。

做Agent做到现在,我个人最大的体会是:不要把Agent理解成一个会聊天的模型,而要理解成一套“围绕目标循环运转的系统”。记忆、目标、执行力这三个词听起来像概念,拆开做其实都是很朴素的工程:该存的东西要存得住、存得对,该守的方向要守得住,每一步行动都要有校验和沉淀。如果你正准备动手做一个Agent,我建议先挑一个你最常做的重复性任务,把这个任务做成具备三要素的闭环,跑通一次之后再考虑扩展。等这个闭环转起来,你会开始看到Agent越来越像“一个成员”,而不是一个偶尔聪明的聊天窗口。最后再分享一个小技巧:每一次执行结束以后,在长期记忆里写一句话的总结,成本极低,但它会是你的Agent用得越久越顺手的核心原因。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进