1. 为什么现在聊 Agent 架构,绕不开 Planner 和 Reflection
AI Agent 这个词在过去一年里被反复提及,但真正落到工程实现层面,很多人会发现一个尴尬的现实:Demo 跑起来很惊艳,上线之后却频繁翻车。问题往往不出在模型本身,而是出在架构设计上——尤其是 Planner 和 Reflection 这两个环节的缺失或错位。
我自己从去年开始陆续搭过几个 Agent 项目,从最简单的单轮 Function Calling 到后来带任务分解、带自我反思的完整闭环,踩过的坑基本覆盖了这条链路上的每一个节点。这篇文章不打算讲空泛的概念,而是把 AI Agent 从 Planner 到 Reflection 的完整架构拆开,讲清楚每一层在干什么、为什么这么设计、实际落地时哪些地方最容易出问题。
如果你正在做 AI Agent 开发,或者准备搭建自己的 Agent 系统,不管是用 Python 还是 Rust,不管底层接的是哪家模型,这套架构思路都是通用的。文章会涉及 Function Calling、MCP(Model Context Protocol)、任务规划、反思机制、工具编排等核心话题,也会给出可以直接参考的实现方案和参数选择逻辑。
先说一个基本判断:一个能用的 Agent,核心不在于模型多强,而在于架构是否形成了闭环。Planner 负责“想清楚要做什么”,Executor 负责“动手做”,Reflection 负责“回头看做得对不对”,三者缺一不可。下面逐层拆解。
2. AI Agent 架构的整体设计与核心思路
2.1 从单轮调用到闭环系统:架构演进的必然性
最早的 Agent 形态其实很简单:用户输入一句话,模型判断要不要调工具,调完把结果拼回上下文,再生成回复。这就是典型的单轮 Function Calling 模式。它的问题在于,一旦任务超过一步,模型就容易“忘记”自己刚才做了什么,或者在一个错误的路径上越走越远。
我最初做的一个日程管理 Agent 就是这样。用户说“帮我安排下周和团队的评审会”,模型调了日历查询接口,拿到一堆空闲时间段,然后直接选了一个时间创建会议——但它没有检查参会人是否都有空,也没有考虑时区问题。结果就是会议创建了,但一半人时间冲突。
这个失败案例暴露的核心问题是:缺少规划层。模型把“查询”和“决策”混在了一步里,没有先分解任务,也没有在执行后验证结果。后来我引入了 Planner 和 Reflection,同样一个任务,流程变成了:先规划出“查询所有参会人日历→找交集→检查时区→创建会议→验证创建结果”这几个步骤,每步执行完做一次轻量反思,确认无误再进入下一步。成功率从不到 40% 提升到了 85% 以上。
所以架构演进不是赶时髦,而是被实际问题逼出来的。单轮调用适合简单问答,一旦涉及多步骤、多工具、有条件分支的任务,就必须上闭环架构。
2.2 四层核心结构:Planner、Executor、Tool Layer、Reflection
一个完整的 Agent 闭环,我习惯把它拆成四层:
- Planner(规划层):接收用户意图,输出任务分解后的步骤列表。它不直接调工具,只负责“想”。
- Executor(执行层):按步骤逐个执行,决定每一步用哪个工具、传什么参数。它是“手”。
- Tool Layer(工具层):实际执行外部调用的地方,包括 Function Calling、MCP 协议对接的各种服务。它是“工具本身”。
- Reflection(反思层):对执行结果做评估,判断是否达成预期,是否需要重试或调整计划。它是“眼睛和大脑的校验回路”。
这四层之间通过一个共享的上下文(Context)串联。Planner 的输出写入上下文,Executor 从上下文读取步骤并写入执行结果,Reflection 读取结果并写入评估结论,必要时触发 Planner 重新规划。
注意:不要把这四层做成四个独立的模型调用就完事。它们共享同一个上下文窗口,但各自的 Prompt 模板和输出格式必须严格区分,否则模型会混淆角色。
2.3 为什么选择“规划-执行-反思”而不是端到端
有人会问:现在模型能力这么强,为什么不直接端到端让模型自己搞定?我的实测结论是:端到端在简单任务上确实省事,但在复杂任务上不可控。
端到端的问题在于,模型在一个超长上下文里同时做规划、执行和验证,很容易出现“自我确认偏差”——它倾向于认为自己刚才做的决策是对的,即使结果是错的。而把规划、执行、反思拆成独立的调用,每次调用只关注一个职责,模型的注意力更集中,出错概率明显下降。
另一个原因是可观测性。拆开之后,每一步的输入输出都可以记录、可以回放、可以单独调试。端到端模式下,你只能看到一个最终结果,中间发生了什么完全是个黑盒。对于生产环境来说,可观测性比省几次调用重要得多。
当然,拆开也有代价:调用次数增加,延迟变高,Token 消耗变大。所以我的建议是分层按需启用——简单任务走轻量路径,复杂任务才启用完整闭环。
3. Planner 层:任务分解的核心逻辑与实操细节
3.1 Planner 的输入输出设计:别让模型自由发挥
Planner 最容易犯的错误就是让它“自由发挥”。如果你只给一句“帮我规划一下这个任务”,模型输出的步骤格式每次都不一样,有的用数字列表,有的用自然段,有的甚至直接开始执行。这给下游的 Executor 解析带来巨大麻烦。
我的做法是强制 Planner 输出结构化的 JSON,格式固定为:
{ "goal": "用户原始意图的复述", "steps": [ { "id": 1, "action": "步骤描述", "tool_hint": "可能用到的工具名", "depends_on": [] } ], "constraints": ["已知限制条件"] }tool_hint不是必须精确,它只是给 Executor 一个提示,Executor 最终决定用什么工具。depends_on用来标记步骤之间的依赖关系,比如第 3 步依赖第 1 步的输出,这样 Executor 就知道不能并行执行。
这个格式看起来简单,但实际用起来效果差异很大。我对比过纯自然语言输出和结构化 JSON 输出,后者在 Executor 端的解析成功率从 70% 左右提升到了接近 100%。原因很简单:结构化输出消除了歧义。
3.2 任务分解的粒度控制:太粗和太细都是坑
Planner 的另一个关键问题是粒度。分解得太粗,Executor 一步做不完;分解得太细,步骤数量爆炸,调用成本飙升。
我的经验法则是:每个步骤应该对应一次工具调用或一次明确的推理动作。比如“查询天气并决定是否带伞”,这应该是两步:先查天气,再根据天气做判断。如果合成一步,Executor 就得在一个步骤里既调工具又做推理,容易乱。
但也不能细到“打开浏览器”“输入网址”“点击搜索”这种程度。那是对 Executor 的过度干预,反而限制了它的灵活性。Planner 应该关注“做什么”,而不是“怎么做”。
实际操作中,我会在 Planner 的 Prompt 里加一条约束:“每个步骤应该是一个可以在 1-2 次工具调用内完成的原子动作,步骤总数控制在 3-8 步之间。”这个范围是我多次调试后总结的,少于 3 步说明分解不够,多于 8 步说明粒度过细或者任务本身太复杂需要拆分。
3.3 动态重规划:当计划赶不上变化
Planner 不是一次性的。执行过程中如果 Reflection 发现某一步失败且无法通过重试解决,就需要触发重规划。这时候 Planner 接收的输入不再是原始用户意图,而是“原始意图 + 已完成步骤 + 失败原因”。
重规划的 Prompt 需要特别设计,关键是让模型不要从头再来,而是基于已有进展调整。我会在 Prompt 里明确写:“以下步骤已经成功完成,不要重复执行:...。以下步骤失败了,失败原因是:...。请只规划剩余未完成的部分。”
这个细节很关键。早期我没加这个约束,模型一重规划就把所有步骤重新列一遍,导致已经完成的操作被重复执行,比如重复发邮件、重复创建订单。后来加了“已完成步骤”的显式声明,这个问题就消失了。
4. Executor 与 Tool Layer:Function Calling 和 MCP 的落地实践
4.1 Function Calling 的边界:什么该做成工具,什么不该
Function Calling 是 Executor 调工具的标准方式,但不是什么功能都适合做成工具。我见过有人把“计算两个数之和”也做成一个工具,这就过度了——模型自己就能算,没必要走一次工具调用。
我的判断标准是:需要访问外部系统、需要实时数据、或者需要执行副作用的操作,才做成工具。比如查数据库、调 API、发消息、写文件,这些必须走工具。而纯逻辑推理、文本生成、简单计算,让模型自己做就行。
工具的数量也要控制。我建议单个 Agent 的工具集控制在 10-20 个之间。太少不够用,太多模型容易选错。如果确实需要很多工具,就做分层——先让模型选工具类别,再在类别内选具体工具。
4.2 MCP 协议接入:让工具层标准化的关键一步
MCP(Model Context Protocol)是最近被讨论很多的一个话题。简单说,它是一套标准化的协议,让 Agent 可以用统一的方式对接各种外部服务,而不需要为每个服务单独写适配代码。
我实际用下来的感受是:MCP 最大的价值在于解耦。以前每接一个新工具,就要改 Executor 的代码、改 Prompt、重新测试。用了 MCP 之后,工具以标准化的 Server 形式注册进来,Executor 只需要知道“有哪些工具可用”和“每个工具的输入 schema”,不需要关心底层实现。
实际接入时,MCP Server 的配置一般长这样:
{ "mcpServers": { "weather": { "command": "npx", "args": ["-y", "@weather/mcp-server"], "env": { "API_KEY": "your_key_here" } } } }这个配置告诉 Agent 运行环境:有一个叫 weather 的 MCP Server,通过 npx 启动,需要传入 API Key。Agent 启动时会自动拉起这个 Server,并获取它提供的工具列表。
注意:MCP Server 的启动方式有 stdio 和 HTTP 两种。stdio 适合本地工具,HTTP 适合远程服务。选择哪种取决于你的工具部署在哪里,不要混用。
4.3 工具调用的错误处理:重试、降级与熔断
工具调用失败是常态,不是异常。网络抖动、API 限流、参数错误,都会导致调用失败。Executor 必须有完整的错误处理策略。
我的策略分三层:
- 重试:对于网络类错误,自动重试 2-3 次,每次间隔递增(比如 1s、2s、4s)。
- 降级:如果重试仍失败,尝试备用方案。比如主搜索 API 挂了,切到备用搜索源。
- 熔断:如果某个工具连续失败超过阈值(比如 5 次),暂时禁用该工具,避免无限重试拖垮整个流程。
这三层策略要写在 Executor 的代码里,而不是指望模型自己处理。模型在 Prompt 层面只能做到“知道失败了”,具体的重试逻辑必须由代码控制。
4.4 并行执行与依赖管理:能并行的绝不串行
Planner 输出的步骤如果有depends_on字段,Executor 就可以据此判断哪些步骤可以并行。比如“查询北京天气”和“查询上海天气”没有依赖关系,可以同时发起。
并行执行对延迟的改善非常明显。我实测过一个包含 6 个独立查询步骤的任务,串行执行耗时约 12 秒,并行执行降到 3 秒左右。当然,并行也会带来新的问题,比如多个工具同时写入同一个资源导致冲突。所以并行只适用于读操作,写操作还是要串行。
5. Reflection 层:让 Agent 具备自我纠错能力
5.1 Reflection 的触发时机:不是每一步都要反思
Reflection 很消耗 Token,如果每一步都反思,成本会翻倍。我的做法是只在关键节点触发反思:
- 每个步骤执行完成后,做一次轻量检查(结果是否为空、是否报错)。
- 整个计划执行完成后,做一次完整反思(目标是否达成、有无遗漏)。
- 遇到异常时,触发深度反思(分析原因、决定是否重规划)。
轻量检查可以用规则实现,不需要调模型。比如检查返回结果是否为空、状态码是否正常。只有规则无法判断时,才调模型做深度反思。
5.2 反思 Prompt 的设计:让模型说真话
Reflection 的 Prompt 设计有个微妙之处:如果你问“这个结果对吗”,模型倾向于说“对的”。这是模型的顺从性偏差。要让它说真话,得换个问法。
我常用的反思 Prompt 模板是这样的:
你是一个严格的结果验证者。请检查以下执行结果是否真正达成了预期目标。 预期目标:{goal} 实际结果:{result} 原始任务:{original_task} 请回答: 1. 结果是否完整达成了目标?如果有遗漏,具体遗漏了什么? 2. 结果中是否有明显的错误或矛盾? 3. 如果满分 10 分,你给这个结果打几分?为什么? 请用 JSON 格式输出:{"passed": true/false, "score": 0-10, "issues": [], "suggestion": ""}关键是“严格的结果验证者”这个角色设定,以及要求打分。打分这个动作会迫使模型做更细致的评估,而不是简单地说“没问题”。
5.3 反思结果的处理:重试、调整还是放弃
Reflection 输出passed: false之后,怎么处理?我的策略是:
score >= 7:认为基本达标,记录问题但不阻塞流程。4 <= score < 7:触发一次重试,把反思中的suggestion作为额外提示传给 Executor。score < 4:触发重规划,让 Planner 重新分解任务。
这个阈值不是固定的,要根据任务的重要程度调整。对于发消息、下单这类有副作用的操作,阈值要调高,宁可多确认几次。对于查询类操作,阈值可以放宽。
5.4 反思的副作用:避免无限循环
Reflection 最大的风险是无限循环:反思发现问题→重试→反思又发现问题→再重试……我遇到过最夸张的一次,一个 Agent 在“查询股票价格”这个任务上循环了 17 次,因为每次返回的价格都略有波动,Reflection 认为“结果不一致”。
解决办法是设置硬性上限:单个步骤最多重试 3 次,整个任务最多重规划 2 次。超过上限就强制终止,返回当前最优结果并附带说明。这个上限要写在代码里,不能靠 Prompt 约束。
6. 完整闭环的串联与工程化落地
6.1 上下文管理:共享但不混乱
四层共享一个上下文,但每层关注的信息不同。我的做法是在上下文里用命名空间区分:
[USER_INPUT] 用户原始输入 [PLAN] Planner 输出的计划 [STEP_1_RESULT] 第 1 步执行结果 [STEP_1_REFLECTION] 第 1 步反思结论 [FINAL_REFLECTION] 最终反思每次调用模型时,只把相关的命名空间注入 Prompt,而不是把整个上下文全塞进去。这样既节省 Token,又避免模型被无关信息干扰。
6.2 状态机设计:让流程可控
整个闭环本质上是一个状态机:PLANNING → EXECUTING → REFLECTING → (REPLANNING | DONE | FAILED)。用状态机管理流程的好处是,每个状态的转换条件明确,不会出现“卡在某个环节”的情况。
我用 Python 实现时,会用一个简单的状态字典来跟踪:
state = { "phase": "PLANNING", "plan": None, "current_step": 0, "results": [], "reflections": [], "retry_count": 0, "replan_count": 0 }每次循环根据phase决定调用哪一层,执行完更新状态。这个结构简单但足够用,不需要引入复杂的工作流引擎。
6.3 日志与可观测性:出了问题能查
Agent 出问题是必然的,关键是出问题后能不能快速定位。我的做法是每一步都记录完整的输入输出,包括 Prompt、模型返回、工具调用参数和结果。这些日志按任务 ID 聚合,方便回放。
日志里特别要记录的是:模型的实际输出和预期格式的差异。很多时候 Agent 失败不是因为逻辑错,而是因为模型输出格式不对导致解析失败。这类问题只有看日志才能发现。
6.4 性能优化:延迟和成本的平衡
完整闭环的延迟主要来自模型调用次数。一个 5 步的任务,如果每步都反思,至少 10 次模型调用。优化方向有几个:
- 轻量检查用规则代替模型调用。
- 能并行的步骤并行执行。
- 简单任务走轻量路径,跳过完整反思。
- 用更小的模型做 Reflection,大模型只用于 Planner 和复杂推理。
我实测下来,这些优化能把平均延迟从 20 秒降到 8 秒左右,Token 成本降低约 40%。
7. 常见问题与排查技巧实录
7.1 Planner 输出格式不稳定怎么办
这是最常见的问题。模型有时候输出 JSON,有时候输出 Markdown 列表,有时候夹杂解释性文字。解决办法有三个:
- 在 Prompt 里明确要求“只输出 JSON,不要有任何其他文字”。
- 使用模型的结构化输出功能(如果支持),强制约束输出格式。
- 在代码里加一层解析容错,尝试从输出中提取 JSON 部分。
我通常三个一起用。即使这样,偶尔还是会有格式问题,所以解析失败时要有兜底逻辑,比如让模型重新输出一次。
7.2 工具调用参数错误怎么排查
工具调用参数错误通常有两类:参数名不对,或者参数值类型不对。排查时先看工具定义的 schema,确认参数名和类型。然后看模型实际传的参数,对比差异。
常见原因是 schema 描述不够清晰。比如一个参数叫date,但没说明格式是YYYY-MM-DD还是时间戳,模型就可能传错。解决办法是在 schema 的 description 里写清楚格式要求,最好给一个示例。
7.3 Reflection 总是说“通过”怎么办
这说明反思 Prompt 不够严格。可以尝试:降低温度参数(比如从 0.7 降到 0.2),让模型输出更确定;在 Prompt 里加入反面示例,告诉模型“以下情况应该判定为不通过”;或者引入外部验证,比如用规则检查结果是否为空、是否符合预期格式。
7.4 任务执行到一半卡住不动
通常是某个步骤在等待一个永远不会满足的条件。比如等待一个异步任务完成,但那个任务已经失败了。排查时看日志里最后一条记录是什么,确认卡在哪个步骤。然后在代码里加超时机制,任何步骤超过设定时间就强制失败并进入反思流程。
7.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决思路 |
|---|---|---|---|
| Planner 输出格式乱 | Prompt 约束不够 | 检查 Prompt 和输出 | 加结构化输出约束 |
| 工具调用参数错 | schema 描述不清 | 对比 schema 和实际参数 | 完善 description |
| Reflection 总通过 | 反思 Prompt 太宽松 | 检查反思 Prompt | 加严格约束和示例 |
| 任务卡住 | 缺少超时机制 | 看日志最后记录 | 加步骤超时 |
| 无限重试 | 缺少重试上限 | 看 retry_count | 代码层设硬上限 |
| 重复执行已完成步骤 | 重规划未排除已完成 | 检查重规划 Prompt | 显式声明已完成步骤 |
8. 一些实操心得和后续扩展方向
搭 Agent 这件事,我的核心体会是:架构比模型重要,约束比自由重要。模型能力再强,如果没有好的架构约束,也会在复杂任务上翻车。Planner 负责把大问题拆小,Executor 负责把小问题解决,Reflection 负责确认解决得对不对,这个闭环一旦跑通,Agent 的可靠性会有质的提升。
另外几个零散但实用的经验:工具数量控制在 20 个以内,超过就分层;反思阈值根据任务风险调整,有副作用的操作阈值调高;日志一定要记全,出问题时能回放比什么都重要;重试和重规划都要有硬上限,不能靠模型自觉。
这个架构后续还可以往几个方向扩展。一是加入长期记忆,让 Agent 跨会话记住用户的偏好和历史操作。二是引入多 Agent 协作,让不同的 Agent 分别负责规划、执行和验证,通过消息传递协作。三是把 MCP 工具层做得更完善,支持工具的自动发现和热插拔。这些方向我都在陆续尝试,有新的心得再整理出来分享。