告别木偶Agent:用分层任务拆解构建可靠智能体系统 做Agent开发这几年我踩过最大的坑就是亲手造出一个又一个木偶Agent——demo阶段看起来什么都会工具调用行云流水一丢到真实场景就崩得稀碎。后来我把这些翻车案例翻来覆去地复盘发现根源几乎一致大多数人都在用一个系统提示词加一堆工具函数把大模型当万能执行者让它自己决定怎么规划、怎么执行、怎么收尾中间没有任何分层和约束。所以要告别木偶Agent真正要做的是分层任务拆解把智能体的决策链拆成意图识别、任务规划、原子执行、结果校验四个层面每层只做一件事层与层之间用结构化协议通信最终得到一个可预期、可观测、可干预、可恢复的可靠智能体系统。这篇文章我会把完整的拆解思路、每层的关键实现、从0到1的代码骨架以及实战中反复踩到的坑全部写出来。适合正在做Agent应用、或者已经在生产环境里被Agent折腾得焦头烂额的开发者参考。先把话说在前面分层不是给系统加复杂度而是把不可控的模型行为变成可控的工程边界。1. 先诊断木偶Agent为什么必崩1.1 木偶Agent的长相一个提示词加上一堆工具木偶Agent的标准造型很容易辨认有一个写满规则的系统提示词罗列了几十个工具函数的说明然后告诉模型你可以使用以下工具完成用户的请求剩下的就看模型临场发挥了。表面上看模型确实会自己选工具、自己拼步骤、自己给结果像模像样。我把这种架构叫木偶Agent是因为它本质上没有自己的骨架只有一套华丽的皮肤。模型说要查库存它就去查库存模型说要发邮件它就去发邮件。看起来是模型在做决策实际上决策质量完全取决于提示词怎么写、模型当天的状态好不好、工具描述是否足够详尽以及最要命的——上下文窗口里有没有被无关信息干扰。我之前给一个做售后工单的团队做过技术咨询他们的第一版Agent就是这种架构。用户问一句我的打印机上周买的现在不工作了能换新吗,这个Agent真的会自己决定先去查订单、再去查保修政策、然后去查退货规则、最后生成回复。demo里演示了无数次都没问题一上线就翻车——它会在查到订单后直接跳到退款完全不看保修条款也会在用户还没有提供故障证据时就自信满满地告诉用户可以换新。这不是模型蠢是架构让模型背了不该背的锅。你让一个系统提示词同时承担意图理解、任务规划、工具编排、结果判定这四个完全不同的职责它怎么可能稳定1.2 三个致命问题上下文稀释、错误放大、无法回滚木偶Agent的可靠性崩塌通常沿着三条路径。第一条是上下文稀释。系统提示词、工具说明、用户输入、历史消息、中间工具返回结果全挤在一个上下文窗口里。模型需要从一大锅信息里找到当前该干什么注意力被严重分散。工具说明写得越长模型越容易忽略关键约束历史消息越多模型越容易忘记最初的用户目标。我在生产环境里见过最夸张的case是模型在第五轮工具调用时已经完全不记得用户要的是查询保修状态开始自顾自地聊起了打印机型号。第二条是错误逐级放大。木偶Agent没有分层的校验机制模型在前面的一个微小误判会沿着后续步骤一路放大。比如它第一步把订单号理解错了后面查到的所有信息都是错的最后生成的结论自然离谱。更麻烦的是每一步的错误都没有独立被发现的机会因为整个流程是一团乱麻——你根本不知道是哪一步开始歪的。第三条是无法回滚。一个复杂任务执行到一半发现路径不对木偶Agent只能继续硬着头皮往下跑或者直接给你一句抱歉我无法完成这个请求。它没有回到上一步换一条路径的概念因为系统压根没记录每一步的状态。出了问题只能从头再来而再来一次可能又在同一个地方翻车。1.3 我们说的可靠到底是什么很多人一听可靠就以为是要做到100%成功这个目标本身就不现实。大模型是概率系统没有任何架构能保证它每次都完美执行。我们追求的可靠应该是四件事可预期、可观测、可干预、可恢复。可预期是指同类输入能得到同类结果不会这次走A流程、下次走B流程可观测是指任何一步出了岔子你能明确知道是哪个环节、哪条链路出的问题可干预是指系统在关键时刻能停下来等人工确认后再继续可恢复是指某个步骤失败时能回到最近的有效状态换个方案重试而不是整体崩溃。想通这一点之后我再看Agent架构就完全换了个视角。我不再去想怎么让模型更聪明而是去想怎么让系统在模型犯傻的时候也能兜得住。分层任务拆解就是在这个思路下被验证过无数次的答案。2. 给Agent装上骨架分层任务拆解的整体设计2.1 四层模型意图层、规划层、执行层、校验层我常用的分层方案是四层每一层解决一类独立的问题。很多人也管这个叫编排-执行-校验实际是一个东西只是叫法不同。层级职责输入输出意图层判断用户想干什么决定要不要进入Agent流程原始用户消息、会话上下文意图标签、置信度、是否触发Agent规划层把用户目标拆成可执行的任务步骤序列用户目标、可用工具清单、上下文摘要结构化的任务描述符列表执行层逐个执行原子步骤每个步骤有明确入参和出参单个任务描述符结构化执行结果校验层验收执行结果判断任务是否完成决定继续/重试/终止执行结果、步骤预期校验结论、下一步动作这四个层级的核心思想是职责单一。意图层只做入口判断规划层只做拆解执行层只做落地校验层只做验收。每一层都短小精悍不需要理解全局所有细节它只需要吃透自己的输入做好自己那件事然后输出给下一层。为什么把意图层单独拿出来因为真实场景里用户的请求有很大概率根本不需要Agent介入。用户可能只是问一句你们几点下班或者我想换个地址这种请求走标准会话或者固定流程就好没必要让模型去规划任务。如果一上来就让规划层拆解等于每句话都要过一遍大模型既慢又贵还容易在无关请求上胡言乱语。执行层单独分离是因为所有跟外部系统打交道的行为都有副作用。查个库存还行但写数据库、发邮件、创建工单这类操作一旦执行错了就很难撤销。把执行层做成一个个独立的、可被校验的原子操作才能在出错时精准定位、局部重试。校验层放在最后是给整个系统兜底。模型的规划再合理执行结果也可能跟预期对不上。这时候必须有个不带感情色彩的质检环节来说这个结果不合格重新来。2.2 层间通信用结构化的任务描述符分层最难的地方不是定义层数而是层与层之间怎么说话。很多团队分层之后依然翻车就是因为在层间传递大段自然语言文本——规划层写了一篇小作文传给执行层执行层读得似懂非懂结果还是乱。正确的做法是定义一套结构化的任务描述符相当于工厂里的工单。上层只下发工单下层只回传回执双方不需要理解对方的完整内部逻辑。一个任务描述符通常包含这些字段任务ID、目标描述、需要调用的工具或服务、输入参数的来源或具体值、预期输出结构、成功判定的标准。比如查询订单状态这个步骤任务描述符会写成调用订单查询服务入参是订单号出参加订单状态、商品列表、购买日期成功标准是返回码为0且订单状态为有效。这样做的好处很明显。第一执行层不需要理解全局目标它只看自己的工单降低了对模型理解力的依赖第二每个步骤的结果可以被单独校验出了错能精确定位到是哪个步骤第三整个流程可以被录音、回放、追踪因为每一步的输入输出都是固定结构。在层级之间传递结构化数据还有个隐藏价值它天然形成了一种接口契约。规划层必须按契约生成步骤执行层必须按契约消费步骤。任何一方越界比如规划层生成了契约之外的步骤或者执行层返回了不符合契约的结果系统都能立即发现并拦截。2.3 贯穿全文的案例售后工单助手后面的所有代码、排查经验我会围绕一个具体的业务场景来讲方便对照。假设我们要做一个企业售后工单自动处理助手用户通过网页或IM提交售后诉求Agent需要自动完成一部分工作比如查订单、查保修政策、判断是否符合换新条件、生成回复、必要时创建换新工单。这个场景非常有代表性它涉及多个外部系统订单系统、政策库、工单系统有明确的业务规则保修期、换新条件有需要人工介入的边界场景退款争议、政策灰色地带还要求所有操作可追溯。任何一个把Agent当demo玩的人碰到这种业务都会头疼。用四层架构来解这个题会非常清晰。意图层判断用户是咨询、报修还是申请换新只有后两种才进入Agent流程规划层决定先查订单还是先查政策按什么顺序推进执行层逐个调用订单查询、政策匹配、工单创建这些服务校验层检查每一步结果是否合理比如用户申请换新但没有提供订单号那查询步骤就是失败的。后面的章节我还会反复回到这个案例。它不复杂但足够说明分层的价值。3. 每一层怎么做才不算伪分层3.1 意图层先学会拒绝和降级意图层是整个系统的第一道闸门。最直接的实现方式是用大模型做分类任务让它从固定的意图枚举里选一个并给出置信度。把意图分类从开放式的任务规划里独立出来是因为分类是模型的强项比让它做多步骤规划稳得多。但我建议加几道保险。第一意图必须做成枚举值不能凭空生成。模型只能在预设的意图列表里选比如咨询、报修、申请换新、投诉、无关闲聊。第二必须输出置信度低于阈值时不允许进入Agent流程直接降级到人工客服或者标准话术。第三对无关闲聊这个意图要特别敏感用户聊闲天绝对不能让Agent去执行任何工具。我在实际项目里见过一个很典型的翻车案例用户只是吐槽了一句你们这个打印机真是气死我了意图层给分到了投诉Agent直接调用了工单创建接口给用户自动开了一个投诉工单还把工单号发给了用户。用户一脸懵运营团队一脸懵。后来我们把意图分类的置信度调高并且给负面情绪表达单独设了一个意图不给它挂任何工具才解决这个问题。意图层的另一项职责是抽取关键实体。用户说我上周买的打印机不工作了系统需要从这句话里抽取出产品名、购买时间范围、故障描述。这些实体后续会作为规划层的输入。实体抽取可以和意图分类放在同一个模型调用里完成返回一个结构化JSON效率更高。但要注意调用一次模型就要求它同时完成意图分类和实体抽取这会增加输出的不确定性。我的经验是最好还是分成两步或者给模型更结构化的输出约束。如果担心延迟用两个小并行调用也可以关键是别让一次模型调用干太多事否则它又会开始自由发挥。3.2 规划层把大任务写成可验收的工单规划层的输入是一个用户目标输出是一串任务描述符。这个环节是整个架构里唯一需要模型发挥创造力的地方也是最需要约束的地方。我看到新手做规划器最常犯的错误是让模型自由输出一段方案文本第一步查询订单第二步分析保修政策第三步生成回复。这种输出没法被执行层消费因为它不是结构化的同一个步骤可能因模型表述不同产生无数种变体解析出来全是坑。正确的做法是让规划器输出严格的JSON结构。我常用的做法是在系统提示词里明确给出一个步骤模板要求模型按模板生成。每个步骤包含step_id、name、description、tool、input_schema、success_criteria。其中tool必须在给定的可用工具清单里选input_schema要说明入参从哪里来success_criteria要有可判断的标准。还有一个非常关键但容易被忽略的点规划器生成的步骤数量一定要控制在合理范围我一般限制在5到8步。步骤太少说明拆解粒度太粗执行层还是得靠模型自由发挥步骤太多则产生大量中间产物系统复杂度和出错概率都会飙升。如果规划流程很大应该再做一层抽象把任务拆成多个子Agent或者子流程而不是把几十个步骤平铺在一个规划器里。规划层还必须有能力处理无法拆解的情况。模型发现目标信息不足、或者用户请求超出了自己的能力范围应该直接返回需要更多信息或者无法完成的结论而不是硬编出一个错误的步骤序列。我在代码实现里会专门检查规划器输出的步骤数如果逻辑上无法闭环就拒绝进入执行阶段。规划层对模型的温度参数设置也很讲究。我的习惯是设成0关闭模型的随机性。规划是一个需要稳定性的环节不需要模型发挥创造性同一个目标每次生成的步骤序列应该尽量一致。否则同样的请求这次先查订单、下次先查政策测试都没法测。3.3 执行层能不用模型就不用模型执行层是系统里最不需要智能的地方也是最需要用工程手段加固的地方。我的原则是确定性操作绝不用模型只有真正需要判断的环节才用模型。什么叫确定性操作查询订单状态、调用库存接口、写入数据库、调用工单系统创建工单这些都是有明确入参和出参的操作直接用代码函数实现即可。一个工具注册表把工具名映射到对应的执行函数执行层拿到任务描述符去注册表里找到对应工具传入参数执行返回结果。那什么时候才需要在执行层用模型只有在结果生成类任务里才需要。比如根据订单和保修信息生成一段给用户的回复话术这个属于生成式任务必须要用模型。再比如判断用户提供的故障描述是否足够明确这也可以交给模型判一下。但这类模型调用应该被封装成一个特殊的工具放进注册表里由执行层统一调用而不是让模型自己在流程里穿插。执行层的另一个职责是参数绑定。每个步骤的入参不能由执行层自由发挥而应该由规划层在任务描述符里明确指出从哪里取。比如查询订单状态的入参是用户消息中抽取的订单号执行层就只负责从上下文中把这个值取出来填进工具调用里。如果取不到对应参数执行层要做的事是返回参数缺失而不是自作主张猜一个值填进去。我在项目里见过最多的问题就是执行层拿到一个参数缺失的任务后模型自作聪明地造了一个假订单号去调用系统。调用是成功了但拿到的是别家用户的订单信息。这种情况在我们明确要求执行层参数缺失必须报错之后才彻底消失。执行层还要统一处理工具调用的异常超时、网络错误、返回值格式不对、业务异常。每个异常都应该转换成标准化的错误码返回给上层。上层拿到错误码才能判断是重试、换方案还是终止。3.4 校验层给系统装一个不带感情色彩的质检员校验层是分层架构里最容易被偷工减料的环节。很多人觉得规划器和执行器都写好了任务跑通就算完事校验层随便写个如果返回码不是0就报错糊弄过去。但真正的可靠性恰恰是靠这一层撑起来的。校验层要做三件事。第一是结构校验检查执行结果是否符合预期的schema字段是否齐全类型是否正确。第二是语义校验检查结果在业务上是否合理。比如售后退换货场景用户申请换新系统查出用户的保修状态是已过保那结论就必须是不符合换新条件如果执行层返回的结果是可以换新这就是语义上的矛盾必须拦截。第三是流程校验检查当前步骤的产物是否满足进入下一步的条件。语义校验怎么做可以用一个独立的小模型调用把执行结果和预期标准放在一个提示词里让它判断是否符合要求并给出原因。这个判断任务本身非常简单模型不容易出错而且它和整个Agent的主流程是解耦的就算偶尔判断失误也只是多一次重试不会污染主流程。校验层还有一个非常重要的职责循环检测。如果一个执行步骤连续两次产生完全相同的状态或者规划器生成的新步骤序列和上一次一模一样那基本可以判断系统进入了死循环。校验层需要记录每一步的状态哈希把关键字段拼成一个字符串算哈希一旦发现重复立即终止并通知上层走人工兜底。3.5 横切关注点状态、记忆与日志分层的系统如果不在状态管理上设计好跑起来仍然会乱成一团。我给每一层都定义了一个简单的状态字段pending、in_progress、success、failed、terminated。每个任务描述符都会有自己独立的生命周期上层协调器只需要根据各步骤的状态来决定下一步动作。记忆管理也是分层的。很多木偶Agent崩溃的根源是它试图把所有的上下文全部塞给模型。分层之后每层应该只看到自己需要的那一部分上下文。执行层做查询订单时不需要看到用户三个小时前跟客服聊了什么规划层做拆解时只需要知道用户的当前目标、相关实体、已有的执行结果摘要不需要具体的工具返回原文。日志系统在分层架构里尤其重要。我给每个请求生成一个trace_id贯穿所有层级的输入输出每层执行完都把结构化日志写下来。出现问题时按trace_id一拉从意图分类到最终回复的每一层输入输出都清清楚楚排查效率比木偶Agent高一个数量级。记忆窗口的设计我倾向于用摘要化存储。不是把每轮对话原文都留给后续模型而是每完成一个阶段就把这个阶段的关键信息压缩成摘要存进一个结构化状态对象里供规划层和后续步骤使用。这样既保留了必要信息又不会无限拉长上下文。4. 从0到1搭一个分层的售后工单Agent4.1 定义核心数据结构代码层面我习惯先用数据类把整个系统要传递的对象定义清楚。这一步看起来不起眼但它是把分层真正落地的关键。没有清晰的数据结构写着写着就会退化成用字典到处传最后又变成一锅粥。from dataclasses import dataclass, field from typing import Optional, Any from enum import Enum class StepStatus(str, Enum): PENDING pending IN_PROGRESS in_progress SUCCESS success FAILED failed TERMINATED terminated dataclass class TaskDescriptor: 任务描述符规划层产出执行层消费 step_id: str name: str # 步骤名称比如查询订单状态 tool: str # 要调用的工具名必须在注册表里存在 input_schema: dict # 入参说明比如{order_id: 来自用户消息的抽取结果} success_criteria: str # 成功判定标准比如订单状态为有效且返回购买日期 inputs: dict field(default_factorydict) # 实际入参值执行前由参数绑定填充 status: StepStatus StepStatus.PENDING result: Optional[dict] None # 执行结果 error: Optional[str] None # 错误信息 dataclass class IntentResult: 意图层输出 label: str # 意图标签必须在预设枚举里 confidence: float # 置信度 0~1 entities: dict # 抽取到的实体比如{product: 打印机, order_id: HD20240501}这两个数据类是整套系统的地基。TaskDescriptor在整个架构里流通意图层负责产出IntentResult规划层把IntentResult转成一个或多个TaskDescriptor执行层逐个消费TaskDescriptor校验层检查它的status和result。4.2 意图层与入口路由意图分类这块我不会让模型自由发挥而是先有一个预设意图枚举再让模型从枚举里选。阈值设成0.8置信度低于这个值直接走人工客服通道。INTENT_LIST [咨询, 报修, 申请换新, 投诉, 无关闲聊] def classify_intent(user_message: str, history: list) - IntentResult: prompt f 判断用户的售后诉求属于哪个意图。只能从以下列表中选择一个{INTENT_LIST}。 同时抽取与售后相关的关键实体产品名称、订单号、问题描述、期望解决方式。 返回JSON格式 {{label: 意图标签, confidence: 0~1, entities: {{product: , order_id: , issue: }}}} 用户消息{user_message} resp call_model(prompt, temperature0) data parse_json(resp) return IntentResult(labeldata[label], confidencedata[confidence], entitiesdata.get(entities, {})) def route(intent: IntentResult, user_message: str, session): if intent.confidence 0.8: return {action: transfer_to_human, reason: 意图置信度不足} if intent.label 无关闲聊: return {action: normal_chat, reply: 您好请问有什么可以帮您} if intent.label in (报修, 申请换新): return {action: start_agent, intent: intent} if intent.label 投诉: # 投诉不走Agent自动处理直接转人工避免误操作 return {action: transfer_to_human, reason: 投诉需要人工介入} return {action: normal_chat, reply: 您好请描述一下您遇到的问题。}这里注意一个细节投诉没有给Agent任何工具权限直接转人工。因为投诉场景里用户情绪占比很高Agent自动触发工具大概率会激化矛盾。这算是业务规则对架构的反向约束——不是所有的意图都适合走Agent意图层必须有能力把这些入口挡在外面。4.3 规划层让模型生成结构化步骤规划器会在意图层确认进入Agent流程后被调用。它拿到用户目标和抽取出的实体生成TaskDescriptor列表。关键点有两个限定规划器可用的工具集合以及强制JSON输出。TOOL_SCHEMA { query_order: {params: [order_id], desc: 根据订单号查询订单状态、购买日期、产品型号}, query_policy: {params: [product_type, issue], desc: 查询某类产品的保修和换新政策}, check_evidence: {params: [issue], desc: 判断用户的故障描述是否明确返回是否充分}, generate_reply: {params: [order_info, policy_info], desc: 生成给用户的最终回复话术}, create_exchange_order: {params: [order_id, customer_message], desc: 创建换新工单}, } def plan(intent: IntentResult, context: dict) - list[TaskDescriptor]: prompt f 用户目标{intent.label} 已抽取实体{intent.entities} 可用工具及其参数{TOOL_SCHEMA} 请将上述用户目标拆解为3~6个步骤。必须遵守以下规则 1. 每个步骤的tool只能从可用工具中选择禁止编造不存在的工具。 2. 每个步骤的输入要么引用用户实体要么引用前序步骤的输出禁止凭空假设参数。 3. 每个步骤必须写清楚成功判定标准。 4. 直接输出JSON数组不要输出任何解释。格式如下 [{{step_id: s1, name: 查询订单, tool: query_order, input_schema: {{order_id: 实体order_id}}, success_criteria: 查询成功且返回订单状态}}] resp call_model(prompt, temperature0) steps_json parse_json(resp) # 硬性校验工具必须存在步骤数必须在合理范围 steps [] for i, raw in enumerate(steps_json): if raw[tool] not in TOOL_SCHEMA: continue # 或者标记为非法步骤让上层决定是否重新规划 steps.append(TaskDescriptor( step_idfs{i1}, nameraw[name], toolraw[tool], input_schemaraw[input_schema], success_criteriaraw[success_criteria] )) if not 3 len(steps) 6: return [] return steps我在这段代码里特意加了一个非法步骤直接丢弃的逻辑。实际项目中如果丢弃后剩余步骤数量不合理我建议直接触发一次重新规划让模型看到你刚才给的步骤不合法请修正。这样可以避免执行阶段踩到规划阶段埋的坑。4.4 执行层工具注册表与参数绑定执行层的核心是一张工具注册表外加一个参数绑定模块。参数绑定的职责是从上下文对象里取出任务描述符需要的实际参数值。def bind_arguments(step: TaskDescriptor, context: dict) - dict: 把input_schema里的参数说明映射为上下文中的实际值 bound {} for param, source in step.input_schema.items(): if source.startswith(实体): key source.replace(实体, ).strip() if key in context.get(entities, {}): bound[param] context[entities][key] else: raise ValueError(f缺少实体参数: {key}) elif source.startswith(前序输出): # 从前序步骤的结果里取值这里简化处理 prev_step_id source.split(:)[1] bound[param] context[step_results][prev_step_id][data][param] else: bound[param] context.get(source) return bound TOOL_REGISTRY { query_order: query_order_service, query_policy: query_policy_service, check_evidence: check_evidence_service, generate_reply: generate_reply_service, create_exchange_order: create_exchange_order_service, } def execute_step(step: TaskDescriptor, context: dict) - TaskDescriptor: if step.tool not in TOOL_REGISTRY: step.status StepStatus.FAILED step.error f未知工具: {step.tool} return step try: args bind_arguments(step, context) except ValueError as e: step.status StepStatus.FAILED step.error str(e) return step step.status StepStatus.IN_PROGRESS try: result TOOL_REGISTRY[step.tool](**args) step.result result step.status StepStatus.SUCCESS except Exception as e: step.status StepStatus.FAILED step.error f{type(e).__name__}: {e} return step这里面的generate_reply和check_evidence实际上内部会调用模型但从执行层的视角看它们只是两个普通工具。这个抽象非常关键——整个Agent流程不知道也不关心最终话术是模型生成还是模板拼出来的它只需要知道这个步骤是否成功、结果是否符合预期。想替换实现的时候只要保证输入输出契约不变里面的逻辑随便改。4.5 主流程把四层串成闭环主流程是一个循环规划器产出步骤执行器逐个执行校验器检查结果有问题就决定是重试、终止还是重新规划。MAX_STEPS 10 STATUS_HASH_SET set() def run_agent(user_message: str, session): # 第一步意图层 intent classify_intent(user_message, session.history) action route(intent, user_message, session) if action[action] ! start_agent: return action # 第二步规划层 context {entities: intent.entities, step_results: {}} steps plan(intent, context) if not steps: return {action: transfer_to_human, reason: 无法生成有效规划} # 第三步执行层校验层循环 for round_no in range(MAX_STEPS): all_success True for step in steps: if step.status StepStatus.SUCCESS: continue step execute_step(step, context) context[step_results][step.step_id] step.result or {} # 校验层介入 if not validate_step(step): step.status StepStatus.FAILED step.error 校验未通过 # 失败两次就终止走人工兜底 if step.retry_count 2: return {action: transfer_to_human, reason: f步骤{step.step_id}多次失败: {step.error}} step.retry_count 1 all_success False continue step.status StepStatus.SUCCESS # 死循环检测把当前所有步骤的状态哈希一下 state_signature hash(tuple(f{s.step_id}:{s.status}:{s.result} for s in steps)) if state_signature in STATUS_HASH_SET: return {action: transfer_to_human, reason: 检测到循环状态} STATUS_HASH_SET.add(state_signature) if all_success: return {action: reply, content: steps[-1].result[reply]} return {action: transfer_to_human, reason: 超过最大执行轮数}这段代码是简化版省略了重试次数字段的定义和validate_step的具体实现但整体骨架就是真实项目里能用的一版。它把每一层的职责都卡得死死的意图层负责判断规划层负责拆解执行层负责干活校验层负责挑刺。主循环只是把四个环节像齿轮一样咬合起来。5. 实战中的坑为什么分层了还会翻车5.1 规划器脑补出不存在的工具即使你明确给了工具清单规划器在复杂场景下还是有概率生成一个根本没注册过的工具名。比如用户说帮我把故障图片发给维修中心它可能自行脑补出一个send_image_to_repair工具。这个工具在系统里压根不存在执行层就会直接报错。我们的对策是三层保险。第一在规划器的提示词里反复强调只能使用给定工具第二在代码解析层直接丢弃不在注册表里的工具调用第三如果解析后发现步骤序列里有非法工具不回执行层直接重新规划一次。实测下来第二层保险能拦截掉80%的脑补剩下20%会被第三层重新规划消化。加了这三道保险之后基本不会再出现莫名其妙的工具名。5.2 工具返回结果太长污染后续上下文这是所有Agent工程都绕不开的问题。一次订单查询可能返回一大串原始数据物流轨迹、订单备注、促销活动信息、优惠券记录。如果把这些原文全部传给规划层或者生成回复的模型上下文窗口很快就会爆炸。我们的做法是给执行结果做摘要化。每个工具的结果都经过一个处理函数只保留后续步骤需要的核心字段。比如query_order的结果只保留订单状态、购买日期、产品型号、是否在保修期、是否符合换新条件。其他原始信息宁可丢掉也不塞给模型。这个取舍刚开始做的时候会很纠结觉得丢信息可惜。但实际验证过之后会发现绝大多数原始字段对业务判断毫无帮助保留它们只会稀释模型的注意力。数据的精简程度决定了Agent的稳定性。5.3 模型在规划阶段自由发挥输出不合法JSON规划器用模型生成JSON最怕的就是它偶尔输出一段带注释、带解释文字、甚至格式直接的伪JSON。我在项目里遇到过模型直接输出一段以下是步骤1. 首先……2. 然后……然后硬塞到JSON解析器里直接报错。解决办法有三步。第一提示词里写死只输出JSON数组禁止任何解释第二用容错解析器先尝试标准解析如果失败用正则把json代码块剥出来再解析第三如果还是解析失败就当作规划失败走重新规划。配置好这三步之后规划器的有效输出率能稳定在98%以上。代码示例里的parse_json函数我实际用的是先剥代码块、再标准解析、再修复常见缺逗号问题的三层方案。这些小技巧看着不起眼但都是实打实提升系统稳定性的东西。5.4 死循环同一个方案反复执行分层之后死循环依然存在但容易被检测得多。最常见的死循环场景是某个步骤校验失败重试一次还是失败系统重新规划结果规划器又生成了和原来一模一样的步骤序列再次执行再次失败周而复始。我们的解法是在主循环里维护一个状态哈希集合。每个轮次结束时把当前所有步骤的ID、状态、关键结果字段拼在一起算哈希如果发现这个哈希已经出现过说明系统没有在往前走直接终止并转人工。这个方案不是完美的它没法做到100%识别所有循环形态但足以拦截最典型的那一种。后来的实践里我又加了一条规则同一个业务步骤失败重试次数不超过2次一旦超过就转人工。两条规则叠加之后死循环基本绝迹。5.5 人工兜底Agent系统的安全网再好的分层架构也不能保证100%自动闭环。尤其是售后场景涉及用户权益、退款金额、政策灰色地带这些情况在业务上天然需要人工决策。所以整个系统的最后一道防线必须是一个设计良好的人工介入通道。我们的人工兜底设计是当校验层连续失败、规划层产出空步骤、或者循环检测触发时系统生成一封包含完整trace_id、用户原话、每一步的输入输出、失败原因的结构化摘要推送给人工客服。客服打开后台点进trace详情能看到从意图分类到最终失败的完整链路直接定位问题不用像以前那样靠猜。这套兜底通道看起来简单但它给整个系统赋予了试错的安全边际。运营团队愿意先让Agent跑50%的流量也是因为确定有人在末端接着出了问题不会造成业务事故。5.6 测试与回归录下每一次翻车现场分层架构让Agent系统变得可测试这件事常常被忽略。木偶Agent你是没法做测试的因为每次运行路径都不可控分层之后每个环节都是确定的接口可以逐层做单元测试。我常用的方法是构建一个回放测试集。每次线上出现问题就把这条会话的完整请求数据、各层输入输出录下来归入回归测试集。改完代码后批量回放这些样本确认之前的翻车场景不再出现。这个方法操作起来成本不高但效果非常显著——它能防止你在修复一个bug的同时把另外几个场景搞坏。状态哈希集合也可以保留下来用于对比。每次测试运行后把最终状态序列和预期状态序列做对比任何偏差都会暴露出来。这个做法在Agent系统里特别有价值因为它能让你像测普通软件一样对智能体系统做自动化回归。最后说点实在的这些方法不是从理论框架里推出来的是我和团队在真实业务场景里一遍遍踩坑踩出来的。从最早的木偶Agent翻车到后来强行分层但层间传自然文本继续翻车再到把层间通信改成结构化任务描述符之后才真正消停整个过程让我确信一件事做可靠智能体系统靠的不是让模型变得更聪明而是用一个明确的结构把模型的每一次“灵光一现”都约束在安全的边界里。如果你现在正准备做一个Agent系统我的建议是先从“给每个步骤写验收标准”开始。别急着写代码先把你要处理的业务拆成一张步骤表每个步骤写清楚工具、入参、出参、成功标准。这张表写好了分层架构自己就会浮现出来写不好那你用再漂亮的框架也还是会翻车。这是我掏心窝子的经验希望你能少走几步弯路。