标题我修改了一下。
一份面向实战的智能体拓扑、状态设计及失效模式指南——帮你分清演示原型与能撑过九十天生产考验的系统之间的区别。
架构师之道
● AI · LLM · Agents | Enterprise Architecture | Digital Transformation
1 引言
半年前,一家物流公司请我去看他们的“客户运营自动化”试点项目。供应商的演示令人惊艳:智能体能报交货时间、修正地址偏差、标记高风险货件转人工处理。在受控演示环境下,它不需要人工介入就能解决94%的测试用例。

我问他们要生产环境的实际数据。对方沉默了一下。CTO调出仪表盘——面对每天约四千张工单、混乱的地址、滞后的追踪信息、愤怒的客户,同样的智能体只解决了11%的案例,其余至少十几个案例里它还凭空捏造了送货承诺。其中一些承诺让公司赔了不少钱——退款、补发,真金白银地往外掏。
演示不是假的,模型本身也没问题。问题出在:没有人真正设计过系统的架构。他们只是把一个好用的模型塞进提示词,外面套个循环,就管它叫智能体。
本文要讲的就是我当时希望那个供应商能拿出来的模式语言——包括拓扑结构、状态设计、工具契约,以及决定一个智能体能否在生产环境活下去的那些失效模式。读完本文,你就能一眼看穿任何一个智能体项目用的是哪种模式,更重要的是,能判断它选得对不对。
2 忘掉“智能体”这个词,先说“拓扑”
“智能体”是营销词汇,“拓扑”才是工程词汇。拓扑就是你的系统结构:有多少个推理循环,它们之间怎么通信,状态归谁管,下一步由谁来决定。生产环境里的智能体崩掉,十有八九不是模型的错,而是拓扑跟任务不匹配——或者更准确地说,是拓扑、任务、状态和工具契约没有对齐。
不管演示文档画得多漂亮,绝大多数智能体架构都可以归结为以下六种基础模式。它们很少单独出现,实际系统常常是几种模式的嵌套或组合。学会辨认它们,因为每一种都有各自的成本、失效模式,以及真正擅长的任务范围。
2.1 单循环(Single Loop)
一个模型、一个上下文窗口、一套工具、一个while循环。这是默认配置,也是主力干将。系统提示词里放着目标,上下文窗口里存着当前工作状态,工具提供操作能力,预算计数器防止它无限跑下去。
成本:最低。 延迟:最低。 擅长:范围狭窄、边界清晰的任务——比如查余额、提取表单字段、基于工具的单一领域问答。
2.2 路由器(Router)
用一个轻量级、速度快的小模型对请求做分类,然后分发给对应的专业处理模块。比如工单分类路由器:支付纠纷走退款流程,物流问题走追踪工具,其他走通用智能体。路由器本身没有自主推理循环,但它分发的下游处理器可能有循环。
成本:低。 延迟:多一次轻量级调用。 擅长:高流量场景,且大多数请求属于有限的几种已知类型。这是生产环境中最被低估的模式,也是我本人会优先采用的模式。
2.3 编排器–工作器(Orchestrator–Worker)
一个编排器把大任务拆成子任务,交给工作器智能体(也可以是普通函数或搜索任务)处理。工作器返回结果,编排器综合汇总。这是人们说“多智能体”时真正指的东西——大多数情况下就是一个编排器加几个专业工作器。
成本:中等。 延迟:中等。 擅长:报告生成、调研、代码审查——这类任务天然可拆解。
2.4 分层式(Hierarchical)
智能体管智能体。主编排器生成子编排器,每个子编排器再管自己的工作器。这是把编排器–工作器模式扩展到超大规模任务的方式,但也正是复杂度和成本开始滚雪球的地方。
成本:高。 延迟:高。 擅长:涉及数千份文档的企业级研究流水线。但凡模式3能搞定的,用分层式通常都是过度设计。
2.5 对等团队(Peer Team)
多个地位平等的智能体通过对话或并行协作,共同达成一个目标——这就是CrewAI和AutoGen的经典画面:研究员、写手、评论家为了一份文档来回争论,直到达成共识。
成本:高——每一次同伴对话都是一次完整模型调用,协调开销不可小视。 延迟:高。 擅长:创意起草、辩论式任务。 不擅长:有严格截止日期和预算限制的事情。
2.6 状态机 / 预定义工作流(State Machine / Predefined Workflow)
控制流在部署前已经确定:查询、校验、扣款、确认——每一步要么是确定性的,要么借助模型辅助。它没有模型自主决定的开放式推理循环,但可以有预定义的有限重试、条件分支或循环边。LangGraph的图模型和n8n的节点模型,就是这种模式套上了工程化的外衣。
成本:每步最低。 延迟:可预测。 擅长:那些80%是已知流程、只有少数模糊决策点的事情。大多数所谓的“智能体”用例,本质上都是这个。这也是清单里最诚实的模式。
2.7 速查表
我给客户看的版本:
这些模式不是互斥的。真实的系统可能是“路由器 → 状态机”,或者“编排器 → 多个状态机”的组合方式。写代码之前,我最常问的一个问题:这个任务是一个带几个判断点的流程,还是一个目标开放、步骤未知的探索型任务? 流程 → 状态机;开放探索 → 单循环或编排器–工作器。几乎不要第一天就上对等团队。
3 在选拓扑之前:先定义任务模型和工具语义
拓扑是骨,领域模型是肉。如果这一步没做好,即使拓扑选对了,也可能因为领域模型混乱而失败。动手画图之前,先回答:
任务有哪些类型?每种任务的关键实体是什么? 工具能力如何形式化?每个工具的前提条件、副作用、失败模式是什么? 状态机有哪些合法状态和迁移?哪些决策可以自动化,哪些必须人工? 哪些步骤是确定性的,哪些步骤需要模型判断?
花半天时间把这些问题写下来,往往比之后花两周修架构更划算。
4 状态:那个所有人都忘了的部分
选对拓扑还不够——真正让生产环境智能体翻车的,是状态。
一个智能体的状态,就是它在每一步之间携带的所有信息:任务目标、已经尝试过什么、排除了什么、工具调用的结果、还剩多少预算。如果状态只放在模型的上下文窗口里,你就有了内存问题——上下文窗口有大小限制、容易塞满噪声、还容易被污染。如果状态放在数据库里,你就有工程问题——每一步都需要保存、加载、版本管理。
我现在给客户立下的规矩是:工作状态(模型当前桌面上正在处理的东西)放在上下文窗口里,但必须狠心裁剪;持久状态(任务已经完成的事项,包括重试和重启后的进展)放在存储里。每一步都封装成可重放的状态转换函数:输入是持久状态加上模型的决策,输出是新状态和待执行动作。不要叫它纯函数——模型调用和工具调用都有非确定性和副作用。重点是可重放、可审计、可恢复。
仅靠一张任务表和step_count字段是远远不够的。要可靠恢复,你需要更细粒度的状态记录。至少拆成三张表:
-- =============================================================-- 任务主表:一个智能体任务的整体信息-- =============================================================CREATETABLE tasks ( id uuid PRIMARYKEY, -- 任务唯一ID pattern text NOTNULL, -- 使用的拓扑模式:如 single_loop / router / orchestrator_worker / state_machine goal text NOTNULL, -- 任务目标描述,作为模型的核心输入 status text NOTNULLDEFAULT'queued', -- 任务状态:queued / running / completed / failed / plan_failed / cancelledresult jsonb, -- 任务最终结果(结构化JSON),可包含摘要、输出、错误信息等 created_at timestamptz NOTNULLDEFAULT now(), -- 任务创建时间 updated_at timestamptz NOTNULLDEFAULT now() -- 任务最后更新时间);-- =============================================================-- 子任务表:编排器拆解出的可独立执行的子任务-- =============================================================CREATETABLE subtasks ( id uuid PRIMARYKEY, -- 子任务唯一ID task_id uuid NOTNULLREFERENCES tasks(id), -- 所属主任务ID seq int NOTNULL, -- 子任务序号,用于排序和展示 description text NOTNULL, -- 子任务描述,模型拆分出的具体指令 tool_name text, -- 需要调用的工具名称,若无需工具可为空 tool_args jsonb, -- 调用工具的参数,JSON格式,由模型生成 status text NOTNULLDEFAULT'pending', -- 子任务状态:pending / running / completed / failedresult jsonb, -- 子任务执行结果,JSON格式 attempt_count int NOTNULLDEFAULT0, -- 已尝试次数,用于重试控制 created_at timestamptz NOTNULLDEFAULT now(), -- 子任务创建时间 updated_at timestamptz NOTNULLDEFAULT now() -- 子任务最后更新时间);-- =============================================================-- 工具调用事件表:记录每一次工具调用的完整信息,用于审计和恢复-- =============================================================CREATETABLE tool_calls ( id uuid PRIMARYKEY, -- 工具调用唯一ID task_id uuid NOTNULLREFERENCES tasks(id), -- 所属任务ID subtask_id uuid REFERENCES subtasks(id), -- 所属子任务ID,可选 tool_name text NOTNULL, -- 调用的工具名称input jsonb NOTNULL, -- 工具调用输入参数,完整记录output jsonb, -- 工具调用输出结果,完整记录 status text NOTNULLDEFAULT'pending', -- 调用状态:pending / running / success / failed idempotency_key text NOTNULL, -- 幂等键,用于防止重复执行有副作用的工具 created_at timestamptz NOTNULLDEFAULT now(), -- 调用创建时间 updated_at timestamptz NOTNULLDEFAULT now() -- 调用最后更新时间);每个工具调用都记录在tool_calls表里,并带有一个幂等键。如果进程崩溃,新起的工人读取任务和子任务状态,从第一个未完成的子任务继续,对于已经执行过的非幂等工具,要么确认结果,要么进入人工补偿流程。这就是“可靠智能体”的全部秘密——它们其实就是可以断点续跑、可审计的任务。
5 工具设计:描述即契约
我总说工具是智能体的双手,但大家真正搞砸的是工具描述。模型读你的工具描述,然后决定用不用它。描述写得敷衍,生产环境里模型一定会用错,百试百灵。
把工具描述当作一份三方契约,至少包含:
- 做什么:
例如“获取已认证账户的当前可用余额”。 - 何时用:
例如“客户问自己有多少钱或欠多少钱时调用。查交易记录请用 get_transactions,别用这个。” - 输入 schema:
明确参数类型、约束、默认值。 - 返回 schema:
例如“返回 {balance: number}。若账户未认证,返回错误对象。” - 副作用与幂等性:
只读?可逆写?不可逆写?重复调用是否安全? - 权限要求:
调用该工具需要什么角色或审批。
再加几条铁律:执行之前,服务端必须校验输入参数和输出结果。模型的传参本质上也是模型输出——它们可能出错,恶意输入下甚至可能被注入攻击。通过工具参数夹带SQL注入字符串不是玩笑,这是家常便饭。工具返回的内容进入上下文前也要过滤或标记,防止提示注入。
6 一个可投产的最小编排器–工作器骨架
下面是一个遵循上述纪律的编排器–工作器实现骨架。它不是一个可运行的完整系统,但展示了关键的控制流和持久化点。依赖PostgreSQL和一个任务队列,每个子任务一次模型调用。
import json, uuidfrom typing import Anyfrom db import get_task, create_subtasks, get_next_pending_subtask, update_subtask, record_tool_callfrom queue import dequeue_taskdef run_task(task_id: uuid.UUID):task = get_task(task_id)plan = llm_call("你是一位研究主管。请把以下任务拆解为3~5个可独立执行的子任务,以JSON格式返回:"'{"subtasks": [{"description": "...", "tool": "...", "args": {...}}]}',task["goal"],)# 校验规划输出try:subtasks = json.loads(plan)["subtasks"]assert 1 <= len(subtasks) <= 8for s in subtasks:assert isinstance(s["description"], str)assert isinstance(s.get("tool"), str)assert isinstance(s.get("args", {}), dict)except Exception:# 记录失败,可重试或转人工update_task_status(task_id, "plan_failed")returncreate_subtasks(task_id, subtasks)while True:sub = get_next_pending_subtask(task_id)if sub is None:breaktry:update_subtask(sub["id"], status="running")# 工作器:可以在这里加入模型调用,决定是否真的调用工具result = run_tool(sub["tool_name"], sub["tool_args"])# 记录工具调用事件record_tool_call(task_id, sub["id"], sub["tool_name"], sub["tool_args"], result, status="success")update_subtask(sub["id"], status="completed", result=result)except Exception as e:record_tool_call(task_id, sub["id"], sub["tool_name"], sub["tool_args"], {"error": str(e)}, status="failed")update_subtask(sub["id"], status="failed", result={"error": str(e)})# 根据策略重试或转人工break# 汇总所有已完成的子任务results = get_completed_subtask_results(task_id)final = llm_call("你是一位编辑,负责整合。请把各子任务结果合并成一份连贯的答案,回应当初的大任务。",json.dumps({"goal": task["goal"], "results": results}),)update_task_status(task_id, "completed", result=final)
这个骨架体现了几个关键点:
规划输出经过schema校验,不合法时任务标记失败而非继续。 每个子任务有独立状态,可单独重试。 每次工具调用都有事件记录和幂等键。 失败后从下一个未完成子任务继续,而不是从步数猜测。
拿真实流量跑一遍,你会精准找到那些交接点——上下文丢失、任务卡住的地方。这恰恰是好事:你希望失败发生在交接层,因为交接层容易埋点、容易修。相比之下,如果子任务拆解本身就开始胡编,那就很难发现,也很难补救。
7 生产现实:真正让你疼的失效模式
经过一年多在制造AI、企业经营管理AI、物流、客服领域的实战交付,我按伤害程度排了个序,以下是最常见的翻车点:
- 静默越权。
智能体自信满满地做了你从未授权的事——发邮件、打折、关工单,一气呵成。对策:加权限层——只读工具随便用,会改数据的工具必须审批或有硬性策略约束。更进一步,引入工具风险分级、运行时策略引擎、细粒度RBAC和审计日志。高危操作需要人工确认或限流。 - 上下文膨胀。
每走一步就往提示词里追加内容,到第9步时模型面对的是自己吐出来的一堆噪声。对策:狠心裁剪,对旧步骤做摘要,或者把状态挪到存储里。 - 交接丢失。
编排器–工作器模式下,编排器把工作器已经花词元生成过的内容又重新总结一遍。对策:让工作器返回结构化JSON,不要返回散文,编排器只负责组装。 - 非幂等操作重复执行。
重试机制可能让同一个扣款、发信操作执行多次。对策:所有写操作工具必须携带幂等键,服务端做去重;非幂等工具进入人工确认队列。 - 模型输出格式错误。
规划或工具参数不是合法JSON,导致整个流程中断。对策:所有模型输出经过schema校验,失败自动重试或转人工。 - 成本爆炸。
对等团队和分层模式烧词元的速度是单循环的5到10倍。要注意计算每个已解决任务的成本,而不是单次运行成本。我曾测得某调研任务:单循环$0.18,编排器–工作器$0.61,对等团队$2.90(具体数字依赖模型和任务,仅供参考)。 - 无声劣化。
模型慢慢不再调用工具,而是靠训练数据瞎猜。对策:监控每种模式的工具调用率、工具调用错误率、循环步数分布、任务完成率,一旦低于阈值就报警。 - 提示注入。
用户输入或工具返回内容里夹带指令,诱导智能体执行非预期操作。对策:对输入和工具输出做过滤、标记,限制模型对工具结果的信任程度。
还有最大的一条,与技术无关:演示和实测的鸿沟。你的测试集是系统开发者自己挑的。从上线第一周开始,就必须拿留出的真实生产流量做评估,否则你迟早会在客户会议上发现自己的“11%版本”——就像那家物流公司一样。
8 什么时候不该用某种模式
单循环?但你的任务其实是个已知流程?——请用状态机。你花的是开放式推理的钱,但你根本不需要开放式推理。 编排器–工作器?但子任务之间无法独立运行?——那你只是在做一个更慢的单循环。每个工作器都在等前一个完成,编排器就多了一跳。 对等团队?但各角色之间没有真正的分歧或互补性?——你是在为“表演”买单。 任何模式?但一段确定性脚本就能搞定?——那就该用脚本。今年我劝一个客户停下:他们的“智能体”是用来解析一个从不变化的发票格式的,其实写个四十行解析器就够了。解析器每次运行成本不到一分钱,而那个智能体每次四美分,还有3%的失败率。我帮他们省下了一笔重复账单——靠的是不建那个架构。
9 我上线任何智能体之前必做的检查清单

10 真正能在生产环境活下来的样子
回到那家物流公司。我们把试点项目重构成了“路由器 + 状态机”:一个便宜的分类器把工单分到五种确定性流程之一,只有真正模糊的案例才交给单循环智能体,同时加上严格的预算、权限控制和状态持久化。第一个月,解决率就从11%爬到了78%——不是因为模型变好了,而是因为系统的形状终于跟工作的形状对上了,状态和工具契约也终于支撑得起真实流量。
模型只是拼图的一块,架构、状态、工具和评估共同决定了系统能不能活下来。所以,动工之前先说出你正在构建的模式名称。如果你叫不出名字,那你没有架构——你只有一个挂着成本的提示词。画出拓扑图,定义好任务模型,把状态存进库,写好工具契约,把上面那份检查清单当作上线前的最后一道工序。
有任何不同的看法,评论区我们可以继续聊~ 😊
架构师之道
架构之道,在于化繁为简,以设计思维驱动技术决策
> 关注作者并添加星标,与‘架构师之道’同行
夜雨聆风