Agent 架构通论
Agent(智能体)不是一个模型或一个库,而是一种软件架构:让 LLM 当「决策器」,在工具与环境之间反复行动,直到目标达成。RAG 与 Agent 讲了 Agent 的最小循环(ReAct)与框架速查;本文在此基础上展开完整架构:形态谱系、五要素、控制循环类型学、记忆与上下文工程、工具层设计、多智能体编排,给出从「能跑」到「可控、可解释、可上线」的设计地图。控制循环的理论源头见 ReAct 论文精读。
一、先搞清楚对象:从 Workflow 到 Agent 的谱系
- Workflow:步骤与分支在代码里写死,确定性执行,结果可预测——适合规则明确、可穷举流程的任务;
- Agent:步骤由模型在运行时决定,「先查什么、调哪个工具、要不要再问一次」都是推理产物——适合任务开放、路径不可预写、需要边做边调整的场景。
两者之间没有硬边界,是一条光谱:
固定脚本 → 参数化脚本 → 规则分支流程 → LLM 单步决策(工具调用)
→ ReAct 循环(单层 Agent) → 多阶段/分层 Agent → 多 Agent 协作
确定性越来越低、弹性越来越高、成本与不可控性同步上升- 判断要不要 Agent,问四个问题:
| 问题 | 答案偏「不需要 Agent」 | 答案偏「需要 Agent」 |
|---|---|---|
| 任务路径能预写穷举吗? | 能,分支有限 | 不能,依赖上下文即兴组合 |
| 失败能重试一次就够吗? | 能,重试语义简单 | 需要观察结果后再调整策略 |
| 需要动真实工具/外部状态吗? | 只读知识即可(考虑 RAG) | 要调用工具并处理副作用 |
| 对延迟/成本敏感吗? | 敏感,每步都贵 | 可接受多轮往返换取正确性 |
- 忠告:Agent 是最后一个选项不是第一个。能固定成 Workflow 就别让模型做决定;能用一次长上下文读文档就别开循环;能 微调 定型行为就不要靠 agent 绕。谱系上「往左移一步」往往省 90% 的稳定性成本。
二、五要素:一个 Agent 系统由什么组成
┌─────────────────────────── 环境 ───────────────────────────┐
│ 外部世界:数据库 / 文件系统 / 网络 API / 浏览器 / 其他 Agent │
└────────────────────────────────────────────────────────────┘
▲工具调用 ▼工具结果
┌────────┐ ┌───────────┐ ┌──────────────────┐ ┌────────────────────┐
│ 决策器 │──▶│ 规划器 │──▶│ 工具执行层(含 MCP) │──▶│ 记忆(工作+长期) │
│ (LLM) │ │ (循环控制) │ │ │ │ │
└────────┘ └───────────┘ └──────────────────┘ └────────────────────┘
▲ │
└──────────── 状态(上下文) 回到决策器 ──────────────┘- 决策器(大脑):承载推理的 LLM,决定「下一步做什么」。可分级路由:复杂规划用强模型、机械执行用快模型;
- 规划器(循环):控制循环的运行时——何时问 LLM、何时执行工具、何时终止,见第三节类型学;
- 工具层:模型能触碰外部世界的一切接口(API / 代码执行 / 检索 / 写文件),标准接入见 MCP;
- 记忆:短期工作记忆(上下文窗口内的消息)与长期记忆(窗口外的外部存储),见第四节;
- 状态与环境:当前目标、已做步骤、中间产物的载体;真实环境必须有沙箱/权限墙,否则 Agent 的每一次幻觉都可能变成一次越权(安全红线见 AI 工程化)。
- 设计顺序建议:先手工写死一个循环把任务跑通,再逐个引入规划/记忆/框架——每一步都要有「多花钱多值」的理由。
三、控制循环类型学:Agent 的「怎么转」
ReAct 是所有循环的地基(最小实现见 RAG 与 Agent 第六节),在此之上按「规划复杂度」分出四类主流模式:
3.1 反应式循环(Reactive):走一步看一步
- 流程:
思考 → 行动(调工具)→ 观察(工具结果)→ 再思考,不预写步骤; - 适合:步骤依赖上一步结果、无法预分解的开放任务(查资料、调试、问答决策);
- 弱点:长任务容易「迷失目标」——上下文越长,模型越容易忘掉最初要干什么,步数越多越贵。
3.2 规划-执行(Plan-and-Execute):先列计划再干
- 流程分两阶段:
- 规划:模型把目标分解成有序步骤清单(步骤可含依赖关系),先输出再动手;
- 执行:逐条执行步骤,每步可带子任务;执行中遇到偏差时,触发再规划(re-plan)而不是硬着头皮走完;
- 适合:任务可预分解、步骤数多、希望中间可控(能看到计划、能人工插改)的场景——大多数生产任务;
- 变体:计划写入外部文件/状态,避免步骤间「上下文漂移」;执行完每步做验证(见 3.3),验证不过就回到规划器。
3.3 反思/验证(Reflection):生成 + 评价 + 修订
- 流程:生成器产出结果(或执行完一步)→ 评价器对照任务目标与约束打回 → 生成器依据反馈修订;
- 适合:答案/方案质量比步数重要、且「评价比生成容易」的任务(写代码后跑测试、写方案后自检);
- 注意:反思循环的成本是生成成本的 2 倍起,只在关键节点反思而非每步都反思;评价器与生成器尽量用不同提示,防止「自己夸自己」。
3.4 分层编排(Hierarchical):主管拆活、工人干活
- 流程:Manager(主管)把大任务拆成子任务派给多个 Worker,各 Worker 独立小循环,结果回报汇总;
- 适合:任务横跨多个领域/工具集、需要隔离上下文(让主循环不被子任务细节淹没)、子任务可并行;
- 它也是「多智能体」的最小形态——详见第六节。
3.5 模式选型速查
| 你的任务特征 | 首选循环 | 理由 |
|---|---|---|
| 未知路径,边做边定 | 反应式 ReAct | 不预设步骤,灵活 |
| 步骤可分解、要求可控可插改 | 规划-执行 | 计划可见,中途可干预 |
| 结果质量>效率,评价比生成本低 | 反思(关键节点) | 自检提升质量 |
| 跨领域、子任务多、要并行 | 分层编排 | 拆开干,隔离上下文 |
| 以上混合 | 规划-执行为主 + 关键步骤反思 | 兼顾可控与质量 |
3.6 终止条件:循环必须「有终点」
没有显式终止,一切循环都是死循环。四类终止信号缺一不可:
- 目标达成:模型显式输出完成标记 + 产物通过验证(最好有程序化验证,见 Agent 评测与可观测);
- 预算耗尽:最大步数 / 最大 token / 最大耗时,三者都要设;
- 异常退出:工具连续报错超过阈值、重试耗尽、检测到上下文溢出;
- 人工接管:不确定或高风险动作必须停下等人(人工审批也是终止)。
四、记忆与上下文工程:Agent 的「记性」
Agent 记性差是头号失败源。按记忆用途分四层:
| 记忆类型 | 载体 | 内容 | 典型实现 |
|---|---|---|---|
| 工作记忆 | 上下文窗口 | 本轮对话/当前任务状态 | 消息历史 |
| 情景记忆 | 长期存储 | 过去任务的经验与结果 | 向量库/记忆服务(见下) |
| 语义记忆 | 长期存储 | 事实、偏好、领域知识 | 向量库 / 知识图谱 |
| 程序性记忆 | 代码/缓存 | 「这类任务怎么干」的套路 | 复用策略模板、技能库 |
- 上下文不是无限的(成本 + 中间丢失,原理见 LLM 原理),工程上永远假设「窗口会满」,提前做预算:
| 策略 | 做法 | 代价/风险 |
|---|---|---|
| 截断 | 丢掉最旧消息 | 丢失早期关键信息 |
| 滚动摘要 | 定期把旧消息总结成摘要 | 摘要本身会失真 |
| 关键信息抽取 | 只保留与目标相关的字段 | 需维护「状态结构」 |
| 外部检索 | 需要的记忆从长期存储按需取回 | 引入检索错误(方法见 RAG) |
| 进度落盘 | 任务状态写入文件/外部存储,跨轮恢复 | 需约定序列化结构 |
- 工具输出治理(最容易被忽略):把工具全量输出直接塞回上下文,几轮后上下文就全是噪声。铁律:工具结果进上下文前先加工——截断到关键字段、由模型/代码总结成一句话结论、只保留与目标相关的部分;
- 长任务不要靠模型「记得」:中间结论、待办清单、已排除方案,写进外部笔记/结构化状态,而不是指望上下文;
- 长期记忆的写入时机:任务完成或阶段切换时总结入长期存储,并定期做合并去重——记忆库不治理会变成垃圾场(数据治理可借鉴 RAG 的版本对齐,见 rag-agent)。
五、工具层设计:模型的手,也是模型的嘴
工具是 Agent 能力的上限,也是风险的入口。工具描述是写给模型看的接口文档:
- 一个工具的四个要素:
{
"name": "search_package", // 短、动词开头、无歧义
"description": "按名称查 npm 包信息。查文档、版本、依赖时才用;查代码语义不要用。参数 name 传包名,不带 version 号", // 写给模型:何时用、何时别用、参数怎么填
"parameters": { // JSON Schema
"type": "object",
"properties": { "name": { "type": "string", "description": "npm 包名" } },
"required": ["name"]
}
}- 工具描述与 schema 的质量,决定模型「会不会用、敢不敢用、用不错」——工具调不对,九成是描述没写清而不是模型笨;
- 执行语义要显式:超时、重试次数、幂等性(重复调用是否安全)、并发上限、返回结构(成功/失败要机器可判,别把错误埋在文本里);
- 工具即攻击面:Agent 的工具调用 = 让模型决定执行任意代码/访问任意数据(详见 MCP 安全红线与 AI 工程化 权限墙)——最小权限 + 敏感动作人工审批 + 全量审计;
- 工具越多,模型选择越难:控制工具集规模,按任务动态装载相关工具(工具检索/分组),而不是一次给几百个。
六、多智能体:真需要,还是营销词
- 先问三个问题,全部为「是」再考虑多智能体:
| 判据 | 说明 |
|---|---|
| 任务能可靠拆成子任务吗? | 拆不开 = 拆了只会让信息在 agent 间丢失 |
| 子任务之间低耦合吗? | 高耦合场景通信成本 > 拆分工的收益 |
| 异构能力/隔离上下文有价值吗? | 如「规划专家」不该被代码细节灌爆上下文 |
- 三种主流拓扑:
| 拓扑 | 结构 | 适合 | 代价 |
|---|---|---|---|
| 编排者-工人(orchestrator-worker) | 主管拆活、工人执行、结果回报 | 任务可拆、需要并行 | 主管是瓶颈与单点 |
| 流水线(pipeline) | 每个 Agent 处理一个阶段,接力 | 阶段清晰、产物可交接 | 错误在阶段间累积 |
| 协商(peer/debate) | 多个 Agent 各自解题再投票/互评 | 关键决策想降低单点偏差 | token 成倍,未必更准 |
- 成本真相:多智能体的 token 开销通常是单 Agent 的数倍,「三个臭皮匠」在没有独立验证机制时并不会更可靠。演进路线:单 Agent → 规划-执行 → 真的瓶颈出现再拆多 Agent,而不是开场就上团队;
- 多 Agent 之间推荐显式任务单/交接协议(目标、输入、验收标准、负责人),而不是自由对话——对话式协作 = 上下文爆炸与责任模糊。
七、从架构到落地:怎么选实现
- 落地形态分层,建议从下往上挑够用的一层:
| 层 | 代表 | 何时选 |
|---|---|---|
| 手写循环 | 自己写的几十行循环 | 学习/实验,先理解再谈框架 |
| 图式编排 | LangGraph 类(显式状态机与循环) | 生产级复杂流程,要可控与可恢复 |
| Agent 框架 | OpenAI Agents SDK / Claude Agent SDK 类 | 官方 SDK 内完成度够的场景 |
| 多智能体框架 | CrewAI / AutoGen 类 | 确实需要角色分工与协作 |
| 协议层 | MCP(工具接入)/ A2A(Agent 互通) | 让工具与 Agent 标准化互操作 |
框架对比表的「适合场景」逐项见 RAG 与 Agent 第六节;工具接入标准见 MCP;与 A2A 的边界辨析见 mcp 第七节。
- 选型一句话:先确定循环与状态模型,再选能表达的框架——你要的若只是「步进式循环 + 显式状态」,一个状态机库或手写循环就够了,不必引整个多智能体框架;
- 无论哪个框架,要求它暴露:步数/预算限制、人工中断点、轨迹审计、状态持久化。做不到这三项的生产级框架要警惕。
八、失败模式与反模式
| 症状 | 根因 | 对策 |
|---|---|---|
| 死循环 / 重复调同一工具 | 缺终止条件,或工具结果没改变状态 | 设步数与重复调用上限;工具结果进入上下文前先加工 |
| 越走越偏,忘掉原始目标 | 上下文漂移 | 目标声明置顶 + 关键信息落盘 + 阶段再规划 |
| 上下文被工具输出灌满 | 工具结果不治理 | 截断/摘要/只取关键字段(见第四节铁律) |
| 错误级联放大 | 一步失败后继续带错执行 | 每步验证;连续失败即止损返回人工 |
| 幻想的工具/参数 | schema 缺枚举或描述不清 | 收紧参数 schema、工具描述写给模型 |
| 过早提交「看起来完成了」 | 无验证环节 | 程序化验证 / 反思循环把关 |
| 越权或误改真实数据 | 工具权限过大 | 最小权限 + 沙箱 + 敏感操作审批(见第五节) |
| 多 Agent 信息丢失 | 子任务结果没结构化回报 | 显式任务单/交接协议(见第六节) |
九、最小实现骨架:规划-执行循环(可运行版)
与框架无关、可直接运行的骨架(requests 直连 DeepSeek 的 OpenAI 兼容接口),对应上文全部循环要点。2026-09-04 已实测:目标
(12+23)*4-7(真值 133),deepseek-chat先规划出 4 步,再连续真实调用add→mul→sub三个工具,notes 逐条落盘、验证通过,共 3027 tokens。运行依赖python3+requests,执行前export DEEPSEEK_API_KEY=sk-...(密钥不写进代码)。
# -*- coding: utf-8 -*-
"""规划-执行(plan-and-execute)最小循环 —— 真实可跑版(DeepSeek deepseek-chat)
依赖: python3 + requests; 环境变量 DEEPSEEK_API_KEY
用法: export DEEPSEEK_API_KEY=sk-... && python3 本文件
五个工程钩子全部为真实逻辑: 工具子集装载 / 结果加工(condense) /
程序化验证(validate) / 预算与终止 / notes 落盘(防漂移)
"""
import os, re, json, requests
API = "https://api.deepseek.com/chat/completions"
MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
# ---- 工具层: 描述写给模型(何时用/参数怎么填), 执行器写给机器 ----
TOOLS_SPEC = [
{"type": "function", "function": {
"name": "add", "description": "整数加法, 返回 a+b。需要把两个数合并时才用。",
"parameters": {"type": "object", "properties": {"a": {"type": "integer", "description": "加数"}, "b": {"type": "integer", "description": "加数"}}, "required": ["a", "b"]}}},
{"type": "function", "function": {
"name": "mul", "description": "整数乘法, 返回 a*b。需要把两个数相乘时才用。",
"parameters": {"type": "object", "properties": {"a": {"type": "integer", "description": "乘数"}, "b": {"type": "integer", "description": "乘数"}}, "required": ["a", "b"]}}},
{"type": "function", "function": {
"name": "sub", "description": "整数减法, 返回 a-b。需要从 a 里减去 b 时才用。",
"parameters": {"type": "object", "properties": {"a": {"type": "integer", "description": "被减数"}, "b": {"type": "integer", "description": "减数"}}, "required": ["a", "b"]}}},
]
def execute_tool(name, args):
"""确定性执行; 失败返回结构化 ok=False, 绝不抛异常让循环崩掉"""
try:
a, b = int(args["a"]), int(args["b"])
v = {"add": a + b, "mul": a * b, "sub": a - b}.get(name)
return {"ok": v is not None, "value": v} if v is not None else {"ok": False, "error": "unknown tool " + name}
except Exception as e:
return {"ok": False, "error": str(e)}
def chat(messages, tools=None, json_mode=False):
payload = {"model": MODEL, "messages": messages, "stream": False}
if tools:
payload["tools"], payload["tool_choice"] = tools, "auto"
if json_mode:
payload["response_format"] = {"type": "json_object"}
r = requests.post(API, headers={"Authorization": "Bearer " + os.environ["DEEPSEEK_API_KEY"]},
json=payload, timeout=60)
r.raise_for_status()
body = r.json()
return body["choices"][0]["message"], body["usage"]["total_tokens"]
def condense(tool_name, args, result):
"""铁律: 工具结果加工成一句话再进上下文, 防噪声灌满"""
if result["ok"]:
return f"{tool_name}({args['a']},{args['b']}) -> {result['value']}"
return f"{tool_name}({args['a']},{args['b']}) FAILED: {result['error']}"
def ask_planner(goal, extra=""):
"""阶段一规划: 先拆成有序步骤(JSON)再动手; 拆失败就退化为单步, 不中断"""
content = ("把下面的目标分解成 2~4 个有序执行步骤, 只输出 JSON: "
'{"steps": ["第1步...", "第2步..."]}\n目标:' + goal + "\n" + extra)
msg, _ = chat([{"role": "user", "content": content}], json_mode=True)
try:
return json.loads(msg["content"])["steps"]
except Exception:
return [goal]
def run_agent(goal, max_steps=15):
state = {"goal": goal, "plan": [], "notes": [], "done": False, "reason": "", "tokens": 0}
state["plan"] = ask_planner(goal)
history = [{"role": "user",
"content": f"目标: {goal}\n计划: {json.dumps(state['plan'], ensure_ascii=False)}"
"\n严格按计划推进: 每轮用工具算一步或输出最终答案。"}]
step = 0
while not state["done"] and step < max_steps: # 预算与终止(3.6)
step += 1
recent = (state["notes"] or ["(尚无进展)"])[-5:] # 目标置顶 + 最近落盘回放, 防漂移
msg, toks = chat(history + [{"role": "user", "content": f"[步骤{step}] 进度: {'; '.join(recent)}\n继续。"}],
tools=TOOLS_SPEC) # 工具子集装载: 此处只挂演示所需三个
state["tokens"] += toks
history.append(msg)
if msg.get("tool_calls"): # think -> act
for tc in msg["tool_calls"]:
name, args = tc["function"]["name"], json.loads(tc["function"]["arguments"])
result = execute_tool(name, args)
state["notes"].append(condense(name, args, result)) # -> observe, 落盘加工结果
history.append({"role": "tool", "tool_call_id": tc["id"],
"content": json.dumps(result, ensure_ascii=False)}) # 结果回灌
if not result["ok"]:
state["notes"].append("该路径失败, 需换方案") # 触发再规划信号
else: # 模型声称完成: 程序化验证, 别只听它说
answer = msg["content"]
nums = re.findall(r"-?\d+", answer.replace(",", ""))
state["done"] = bool(nums) and float(nums[-1]) == 133.0
state["reason"] = "validated(最终答案=133)" if state["done"] else "product_failed_validation"
state["notes"].append("最终答复: " + answer.strip()[:60])
if not state["done"]: # 验证不过 -> 回规划器换路径
state["plan"] = ask_planner(goal, "上次验证失败, 换一条路径重新规划。")
if not state["done"]:
state["reason"] = "step_budget_exhausted"
return state
if __name__ == "__main__":
assert os.environ.get("DEEPSEEK_API_KEY"), "请先 export DEEPSEEK_API_KEY=sk-..."
# 真值 133, 必须连续 add -> mul -> sub 三次真实工具调用才能算对
s = run_agent("计算 (12+23)*4-7, 只回答最终数字")
print("plan :", s["plan"])
print("notes :")
for n in s["notes"]:
print(" -", n)
print("done :", s["done"], "| reason:", s["reason"], "| tokens:", s["tokens"])
# 实测输出(2026-09-04, deepseek-chat):
# plan : ['先计算括号内的加法:12+23=35', '再计算乘法:35*4=140',
# '最后计算减法:140-7=133', '输出最终数字133']
# notes : ['add(12,23) -> 35', 'mul(35,4) -> 140', 'sub(140,7) -> 133', '最终答复: 133']
# done : True | reason: validated(最终答案=133) | tokens: 3027- 骨架拆解出的五个工程钩子,对应本页各节:工具子集装载(第五节)、结果加工(第四节)、程序化验证与再规划(第三、八节)、预算与终止(3.6)、notes 落盘(第四节);
- 把骨架换成 LangGraph 类状态机时,
state就是图的共享状态,notes与plan成为显式节点,循环/分支变成图边——先理解这个骨架,再看任何框架都不晕。
十、踩坑清单
- 把 Agent 当银弹:能写死流程的任务上循环,又慢又不可控——先往 Workflow 移;
- 循环无终止:没有步数/token/时间预算,或终止全靠模型自觉;
- 不设程序化验证:模型说「完成」就信,结果验证不过的「假完成」上线;
- 上下文不管饱:工具全量输出直接回灌,几轮后模型在噪声里迷失;
- 中间状态不落盘:长任务一断(超时/重启/换线程)就全忘;
- 目标会丢:任务声明不置顶,模型越走越偏没人拉回来;
- 工具描述敷衍:schema 里只有字段没有「何时用/何时别用」,模型乱调或不敢调;
- 工具权限过大:给了 Agent 的生产权限没有沙箱与审批,一次注入翻全墙;
- 直接上多智能体:单 Agent 的瓶颈还没找到就拆团队,成本翻倍问题依旧;
- 没有可观测与评测就上线:Agent 的失败是概率性的,看不见轨迹就无法迭代(见 Agent 评测与可观测);
- 迷信框架:框架解决的是「状态与循环的工程表达」,不解决「目标会不会丢」——架构设计仍然要自己做。
关联阅读:ReAct 循环的最小实现与工具调用示例在 RAG 与 Agent;工具接入标准见 MCP;评测、可观测与门禁是 Agent 上线的另一半,见 Agent 评测与可观测;把「一步调用」跑稳跑便宜的工程手段见 AI 工程化。