多代理实战:五个子代理与ReAct架构重构研发流水线
1. 从“一个万能Prompt”到多个专用代理我为什么改架构1.1 单一智能体的三大瓶颈最早做智能体的时候我习惯把所有逻辑塞进一个超长Prompt让大模型既当产品经理又当工程师还当测试员。结果很典型需求只要稍微复杂一点输出就开始“精神分裂”。同一个任务里前半段还在分析业务后半段突然开始写代码写代码的时候又把需求分析结论重新解释了一遍解释完自己都忘了前面定的接口约束。这种单代理模式的瓶颈其实非常明确。第一条是上下文长度有限超长Prompt会把有效信息稀释模型在中间层就容易丢失关键约束第二条是指令冲突你在一个Prompt里同时要求“先分析再编码最后测试”模型很难稳定地遵守顺序经常跳步第三条最难处理——你没法定位错误。一旦最终结果不对你不知道是分析阶段就错了还是编码阶段跑偏了还是在测试阶段没发现回归。没有过程数据等于没有排查路径。我意识到要换思路把一个大而全的代理拆成多个各司其职的子代理每个子代理只负责一个明确子问题再通过一套标准接口把它们串起来。这就是多代理实战的核心动机。1.2 什么样的任务适合拆分给多代理不是说所有场景都适合多代理。我拆的时候给自己定了一个判断标准任务里是否包含明显不同的“工种”。比如一个流程里既有信息检索又有方案设计又有代码生成又有结果验证这些工种对提示词、工具、模型参数的需求都不一样硬塞在同一个代理里必然互相干扰。而多代理的价值就在于隔离。每个子代理可以单独调优单独测试单独换模型出问题也能精准定位到某个环节。我做这套系统时选了研发流水线作为载体因为研发本身就有非常清晰的角色划分需求澄清、代码实现、测试验证、文档整理。这个天然结构特别适合代理化。2. 五个子代理的角色分工把一条研发流水线装进系统2.1 调度子代理任务拆解与结果聚合第一个子代理是调度者它不直接写代码也不分析业务只做一件事把用户提交的原始需求拆解成可执行子任务并决定子任务的执行顺序。我给它定义的职责有三层第一步识别需求属于哪个范畴是纯文档任务、代码任务还是混合任务第二步把混合任务拆成原子子任务并按依赖关系排序第三步调用对应的下游子代理再收集它们的结果生成最终的汇总报告。这里的关键是调度子代理的输出必须是结构化JSON不能是自由文本。我定义的输出格式包含tasks数组每个元素有task_id、description、assignee、depends_on四个字段。为什么强制结构化因为下游子代理不能靠“读自然语言”来理解任务它们需要一个稳定、可解析的输入接口否则整个系统在第一个环节就会陷入文本解析的泥潭。实际开发中我用了一张“任务卡”来约束调度子代理的输出格式大致如下{ overview: 基于用户需求生成的可执行计划, tasks: [ { task_id: T001, description: 整理用户需求中的功能清单和非功能指标, assignee: requirement_agent, depends_on: [] }, { task_id: T002, description: 根据需求清单生成单元测试用例并执行, assignee: tester_agent, depends_on: [T001] }, { task_id: T003, description: 实现核心功能代码并通过测试用例, assignee: developer_agent, depends_on: [T001, T002] } ] }2.2 需求分析子代理把模糊诉求变成明确输入第二个子代理是需求分析师。很多人一开始会忽略它觉得“用户已经把需求说清楚了直接让模型写代码不就行了”但实际跑下来十个原始需求里有八个是模糊的比如“做一个用户登录功能”“优化一下性能”这种描述根本没有可执行性。需求分析子代理的职责是向用户追问或者根据上下文补全缺失信息最终输出一份结构化的需求清单。我给它定义的核心输出包括功能列表、边界条件、验收标准、依赖资源。其中验收标准是最重要的一项因为它直接喂给后面的测试验证子代理是下游所有验证动作的基准。有一个细节值得单独说需求分析子代理不能无限制追问否则用户会烦。我在它的ReAct循环里加了“追问次数上限”默认最多两轮。如果两轮后信息还不足就基于合理默认值继续向下执行并在需求清单中把“假设条件”单独标注出来。这样既保证了流程不卡死也让后续环节知道哪些判断是基于假设的。2.3 开发实现子代理代码产出的核心执行者第三个子代理是开发者它是整套系统里工作量最大的角色。它的输入是需求分析子代理输出的结构化需求清单它的职责是按清单实现代码并且主动调用工具进行代码质量检查。这个子代理我做了最重的ReAct配置。它注册的工具包含文件读写工具、代码搜索工具、以及一组代码检查工具。开发者代理在生成代码后必须自己先跑一遍静态检查把明显的问题修掉再把结果交给测试验证子代理。这里有一个很多人容易忽略的点开发者代理不能直接访问测试结果否则它会陷入“改代码-跑测试-再改”的无限制循环。它只负责产出“自我检查通过”的版本是否真正满足验收标准由独立的测试验证子代理来判断。我在开发者代理的System Prompt里明确写了一条规则所有代码修改都必须附带修改原因并且在最终交付结果中列出“未能实现的需求项”。这条规则帮我过滤了大量“看起来完成实际上偷工减料”的情况。2.4 测试验证子代理用目标反馈兜住质量第四个是测试验证子代理它相当于质量网关。它不信任任何下游代理的“自我评估”只相信实际运行结果。它的核心动作是把开发实现子代理交付的代码在模拟环境中编译、运行执行它自己生成的测试用例并把“通过/失败”结果回传。测试验证子代理的技术含量在于测试用例生成。我要求它必须覆盖三类用例正常流程、边界条件、异常分支。正常流程保证主链路通边界条件检查空值、超长输入、并发场景异常分支验证程序在出错时不会崩溃而是能给出正确提示。跑测试时它还会记录失败信息包括错误日志、堆栈、触发步骤。这些信息会被原样返回给调度者由调度者决定是让开发者子代理二次修改还是直接判定任务失败。把质量判断权完全独立出来之后系统的最终交付质量有了非常明显的提升。原因是开发和测试使用不同的提示词语境模型对同一条产出的“自我感觉良好”不会传染到质检环节。2.5 文档沉淀子代理交付物闭环的最后一块拼图第五个子代理负责文档它在上游所有环节的结果都产出之后开始工作。输入是需求清单、实现代码、测试结果三份材料输出是使用说明、变更记录和技术要点。为什么要单独做一个文档子代理而不是让开发者顺手写完因为“写代码”和“写文档”的语言风格差距很大一个代码提示词优化的代理去写文档经常把说明书写得跟代码注释一样毫无可读性。文档子代理我单独配置了长文本生成的Prompt要求它面向两类读者普通使用者和后续维护者。它在部分程度上有“编”的风险这是所有生成式系统的通病。所以我给它加了约束文档里涉及功能名、参数名、接口路径时必须从开发者代理的交付物里提取并逐字引用不得自行发挥。每次生成完成后它还要输出一份“未引用原文清单”方便我人工抽查。如果用一个表格概括这五个角色大概是这个样子子代理核心输入核心输出关键约束调度者用户原始需求结构化任务清单输出必须是JSON任务必须排序需求分析师结构化任务卡需求清单与验收标准追问最多两轮假设条件要标注开发者需求清单通过自检的代码必须附带修改原因列明未实现项测试验证者代码与需求清单测试结果与失败日志覆盖正常/边界/异常三类用例文档沉淀者需求代码测试结果使用说明与技术文档关键信息必须逐字引用交付物3. ReAct架构实现让子代理一步一步“想清楚再做”3.1 ReAct循环拆解Thought、Action、Observation三个状态每个子代理的核心循环我都用ReAct架构来实现。ReAct这个名字听起来玄其实就是一个“思考-行动-观察”的循环先让模型输出当前思考结论再根据思考结论决定要不要调用工具、调用哪个工具拿到工具返回结果后继续思考直到它认为不需要调用工具可以给出最终答案。拆成状态来看ReAct循环一共三步Thought模型输出“当前我应该做什么”。比如需求分析代理拿到任务卡后可能先输出“用户需求里缺少验收指标我需要先查看历史对话中是否包含性能要求”。Action模型从工具列表中选择一个并填写参数。它可能是搜索知识库、读文件、执行命令或者请求用户补充信息。Observation系统把工具的返回结果拼到上下文里让模型获得新信息再进入下一轮Thought。这个循环的关键价值和普通“直接生成答案”的区别在于每次工具返回都会变成可追溯的中间记录。我可以回放某个子代理的全部思考路径找到它为什么会做出错误决策。单代理模式下你拿到的是一个已完成的黑盒ReAct模式下你拿到的是完整决策日志。3.2 基于LangChain的ReAct子代理实现我整套系统是基于LangChain搭的但只用到了它的create_react_agent和AgentExecutor两个核心组件。每个子代理共享同一个ReAct执行框架只是Prompt和工具注册表不同。以测试验证子代理为例核心代码长这样from langchain.agents import create_react_agent, AgentExecutor from langchain_core.tools import tool from langchain_openai import ChatOpenAI tool def run_test_case(project_path: str, test_file: str) - str: 在指定项目中运行一个测试文件返回测试结果与失败日志。 # 这里会通过本地命令行执行 pytest result execute_pytest(project_path, test_file) return result tools [run_test_case] prompt PromptTemplate.from_template( 你是测试验证子代理负责生成并执行测试用例。 你的输入是需求清单和代码交付结果。 你需要先思考哪些场景需要覆盖然后调用 run_test_case 工具执行测试。 只有当你拿到全部测试结果后才能输出最终验收结论。 思考过程{agent_scratchpad} ) agent create_react_agent( llmChatOpenAI(modelgpt-4o, temperature0), toolstools, promptprompt ) executor AgentExecutor( agentagent, toolstools, max_iterations8, handle_parsing_errorsTrue, verboseTrue )注意这里的max_iterations8它不是随便设的。我统计过测试验证代理正常处理一个规模适中的任务平均需要4到5轮循环所以8作为上限留出了余量又不会让它在出错时无限空转。3.3 不引入框架用原生Python实现的最小ReAct循环如果你不想引入LangChain想彻底搞清楚ReAct内部原理也可以自己实现一个最小版本。底层逻辑其实很直接把Thought、Action、Observation拼成多轮消息不断交给大模型直到模型给出最终答案。下面这是我早期验证原型时写的核心循环import json from openai import OpenAI client OpenAI() def react_loop(user_input, tools, max_steps5): messages [{role: user, content: user_input}] for step in range(max_steps): response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, tool_choiceauto ) msg response.choices[0].message messages.append(msg) if not msg.tool_calls: return msg.content for call in msg.tool_calls: result dispatch_tool(call.function.name, call.function.arguments) messages.append({ role: tool, tool_call_id: call.id, content: result }) return 达到最大循环次数任务未完成这个版本的思路是让模型自己决定是否需要调用工具如果需要代码就负责执行工具并返回结果然后模型继续下一轮思考。LangChain内部做的也就是类似的事只是多了提示词解析、错误恢复和迭代控制。自己实现一遍最大的收获是理解了为什么很多子代理会“跑偏”。问题通常不出在模型本身而在于你给它的工具定义不够清晰。工具描述写得太含糊模型会把它不需要的工具也选进来造成无效循环。4. 子代理之间的通信与结果汇聚4.1 传递白板式的共享消息结构五个子代理之间不是直接互相调用的而是通过一个统一的消息总线交换数据。我把它称为“共享白板”本质上就是一个全局字典每个子代理执行完任务后把结果写入白板下一个子代理从白板里读自己需要的字段。为什么不用直接函数调用因为直接调用会让系统变成一张缠成一团的网状结构后期加一个角色就要改一堆上下游接口。共享白板把耦合降到了最低每个子代理只依赖白板不依赖其他子代理的具体实现。白板的结构我定义如下whiteboard { original_request: 用户的原始需求, requirements: None, # 需求分析子代理写入 implementation: None, # 开发子代理写入 test_results: None, # 测试子代理写入 documentation: None, # 文档子代理写入 execution_meta: { task_status: {}, error_logs: [], token_usage: {} } }4.2 阶段调用链与异常回滚调用链是按阶段顺序执行的但并不是简单的“做完A再做B”。调度子代理会根据任务卡中的depends_on字段动态生成执行图。没有依赖关系的任务可以并行有依赖关系的任务必须等前置任务完成。我用一个轻量级状态机管理这个过程。每个任务有四种状态pending、running、succeeded、failed。子代理返回结果后状态机根据结果更新任务状态。一旦某个任务失败所有依赖它的下游任务都会被标记为blocked调度者会重新决定是尝试修复还是宣告整个流程失败。当时有一个细节差点坑了我某个子代理虽然在流程上失败了但它白板上其实已经写入了部分有效结果比如测试代理已经生成了20个用例只是在执行时挂了。后来我加了一条规则失败的任务必须保留所有部分产物到白板并且用状态字段标记这是一份“不完整交付物”。这条规则在后来的问题排查中帮了大忙。4.3 日志与可观测性设计多代理系统做出来之后调试难度是成倍增加的。单代理你只需要看一次对话多代理你至少要同时看五段思考日志加一段调度日志。如果不做可观测性设计系统一跑起来就是一团乱麻。我给系统加了一个环形缓冲日志模块每个子代理的Thought、Action、Observation都被记录为独立事件附带一个trace_id贯穿整个流程。排查问题时按trace_id过滤日志就能看到从用户原始需求到最终交付文档的完整链路。日志里我额外记录了两个指标每个子代理消耗的token数和每一步的执行耗时。这两个指标最终导出了一张成本分布表让我一眼看出哪个环节最烧钱是后续优化的重要依据。5. 落地过程中踩过的坑和最终调优方案5.1 子代理不结束无限循环与重复思考第一个让我头大的坑是子代理陷入无限循环。表现很典型模型反复输出Thought说“我需要再确认一下需求”然后重复调用同一个查询工具拿回一样的答案又继续思考。循环了十几轮就是不结束。这个问题本质上是大模型在试图规避风险——它觉得信息不够不敢下最终结论。解决办法是双管齐下一方面设置max_iterations强制终止另一方面在System Prompt里明确提示“当工具返回结果不足以支撑决策时基于现有信息做出合理默认判断并注记假设条件”。加了这条之后极少再出现空转循环。5.2 上下文污染结果被拼成“四不像”第二个坑是子代理之间的上下文污染。早期我为了让调度者能给出更全面的最终报告会把前序所有子代理的完整输出都塞进它的Prompt里。结果调度者被文件字节数、测试失败日志、代码片段干扰输出的总结反而偏离了用户关心的重点。这正是做多代理系统最容易犯的错试图让一个代理掌握全部信息结果把所有代理都变成大杂烩。修复方式是“按需取读”调度者只读取白板中的摘要字段而不是原始长文本。我在每个子代理交付时都会额外生成一份200字以内的摘要供上游汇总使用。这样信息量没有损失太多噪声却过滤掉了大半。5.3 成本失控一次全流程的教训当时有天真地让系统自动跑了一个规模较大的需求结果单次流程消耗了非常大的token量其中大头不是代码生成而是测试失败之后的重复循环。开发者改一次测试跑一次不通过开发者再改测试再跑来回转圈。我后来引入两个降本机制第一个是“快速失败”测试代理在执行测试之前会对代码做静态检查如果静态检查有明显语法错误直接打回不启动测试环境第二个是“限次迭代”开发者代理总共只允许处理两次返工两次之后任务直接标记失败交给人工处理。这两个机制把成本降低了约一半而且并没有明显降低任务成功率。5.4 超时与流程卡死生产可用的容错机制最后一个问题是超时。某子代理调用外部工具时可能长时间无响应比如网络抖动、服务超时或者是生成的测试命令卡在等待输入。没有超时控制整个流程就永远挂在那里。我为每个工具调用都包了一层异步超时控制超过指定时间自动终止并把“工具超时”作为Observation返回给子代理让它决定是重试还是切换方案。配合整体流程的超时保护系统在长时间运行场景下终于敢留夜间自动执行了。6. 实测效果、适用范围与未来可扩展方向6.1 与单代理对比的实测数据我拿同一批需求做过对比测试一组用调优前的单代理超长Prompt处理一组用现在这套五子代理系统处理。在任务完成率、过程可追溯性和成本三个维度上差异非常明显。单代理在需求稍微复杂时完成率开始断崖下跌而且失败原因不固定很难定位。五子代理系统的完成率明显更高尤其是在带有“先分析后开发再验证”这类天然分阶段需求上优势特别突出。虽然多代理的总token消耗略高于单代理但这个差值完全在可接受范围内换来的是过程可控性和结果稳定性绝对值上其实非常划算。6.2 什么项目不适合这种架构也必须说实话不是所有项目都适合多代理。如果你的需求非常短比如“把这段文本翻译成英文”把它拆成五个子代理只会增加成本和延迟。多代理架构适合的是流程长、工种多、需要高可靠性的任务对于简单任务单代理仍然是最优解。另外要提醒一点多代理系统对下游模型能力的要求更高。如果你用的是能力偏弱的模型子代理可能连基本的Thought都写不清晰更不用说规划任务和调用工具了。这套架构至少要使用当前主流旗舰模型否则体验会很难受。6.3 下一步迭代思路记忆、路由与人工介入目前系统已经能稳定跑通端到端流程但我的迭代清单上还排着几个方向。第一个是长时记忆把每次任务完成后的经验沉淀下来下次遇到相似需求时调度者可以读取历史经验调整任务拆解策略。第二个是动态路由现在五个子代理是固定角色将来可以做成插件式注册根据任务类型动态加载不同角色。第三个更重要也是我一直坚持的想法给系统加上“人工审批点”。在某些关键节点比如调度者拆分任务之前、测试代理判定失败准备打回之前先暂停并通知负责人确认。这种半自动模式比全自动在真实生产环境里更稳妥也更接地气。做多代理实战这段时间我最深的体会是不要想着用一个大而全的代理解决所有问题也不要用一堆功能互相重叠的代理制造混乱。给每个代理明确的职责、清晰的结构化输入、可控的工具集合以及完整的过程日志系统自然就会稳定下来。你自己动手搭一套的话建议先从一个最小闭环开始哪怕只有两个子代理先跑通ReAct循环和数据传递再逐步加角色。过程中保留好每一次的决策日志那才是整个系统最值钱的部分。