Agent 系统组成
flowchart TD
U[用户目标] --> P[Planner
拆解目标 / 选策略]
P --> X[Executor
执行当前步骤]
X --> T[Tools
搜索 / API / 文件 / 浏览器]
T --> X
X --> M[Memory
任务状态 / 历史 / 长期信息]
M --> P
X --> R{完成或重规划?}
R -->|重规划| P
R -->|完成| O[最终结果]
上一篇讲的是概念:Agent 不是“更会聊天的模型”,而是一种围绕目标推进任务的系统。 这一篇往下走一步,讨论它到底是怎么“搭起来”的。
很多入门材料会把 Agent 简化成先看定义:一个 LLM + 一组工具。这个说法不算错,但太薄。它只能帮助你理解 Agent 为什么比普通 Chatbot 更像“执行者”,却不足以解释:为什么有些 Agent 能稳定做完任务,有些却会在第三步开始乱跑;为什么同样接了工具,有的系统可控,有的系统不可控;为什么很多问题并不出在模型本身,而是出在状态管理、工具设计和执行边界上。
从工程视角看,一个可用的 Agent,至少需要处理四类问题:
- 目标如何拆解
- 动作如何执行
- 上下文如何维持
- 外部能力如何接入
这四类问题,通常就对应四个核心部件:
- Planner(规划器)
- Executor(执行器)
- Memory(记忆)
- Tools(工具)
OpenAI 在面向开发者的 Agent 指南里强调,构建 Agent 的关键不只是模型调用,而是把模型、工具、状态和工作流组织成一个可反复运行的系统。Anthropic 也明确指出,许多成功的 Agent 系统并不复杂,但它们会非常认真地处理任务分解、工具定义和执行闭环,而不是把一切都丢给一个 prompt。(developers.openai.com) (OpenAI 开发者)
先给一个系统视角:Agent 不是“一个角色”,而是一条循环
如果只看静态结构,很容易误以为 Planner、Executor、Memory、Tools 是四个独立模块,像传统分层架构那样各管一摊。 但 Agent 更接近一个循环系统,不是一个静态页面。
它的典型运行方式是:
接收目标
-> 判断当前状态
-> 决定下一步
-> 调用工具或执行动作
-> 读取结果
-> 更新状态
-> 再决定下一步
-> ...
-> 结束 / 失败 / 请求人工确认
所以,理解 Agent 组成时最重要的一点是:
四个组件的意义,不是把功能机械分箱,而是共同支撑这个“观察—决策—执行—反馈”的闭环。
四大支柱:Planner、Executor、Memory、Tools
先看定义:Agent 系统通常由 Planner 负责“怎么走”、Executor 负责“怎么做”、Memory 负责“现在知道什么”、Tools 负责“能做什么”,而 LLM 经常在其中扮演推理和协调核心。
| 组件 | 英文 | 核心职责 |
|---|---|---|
| 规划器 | Planner | 理解目标、拆分步骤、决定策略、必要时重规划 |
| 执行器 | Executor | 落实动作、调用工具、处理返回结果和异常 |
| 记忆 | Memory | 维持上下文、任务状态和长期可复用信息 |
| 工具 | Tools | 提供检索、计算、读写、API 调用等外部能力 |
这四个概念里,Planner 和 Executor 更像逻辑角色,Memory 和 Tools 更像系统能力。 它们不一定都要做成单独服务,也不一定都要用不同模型实现,但在设计上最好把职责想清楚,否则系统一复杂就会互相污染。
Planner:决定“往哪里走”,而不只是“下一步调哪个工具”
规划器最容易被误解成“把大任务拆成几步清单”。这只是它最表层的工作。
一个成熟一点的 Planner,至少要处理四件事:
- 理解目标和约束
- 选择策略
- 决定步骤顺序
- 根据执行反馈修正计划
Planner 的真正职责:不是列计划,而是管理任务方向
当用户说:
“帮我分析这个报错,定位原因,并给出可验证的修复方案。”
Planner 真正要回答的不是“第一步、第二步、第三步是什么”这么简单,而是:
- 这是一次性回答,还是多步任务?
- 先查日志,还是先读代码?
- 需不需要补充信息?
- 是否要先验证环境?
- 哪些步骤可以并行,哪些必须串行?
- 执行失败时应该换路,还是停止?
这意味着,Planner 管的是任务推进策略,而不仅是一个待办列表。
Planner 常见的三种工作方式
| 方式 | 特点 | 适用情况 |
|---|---|---|
| 隐式规划 | 不显式输出完整计划,每轮只决定下一步 | 简单任务、低成本链路 |
| 显式规划 | 先产出整体计划,再按计划执行 | 复杂多步任务、需要全局视角 |
| 动态重规划 | 执行中持续根据反馈修正原计划 | 开放环境、工具结果不确定、任务容易偏航 |
很多教程会默认“先规划再执行”看起来更高级,但现实里不一定总是最优。 对于短任务,显式规划可能只是在增加 token 消耗和路径长度。Anthropic 对 Agent 实践的一个重要建议,就是优先使用简单、可组合的模式,而不是一开始就引入重型框架和复杂规划器。(anthropic.com) (Anthropic)
Planner 的失败模式
规划器常见问题不是“不会规划”,而是规划得看起来很合理,但对执行不友好。
典型失败包括:
- 过度规划:对一个三步任务先写一大段宏观计划,徒增成本;
- 伪规划:输出看似完整的步骤,其实没有实际约束意义;
- 不重规划:明明执行结果已经改变前提,仍机械按原路线继续;
- 规划脱离工具现实:计划里要求的动作,实际上系统根本没有对应工具;
- 忽略终止条件:不断追加新步骤,任务越做越远。
所以,Planner 的价值不在于“写得像项目经理”,而在于它能否在有限工具和真实反馈下持续做出合适决策。
Executor:负责把“想法”变成“动作”
如果说 Planner 负责方向,Executor 负责的就是落地。
它通常要做这些事:
- 接收当前步骤或子任务;
- 选择或调用工具;
- 传递参数;
- 处理工具结果;
- 捕获异常;
- 将结果写回任务状态;
- 决定是否能结束当前步骤。
先看定义:Executor 不是“简单调用 API 的胶水层”,而是把计划真正转化为系统行为的执行层。
Executor 处理的,不只是“调用成功”
一个很常见的误区是:认为执行器的职责就是“模型决定调用哪个工具,然后代码把工具跑一下”。
实际上,生产可用的 Executor 更关心的是:
- 这个工具是否真的该现在调用?
- 参数是否完整、格式是否正确?
- 工具返回的是成功、失败,还是部分成功?
- 返回值是否需要清洗、结构化、摘要化后再送回模型?
- 这次失败是否可以重试?
- 如果不能重试,是该降级、换路,还是请求人工确认?
这意味着,Executor 不是一个被动 adapter,而是一个带有动作控制与错误处理能力的运行时层。
Executor 为什么不能“完全听模型的”
如果执行器对模型输出无条件照做,会出现很多问题:
- 模型参数拼错,直接导致工具报错;
- 模型把上一步结果误读,连续执行错误动作;
- 模型频繁调用同一个工具,陷入无效循环;
- 模型在高风险场景下跳过确认直接执行;
- 模型把模糊意图翻译成过于激进的操作。
因此,Executor 往往需要加一层非模型约束,例如:
- 参数 schema 校验;
- 工具白名单;
- 高频调用限流;
- 步数预算;
- 高风险动作确认;
- 幂等性检查;
- 超时与取消机制。
这里的原则很简单:
Planner 可以相对开放,但 Executor 必须相对保守。
Memory:Agent 真正的“上下文底盘”
上一篇提到,Agent 和普通聊天系统的一个关键差异,是它要处理多步任务。 而只要有多步任务,就一定会有状态。
这个状态并不只来自“对话历史”,还包括:
- 当前任务做到哪一步;
- 已经拿到哪些中间结果;
- 哪些工具已经调过;
- 哪些约束仍然有效;
- 哪些用户偏好应被继承;
- 哪些信息只对当前会话有效,哪些应该长期保留。
这些东西合在一起,才构成 Agent 的 Memory。
记忆不只是一种,至少有三层
| 类型 | 英文 | 作用 | 常见实现 |
|---|---|---|---|
| 短期记忆 | Short-term Memory | 当前对话和最近几步上下文 | 上下文窗口、滑动历史 |
| 工作记忆 | Working Memory | 当前任务中的中间状态、计划、结果、待办 | 状态对象、任务上下文、结构化变量 |
| 长期记忆 | Long-term Memory | 跨会话保留的偏好、历史、稳定知识 | 数据库、向量库、用户档案、事件日志 |
为什么工作记忆比很多人想象中更重要
很多初学者一提到 Memory,就想到“长期记住用户偏好”或者“向量库召回历史”。 这些当然重要,但在实际 Agent 系统中,工作记忆往往更关键。
因为任务执行中的大多数问题,都发生在当前会话内部:
- 忘了上一步已经查过某个文件;
- 忘了某个 API 返回过错误码;
- 忘了用户刚刚否决过某种方案;
- 忘了任务已经进入“等待确认”状态;
- 重复执行已经完成的步骤。
这些都不是“长期记忆缺失”,而是“任务状态管理失败”。
所以,在很多系统里,真正该先设计好的不是记忆召回算法,而是:
- 当前任务状态有哪些字段;
- 哪些字段由工具更新;
- 哪些字段由模型读写;
- 哪些字段必须结构化;
- 哪些字段会影响终止条件。
记忆的一个常见误区:把所有东西都塞进上下文
上下文窗口变大后,很容易出现一个错误直觉: “反正上下文很长,那就把所有历史都带上。”
这通常会带来三个问题:
- 成本上升:每一步都在重复消耗无关上下文;
- 注意力稀释:真正关键的状态被淹没;
- 行为漂移:模型抓住旧信息不放,忽略当前最新状态。
Anthropic 在 context engineering 的文章里反复强调,问题不在于上下文够不够长,而在于你是否把当前步骤真正需要的状态组织清楚。(anthropic.com) (Anthropic)
所以,Memory 设计的重点不是“存更多”,而是“让该出现的信息在该出现的时候出现”。
Tools:决定 Agent 到底能做什么,也决定它会怎么出错
工具是 Agent 与外部世界接触的接口。 没有工具,Agent 只能在文本里“思考”;有了工具,它才能真正检索、计算、读写、调用业务系统、产生状态变化。
MCP 的官方介绍把这件事说得很清楚:它的目标就是让 AI 应用能够连接外部数据源、工具和工作流,从而访问信息并执行任务。(modelcontextprotocol.io) (模型上下文协议)
工具的常见类型
| 类型 | 作用 | 例子 |
|---|---|---|
| 信息获取 | 获取外部事实或状态 | 搜索、数据库查询、读取文档、网页抓取 |
| 计算执行 | 补足模型在精确计算和程序执行上的不足 | 计算器、代码执行、SQL 执行 |
| 读写操作 | 与文件或系统状态交互 | 文件读写、对象存储、表单填写 |
| 业务动作 | 触发真实业务流程 | 发邮件、建工单、改订单、调内部 API |
工具设计的质量,会直接决定 Agent 的质量
Anthropic 专门写过一篇关于工具设计的工程文章,核心观点非常直接:Agent 的效果,很大程度上取决于你提供给它的工具是否设计得足够好。(anthropic.com) (Anthropic)
一个好工具通常具备这些特征:
- 名称明确,不和别的工具重叠;
- 描述清楚,告诉模型它什么时候该用、什么时候不该用;
- 参数 schema 明确,减少猜测;
- 返回结果结构化,便于后续处理;
- 失败语义清晰,模型能据此调整下一步;
- 权限边界清楚,不把高风险能力随便暴露。
而一个坏工具会让 Agent 出现两类典型问题:
- 误用:明明不该调用,却频繁调用;
- 弃用:明明该用,却完全想不到去用。
很多所谓“模型不会用工具”的问题,本质上其实是工具设计得不适合被模型理解。
工具越多,不一定越强
这是 Agent 系统里一个很常见但不直观的 trade-off。
直觉上,好像工具越多,Agent 能力越强。 但实际情况往往是:
- 选择空间过大,模型更难做对决策;
- 工具之间语义重叠,容易误选;
- 上下文中塞入太多工具描述,成本和噪声都上升;
- 工具链变长,失败路径变复杂。
Anthropic 关于 MCP 和代码执行的文章甚至指出,随着工具数量增加,单纯把所有工具定义一股脑塞进上下文,会显著拖慢系统并推高成本。(anthropic.com) (Anthropic)
所以更稳妥的原则通常是:
- 先给最小必要工具集;
- 再按任务域分层暴露工具;
- 能合并就别重复;
- 工具要面向任务,而不是面向“展示能力”。
四个组件如何协同:Agent 循环
把四个组件放到一起,就能得到一个更完整的运行视图:
User Goal
-> Planner 理解目标与约束
-> Memory 提供当前状态与历史上下文
-> Planner 决定下一步
-> Executor 调用 Tools 执行动作
-> Tools 返回结果
-> Executor 处理结果并写入 Memory
-> Planner 根据最新状态继续决策
-> ...
-> 输出结果 / 请求确认 / 终止
也可以把它抽象成更常见的循环:
Observe -> Think -> Plan -> Act -> Observe -> ...
| 阶段 | 发生什么 |
|---|---|
| Observe | 读取用户输入、环境状态、工具结果、已有记忆 |
| Think | 判断当前任务状态,理解问题所在 |
| Plan | 决定下一步执行策略或更新计划 |
| Act | 调用工具、执行动作、产出中间结果 |
| Observe | 读取执行反馈,决定是否继续循环 |
这里有一个非常重要的工程理解:
Agent 不是“先想完再做”,而是“边做边更新状态,边更新状态边调整路径”。
这也是它与传统脚本式工作流的根本差异。
LLM 在系统里到底扮演什么角色
很多人会说“LLM 是 Agent 的大脑”。这个比喻有帮助,但也容易过度人格化。
更准确一点说,LLM 在系统里通常扮演的是三个角色:
- 解释器:理解用户目标和工具返回结果;
- 决策器:决定下一步行动;
- 生成器:生成计划、参数、答案或中间总结。
这意味着,LLM 更像一个通用推理控制器,而不是独立承担所有系统功能的“人格核心”。
单模型与多模型
在实现上,LLM 可能有两种常见组织方式:
| 模式 | 特点 | 适用情况 |
|---|---|---|
| 单模型 | 同一个模型同时负责规划和执行决策 | 简单系统、实现快、调试路径短 |
| 多模型 | 强模型负责规划,快模型负责执行或摘要 | 复杂任务、成本敏感、需要分层控制 |
OpenAI 的 Agent 指南也强调,模型选择应和任务复杂度匹配:不是所有步骤都值得用最强模型,很多场景更适合把高成本推理留给关键步骤,把普通执行交给更便宜、更快的模型。(developers.openai.com) (OpenAI 开发者)
别把“Planner = 一个模型”“Executor = 另一个模型”当成硬规则
这是一个非常常见的误解。
Planner、Executor 是逻辑职责,不一定非要映射成两个独立模型。 在很多系统里:
- Planner 和 Executor 的决策都由同一个模型完成;
- 只是通过不同 prompt、不同状态输入、不同控制逻辑来区分角色;
- 真正的执行动作由程序代码和工具运行时承担。
所以,“分角色”是设计方法,不是部署规定。
常见架构模式
不同任务复杂度,对应的 Agent 架构也会不同。 不是所有系统都需要完整分层,也不是所有系统都适合单循环。
1. 单循环架构
这是最常见的最小形态:
观察 -> 决策 -> 调工具 -> 读取结果 -> 再决策
优点是简单、实现快、路径短。 缺点是复杂任务容易“走一步看一步”,缺少全局视野。
适合:
- 步骤较少的任务;
- 工具数量有限;
- 用户容忍一定程度的互动补充;
- 原型验证阶段。
2. Plan-and-Execute 分层架构
先由 Planner 生成一个粗计划,再由 Executor 逐步执行,每步执行后必要时回写状态并触发重规划。
优点:
- 更适合长任务;
- 更容易表达顺序依赖;
- 更利于插入检查点和人工确认。
缺点:
- 计划可能一开始就错;
- 维护计划本身有成本;
- 如果环境变化快,原计划很快失效。
适合:
- 明确多步任务;
- 步骤间依赖强;
- 需要更强可解释性。
3. 模块化架构
除了 Planner、Executor、Memory、Tools,还引入更多可替换角色,例如:
- Critic / Reviewer
- Router
- Safety Guard
- Summarizer
- Retriever
优点是灵活、可实验、可替换。 缺点是复杂度增长快,调试难度上升。
适合:
- 平台型系统;
- 高定制场景;
- 需要多种执行策略共存;
- 后续可能发展成多 Agent 系统。
模式选择建议
| 任务特征 | 更适合的模式 |
|---|---|
| 步骤少、路径短、工具少 | 单循环 |
| 步骤多、依赖强、需要计划可见性 | Plan-and-Execute |
| 工具多、场景复杂、需要治理与可替换性 | 模块化 |
关键不是“哪种更高级”,而是复杂度是否真的值得引入。
和传统软件架构相比,Agent 架构难在哪
Agent 系统之所以难,并不是因为它组件更多,而是因为它的控制流不再完全预定义。
| 维度 | 传统软件 | Agent 系统 |
|---|---|---|
| 控制流 | 开发时写死 | 运行时部分由模型决定 |
| 状态推进 | 规则明确、分支固定 | 依赖上下文和执行反馈 |
| 错误处理 | 显式分支和异常捕获 | 还要处理模型误判和工具误用 |
| 测试方式 | 输入输出相对稳定 | 非确定性更强,需要行为评测 |
| 扩展方式 | 改代码、加模块 | 既要改代码,也要改工具、上下文、提示和评测 |
这意味着,做 Agent 时你不能只像写传统后端那样思考“接口和数据库”;也不能只像写 prompt 那样思考“语气和格式”。 它要求你同时处理:
- 运行时决策;
- 工具能力暴露;
- 状态组织;
- 非确定性测试;
- 安全和治理。
这也是为什么很多看起来“只是接几个 API”的 Agent,最后会演变成一个完整系统工程。
实践中最容易踩的坑
1. 过早模块化
还没验证任务路径,就先拆 Planner、Executor、Critic、Router、Memory Manager、Tool Broker 六七个模块。 结果是系统看起来很完整,但没有一个链路真正稳定。
更稳妥的顺序通常是:
- 先做最小闭环;
- 识别真正痛点;
- 再把痛点位置模块化。
2. 把 Memory 当日志堆栈
什么都记、什么都传、什么都不淘汰。 最后模型在庞杂上下文里抓不住重点,成本还一路上升。
3. 工具设计面向开发者,而不是面向模型
接口从人类程序员视角看很优雅,但模型很难判断何时使用、如何组合。 结果就是“看起来工具很多,实际上 Agent 不会用”。
4. 缺少终止条件
这是最典型的 Agent 工程事故来源之一。 如果没有明确的:
- 最大步数;
- 总超时;
- 预算上限;
- 完成判定;
- 人工接管点;
系统就会在“不太确定但还想再试一下”的状态里不断循环。
5. Executor 过于放权
让模型自由发起任何动作,而没有参数校验、权限约束和高风险确认。 这类系统可能在 demo 里很惊艳,但离生产往往还很远。
一个最小 Agent 的实现骨架
如果只追求“最小可运行”,一个 Agent 可以非常简单:
- 接收用户目标;
- 把当前上下文发给模型;
- 模型返回“回答”或“工具调用请求”;
- 如果是工具调用,则执行工具;
- 把工具结果放回上下文;
- 继续循环,直到模型给出最终结果或达到终止条件。
可以抽象成这样:
while not done:
state = read_context_and_memory()
decision = model(state)
if decision == tool_call:
result = executor.run(tool, args)
memory.update(result)
else:
return final_output
这就是很多“Hello World Agent”的真实骨架。 后续所谓更复杂的能力——ReAct、Plan & Execute、Reflection、Multi-Agent——本质上都是在这个最小闭环上增加控制、分层和治理。
设计建议:从“能跑”到“能管”
如果你要开始搭一个 Agent,下面这些建议通常比“先选最强框架”更重要:
1. 先确定状态模型,再写 prompt
很多 Agent 问题看起来像 prompt 问题,其实是状态模型不清楚。 先定义:
- 当前任务有哪些状态字段;
- 哪些状态由工具更新;
- 哪些状态由模型解释;
- 完成和失败如何表示。
2. 工具少一点,但定义好一点
比起一开始暴露二十个工具,更好的做法通常是先暴露三个真正必要、语义清晰、返回结构稳定的工具。
3. 给 Planner 自由,给 Executor 护栏
规划层可以更灵活;执行层要更保守。 把风险控制压在 Executor 和工具运行时,而不是完全寄希望于模型“自己懂”。
4. 把工作记忆结构化
与其在上下文里堆自然语言历史,不如显式维护:
- 当前计划
- 已完成步骤
- 待确认事项
- 最近工具结果
- 失败原因
- 预算与步数
这会显著提高系统的可控性。
5. 一开始就设计终止条件
最大步数、总超时、预算上限、人工中断、高风险确认点,这些都不是后期“补一下”的细节,而是 Agent 运行时的基本生命体征。
核心概念速查
| 概念 | 一句话 |
|---|---|
| Planner | 负责理解目标、选择策略、拆解步骤并在必要时重规划 |
| Executor | 负责把计划转成动作,调用工具并处理执行结果 |
| Memory | 负责维持短期上下文、任务状态和长期可复用信息 |
| Tools | 提供 Agent 与外部世界交互的能力接口 |
| Agent Loop | 观察、决策、执行、反馈的持续闭环 |
| Working Memory | 当前任务中的中间状态,比长期记忆更直接影响执行质量 |
| Plan-and-Execute | 先做整体计划,再逐步执行的分层架构模式 |
小结
Agent 系统的核心,不是“模型会不会思考”,而是 Planner、Executor、Memory、Tools 这四类能力能否形成一个稳定闭环。
Planner 决定方向,Executor 负责落地,Memory 维持状态,Tools 连接外部世界。LLM 往往承担其中的解释、决策和生成角色,但它不是全部;真正决定系统是否可用的,往往是状态如何组织、工具如何设计、执行层如何加护栏、以及整个循环何时结束。
理解了这四个组成部分,下一步就可以进入 Agent 最经典的模式之一: 为什么“思考—行动—观察”这种交替循环,会成为很多 Agent 实现的基础。