Agent架构三大工程理念:模块化、状态机与可观测性
做AI Agent开发的朋友应该都有同感演示的时候什么都能干一上真实业务就原形毕露。要么在工具调用里循环出不来要么上下文越滚越大模型直接“失忆”要么它自作主张做了你没让它做的事。我研究了一圈开源的Agent框架之后OpenClaw给我留下的印象比较深——它没有把重心放在“让模型多聪明”上而是花大力气解决“怎么让系统可控、可预测、可维护”。这篇文章我会拆解OpenClaw这类的架构设计中非常值得借鉴的三大工程理念以及怎么把它们落地到自己的项目里。不管你是正在从零搭一个智能助手还是被现有Agent折腾得焦头烂额这篇都值得花点时间看完。1. 先从问题说起Agent项目为什么总是“演示即巅峰”1.1 失控的三个典型表现先说一个我实际踩过的场景。某次接了一个内部知识库问答助手的活最初的方案特别“暴力”给大模型塞了一堆工具函数定义包括查数据库、读文档、发邮件、改工单状态然后让它自由发挥。DEMO演示时确实惊艳问它“帮我查一下上个月的项目总结”它能自己拆解步骤、调用工具、整理回答全场鼓掌。结果一上测试环境就露馅了。典型的三类问题第一类是循环空转。模型在一个任务上反复调用工具查完A再查B查完B又觉得应该查A来回折腾十几轮消耗大量 token最后也没有产出有效结果。你问它在干嘛它说“我在确认信息完整性”但状态实际上已经卡死了。第二类是越权操作。模型在执行某个查询任务的过程中自己“顺手”调用了发送邮件的工具把未经验证的中间结果发给了客户。这在技术上是“工具调用自由度过高”在业务上就是事故。第三类是记忆混乱。任务稍微长一点对话历史叠加工具返回结果上下文越滚越大。模型开始答非所问甚至把上一个任务的中间数据当成当前任务的依据输出完全错误的结论。这些问题的根源并不在模型本身而在架构设计。大模型本质上是一个概率系统它每一次输出都是“看起来最合理”的续写而不是经过严格逻辑推导的结果。如果你把一个业务系统直接建立在“概率系统自由发挥”的基础上那失控才是常态不失控才是运气。1.2 OpenClaw的解题思路把不确定性关进笼子里OpenClaw这类的框架之所以值得研究不是因为它有哪些黑科技模型而是它的架构假设很清醒模型不可靠所以系统必须可靠。它不会指望大模型每次都做出正确的决策而是通过架构手段把模型的不确定性限制在一个可控的范围内。具体来说它做的事情可以归纳为一句话把Agent从“一个什么都能干的智能体”改造成“一套有明确分工、有执行流程、有监控手段的工程系统”。这里有一个很重要的认知转变。很多人设计Agent时喜欢追求“模型自主性”恨不得让模型自己规划一切、执行一切。但真实工程里自主性越高可控性就越低。OpenClaw的选择是走另一条路让模型在明确的边界内做决策而不是让模型定义边界。这套思路落到架构上就是三个层次的约束结构约束把系统拆成多个职责单一的模块模型只是其中一个环节而不是全部。流程约束用确定性的状态机管理Agent的运行流程让每一步都可预期、可跳转、可终止。反馈约束全链路可观测、可干预用户和开发者随时都能看到Agent在做什么、为什么这么做、怎么让它停下来。下面三个章节我就把这三大工程理念逐一拆开来讲。2. 工程理念一职责收敛把Agent拆成“一个团队”而不是“一个人”2.1 模块化对Agent意味着什么很多初级设计把Agent想象成一个“超人”既懂用户意图又会调用工具还能自我反思、总结输出。这种“全能超人”式设计最容易翻车因为所有能力都写在一个超长提示词里彼此干扰严重。你可以类比一下人类团队的工作方式一个成熟的公司不会让一个员工同时负责销售、研发、客服和财务。每个岗位职责清晰协作通过固定接口沟通出了问题能定位到具体环节。Agent架构也一样职责越收敛系统越可控。OpenClaw架构中的一个核心思想就是单一职责原则。它不设计一个大一统的Agent核心而是拆成若干各司其职的模块每个模块只负责一件事并通过结构化的消息协议进行通信。这样做带来的直接好处有三个一是可单独测试。每个模块可以独立验证查工具的模块只测查工具规划模块只测规划质量不用每次都在完整链路上跑。二是可局部替换。某个模块效果不好直接换实现方案不用动其他模块。比如后来我们把规划器从“模型自由生成步骤”换成了“基于模板的受限规划”只改了一个模块其他完全不受影响。三是可优雅降级。某个模块挂掉了系统能感知到并进入降级模式而不是整体崩盘。2.2 OpenClaw是怎么划分模块的我参考这套思路做了职责拆分最终落地的模块划分大概是这样的模块核心职责关键设计点意图识别模块判断用户想做什么输出意图分类而不是自由回答任务规划模块将意图拆解为有序步骤步骤数量受限避免无限展开工具执行模块调用外部工具/API统一封装返回结构化结果校验模块验证执行结果是否满足需求有明确的通过/失败标准记忆管理模块维护短期和长期上下文按需摘取而不是全量堆叠输出模块组织最终回复只基于校验后的数据生成这个设计中最重要的一个点是大模型出现在多个模块中但每个模块中的模型职责非常单一。意图识别就只输出分类标签校验模块就只做“通过/不通过”判断规划模块才输出步骤列表。相比一个提示词干所有事这种拆分让每次模型调用的不确定性大幅下降。模块之间的通信协议也要提前定义好。我建议直接用结构化消息JSON每个消息带上消息类型、发送方、接收方、时间戳、内容体。这样做的好处是日志可追踪每个模块的输入输出都能被记录和回放。实操中还有一条经验不要给工具模块太多“自由”。工具模块内部的每一次调用都应该有独立的参数校验、超时控制和结果规范。宁可多写一点胶水代码也不能让模型直接裸调API。3. 工程理念二流程显式化用确定性状态机约束自由发挥3.1 有限状态机让Agent知道自己“在哪一步”如果说模块化解决了“谁干什么”的问题那流程显式化解决的就是“下一步干什么”的问题。没有流程约束的Agent本质上就是一个模型在无限循环里自己做决定先调用哪个工具、什么时候停止、什么算完成全凭模型心情。心情好的时候逻辑正确心情差的时候原地打转。OpenClaw这类框架的解法是引入有限状态机FSM把Agent的运行过程定义为有限个状态和明确的迁移条件。我设计的Agent调度状态机大概是这样的一组状态IDLE空闲等待新任务PLAN规划阶段生成任务步骤EXECUTE执行阶段调用工具VERIFY验证阶段检查结果REVIEW需要用户确认的中间状态DONE任务成功完成FAILED任务失败进入异常处理ABORTED用户主动终止每个状态之间的迁移条件必须是确定性的、由代码控制的。比如从EXECUTE到VERIFY的迁移条件是“所有工具调用都已返回结构化结果”从VERIFY到DONE的迁移条件是“校验模块判定通过”。模型只能决定“在这个状态下我要做什么”不能决定“我要跳到哪个状态”。这样做的好处非常明显Agent永远知道自己处于什么阶段系统永远知道Agent在做什么。出了问题可以直接看状态不用靠猜。3.2 状态机的关键设计人工介入位与回退状态机光有状态还不够还要有两个关键设计人工介入位和回退机制。人工介入位指的是在流程中预设一些“必须由人来确认”的节点。比如发送邮件、修改数据、执行支付等高风险操作绝对不能由模型自行决定。在OpenClaw的设计理念里这类操作前会强制进入REVIEW状态等待用户明确确认后才继续。有人会担心强制人工介入会不会拖慢效率我的体会是效率的优先级应该让位于安全尤其是涉及真实业务后果的操作。而且介入位不是每个任务都有只在风险阈值达到时触发。回退机制也很重要。Agent执行过程中如果校验失败应该定义清楚回退策略重试当前步骤、回到上一步重新规划、还是直接进入FAILED状态等待人工处理。这些策略都要在状态机迁移条件里写清楚而不是靠模型的临场判断。我还建议给每个状态加上最大停留时间。在EXECUTE状态里如果超过N分钟没有新的工具返回直接判定超时进入FAILED这是防“绕圈”最简单粗暴也最有效的手段。# 状态机核心逻辑示意 class AgentStateMachine: def __init__(self): self.state IDLE self.max_steps 20 self.current_step 0 def transition(self, event): # 确定性状态迁移规则 if self.state IDLE and event new_task: self.state PLAN elif self.state PLAN and event plan_ready: self.state EXECUTE elif self.state EXECUTE and event all_tools_returned: self.state VERIFY elif self.state VERIFY and event verification_passed: self.state DONE elif self.state VERIFY and event verification_failed: self.state PLAN # 回到规划重新拆解 elif self.state VERIFY and event needs_human_input: self.state REVIEW elif event user_abort: self.state ABORTED self.current_step 1 if self.current_step self.max_steps: self.state FAILED这段代码只是一个骨架但已经能说明问题Agent的每一步走向都不是模型决定的而是由规则引擎决定的。模型在EXECUTE阶段输出的是“工具调用参数”而不是“下一步去哪”。4. 工程理念三可观测、可干预系统不透明就谈不上可控4.1 结构化日志与全链路追踪第三大理念也是我觉得最容易被忽略的一点可观测性。没有可观测性的Agent系统就像一台没有仪表盘的飞机飞得再高也是盲飞。OpenClaw架构里对日志的要求很高。不是简单打印几行文本记录而是对每一次状态迁移、每一次模型调用、每一次工具调用都产生结构化日志。我自己的实现中每一条日志长这样{ type: tool_call, state: EXECUTE, tool: search_knowledge_base, params: {query: 上月项目总结, limit: 5}, result_summary: OK, token_usage: 1234, latency_ms: 456, time: 2025-07-11T10:22:33Z }每一条日志都带上下文的链路ID。这样一旦某个任务出了问题我可以把整个执行过程完整回放模型当时看到了什么、做了什么判断、调用了什么工具、结果是什么。这种回放能力在调试Agent时是刚需。另外还有一些核心指标需要持续统计任务成功率多少任务走到了DONE状态多少走到FAILED。平均执行步数一个任务平均要经过多少个状态步数异常偏高说明规划质量差。工具调用失败率哪个工具经常失败是参数问题还是服务问题。Token消耗分布哪些任务吃token最凶是上下文管理出了问题还是规划太啰嗦。人工介入频率需要用户确认的节点占比比例过高说明模型决策质量不足。这些指标的价值不只是监控更是优化依据。我后来发现任务平均步数从18步降到7步就是因为把“校验失败后直接回PLAN”的策略改成了“先回EXECUTE重试当前工具一次不行再回PLAN”。没有数据支撑这种优化全靠拍脑袋。4.2 给用户“看得见”的参与感与纠偏能力可观测不只是给开发者的也要给最终用户。很多Agent产品的问题是用户只看到最终输出看不到过程。一旦输出不符合预期用户没有任何办法纠偏只能清空重来。这种产品体验很容易让用户对整个系统失去信任。OpenClaw带给我的启发是把Agent的关键状态和中间决策“透明化”给用户。当然不是把所有技术细节一股脑堆到界面上而是把高层次的进度和决策理由呈现出来。比如任务进入PLAN阶段时界面显示“正在拆分任务”进入EXECUTE且正在调某个工具时显示“正在查询项目资料”进入REVIEW阶段时明确提示“以下操作需要您的确认是否继续”。这样做还有一个隐藏的好处用户在关键节点可以干预决策。如果用户看到Agent计划要去修改线上数据这个环节正好是人工确认位用户直接选择拒绝系统进入ABORTED状态避免事故。我自己的项目里就发生过一次真实的“用户干预救命”的案例某个Agent在提炼数据时规划出了一个“直接update数据库”的步骤但该步骤包含了一个误判条件。由于这步操作前有REVIEW介入位用户当场否掉了系统回退到PLAN重新规划。事后我们复盘如果当初没有介入位这个操作会造成不小的问题。5. 实操复盘参考这套思路搭一个最小可用的Agent助手5.1 模块划分与消息协议说完理念说点能直接落地的东西。我参考OpenClaw的思路从零搭过一个小型Agent助手技术栈不算复杂Python做调度核心大模型接口做意图识别、规划和校验工具执行层封装了两个内部API和一个数据库查询接口。先定义消息协议。模块之间不直接调用函数而是通过一个队列传递结构化消息。这样做的好处是解耦每个模块都可以独立启动、独立测试。# 消息协议示例 dataclass class AgentMessage: msg_id: str msg_type: str # intent / plan / tool_call / tool_result / verification / done sender: str receiver: str payload: dict timestamp: float实际的业务消息类型就六种每个模块只处理自己相关的类型。5.2 核心调度代码骨架调度核心是一个循环从消息队列里取消息根据当前状态路由到对应的处理模块处理完成后产生新消息推动状态机迁移。核心代码结构大致如下# 简化后的调度核心 def agent_loop(initial_message): state_machine AgentStateMachine() queue [initial_message] while queue: message queue.pop(0) current_state state_machine.state if current_state IDLE and message.msg_type intent: # 意图识别完成进入规划 plan planner.generate_plan(message.payload) state_machine.transition(plan_ready) queue.append(AgentMessage(plan_msg, plan, planner, executor, plan, now())) elif current_state EXECUTE and message.msg_type plan: # 执行器按步骤调用工具 results executor.execute_plan(message.payload) state_machine.transition(all_tools_returned) queue.append(AgentMessage(verify_msg, verification, executor, verifier, results, now())) elif current_state VERIFY and message.msg_type verification: ok verifier.check(message.payload) if ok: state_machine.transition(verification_passed) return build_final_response(message.payload) else: state_machine.transition(verification_failed) queue.append(AgentMessage(replan_msg, plan, verifier, planner, message.payload, now())) # 超时与最大步数保护 if state_machine.current_step state_machine.max_steps: return task_failed_timeout这个骨架看起来简单但真正跑起来的工程细节都在那些看似不起眼的处理逻辑里。5.3 关键经验这些配置项一定要提前想好第一次实现时我踩了不少坑总结下来有几个配置项最好在一开始就设计好第一是规划步骤的上限。我最初没对规划步骤做限制结果模型在任务稍微复杂时列出来的步骤动辄十几步。更可怕的是有些步骤之间根本没有依赖关系执行时大量重复调用工具。后来加了“单次任务最多5步”的硬限制超出时强制要求模型做步骤抽象问题明显缓解。第二是工具调用的超时和重试策略。没有超时控制的话一个外部API卡住整个Agent卡死在那里一动不动。我的做法是每个工具执行器都配置独立超时时间默认3秒超时后先重试一次再失败就返回失败结果给校验模块由状态机决定回退方向。第三是上下文管理策略。模型调用的prompt永远不要全量拼接历史记录。我维护了一个“上下文窗口”只保留最近的用户消息、最近一轮的工具返回结果、以及当前规划步骤。更早的内容需要时通过记忆管理模块按需检索。第四是校验标准要可执行。校验模块不能只写“检查结果是否合理”这种标准模型实现不了。要拆成可量化的硬指标例如“所有必填字段是否存在”“数据条数是否大于0”“结果是否包含用户查询的关键实体”。每一项都是规则判断不依赖模型主观感受。6. 踩坑日记常见问题与排查实录6.1 模型输出格式不稳定这是使用大模型做模块时最常遇到的坑。你要求它输出JSON它偶尔会在JSON前后多解释两句你要求它只输出一个分类标签它偶尔给出完整句子。我的处理方案是两层防护。第一层是token级约束能通过结构化输出方式强制格式的绝不用自由文本再解析。第二层是容错解析如果强制手段没生效用正则提取JSON片段再二次解析解析失败就重试一次再失败直接把该模块标记为异常进入FAILED状态。不要在这种问题上无限重试重试超过两次就该走异常路径了。6.2 Agent在工具调用里“绕圈”现象是模型反复调用同一个或几个工具每次参数略有差异但始终不满足校验条件。我从OpenClaw思路里的体会是出现这种情况大概率是规划或校验环节出了问题。排查时先看结构化日志它在绕圈时每次工具返回了什么校验模块为什么不通过如果是因为校验条件过于严格比如要求搜索结果必须包含某个实体但知识库里确实没有那就要优化校验规则而不是责怪模型。另外一定提前设置最大步数保护。我踩过的教训是某次线上任务因为没有步数限制一个Agent在30分钟内完成了42次工具调用直到API配额用完才停。6.3 上下文爆炸与记忆丢失上下文爆炸最典型的症状前期对话的信息到后面模型完全“记不住”。原因不一定是模型能力差而是你把它能看到的上下文塞了太多无关内容。LlamaIndex等社区讨论中经常提到的一个做法是分层记忆我测试下来的经验是短期记忆保持在最近几轮对话长期记忆通过向量检索按需抽取而不是一股脑全塞进prompt。需要特别警惕的是工具返回结果。一个查询接口可能返回几百行数据如果全量塞入上下文几轮下来就爆了。我的做法是先对工具返回结果做摘要只保留与当前任务直接相关的字段再交给模型处理。6.4 用户干预失效Agent“不听劝”有一种情况比较隐蔽你的系统设置了人工介入位但用户在介入位拒绝之后模型还是按原计划执行了。排查后发现问题出在状态机迁移没有保持“用户拒绝”这个状态。用户在REVIEW状态拒绝了某个操作代码没把状态置为ABORTED或回退到PLAN而是让模型重新生成了一条消息这条消息恰好又触发了EXECUTE的迁移条件。解决方式比较本质任何用户干预指令都必须作为最高优先级事件直接改变状态机的当前状态而不是作为一条普通消息排队处理。这也是为什么状态机管理中要把“用户事件”和“系统内部事件”分开处理的原因。6.5 一套好用的排查路径最后分享一套我自己整理的排查路径遇到Agent行为异常时按这个顺序走省时间先看状态机当前停在哪一个状态——锁定范围。回放结构化日志找到发生异常前的最后一次状态迁移——定位触发原因。直接查看异常状态对应的模块输入输出判断是模块逻辑问题还是模型输出问题。如果是模型输出问题检查该模块的prompt和模型参数必要时调整输出约束手段。如果是模块逻辑问题检查消息协议和状态迁移条件补上遗漏的边界分支。写在最后的个人体会坦白说我在研究OpenClaw这些理念之前一直觉得Agent项目做不好是因为模型不够聪明。等到真正按照模块化、状态机、可观测这套思路重构之后才意识到大部分失控问题根本不是模型的锅而是架构缺少约束。模型的能力很重要但稳定产出的能力更多来自系统设计本身。我现在做Agent相关项目时第一件事永远是画状态机第二件事是定日志规范第三件事才是设计prompt。整个过程下来最大的体会是好的Agent架构不是给模型更多自由而是给它一个足够稳固的边界让它每次发挥都能落在预期范围内。如果这篇文章对你有启发建议直接拿一个小项目练手。不用一开始就追求多复杂的架构先把一个任务的“规划到执行到校验”跑通再逐步加人工介入位和可观测能力。踩过一轮坑之后你会回来感谢这套理念的。