ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Agent工作原理详解:从ReAct循环到记忆与工具调用的架构拆解

Agent工作原理详解:从ReAct循环到记忆与工具调用的架构拆解

这个系列聊到第三篇了,前两篇我们把Agent能做什么、为什么值得折腾说得七七八八,但这几天还是陆续有读者在问同一个问题:Agent到底是怎么工作的?问得多了,我觉得确实有必要花一整篇把内部机制摊开来聊清楚。

先说结论:Agent不是某个新发布的大模型,而是一种架构模式——它由“一个大模型 + 一套调度逻辑 + 一组外部工具 + 记忆读写能力”组合而成。所谓“它会干活”,本质上就是这套架构在反复执行同一个循环:观察现状、思考下一步、调用工具、拿到结果、再观察。这篇就是把这个循环的每一个环节拆开,讲讲LLM在里面到底承担什么、规划是怎么落地的、记忆存在哪里、工具调用又经历了什么。

这篇内容适合几类人看:想搞清楚Agent原理再决定要不要上手的开发者,已经在用某个Agent框架但总觉得隔着一层纱的同学,以及需要向团队解释“Agent为什么不能按我预期干活”的技术负责人。纯入门也没关系,后文涉及的关键概念我会尽量用大白话和实际案例拆开讲。

1. Agent和聊天机器人到底差在哪

1.1 对话是“表达”,Agent是“执行”

很多人第一次接触Agent时都会下意识觉得:它不就是个更聪明的聊天机器人吗?不是的。这两者的根本差异在于,聊天机器人(Chatbot)的任务是“生成一段合理的回复”,而Agent的任务是“完成一个目标”。

举一个最简单的例子。你问普通聊天机器人:“帮我订一张今晚去A市的高铁票”,它能给你一段流程说明,告诉你打开哪个App、怎么选座位、怎么支付,甚至能说得很有礼貌、很详尽。但它永远不会真的帮你下单,因为它压根没有下单这个动作的入口。Agent不一样,它在接收这个目标之后,会把任务拆成多个步骤:查车次、选票、填乘车人、支付确认、出票。每一个步骤对应一个真实的系统动作,做完一步再走下一步,直到整个目标闭环完成。

这个差别的底层逻辑是:普通对话系统把大模型当成“知识的出口”,Agent则把大模型当成“决策的大脑”。同样是读了一堆资料才能回答,前者把答案说出来就结束了,后者要把答案转化成一连串对真实世界的操作指令。

1.2 用户感知不到的三个“隐藏引擎”

如果从架构视角看,Agent比聊天机器人多出三块东西,这也是它看起来“能干”的来源:

  • 规划引擎:负责把大目标拆解成小步骤,并且在执行过程中根据新信息不断调整步骤。
  • 记忆系统:负责记录当前任务进展、历史偏好以及跨任务的长期信息,让Agent的行为有连续性。
  • 工具执行层:负责把大模型“想做”的事变成真实操作,比如读写文件、查询数据库、调用API、发送消息。

这三块在用户界面上完全看不见,用户只感知到“这个AI能直接把事办了”。但实际开发里,这三块恰恰是决定Agent上限和下限的核心。模型选得再好,规划引擎写得稀烂,Agent照样会在一件简单任务里绕圈子。

1.3 一个最容易踩的误区:以为Agent是“某个新模型”

我被问过很多次:“是不是要等某个更强的模型出来,Agent才真正能用?”这个问题本身就把Agent理解错了。

Agent不是某个具体的模型,而是“架构 + 模型 + 工具 + 交互策略”的组合。同一个Agent框架,把底座模型从A换成B,任务效果会变化,但架构逻辑完全不变。反过来,哪怕底座模型很强,如果工具接口描述模糊、记忆检索混乱、规划策略粗糙,Agent依然会表现得像个“空有智商但没有执行力的人”。

这个认知会在你选型和排错时特别有用。遇到Agent表现差,先别急着怀疑模型能力,而是按“规划链路是否合理、记忆检索是否命中、工具调用是否正确”三个方向去排查,大概率能找到真正的问题所在。

2. 喊LLM干活的姿势:ReAct闭环与决策提示词

2.1 LLM在Agent里干的不是“回答”,而是“决策”

大模型本身是一个文本预测模型,它的强项是“根据上文生成下文”。在Agent架构里,我们需要利用的恰恰是这个能力,但把任务目标换一下:不再要求它生成给人类看的话,而是生成给“执行引擎”看的决策指令。

举个例子。系统给模型的输入可能是这样一段拼接起来的文本:

  • 你现在是一名任务执行调度器。
  • 用户的目标是:把会议纪要整理成日报并发送邮件。
  • 当前已有的历史记录如下:
    1. 你已经读取了会议纪要文件,发现里面有5条待办事项。
    2. 你还没确定收件人。
  • 你的可选工具列表包括:read_file、search_contacts、create_draft_email、send_email。
  • 请输出你下一步要做的动作,以及对应的参数。

模型读完之后,输出的不再是一段“回答”,而是一个结构化的动作标记,比如一个JSON:{"tool": "search_contacts", "args": {"query": "直属上级"}}。系统解析出这个JSON,去调用真实的通讯录查询接口,把结果拿回来再喂给模型,进入下一轮决策。

这就是Agent底层最朴素的运转方式:输入历史 + 当前状态 + 可用工具,输出下一步动作,循环往复。

2.2 ReAct:让“想一下”和“做一步”交替发生

在Agent的实际落地里,有一种被广泛使用的交互模式叫ReAct,全称是Reasoning and Acting。它强调的是一件事:让大模型先输出一段推理,说明自己“为什么这样做”,然后再输出具体动作,而不是直接跳到一个结论。

这么做的好处我在实际项目中体验很深。模型直接输出动作时,经常会在信息不够的情况下就开始执行;而要求它先写一句思考,它就有机会发现自己漏了什么。比如你要它订票,它如果先写思考过程,就会意识到“我还不知道乘车人身份证号,需要先问用户”,而不是直接去调用订票接口报错。

ReAct循环的典型结构是:

Thought: 用户要我把会议纪要整理成日报,我需要先找到纪要文件。 Action: read_file Action Input: {"file": "meeting_notes_may.txt"} Observation: 文件内容包含5条待办事项…… Thought: 我已经拿到会议内容,下一步需要确定收件人。 Action: search_contacts Action Input: {"query": "直属上级"}

每个回合都是“思考 → 行动 → 观察 → 思考”,直到模型判断目标完成,输出一个类似“FINISH”的结束标记。

在系统实现层面,这个循环通常由一个Python脚本或对应逻辑的主循环控制,伪代码大致长这样:

def run_agent(user_goal, tools, max_steps=10): history = [] for step in range(max_steps): response = llm_chat( system_prompt + user_goal + format(history) + format(tools) ) thought, action = parse_response(response) if action == "FINISH": return finalize(response) result = execute_tool(action) history.append((thought, action, result)) return timeout_result(max_steps)

这一步是整个Agent主循环的骨架,我建议任何想理解Agent原理的人,先把上面这段逻辑吃透,后面所有花哨的规划、记忆、多代理交互,本质都是在往这个循环里加东西。

2.3 决策提示词怎么设计才靠谱

同样的模型,同样的工具,提示词写法不同,Agent的成功率可以差出一大截。我在实践里总结出五个在Agent系统提示词里必须写清楚的东西:

  • 角色定位:明确告诉模型“你是调度器/执行器,不是聊天助手”。
  • 当前目标:把用户请求转化为一个简洁的目标说明,避免模型发散。
  • 可用工具清单:逐一说明每个工具的名字、用途、关键参数,最好附一个使用示例。
  • 历史记录格式:告诉模型之前发生过什么,观察结果如何呈现。
  • 停止条件:明确什么时候该结束循环,避免模型陷入无休止的补充操作。

下面是一个我经常在原型阶段使用的极简决策提示模板,适合快速验证Agent循环:

你是一个任务执行调度器。你的工作是根据用户目标、历史记录和可用工具,输出下一步行动。 用户目标:{goal} 已有历史记录: {history} 可用工具: - read_file: 读取本地文件,参数 path: 文件路径 - search_contacts: 搜索联系人,参数 query: 关键词 - send_email: 发送邮件,参数 to, subject, body 规则: 1. 每次只做一件事,逐步推进。 2. 先输出简短的 Thought,再输出 Action 和 Action Input。 3. 如果目标已完成,输出 FINISH。 4. 不要解释给最终用户听,不要寒暄。

这套提示词的核心思路是:把展示给读者的“自然语气”全部剥离,只留决策所需的要素。很多新手第一次搭Agent,提示词写得太像聊天指令,模型自然就切换回了“聊天模式”。

2.4 结构化的Action输出是Agent闭环的黏合剂

大模型的输出天生是自由文本,但Agent执行引擎需要结构化指令才能跑起来。所以中间必须有一个“解析”环节,把模型的自由文本转成可执行的函数调用。

目前最主流的方式是让模型输出JSON,系统层面用JSON解析器去读取。这个方案简单,但有一个坑:模型偶尔会生成格式错误的JSON,比如多一个逗号、括号没闭合。解析一失败,整个循环就会中断。我在项目里一般会在解析层加一个“容错修复”函数,比如对明显的截断符号做补齐、对多余的换行做清理,如果修复失败再让模型重写。

真实生产环境里,更稳的方案是使用“结构化输出模式”,也就是由框架层面约束模型的输出格式,让模型直接返回符合schema的内容,而不是靠事后解析。无论是哪种方案,原则都一样:让模型把意图落在结构上,后面才有可靠的执行。

3. “想清楚再做”:任务规划是怎么发生的

3.1 规划的本质是“目标到动作之间的路径生成”

规划模块解决的核心问题是:用户只给了一个最终目标,Agent怎么知道先做什么、再做什么?

如果目标特别简单,比如“把当前时间输出给用户”,那不需要规划,直接调一个工具就行。但真实任务通常不会这么乖巧,比如“把最近一周的销售数据整理成表格,发给销售总监”,里面至少包含查数据、清洗、汇总、制表、找联系人、写邮件、发送等多个环节。规划模块的作用,就是把这个链条生成出来,并且在过程中不断调整。

一个朴素但有效的方案是“让模型自己规划”。在每轮循环开始前,我们可以在提示词中要求模型先输出当前这一步的行动,同时输出一个“剩余子任务清单”。这样每一步都在动态更新,既不过度规划,也不会让模型瞎猜太远的未来。

3.2 一次性规划与动态规划的取舍

关于Agent怎么做规划,业内大致有两条思路:

  • 一次性规划:在最开始让模型输出完整的步骤列表,然后依次执行,适合稳定、线性的任务。
  • 动态规划:边执行边规划,根据观察到的结果决定下一步,适合不确定性强、依赖外部反馈的任务。

我在实际使用中更倾向于动态规划,理由很简单:真实世界的工具返回结果充满不确定性。你查一个数据库,可能字段和你预想的不一样;你读一个文件,可能根本没有你要的内容。一次性规划把所有步骤都定死,中途一旦发现某个假设不成立,整个流程就崩了。动态规划相当于允许Agent在每个循环里根据最新观察重写剩余路径,容错能力会强很多。

动态规划的实现方式不复杂,核心就是在每轮循环里把“剩余计划”也交给模型维护,观察到新信息后让它决定是继续原计划还是修改计划。本质上还是ReAct循环,只是多了一个“重新规划”的意识。

3.3 规划翻车的三类典型现场

我调试Agent时,见过太多次规划环节翻车,总结下来主要是三类:

  • 拆得太粗:模型觉得“发送邮件”是一步,但真实场景里还要先找收件人、确认正文模板、决定附件,这都属于该拆开的子步骤。拆太粗会导致某一步工具调用参数严重依赖猜测。
  • 拆得太细:模型每一步只做一个极小的动作,比如“打开软件”“点击新建”“输入标题”“输入正文”,导致整个任务要跑二十多轮,token成本直线上升,还增加了中途出错概率。
  • 子步骤顺序错误:比如先发送邮件再写正文,或者先支付再锁座,这会让后续操作失败。

针对这几类问题,我常用的处理方式是给模型一个“步骤合并与拆分”的约束:只有涉及不同领域操作时才必须拆分,其余可以合并;同时在每个子步骤前要求模型标注输入依赖,比如“这一步需要上一步的返回结果作为参数”。依赖标注能有效防止顺序错乱。

3.4 简化版规划器的实现思路

有些同学会问,规划是不是一定要用很复杂的框架?其实一个最小可用的规划器,用几行伪代码就能表达:

def plan_and_execute(goal): current_plan = [] done = False while not done: if need_replan(current_plan, last_observation): current_plan = llm_generate_plan(goal, history) next_step = current_plan.pop(0) observation = execute_step(next_step) history.append(observation) if llm_is_finished(goal, history): done = True return history

这个版本已经具备规划、执行、观察、重新规划的核心能力。生产系统会在这个基础上加更多细节,比如失败重试、状态管理、并发执行,但主干逻辑就是这个。

想理清Agent工作原理的人,我建议先亲手把这个最小循环跑通,再去看各种框架里复杂的规划器,会轻松很多。

4. 记忆机制:Agent的短期工作台和长期仓库

4.1 短期记忆就是上下文窗口

Agent在每轮决策时,要把已有的历史(思考、行动、观察结果)全部拼到上下文里喂给模型,让模型“记得”前面发生过什么。这个上下文窗口就是Agent的短期记忆,也叫工作记忆。

短期记忆的设计是“全量拼接”,也就是把所有轨迹都塞进提示词。好处是模型能掌握最完整的信息;坏处也很明显——上下文窗口是有限的,任务一长,历史记录可能把窗口撑爆,或者因为历史太长导致模型注意力分散,反而抓不住重点。

我在做长链路任务时,会做一个“轨迹裁剪”操作:把旧的、已经被利用过的观察结果压缩成一条摘要,只保留最近几轮完整内容。这样短期记忆的消耗能降低不少,模型的决策精度也更稳定。

4.2 长期记忆:跨任务的信息怎么存

用户第一次和Agent交互时可能会说“我坐高铁喜欢靠窗”,第二次交互时没有重复这句话,但Agent依然应该记得这个偏好。这就需要长期记忆系统:把有价值的信息写入外部存储,在后续任务需要时检索出来,注入到当前上下文。

目前最常用的长期记忆存储是“向量数据库 + 语义检索”。过程大致是这样的:

  1. 把文本(比如用户偏好、任务总结)通过嵌入模型转成向量。
  2. 存进专门的向量存储。
  3. 新任务进来时,把当前目标也转成向量,在存储里做相似度检索。
  4. 把命中的文本拼进提示词,让Agent“记起”相关信息。

除了向量检索,很多系统也会用传统的关键词检索或SQL结构化存储来配合,比如用户信息、时间区间等强结构化数据用数据库更可靠,而开放性的偏好、习惯用向量库更合适。

4.3 记忆写入策略:不是所有对话都值得存

很多人第一次做长期记忆时会犯同一个错:把Agent看到的、说过的所有内容全存下来。结果就是记忆库越来越脏,检索时什么都命中不了,或者命中一堆无关内容把上下文撑变形。

记忆写入需要策略。我一般会把内容分成三类:

  • 用户明确表达的偏好或约束,比如“我不吃辣”“我要靠窗”,这类必须存。
  • 任务执行过程中的中间状态,比如“已经查到票了”,这类是短期记忆,任务结束就该清理。
  • 总结性事实,比如“完成了一次出差行程安排,往返时间分别是……”,这类可以做压缩存储,用于未来同类任务参考。

写入时机也要控制,不能每轮循环都写入。比较稳妥的做法是:任务完成后统一提取一次“值得长期记住的事”,再批量写入。这样既降低存储成本,也避免执行过程中写入噪声信息。

4.4 记忆污染:我踩过最狠的坑

长期记忆用起来之后,我遇到的最大问题是“记忆污染”。具体表现是:一次任务中存储了一条当时正确、但后来已经被推翻的信息,后续任务检索到这条过期信息,Agent做了错误决策。

举个真实场景模拟:某用户第一次说“靠窗位置好”,系统存了偏好。后来该用户在另一次对话里明确说“不,以后不要靠窗了,太晒”,系统如果没有做覆盖更新,记忆库就会同时存在“靠窗好”和“不要靠窗”两条矛盾信息。下次Agent检索,旧信息权重可能更高,就又选了靠窗。

这个问题的本质是:记忆系统只做了“追加写入”,没有做“版本管理”。后来我的处理方式是给每条记忆增加时间戳和来源,写入新记忆时先检测是否存在同主题旧记忆,如果存在就标记为“已覆盖”或直接把旧记忆判断为过期。检测同主题可以靠规则映射,也可以靠语义相似度判断,条件允许时语义判断更可靠。

长期记忆是一把双刃剑,用得好Agent像一个人体贴的助手,用不好就是一个带偏见的糟糕同事。建议所有准备上长期记忆的团队,提前设计好覆盖和清理机制。

5. 工具调用:Agent的手是怎么穿过对话框伸出去的

5.1 为什么Agent必须会调用工具

大模型的知识来自训练数据,天然有时效性问题和事实盲区。比如它可能不知道今天的最新车次表,也不知道你公司通讯录里某个人的邮箱地址。Agent之所以“能干活”,核心就是因为它能通过工具实时获取外部信息、触发外部操作。

在架构里,工具被抽象成函数。每个工具都有三要素:名字、描述、参数说明。Agent看到这些说明,才能判断“解决当前问题应该调用哪个函数、参数填什么”。

5.2 Function Calling的三步流程

业界把Agent调用外部函数的过程称为Function Calling,流程分成三步:

  1. 工具声明:系统把可用工具的描述(name、description、parameters)传给模型,通常以JSON Schema的形式表示。
  2. 模型决策:模型根据当前上下文选择是否调用工具,并生成参数JSON。
  3. 运行时执行:系统解析模型输出的函数名和参数,做校验后真正执行函数,把返回值传给模型作为下一轮观察。

以一个搜索联系人的伪代码片段为例:

tools = [ { "name": "search_contacts", "description": "根据关键词搜索企业通讯录中的联系人", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词,比如姓名或职位"} }, "required": ["query"] } } ] # 模型输出的结果 model_response = '{"tool": "search_contacts", "args": {"query": "直属上级"}}' parsed = parse_json(model_response) result = execute_tool(parsed)

这段逻辑看着简单,但展开到真实系统里,工具数量可能成百上千,参数类型五花八门,这一步就会变成稳定性考验的主战场。

5.3 工具返回结果怎么回填上下文

工具执行完之后的返回值,是整个Agent循环的关键“观察”信号。这个信号描述得差,模型下一轮决策就会跑偏。

我在项目里的做法是:无论工具返回的是JSON、文本还是报错,都先把数据洗干净再拼进历史记录。比如数据库查询的返回字段名如果和模型预期不一致,我会先做一个字段映射;工具报错时,我会把错误信息整理成“工具名 + 错误码 + 可读描述”三段式,方便模型理解并修正计划。

另外,工具返回数据可能非常大。一次性把几万行查询结果塞给模型,直接浪费大量token,且容易让模型看漏重点。通常的做法是先让Agent基于任务目标对结果做过滤/摘要,再进入下一轮。例如查询结果过多时,系统先自动做统计摘要,只有模型明确需要明细时才传完整数据。

5.4 工具调用最常见的四类翻车

跑了一年多Agent相关的项目,工具调用环节的“翻车名场面”我基本都见过,归纳成四类给各位避坑:

  • 参数幻觉:模型生成工具不存在的参数,比如工具只接受“days”,模型却传了“date_range”。这类问题通常可以通过更严格的参数描述和工具调用示例来缓解。
  • 时间与时区:用户说“明天上午开会”,Agent填参数时没有换算时区,导致系统按UTC时间处理。工具参数设计时最好把时区和默认值写清楚,模型生成前也最好先问一次用户意图。
  • 返回值过大:工具把大文本直接返回,撑爆上下文。这类问题要前置做结果截断和摘要。
  • 静默失败:工具内部报错,但Agent以为成功,直接进入下一步。这类问题必须靠返回值里的status字段区分成功与失败,不能只看有没有数据。

5.5 工具调用失败后的重试策略

工具调用失败不丢人,关键是有没有挽回机制。我目前常用的策略是分层重试:

第一层是参数修复重试。如果错误提示“缺少必填参数”,系统自动把错误信息回传给模型,让模型重新生成参数。这种问题多数一次就能修复。

第二层是降级处理。比如主搜索工具挂了,自动切换到备用的搜索接口,或者强制改用简单的关键词搜索模式。这要求前期把工具逐一梳理出备选方案。

第三层是人工转交。如果连续两三轮重试还是失败,直接把错误上下文打包,推给用户在界面上确认。硬着头皮让Agent继续试,只会浪费token,还可能把错误扩散到后续步骤。

6. 一次真实任务的完整推演:从收到指令到发出邮件

6.1 场景与初始状态

说了这么多原理,我把一个典型任务从头到尾推演一遍,这样整个Agent循环会清晰很多。

假设用户给Agent发来一句话:“帮我把今天上午会议的纪要整理成一条简明日报,发给我的直属上级。”

Agent收到这条消息后的初始状态是:用户目标明确,但没有任何历史记录,没有会议纪要文件路径,不知道收件人是谁,也没确认邮件格式。这个时候如果Agent直接调create_draft_email,参数必然全是编的。靠谱的Agent应该从“找纪要文件”开始。

6.2 逐步推演整个决策循环

下面是Agent内部一步步发生的事。我把它整理成表格,方便对照每个循环的元素。

步骤观察到的信息思考动作返回结果
1初始状态,只知道用户目标需要先找到会议纪要文件read_file,参数待确认错误:缺少文件路径
2读了用户提供的工作目录列表目录里有meeting_notes_20260605.md,大概率是目标文件read_file,path=meeting_notes_20260605.md成功,返回3段会议记录,含5条待办事项
3今天上午的会议内容是项目评审,涉及两个模块排期、一个风险点需要从中抽取最重要的信息压成日报无外部工具,直接用LLM生成摘要生成3行日报草稿
4日报已生成,但还不知道发给谁用户的“直属上级”是谁,需要查通讯录search_contacts,query=直属上级返回一位联系人,邮箱为leader@example.com
5收件人已知,正文草稿就绪应该先把邮件写成草稿供用户确认,而不是直接发送create_draft_email,to、subject、body草稿创建成功,返回草稿ID
6草稿已创建,仍未发送直接发送不可撤回,存在风险询问用户是否需要直接发送,等待确认用户回复“确认发送”
7用户已确认可以执行最终发送动作send_email,draft_id=xxx发送成功,任务完成

6.3 推演中体现的三个关键设计

这个推演能体现Agent落地时的三个关键设计考量,值得单独拿出来讲。

第一,工具步骤依赖。第1步失败是因为缺少依赖参数,第2步通过先读取目录补齐依赖,这印证了规划阶段要标注子步骤依赖的价值。

第二,重要操作前置确认。第6步把“创建草稿”和“发送邮件”拆成了两个动作,并且在最终发送前插入一个人工确认环节。这个设计在真实场景中极其重要,邮件发出去不能撤回,Agent自动执行高风险动作必须设置安全闸门。

第三,LLM本身也算一种工具。第3步做摘要时没有调用外部函数,但本质上是用LLM完成了“文本处理工具”的职责。很多Agent架构就是混用外部工具和非外部能力。

完整推演一趟之后,你会发现Agent并不是什么玄学,它只是在一个“观察-思考-行动”的循环里,反复利用模型推理和外部工具逐步逼近目标。所有复杂功能,都是在这个循环上叠加策略。

7. Agent真正跑起来之后,我反复吃的亏和改法

7.1 模型幻觉会让Agent做出“看起来合理但错误”的行为

在Agent场景里,幻觉的危害比聊天场景大得多。聊天场景里模型编一段内容,用户可能无所谓;但Agent如果因为幻觉编造一个不存在的文件路径、一个错误的联系人、一组错误参数,实际后果是系统真去执行了,损失可能是实打实的。

应对幻觉我没有特别神奇的方案,就是老四条:能调工具查证就不要让模型凭空猜测;关键参数做规则校验,比如路径格式、邮箱格式必须匹配才能执行;执行前把待确认的关键信息截图给用户过目;以及给模型足够权限之前,先在人可以干预的环境里跑段时间观察。

7.2 Agent卡死循环,烧的不是算力是钱

Agent在复杂任务里最让我头疼的问题是“循环空转”。它会在同一件事上反复做类似动作,比如反复读取同一个文件、反复生成相似摘要,就是不给FINISH。这个问题直接反映在账单上——每一轮循环都在消耗token,空转十轮的成本可以顶一篇长文的生成成本。

我现在的做法是双保险。硬限制层面:设置最大步骤数,一般是8到15步,超出直接终止并转人工;软限制层面:记录每一步的“状态摘要”,如果连续几轮状态没有实质变化,就强制触发“重新规划”或直接结束。

7.3 错误一旦累积,后面全白做

Agent是长链路执行,前面一个小错误会被后续步骤放大。比如第一步把纪要文件读错了,后面所有摘要、邮件、发送全部基于错误内容进行,等发送完了才发现源头不对,这个返工成本极高。

我习惯在每个关键节点加一个“前后一致性校验”。比如摘要生成后,自动比对摘要中提到的事实是否都能在原文里找到支撑;邮件发送前,让另一个模型实例做一次独立复核。这个“双重检查”的做法会让成本增加一些,但和发错邮件的代价比起来,非常划算。

7.4 权限收敛:给Agent最小权限,别给最大便利

很多Agent之所以不敢在真实业务里放开,核心就是权限问题。让Agent去调用生产系统接口,等于把一个能“自动做决定”的实体放进了核心链路。我的原则很明确:Agent默认只读,写完数据必须二次确认;涉及资金、对外发布、删除类操作,一律需要人工审批接口介入。

权限收敛不是不信任Agent,而是承认模型输出天然存在不确定性。把权限做小,Agent做错事的破坏范围就小,出现问题时你才有心情去优化它的决策质量,而不是先停机抢救业务。

7.5 调试Agent的方法论:留痕、回放、评估

Agent调试和普通程序调试最大的不同是:普通程序每一步是可复现的,而Agent每一步依赖模型概率输出,不好复现。我调试Agent时最依赖的方法是“全链路留痕”。

我会让Agent把每一步的完整输入输出都写成日志,包括:系统提示词、模型决策结果、工具调用参数、工具返回值、每一轮消耗的token数。出问题时,把这段“回放记录”对着看一遍,基本都能定位到是规划、记忆、工具还是权限哪个环节出了问题。

在此基础上,再针对高频场景准备一批固定的测试用例,每次调整规划提示词或工具描述后自动跑一遍,对比前后的成功率。没有这套回归测试,Agent的每轮优化都是在赌运气。

整套内容写下来,我最想分享的实际建议是:做Agent不要一上来就追求复杂的架构,先把“观察-思考-行动”的最小循环跑通,再逐步叠加规划、记忆和权限控制。尤其是刚开始接触的同学,在最小循环上把日志和重试做好,往后加功能会顺很多。我每次做新项目都是先写清楚“任务说明书”,把可能出现的边界情况、工具调用顺序、哪些操作需要人工确认都列出来,再开始写调度逻辑,这一步能规避掉大部分返工。AgEE的工作原理说穿了不复杂,真正决定它能跑多远跑多稳的,都是那些循环之外的细节。

RELATED READING

延伸阅读

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