AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套完整闭环的人并不多。大多数人卡在同一个地方:把 Agent 理解成"套了壳的对话机器人",结果做出来的东西只能聊天,不能干活。我自己从去年开始陆续搭过几套不同形态的 Agent,从最简单的单轮工具调用,到带规划、带反思、带记忆的多轮闭环,踩过的坑基本覆盖了这条链路上的每一个环节。这篇内容想做的事情很直接:把 AI Agent 从 Planner 到 Reflection 的完整架构拆开,讲清楚每一层到底在解决什么问题、为什么这么设计、实际落地时会遇到哪些坑。不管你是刚接触 Agent 开发的新手,还是已经搭过 Demo 但跑不稳的开发者,都能从里面找到可以直接抄作业的部分。
1. 先把 AI Agent 的边界划清楚:它到底不是什么
1.1 对话机器人和 Agent 的本质分界线
很多人第一次接触 Agent 这个概念时,会下意识地把它等同于"更聪明的聊天机器人"。这个理解偏差会导致后面所有的架构设计都跑偏。对话机器人的核心循环是"输入-生成-输出",它的能力边界由模型本身的参数决定,模型没见过的东西它就是不会。而 Agent 的核心循环是"输入-规划-行动-观察-再规划",它的能力边界由模型加上它能调用的工具共同决定。
举个具体的例子。你问聊天机器人"帮我查一下明天北京的天气",它要么编一个答案,要么说"我无法获取实时信息"。但一个 Agent 拿到同样的请求,它会先判断"我需要调用天气查询工具",然后生成一个结构化的调用请求,拿到真实数据后再组织语言回复你。这个差异看起来很小,但背后是完全不同的架构。
判断一个系统是不是 Agent,我通常用三个标准去卡:第一,它能不能自主决定下一步做什么,而不是每一步都等人指令;第二,它能不能调用外部工具去改变状态或获取信息;第三,它能不能根据行动的结果调整后续策略。三条都满足,才算真正意义上的 Agent。
1.2 为什么"套壳对话"做不出能干活的产品
我见过不少团队做 Agent 产品,本质上就是给大模型加了一个系统提示词,告诉它"你是一个助手,你可以调用这些工具",然后就上线了。结果用户用两次就发现,这个 Agent 要么该调工具的时候不调,要么调了工具之后不知道怎么处理返回结果,要么在多轮对话里把之前的目标忘得一干二净。
问题的根源在于,他们把 Agent 当成了一个"提示词工程"问题,而实际上这是一个"系统架构"问题。提示词只能影响单次生成的质量,但 Agent 需要的是跨多轮、跨多个决策点的状态管理和策略控制。这就好比你不能靠给一个人写一张纸条,就让他完成一整套复杂的项目管理流程,你需要的是流程本身、检查点、反馈机制。
所以真正要搭 Agent,第一步不是去调提示词,而是先把架构分层想清楚。下面这张表是我自己总结的 Agent 核心分层,后面所有内容都围绕它展开。
| 层级 | 核心职责 | 典型实现 | 缺失后的表现 |
|---|---|---|---|
| Planner | 任务分解与路径规划 | LLM 生成步骤序列 | Agent 只会单步响应,无法处理复杂任务 |
| Executor | 执行具体动作 | Function Calling / 工具调用 | 规划了但执行不了 |
| Memory | 状态与上下文管理 | 短期对话缓存 + 长期向量存储 | 多轮之后丢失目标 |
| Reflection | 结果评估与自我修正 | 自我批判 + 重试机制 | 错了不知道错,一直错下去 |
这张表不是理论模型,是我在实际项目里反复验证过的。任何一个层级缺失,Agent 的表现都会明显退化。接下来我逐层拆解。
2. Planner 层:任务分解才是 Agent 的"大脑"
2.1 为什么 Planner 是第一个必须做好的环节
Planner 的核心任务是把用户的一句模糊指令,拆解成一组可执行的步骤。这一步做不好,后面全是白搭。我见过最典型的失败案例是:用户说"帮我整理一下这个月的销售数据并生成报告",Agent 直接就开始调用"生成报告"工具,结果因为没有先查数据,报告里全是空的。
Planner 要解决的核心问题是"顺序"和"依赖"。哪些步骤必须先做,哪些可以并行,哪些步骤的输出是下一步的输入。这些关系如果不在规划阶段理清楚,执行阶段就会乱套。
我自己的做法是,在 Planner 层强制输出结构化的步骤列表,每个步骤包含四个字段:步骤编号、动作描述、依赖步骤、预期输出。这样做的目的是让后续的 Executor 有明确的执行依据,而不是拿到一段自然语言描述自己去猜。
{ "plan": [ { "step_id": 1, "action": "查询本月销售数据", "depends_on": [], "expected_output": "结构化的销售记录列表" }, { "step_id": 2, "action": "按品类聚合销售数据", "depends_on": [1], "expected_output": "各品类销售额汇总" }, { "step_id": 3, "action": "生成分析报告", "depends_on": [2], "expected_output": "Markdown 格式报告" } ] }这个结构看起来简单,但它带来的好处是巨大的。Executor 可以按依赖关系拓扑排序执行,Reflection 可以逐步验证每一步的输出是否符合预期,Memory 可以精确记录每一步的状态。
2.2 规划粒度的取舍:拆太细和拆太粗都是坑
规划粒度是 Planner 设计里最需要经验的地方。拆得太细,步骤数量爆炸,每一步都要调用一次模型,token 消耗和延迟都会飙升。拆得太粗,单步内部逻辑复杂,出错之后很难定位是哪一步的问题。
我的经验法则是:单个步骤应该对应一次工具调用或一次明确的推理动作。如果一个步骤需要调用两个不同的工具,那它就应该被拆成两步。如果一个步骤只是"思考一下",那它可能不需要单独成步,可以合并到相邻步骤里。
还有一个容易被忽略的点:Planner 不应该一次性规划到底。对于复杂任务,更好的做法是分层规划。第一层先规划出大阶段,比如"数据获取阶段""数据处理阶段""报告生成阶段",然后在执行每个阶段之前,再对该阶段做细粒度规划。这样做的好处是,如果中途发现某个阶段的规划不合理,只需要重新规划那个阶段,不用推翻整个计划。
2.3 规划失败的三种典型模式和应对
Planner 出错的方式基本可以归为三类。第一类是遗漏步骤,比如用户要求"对比 A 和 B 两个方案",Agent 只分析了 A 就出结论了。第二类是顺序错误,比如先执行了依赖数据的步骤,再去获取数据。第三类是幻觉步骤,规划出了一个根本不存在的工具调用。
针对这三类问题,我在实际项目里加了几个约束。对于遗漏步骤,在 Planner 的提示词里强制要求"列出所有必要的子任务,并检查是否有遗漏";对于顺序错误,用依赖字段做拓扑校验,发现循环依赖或前向依赖就报错重规划;对于幻觉步骤,维护一个可用工具的注册表,Planner 输出的每个 action 都要和注册表做匹配校验,匹配不上就触发重规划。
这些约束看起来是在给 Planner 加限制,但实际上它们大幅提升了整个 Agent 的稳定性。我实测下来,加了工具注册表校验之后,幻觉步骤的出现率从大概 15% 降到了 2% 以下。
3. Executor 与 Function Calling:让规划真正落地
3.1 Function Calling 的工作机制和常见误解
Function Calling 是 Executor 层最核心的机制。它的本质是让模型输出一个结构化的函数调用请求,而不是直接输出自然语言。模型本身并不执行函数,它只是生成"我要调用哪个函数、传什么参数"这样的结构化数据,真正的执行是由你的代码完成的。
这里有一个非常常见的误解:很多人以为 Function Calling 是模型在"调用"工具。不是的。模型只是输出了一个 JSON,你的程序解析这个 JSON,找到对应的函数,执行它,然后把结果再喂回给模型。整个链路里,模型负责"决策",你的代码负责"执行"。
理解这一点非常重要,因为它决定了你的架构设计。你需要一个工具注册中心来管理所有可调用的函数,需要一套参数校验机制来确保模型生成的参数是合法的,需要一套错误处理机制来应对工具执行失败的情况。
# 工具注册的简化示例 TOOL_REGISTRY = {} def register_tool(name, description, parameters): def decorator(func): TOOL_REGISTRY[name] = { "function": func, "description": description, "parameters": parameters } return func return decorator @register_tool( name="query_sales_data", description="查询指定时间范围的销售数据", parameters={ "start_date": {"type": "string", "required": True}, "end_date": {"type": "string", "required": True} } ) def query_sales_data(start_date, end_date): # 实际的数据查询逻辑 return fetch_from_database(start_date, end_date)这个注册机制的好处是,Planner 和 Executor 共享同一份工具定义,不会出现"规划了一个不存在的工具"这种情况。
3.2 参数校验:模型生成的参数不能直接信
模型生成的参数经常有问题。日期格式不对、必填字段缺失、数值类型传成了字符串,这些都是家常便饭。如果你直接把模型生成的参数传给函数执行,轻则报错,重则产生脏数据。
我的做法是在工具注册的时候就定义好参数的 schema,包括类型、是否必填、取值范围。执行之前先做一轮校验,校验不通过就把错误信息返回给模型,让它重新生成参数。这个重试机制非常关键,它让 Agent 有了"自我纠错"的能力。
参数校验还有一个进阶用法:默认值填充。有些参数模型经常忘记传,但业务上有合理的默认值,比如查询数据时如果不指定条数,默认返回 100 条。这种情况下,与其让模型重试,不如在注册工具时就设好默认值,减少不必要的往返。
3.3 工具执行失败的分类处理
工具执行失败是必然会发生的,关键是怎么处理。我把失败分成三类,每类用不同的策略。
第一类是可重试失败,比如网络超时、临时性的服务不可用。这类失败直接重试,通常重试 2 到 3 次就能成功。第二类是参数错误,比如查询了一个不存在的用户 ID。这类失败要把错误信息返回给模型,让它修正参数后重试。第三类是不可恢复失败,比如权限不足、资源不存在。这类失败要直接终止当前步骤,并把失败原因记录到 Memory 里,让 Planner 决定是跳过这一步还是调整整个计划。
这里有个经验:不要把工具执行的所有错误都直接抛给模型。有些错误模型看了也没用,比如数据库连接失败,模型既不知道原因也不知道怎么修。这种情况下,更好的做法是在 Executor 层做重试和降级,只把模型能处理的错误返回给它。
4. Memory 层:Agent 的"记性"决定了它能走多远
4.1 短期记忆和长期记忆的分工
Memory 层经常被低估。很多人觉得 Memory 就是"把对话历史存下来",但实际上 Agent 的 Memory 要复杂得多。我把它分成短期记忆和长期记忆两部分。
短期记忆是当前任务执行过程中的状态,包括已经完成了哪些步骤、每一步的输出是什么、当前正在执行哪一步。这部分数据的特点是生命周期短、访问频繁、结构性强。我通常用一个任务级的上下文对象来管理,任务结束就释放。
长期记忆是跨任务的知识,包括用户的偏好、历史交互记录、领域知识。这部分数据的特点是生命周期长、访问频率低、需要语义检索。我通常用向量数据库来存储,通过语义相似度来检索相关记忆。
两者的分工很明确:短期记忆保证当前任务不跑偏,长期记忆让 Agent 越用越懂用户。缺了短期记忆,Agent 在多步任务里会迷失方向;缺了长期记忆,Agent 每次都是从零开始,用户体验很差。
4.2 上下文窗口管理:不是塞得越多越好
大模型的上下文窗口是有限的,而且塞得越多,推理成本越高、速度越慢。所以 Memory 层的一个核心任务就是上下文压缩和筛选。
我的策略是分层保留。最近几轮的对话完整保留,因为这部分和当前任务最相关。更早的对话做摘要压缩,只保留关键信息,比如用户的核心诉求、已经确认的结论。工具调用的原始返回结果如果很长,只保留关键字段,其余丢弃。
还有一个技巧是按需检索。不是把所有记忆都塞进上下文,而是根据当前步骤的需要,从长期记忆里检索最相关的几条。比如当前步骤是"生成报告",那就检索用户之前对报告格式的偏好;当前步骤是"查询数据",那就检索用户常用的数据筛选条件。
4.3 记忆写入的时机和去重
记忆写入的时机很关键。写太早,可能把中间过程的错误信息也写进去了;写太晚,可能任务结束了还没写。我的做法是在每个步骤成功完成后写入该步骤的关键结果,在任务整体完成后写入任务级的总结。
去重是另一个容易被忽略的问题。同一个用户偏好可能被反复写入,导致长期记忆里全是重复内容。我的做法是在写入前先做一次相似度检索,如果已经有高度相似的记忆,就更新而不是新增。这样长期记忆的质量会高很多。
5. Reflection 层:让 Agent 知道自己错了
5.1 Reflection 的本质是自我评估
Reflection 是 Agent 架构里最容易被跳过、但价值最高的一层。它的核心作用是让 Agent 在执行过程中评估自己的输出质量,发现偏差后自我修正。
Reflection 的实现方式通常有两种。一种是自我批判,让模型对自己的输出打分并给出改进建议。另一种是外部验证,用规则或另一个模型来检查输出是否符合预期。实际项目里我通常两种结合使用。
自我批判的提示词设计很关键。如果只是问"这个结果好不好",模型倾向于说"很好"。更好的问法是"这个结果是否满足以下标准:标准一、标准二、标准三,如果不满足,具体哪里不满足"。把评估标准明确列出来,Reflection 的有效性会大幅提升。
5.2 什么情况下触发 Reflection
不是每一步都需要 Reflection,那样成本太高。我通常在这几种情况下触发:步骤输出为空或明显异常时、步骤输出与预期格式不符时、关键决策步骤完成后、整个任务完成前的最终检查。
触发条件的设计需要在成本和效果之间找平衡。触发太频繁,token 消耗和延迟都会上去;触发太少,错误会累积到最后一发不可收拾。我的经验值是,一个中等复杂度的任务,Reflection 触发 2 到 3 次是比较合理的。
5.3 修正策略:重试、回退还是重规划
Reflection 发现问题之后,有三种修正策略。重试是最轻量的,适用于参数错误、格式错误这类局部问题,重新执行当前步骤即可。回退适用于当前步骤依赖的前置步骤有问题,需要回退到出错的步骤重新执行。重规划是最重的,适用于整个计划方向有问题,需要 Planner 重新生成计划。
选择哪种策略,取决于问题的严重程度。我的判断标准是:如果问题局限在当前步骤,重试;如果问题影响后续多个步骤,回退;如果问题动摇了整个任务的基础假设,重规划。
这里有个坑要注意:重试和回退都要设置次数上限。我见过 Agent 陷入无限重试循环的情况,一个步骤反复失败反复重试,token 烧了一大堆,任务还是没完成。设置上限之后,超过上限就升级到重规划,或者直接终止并报告失败。
6. MCP 与工具生态:Agent 能力扩展的标准化路径
6.1 MCP 解决了什么问题
MCP 是最近被讨论很多的一个概念,它的全称是 Model Context Protocol,本质上是一套标准化的协议,用来规范 Agent 和外部工具之间的通信。在没有 MCP 之前,每接一个工具都要写一套适配代码,工具多了之后维护成本极高。MCP 的出现让工具的接入变得标准化,一个工具只要实现了 MCP 协议,理论上可以被任何支持 MCP 的 Agent 调用。
从架构角度看,MCP 把工具的定义、调用、结果返回都标准化了。Agent 不需要知道工具的具体实现,只需要知道工具的能力描述和调用方式。这大幅降低了 Agent 扩展新能力的成本。
6.2 没有 MCP 能不能开发 Agent
可以,而且很多早期 Agent 就是这么做的。没有 MCP 的情况下,你需要自己定义工具接口、自己实现调用逻辑、自己处理结果解析。这套东西做下来,工作量不小,而且每个 Agent 项目都要重复一遍。
MCP 的价值在于复用。当你有了多个 Agent 项目,或者需要接入大量工具时,MCP 带来的标准化收益就非常明显了。但如果只是做一个简单的、工具数量很少的 Agent,自己实现一套轻量的工具调用机制也完全可行,不必为了用 MCP 而用 MCP。
6.3 工具选型的实际考量
工具选型的时候,我主要看三个维度:稳定性、覆盖度、接入成本。稳定性是指工具本身是否可靠,会不会经常超时或返回错误。覆盖度是指工具的能力是否覆盖了目标场景的需求。接入成本是指把工具接进 Agent 需要多少工作量。
对于高频使用的核心工具,我倾向于选择成熟稳定的方案,哪怕接入成本高一点。对于低频的辅助工具,可以用轻量方案快速接入。MCP 在这方面的优势是,它让工具的替换成本变低了,如果某个工具不好用,换一个实现了同样 MCP 协议的工具,Agent 侧几乎不用改代码。
7. 从零搭一套闭环:我的实际落地路径
7.1 最小可用闭环的搭建顺序
如果你现在要从零搭一套 Agent,我建议的顺序是:先做 Executor 和 Function Calling,再做 Planner,然后加 Memory,最后加 Reflection。这个顺序的逻辑是,先保证 Agent 能调用工具干活,再让它能规划复杂任务,再让它记住上下文,最后让它能自我纠错。
很多人一上来就想做全套,结果每个模块都做了一半,跑起来到处是问题。先把最小闭环跑通,再逐步加层,每一步都有可验证的产出,这样调试起来容易得多。
最小闭环的验证标准很简单:给 Agent 一个需要两步工具调用才能完成的任务,它能自己规划、自己调用、自己整合结果。这个跑通了,再往上加东西。
7.2 调试 Agent 的实用技巧
调试 Agent 比调试普通程序难,因为它的行为有随机性。我的做法是全链路日志,把每一次模型调用、每一次工具调用、每一次 Reflection 的输入输出都完整记录下来。出问题的时候,回放日志就能定位到是哪一步出的错。
还有一个技巧是固定随机种子。在调试阶段,把模型的 temperature 设成 0,让输出尽可能确定。这样同一个输入多次运行的结果基本一致,方便复现问题。上线之后再调高 temperature 增加灵活性。
另外,我强烈建议做一个可视化执行轨迹。把 Agent 的每一步决策、每一次工具调用、每一个中间结果用时间线的形式展示出来。这个东西在调试复杂任务的时候价值巨大,一眼就能看出是哪一步跑偏了。
7.3 上线前必须检查的几个点
Agent 上线之前,有几个点必须检查。超时控制,每个工具调用、每次模型调用都要设超时,避免整个任务卡死。成本控制,设置单次任务的 token 上限和工具调用次数上限,避免异常情况下烧掉大量资源。降级方案,当模型服务不可用或工具全部失败时,Agent 要能优雅地返回错误信息,而不是直接崩溃。日志脱敏,记录日志的时候要把敏感信息过滤掉,避免数据泄露。
这几个点看起来是工程细节,但它们决定了 Agent 能不能稳定运行。我见过太多 Demo 跑得很好但一上线就各种问题的案例,基本都是这些细节没做到位。
8. 几个我踩过的坑和对应的解法
8.1 规划循环:Agent 反复规划不执行
这是最常见的问题之一。Agent 拿到任务后,规划了一版,觉得不满意,又规划一版,反复规划就是不进入执行阶段。根本原因是 Planner 的退出条件不明确。
我的解法是给 Planner 设置明确的终止条件:规划次数上限、规划结果稳定度阈值。规划次数超过上限,强制进入执行;连续两次规划结果高度相似,认为已经收敛,进入执行。这两个条件加上之后,规划循环的问题基本消失了。
8.2 工具调用风暴:一次任务调用几十次工具
有些 Agent 会陷入工具调用风暴,一个简单任务调用几十次工具,token 消耗巨大。原因通常是 Reflection 触发了过多的重试,或者 Planner 把任务拆得过细。
解法是设置工具调用次数上限,并在接近上限时触发告警。同时优化 Planner 的规划粒度,减少不必要的步骤拆分。还有一个技巧是给工具调用加缓存,相同的参数在短时间内重复调用直接返回缓存结果。
8.3 上下文污染:早期错误信息影响后续决策
当 Agent 执行了多步之后,上下文里积累了大量信息,其中可能包含早期的错误信息。这些错误信息如果被后续步骤的模型看到,可能会误导决策。
解法是在 Memory 层做上下文清理,把已经修正的错误信息标记为"已解决",在构造上下文时过滤掉。同时,关键决策步骤的上下文要精简,只保留和当前决策直接相关的信息。
8.4 反思过度:Agent 陷入自我怀疑
Reflection 用过头也会出问题。Agent 对自己的输出反复批判,反复修改,就是不肯输出最终结果。这种情况在创意类任务里特别常见。
解法是给 Reflection 设置明确的通过标准,达到标准就停止反思。同时限制反思次数,超过次数就强制输出当前最优结果。反思的目的是提升质量,不是追求完美,这个度要把握好。
9. 关于 Agent 架构演进的一点个人观察
搭了几套 Agent 之后,我最大的体会是:Agent 的架构复杂度应该和任务的复杂度匹配。简单的任务不需要全套架构,一个 Function Calling 加一个简单的循环就够了。硬套全套架构,只会增加复杂度和调试难度。
另一个体会是,Agent 的瓶颈往往不在模型,而在工程。模型能力已经足够强了,真正决定 Agent 好不好用的是工具的质量、错误处理的完善度、上下文管理的精细度。这些工程细节做扎实了,Agent 的表现会有质的提升。
还有一个趋势值得关注:Agent 的评估和可观测性正在变得越来越重要。以前大家只关注 Agent 能不能跑通,现在开始关注 Agent 跑得好不好、稳不稳、成本高不高。配套的评估框架和监控工具会越来越成熟,这块值得提前布局。
最后分享一个我最近在用的技巧:给 Agent 加一个"执行预算"的概念。每个任务分配一个预算,包括 token 预算、工具调用次数预算、时间预算。Agent 在执行过程中实时检查预算消耗,接近预算上限时自动切换到更保守的策略。这个机制让 Agent 在资源受限的情况下也能稳定运行,避免失控。