☰
AI Agent架构分层实战:Planner、Executor、Memory、Reflection四模块闭环设计
2026/10/14 5:29:10 网站建设 项目流程

1. 为什么我们需要重新理解 Agent 的架构分层

很多人第一次接触 AI Agent,脑子里浮现的画面就是一个更聪明的聊天机器人。但真正动手搭过几个能跑通任务的 Agent 之后,你会发现它和聊天机器人根本是两种东西。聊天机器人是“你问我答”,Agent 是“你给目标,它自己想办法”。这个差别听起来不大,但落到工程实现上,复杂度差了一个数量级。

我最初做 Agent 的时候,踩过一个很典型的坑:把所有逻辑塞进一个巨大的提示词里,让模型自己判断该干什么、该调什么工具、该不该停下来。结果就是,简单任务还行,稍微复杂一点就开始胡言乱语,要么死循环,要么提前收工。后来我才意识到,问题不在于模型不够强,而在于我没有给它一个清晰的架构。

一个能稳定完成任务的 Agent,本质上需要四个核心模块协同工作:Planner(规划器)、Executor(执行器)、Memory(记忆系统)、Reflection(反思机制)。这四个模块构成了一个完整的闭环,缺了任何一个,Agent 都会表现得像个半成品。

这篇文章我会把这四个模块逐一拆开,讲清楚每个模块解决什么问题、怎么实现、有哪些坑。同时我会给出一个可以直接参考的代码骨架,让你看完就能动手搭一个属于自己的 Agent。不管你是刚入门的新手,还是已经做过几个 Demo 的老手,应该都能从中找到一些有用的东西。

2. Planner:Agent 的大脑,决定“先做什么”

2.1 Planner 到底在解决什么问题

Planner 的核心职责只有一个:把用户给的一个模糊目标,拆解成一系列可执行的具体步骤。

举个例子,用户说“帮我调研一下最近三个月 AI Agent 领域的重要进展,整理成一份报告”。这句话对人来说很自然,但对机器来说,它包含了很多隐含信息:调研范围是什么、从哪里获取信息、怎么判断“重要”、报告格式是什么、多长合适。Planner 要做的,就是把这些隐含信息显式化,变成一步步可以执行的动作。

没有 Planner 的 Agent,就像一个没有施工图纸的装修队,干到哪算哪,最后出来的东西大概率不是你想要的。

2.2 三种主流的 Planner 实现思路

目前市面上常见的 Planner 实现,大致可以分成三类:

第一类:一次性规划(One-shot Planning)

这是最简单的方式。把用户的目标丢给模型,让它一次性输出完整的步骤列表,然后按顺序执行。优点是实现简单、延迟低;缺点是如果中间某一步执行失败或者环境发生变化,整个计划就废了,没有调整余地。

第二类:迭代式规划(Iterative Planning)

每执行完一步,就把当前状态和剩余目标重新丢给模型,让它决定下一步做什么。这种方式灵活度高,能应对动态变化,但缺点是调用次数多、成本高,而且容易出现“走着走着忘了最初目标”的情况。

第三类:分层规划(Hierarchical Planning)

先把大目标拆成几个高层阶段,每个阶段再拆成具体步骤。这种方式兼顾了全局视野和执行灵活性,是我个人最推荐的做法。比如上面那个调研任务,可以先拆成“确定调研维度 → 收集信息 → 筛选整理 → 撰写报告”四个阶段,每个阶段内部再细化。

2.3 Planner 的提示词该怎么写

很多人写 Planner 的提示词,就是一句“请把以下任务拆解成步骤”。这样写出来的计划,质量完全看运气。我实测下来,一个高质量的 Planner 提示词至少需要包含以下几个要素:

  • 角色设定:告诉模型它是一个任务规划专家,而不是通用助手
  • 输出格式约束:明确要求输出 JSON 或者特定格式的步骤列表,方便后续程序解析
  • 步骤数量限制:一般建议 3 到 7 步,太少不够细,太多容易失控
  • 依赖关系说明:要求模型标注哪些步骤可以并行、哪些必须串行
  • 终止条件:明确告诉模型什么情况下算任务完成

下面是我常用的一个 Planner 提示词模板:

PLANNER_PROMPT = """ 你是一个任务规划专家。你的职责是将用户的目标拆解成清晰、可执行的步骤。 要求: 1. 步骤数量控制在 3-7 个之间 2. 每个步骤必须是一个具体的动作,而不是抽象的描述 3. 标注每个步骤的依赖关系(依赖哪些前置步骤) 4. 标注每个步骤是否需要调用外部工具 5. 输出格式为 JSON 数组 用户目标:{goal} 输出示例: [ {{"step_id": 1, "action": "搜索最近三个月的AI Agent相关新闻", "depends_on": [], "need_tool": true}}, {{"step_id": 2, "action": "筛选出提及次数最多的五个技术方向", "depends_on": [1], "need_tool": false}} ] """

注意:提示词里的输出示例非常重要。模型看到具体格式之后,输出的稳定性会明显提升。我试过不加示例,解析失败率大概在 20% 左右;加上示例之后,基本能降到 5% 以下。

2.4 Planner 的常见坑与应对

坑一:计划过于笼统。模型经常输出“分析数据”“整理结果”这种没有操作性的步骤。解决办法是在提示词里明确要求“每个步骤必须能对应到一个具体的工具调用或明确的思考动作”。

坑二:步骤之间循环依赖。比如步骤 2 依赖步骤 3,步骤 3 又依赖步骤 2。这种情况在复杂任务里偶尔会出现。我的做法是在解析阶段做一个简单的拓扑排序检测,发现环就重新生成。

坑三:忽略中间状态。有些 Planner 生成的计划,步骤之间是断裂的,前一步的输出没有传递给后一步。解决办法是在每个步骤的定义里,明确标注输入和输出。

3. Executor:把计划变成现实的手

3.1 Executor 的职责边界

Planner 负责“想”,Executor 负责“做”。这个“做”包含三件事:调用工具、处理返回结果、决定是否继续。

很多人会把 Executor 和工具调用混为一谈,其实不完全一样。工具调用只是 Executor 的一个动作,Executor 还需要判断工具返回的结果是否有效、是否需要重试、是否应该把结果传给下一步。

3.2 工具调用的设计原则

工具是 Agent 和外部世界交互的接口。设计工具的时候,有几个原则我踩过坑之后觉得特别重要:

原则一:工具粒度要适中。太细的工具会导致调用次数爆炸,太粗的工具又失去了灵活性。我的经验是,一个工具最好对应一个完整的原子操作,比如“搜索网页”是一个工具,“打开某个网页”是另一个工具,而不是把这两件事合成一个。

原则二:工具描述要精确。模型是根据工具描述来决定调不调的。描述写得模糊,模型就会乱调。比如“获取信息”这种描述就是灾难,应该写成“根据关键词搜索网页,返回前 10 条结果的标题和摘要”。

原则三:工具要有明确的错误返回。工具执行失败的时候,不要直接抛异常,而是返回一个结构化的错误信息,让 Executor 能判断是重试还是跳过。

下面是一个工具定义的示例:

tools = [ { "name": "web_search", "description": "根据关键词搜索网页,返回前10条结果的标题、摘要和链接", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"}, "num_results": {"type": "integer", "default": 10} }, "required": ["query"] } } ]

3.3 Executor 的执行循环

Executor 的核心是一个循环:取出下一步 → 判断是否需要工具 → 执行 → 处理结果 → 更新状态 → 判断是否继续。

这个循环看起来简单,但有几个细节决定了 Agent 的稳定性:

细节一:最大步数限制。一定要设一个上限,比如 20 步。不然遇到死循环,你的 API 账单会教你做人。我见过一个案例,Agent 因为工具一直返回空结果,反复重试了 200 多次。

细节二:状态传递。每一步的输出要结构化地存下来,传给下一步。我一般用一个字典来维护全局状态,每个步骤执行完就更新这个字典。

细节三:异常处理。工具调用失败、模型返回格式错误、超时,这些都要有对应的处理逻辑。我的做法是给每个步骤设置最大重试次数(一般 2-3 次),超过就标记为失败,让 Planner 决定是否调整计划。

3.4 一个可运行的 Executor 骨架

class Executor: def __init__(self, planner, tools, max_steps=20): self.planner = planner self.tools = {t["name"]: t for t in tools} self.max_steps = max_steps self.state = {} def run(self, goal): plan = self.planner.plan(goal) step_count = 0 for step in plan: if step_count >= self.max_steps: return {"status": "max_steps_reached", "state": self.state} result = self.execute_step(step) self.state[step["step_id"]] = result step_count += 1 if result.get("status") == "failed": # 触发重新规划 plan = self.planner.replan(goal, self.state) return {"status": "completed", "state": self.state} def execute_step(self, step): if not step.get("need_tool"): return {"status": "success", "output": step.get("action")} tool = self.tools.get(step.get("tool_name")) if not tool: return {"status": "failed", "error": "tool_not_found"} for attempt in range(3): try: result = tool["func"](**step.get("params", {})) return {"status": "success", "output": result} except Exception as e: if attempt == 2: return {"status": "failed", "error": str(e)}

这个骨架不完整,但核心逻辑都在里面了。你可以根据自己的需求往里填东西。

4. Memory:让 Agent 不再“金鱼脑”

4.1 为什么 Memory 是 Agent 的刚需

没有 Memory 的 Agent,每执行一步都是“失忆”状态。它不记得之前做过什么、得到过什么结果、用户有什么偏好。这在简单任务里问题不大,但一旦任务步骤超过 5 步,或者需要跨会话保持上下文,就会彻底崩掉。

我做过一个对比实验:同样的任务,加 Memory 和不加 Memory,完成率差了将近 40%。这个差距在复杂任务上还会更大。

4.2 短期记忆与长期记忆的分工

Memory 一般分成两层:

短期记忆(Short-term Memory):存当前任务执行过程中的中间状态、工具返回结果、已经完成的步骤。它的生命周期就是一次任务,任务结束就清空。实现上一般就是一个字典或者列表,跟着 Executor 的状态走。

长期记忆(Long-term Memory):存跨任务的信息,比如用户的偏好、历史任务的总结、领域知识。它的生命周期是永久的,需要持久化存储。实现上一般用向量数据库,把信息转成 embedding 存起来,需要的时候做相似度检索。

这两层记忆的分工很明确:短期记忆保证当前任务不跑偏,长期记忆让 Agent 越用越聪明。

4.3 向量检索在 Memory 中的实际应用

长期记忆的核心是检索。当 Agent 遇到一个新任务时,它需要从历史记忆里找到相关的信息。这个过程一般是这样:

  1. 把当前任务的目标转成 embedding
  2. 在向量数据库里做相似度搜索,取 top-k 条相关记忆
  3. 把这些记忆作为上下文,注入到 Planner 的提示词里

这里有个细节很多人会忽略:检索的时机。不是每次调用都要检索,那样成本太高。我的做法是在任务开始时检索一次,然后在每个关键步骤(比如 Planner 重新规划时)再检索一次。

4.4 Memory 的常见坑

坑一:记忆污染。如果长期记忆里存了大量低质量的信息,检索出来的结果反而会干扰 Agent 的判断。解决办法是给每条记忆加一个质量分数,检索的时候按分数加权。

坑二:记忆冲突。新旧记忆矛盾的时候,Agent 不知道该信哪个。我的做法是给记忆加时间戳,检索时优先返回较新的记忆,同时在提示词里明确说明“以下信息按时间倒序排列”。

坑三:检索不到。有时候明明存了相关信息,但检索不出来。这通常是 embedding 模型的问题,或者查询语句和存储语句的表达差异太大。解决办法是用同一个模型做 embedding,同时在存储的时候把信息改写成多种表达方式。

5. Reflection:Agent 的自我进化机制

5.1 Reflection 到底在反思什么

Reflection 是 Agent 架构里最容易被忽略、但价值最高的模块。它的核心作用是:在任务执行过程中或结束后,回顾自己的表现,找出问题,并调整后续行为。

Reflection 一般反思三件事:

  • 结果对不对:当前步骤的输出是否符合预期
  • 方法好不好:有没有更高效的路径
  • 计划要不要改:基于当前进展,原计划是否还适用

5.2 两种 Reflection 的触发时机

在线反思(Online Reflection):在每个步骤执行完之后立即触发。优点是反馈及时,能快速纠偏;缺点是增加延迟和成本。

离线反思(Offline Reflection):在任务结束后统一反思。优点是成本低,不影响任务执行;缺点是无法纠正当前任务,只能优化未来的任务。

我的建议是两者结合:关键步骤用在线反思,普通步骤用离线反思。判断标准是这个步骤失败会不会导致整个任务失败。

5.3 Reflection 的提示词设计

Reflection 的提示词和 Planner 不一样,它需要模型做的是“批判性思考”,而不是“创造性规划”。所以提示词的重点应该放在:

  • 要求模型指出具体问题,而不是泛泛而谈
  • 要求模型给出可操作的改进建议
  • 要求模型判断是否需要重新规划

一个我常用的 Reflection 提示词模板:

REFLECTION_PROMPT = """ 你是一个任务质量审核专家。请审视以下任务的执行情况,找出问题并给出改进建议。 原始目标:{goal} 已执行步骤:{executed_steps} 当前状态:{current_state} 请回答以下问题: 1. 已执行的步骤是否有效地推进了目标?如果有偏离,具体偏离在哪里? 2. 当前状态是否足以完成剩余目标?如果不足,缺什么? 3. 是否需要调整原计划?如果需要,给出调整后的步骤。 输出格式: {{ "is_on_track": true/false, "issues": ["问题1", "问题2"], "suggestions": ["建议1", "建议2"], "need_replan": true/false, "new_plan": [...] }} """

5.4 Reflection 的实战效果

我在一个信息整理类的 Agent 上做过测试。不加 Reflection 的时候,任务完成率大概 65%,主要失败原因是“信息收集不全”和“整理逻辑混乱”。加上 Reflection 之后,完成率提升到 85% 左右。提升主要来自两个方面:一是 Reflection 能发现信息收集的遗漏,触发补充搜索;二是 Reflection 能发现整理逻辑的问题,触发重新组织。

不过 Reflection 也不是万能的。它最大的问题是会增加 token 消耗,大概增加 30% 到 50%。所以要不要加、加在哪,需要根据任务的价值来判断。低价值任务不值得加,高价值任务加了很划算。

6. 四个模块如何串成一个完整闭环

6.1 闭环的整体流程

把 Planner、Executor、Memory、Reflection 串起来,一个完整的任务执行流程是这样的:

  1. 用户输入目标
  2. Memory 检索相关历史信息,注入上下文
  3. Planner 生成初始计划
  4. Executor 执行第一步
  5. Reflection 检查第一步结果
  6. 如果结果有问题,触发重新规划;如果没问题,继续下一步
  7. 每一步的结果都写入 Memory
  8. 任务完成后,Reflection 做整体复盘,把经验写入长期记忆

这个闭环的关键在于:每一步都有反馈,每一步都能调整。而不是一条路走到黑。

6.2 模块之间的接口设计

四个模块之间的接口设计,决定了整个系统的可维护性。我的经验是,接口要尽量简单,数据格式要统一。

模块输入输出
Planner目标 + 历史记忆步骤列表(JSON)
Executor单个步骤执行结果(JSON)
Memory查询语句 / 待存储信息相关记忆列表 / 存储确认
Reflection目标 + 已执行步骤 + 当前状态反思报告(JSON)

所有模块的输入输出都用 JSON,这样任何一个模块出问题,都容易定位和替换。

6.3 一个完整的闭环示例

假设用户的目标是“帮我找三篇关于 Agent 架构的优质文章,总结核心观点”。

  • Memory先检索:用户之前有没有做过类似任务?有没有偏好?假设检索到“用户偏好技术深度高的文章”。
  • Planner生成计划:搜索文章 → 筛选优质文章 → 提取核心观点 → 整理总结。
  • Executor执行第一步:调用搜索工具,返回 10 条结果。
  • Reflection检查:10 条结果里有多少是相关的?如果相关度低,触发重新搜索。
  • Executor执行第二步:筛选出 3 篇。筛选标准是什么?这里需要 Planner 补充判断逻辑。
  • Reflection检查:筛选出的 3 篇是否覆盖了不同角度?如果都是同一个角度,触发补充搜索。
  • Executor执行第三步和第四步:提取观点、整理总结。
  • Reflection做整体复盘:这次任务哪里做得好、哪里可以改进,写入长期记忆。

这个流程走下来,任务完成的质量会比没有闭环的 Agent 高出一大截。

7. 实操中遇到的典型问题与排查思路

7.1 常见问题速查表

问题现象可能原因排查方向解决方案
Agent 死循环步骤没有终止条件检查 Planner 输出加最大步数限制
工具调用失败率高工具描述不清晰检查工具定义细化工具描述和参数
任务完成度低计划太笼统检查 Planner 提示词要求输出可执行步骤
结果前后矛盾Memory 冲突检查记忆检索逻辑加时间戳和质量分
响应太慢Reflection 太频繁检查反思触发时机改为关键步骤触发
Token 消耗大上下文太长检查 Memory 注入量限制检索条数

7.2 三个我踩过的坑

坑一:Planner 和 Executor 的职责混淆。我一开始让 Executor 也参与决策,结果就是两个模块互相打架,行为不可预测。后来我把决策权完全收归 Planner,Executor 只负责执行,问题就解决了。

坑二:Memory 存了太多没用的东西。早期我把所有中间结果都存进长期记忆,结果检索出来的全是噪音。后来我改成只存“任务总结”和“用户反馈”,质量立刻上来了。

坑三:Reflection 太理想化。我一开始期望 Reflection 能发现所有问题,结果发现它对“逻辑错误”很敏感,但对“事实错误”几乎无感。后来我在 Reflection 里加了一个事实校验的步骤,专门检查关键事实。

7.3 性能优化的几个实用技巧

  • 缓存 Planner 的输出:相似的目标可以复用计划,省一次模型调用
  • 并行执行无依赖的步骤:Planner 标注了依赖关系之后,Executor 可以并行跑
  • 限制 Memory 的检索条数:一般 3 到 5 条就够了,多了反而干扰
  • Reflection 用更小的模型:反思任务比规划任务简单,可以用便宜一点的模型

8. 关于架构选型的一些个人体会

搭 Agent 这件事,最容易犯的错误就是“过度设计”。我见过不少人一上来就想搞一个全能 Agent,什么模块都往里塞,结果复杂度爆炸,调试都调不动。

我的建议是:从最小闭环开始,按需扩展。先搭一个只有 Planner 和 Executor 的版本,跑通几个简单任务。然后加上 Memory,解决上下文丢失的问题。最后加上 Reflection,提升任务质量。每一步都验证有效之后再往下走。

另外,不要迷信“全自动”。有些环节人工介入一下,效果反而更好。比如 Planner 生成计划之后,让用户确认一下;Reflection 发现重大问题的时候,让用户决策一下。Agent 的价值是提效,不是完全替代人。

最后分享一个我常用的调试技巧:把 Agent 的每一步执行都打上详细日志,包括输入、输出、耗时、token 消耗。出问题的时候,回放日志比盯着代码看快得多。这个习惯帮我省了无数时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询