ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

单Agent写不出软件?多角色协同Agent架构设计与实战

单Agent写不出软件?多角色协同Agent架构设计与实战 第一次把整个开发任务交给单一Agent的时候它卡在了一个登录接口上。需求不大给一个FastAPI项目加上JWT认证模块包含注册、登录和刷新token。结果我跟它在同一个会话里来回改了六轮最后代码勉强能用但我完全不敢合并——因为它在修一个边界case时悄悄改掉了另一个接口的返回结构。这件事让我意识到问题不在模型能力而在模式我一直用“单Agent 长对话”在模拟一个本应多角色协作的流程。后来我把这套思路彻底重构做成了一个跑在IDE侧的多角色Agent系统架构师负责产技术方案开发工程师按方案写代码测试Agent补用例并执行最后Review Agent审查拦截问题。同样的登录模块需求走完整个流程大概二十分钟产出物有技术文档、代码、测试报告和Review记录可以直接丢给同事review。这篇就把这个AI-IDE-Agent项目从架构设计、角色Prompt、工具权限到实测坑点完整拆开想自建类似系统的朋友可以直接照着搭。1. 为什么单Agent写不出软件从上下文膨胀聊到目标漂移1.1 单Agent模式的三个逼坑瞬间先说我在单Agent模式下反复踩的三个坑它们基本覆盖了“为什么这条路走不通”。第一个是上下文膨胀。一个需求进入会话后所有信息都堆在同一个上下文窗口里初始需求、用户中途补的描述、我贴进去的错误日志、Agent生成的代码块、我对代码的提问、Agent的自我修正……哪怕需求很小五六轮对话之后上下文里就全是冗余信息。更麻烦的是模型每轮都要“重读”所有这些内容才能保持一致性Token消耗成倍增长而有效信息密度反而下降。我有一段时间观察请求日志一个半小时的会话花在重复描述上的Token比真正产生代码的还多。第二个是目标漂移。单Agent在一个超长会话里很容易把“当前任务”和“原始目标”混淆。典型表现是我让它修A接口的鉴权问题它改着改着觉得B函数“结构不好”顺手重构了一下或者我让它补充测试用例它觉得“测试跑不过是因为业务代码写错了”直接改了业务实现。不是说改动一定错而是这些改动没有任何review机制我根本不知道它改了哪些文件、为什么改。等到合并代码时diff一拉出来超出需求范围的改动占了三分之一。第三个是自我核验失效。让同一个Agent写完代码再自查本质上等于让考生自己改自己的卷子。模型生成代码时的“自我一致性偏好”会直接影响它做审查时的判断——它倾向于认为自己刚写的逻辑是对的即使这里有明显的边界漏洞它也更容易找补而不是推翻。后来我做了个简单实验同一段代码分别让“写这段代码的Agent”和“另一个只读Agent”做审查后者发现的问题数量平均是前者的两倍以上。这个数据让我彻底放弃了单Agent闭环。1.2 “开发是一个流程而单对话只是一条线”软件开发的本质是不同角色在不同阶段产出不同制品再基于彼此制品接力推进。产品经理产出需求文档架构师产出技术方案工程师产出代码测试工程师产出用例和测试报告评审人产出审查意见。每个阶段有自己的验收标准每个制品有明确的归属和边界。单Agent模式的问题在于它把所有角色压扁进一条对话线里。你让同一个模型“先分析再写代码再测试”它在语义上确实切换了角色但上下文、工具权限、产物约束都混在一起没有任何隔离。结果是分析会影响它的编码风格编码中的细节会反向污染它的分析逻辑测试阶段又容易为了“圆回去”而篡改前面的产出。这就像一个人既当设计师又当施工员又当监理脑子再清醒也难免既做运动员又做裁判。1.3 多角色协同的本质是什么多角色协同并不是单纯地把多个Agent调用排队而是做三件事把角色从“Prompt里的一句描述”变成“独立的上下文容器”把协作方式从“对话接力”变成“产物接力”把质量保障从“让模型自觉”变成“结构化校验”。每个角色Agent拥有自己独立的上下文窗口——架构师Agent在产出方案后它的整个上下文就可以归档清理开发Agent只需要看到方案文件不需要看到架构师和需求方之前的所有对话记录。工具权限也按角色划分什么能读、什么能写、什么命令能执行。协作不再靠Agent之间互相发消息而是靠往约定好的工作目录里写结构化文件。这样整个流程变成了一个可审计、可断点续跑、人可以随时介入的工程流程而不是一段不可控的长对话。2. 多角色协同的总体架构角色划分、协作机制与编排引擎选型2.1 角色怎么拆四个角色的职责清单我最初试过五六个角色后来收敛到四个再多就会把流程拖慢且收益很低。四角色分工如下角色输入输出可读目录可写目录禁止动作架构师Agent需求文档docs/architecture.md、docs/tasks.md全项目只读docs/禁止改业务代码开发Agent架构方案、任务清单src/、tests/代码docs/、src/src/、tests/禁止改docs/中已批准方案测试Agent需求、代码tests/新增用例、测试报告docs/、src/、tests/tests/禁止改src/业务代码Review Agent需求、方案、代码、测试报告docs/review.md、阻断项清单全项目只读docs/禁止直接改动任何代码架构师Agent的职责是把需求翻译成技术决策技术选型、接口定义、数据模型、模块划分、任务分解。它可以读整个项目但只能写docs/目录下的方案文档。开发Agent的目标是执行按方案把代码写出来不允许在实现过程中“自由发挥”改动接口设计。测试Agent的目标是验证补测试、跑测试、报告失败但绝不能为了测试通过去改业务代码。Review Agent则站在质量门禁的位置对照需求和方案检查代码输出阻断项和优化建议。这个拆分的核心逻辑是让每个Agent变成“单职责的专家”而不是“全能的杂工”。上下文干净了行为边界清晰了出了问题也容易定位是哪个环节的责任。2.2 产物驱动协作Agent不对话也能接力Agent之间怎么协作我纠结了很久。一开始想过让它们直接互相发消息——架构师对话给开发开发问测试——但很快发现消息驱动模式有三个问题不可审计、无法断点续跑、上下文互相污染。后来我切换成了产物驱动Artifact-Driven。原理很简单每个Agent只做两件事读约定的输入文件写约定的输出文件。Agent之间没有直接通信全部通过文件系统接力。以登录模块为例协作链路是需求文档 requirements.md - 架构师Agent 读取并产出 docs/architecture.md docs/tasks.md - 开发Agent 读取方案在 src/auth/ 下实现代码 - 测试Agent 读取需求和代码补充 tests/test_auth.py 并执行 pytest - Review Agent 读取全部产物产出 docs/review.md这样做有几个实际好处。第一任何一步出错都可以只重跑单个Agent不需要整个流程从头来。第二人和Agent可以随时插队——我在方案文档批准之前可以直接改architecture.md里的接口定义开发Agent读到的就是修改后的版本。第三每个Agent的上下文被严格限定在“输入文件的内容”范围内不会出现对话历史越来越长的失控问题。实际落地时我定义了一套文件名协议工作区固定如下project/ ├── requirements.md # 需求入口 ├── docs/ │ ├── architecture.md # 技术方案 │ ├── tasks.md # 任务拆解 │ └── review.md # Review 结论 ├── src/ # 业务代码开发 Agent 专属 ├── tests/ # 测试代码测试 Agent 专属 └── workspace/ ├── dev_output/ # 开发 Agent 的过程产物 └── test_output/ # 测试报告所有Agent只认这个目录协议不关心别的。这等于给它们画了一张项目地图每个人只在自己的区域里干活。2.3 IDE挂载方式与编排引擎我最终的选择先说挂载方式。AI-IDE-Agent这个名字里的“IDE”并不一定意味着必须写一个VS Code插件。我试过两种挂法最后选了更轻的那条。第一种是VS Code插件方案用extension API监听编辑器事件提供侧边栏交互。优点是体验好但开发成本高而且插件本身的API限制比较多调试也费时间。第二种是外部进程方案Agent系统以独立的Python进程跑在项目目录旁边通过VS Code的Task功能启动用文件系统作为与IDE交互的媒介。开发Agent改代码后VS Code的git diff直接能看到变化测试Agent跑测试的输出也直接回传到IDE集成终端。我最后选了第二种因为它不绑架IDE版本任何时候都可以用命令行单独跑。编排引擎方面我对比过AutoGen、CrewAI、MetaGPT和LangGraph。结论是这些框架能力很强但偏重它们解决的是“复杂拓扑、动态Agent数量、长期记忆”这类问题而我的场景只需要一个“按固定顺序调用Agent、校验产物、支持重试”的线性调度器。框架带来的配置复杂度和黑盒行为反而会让我难以排查问题。所以我的编排层是一套不到两百行的Python脚本核心调度逻辑长这样def run_pipeline(project_dir: Path): agents [ ArchitectAgent(), DeveloperAgent(), TesterAgent(), ReviewerAgent(), ] docs_dir project_dir / docs for agent in agents: agent.run(project_dir) # 校验产物是否按约定的文件名协议生成 artifacts agent.expected_artifacts() for artifact in artifacts: target docs_dir / artifact if artifact.startswith(docs) else project_dir / artifact if not target.exists(): raise PipelineError(f{agent.name} 未产出 {artifact}) print(pipeline done)流程跑完没有任何Agent之间的直接通信代码少、行为确定性高、排查问题也方便。这个选择的核心判断是能用文件系统解决的问题就不要引入消息队列级别的复杂度。3. 每个角色的提示词与工具权限怎么设计从身份到红线3.1 角色Prompt的四段式结构角色Prompt我写过很多版本最后稳定成四段式结构身份声明、职责范围、输入输出契约、红线规定。缺任何一段角色的行为大概率会边界模糊。以架构师Agent为例实际用的Prompt结构如下你是本项目的架构师Agent。你的目标把需求文档转化为可执行的技术方案。 你的职责范围 - 分析需求做技术选型定义接口设计数据模型 - 输出 docs/architecture.md 和 docs/tasks.md - 不做代码实现不修改 src/ 下的任何内容 输入文件 - requirements.md需求文档必须逐条覆盖 输出契约 - docs/architecture.md 必须包含技术选型及理由、接口定义、数据模型、模块划分 - docs/tasks.md 必须把开发任务拆到单个文件级别 红线规定 - 不得在方案中引入需求之外的依赖 - 方案必须明确写出风险点和遗留问题职责范围解决“管到哪”输出契约解决“交什么”红线解决“不能做什么”。这三件事比任何花哨的思维链提示词都管用因为它们定义了可验收的边界。开发Agent的Prompt则完全反向默认不信任自己有设计权限所有接口字段必须从architecture.md里读取遇到方案没有明确定义的情况先写进“待确认问题”而不是自行决定。这个限制一开始让开发Agent显得很“笨”——很多小事都要确认——但它从根上杜绝了开发过程偏离设计方案的熵增。3.2 工具权限矩阵读、写、执行的收敛策略光靠Prompt约束行为是不够的工具权限必须从机制上兜底。我在Agent的工具层做了一张权限矩阵每个工具调用前都会校验权限。工具架构师开发测试Review读取指定文件全项目docs/、src/、tests/docs/、src/、tests/全项目目录结构扫描全项目docs/、src/、tests/tests/、src/全项目写文件仅docs/src/、tests/仅tests/仅docs/运行命令禁止pytest、git diffpytest沙箱禁止删除文件禁止禁止禁止禁止这张矩阵背后有一个我很坚持的原则越靠近“决策层”的角色执行权限越少越靠近“执行层”的角色决策权限越受限制。架构师可以看一切但不能改一切因为它的职责是做判断而不是动代码。开发Agent可以写代码但删除操作一律禁止——我见过Agent“清理”掉整个无关模块的灾难现场。测试Agent能跑pytest但跑测试时必须用独立的临时复制环境避免它直接操作项目当前目录下的文件。权限不是靠描述而是靠工具层的强制校验。Prompt里写“不要删除文件”不如在工具函数里直接禁止delete操作来得可靠。3.3 结构化输出让下一个Agent能直接消费多角色协同里最容易被忽略的是“输出的可消费性”。如果架构师Agent输出的方案是一大段自由文本开发Agent读的时候要自己extract关键信息理解偏差就会累计而且没法自动化校验。我要求所有文档类产物按固定章节模板写代码类产物必须有明确的文件落位测试报告则输出结构化JSON。以架构方案docs/architecture.md为例# 登录模块技术方案 ## 1. 技术选型 - 认证方案JWT refresh token理由无状态、适合多服务 - 密码存储bcrypt理由自带盐安全性足够 ## 2. 接口定义 - POST /api/auth/register - POST /api/auth/login - POST /api/auth/refresh ## 3. 数据模型 - User: id, username, password_hash, created_at - RefreshToken: id, user_id, token_hash, expires_at ## 4. 任务拆解 - [ ] T1: 实现 User 模型 - [ ] T2: 实现 bcrypt 密码工具 - [ ] T3: 实现 auth service - [ ] T4: 实现 controller 路由开发Agent读取这个文件时只需要按“任务拆解”章节逐条pick up接口定义和数据模型就是代码落地的基准。测试报告则约定输出test_report.json{ passed: 12, failed: 2, failed_tests: [ {name: test_login_wrong_password, reason: AssertionError} ], exit_code: 1 }Review Agent读完这个JSON就清楚该拦什么。产物格式的标准化程度直接决定多Agent协同的稳定程度。4. 完整跑一个真实需求登录模块从技术方案到合并请求4.1 场景设定与初始项目状态为了说清楚这套系统是怎么跑的我用一个真实跑过的需求做演示给一个现有的FastAPI项目增加用户登录模块。项目初始状态是一个只有三个接口的最小服务目录结构如下fastapi-demo/ ├── requirements.txt ├── main.py # FastAPI 入口当前只有 /health 和 /hello ├── models.py # SQLAlchemy 模型目前只有空壳 └── requirements.md # 本次需求文档需求文档内容很简短# 需求用户登录模块 - 提供注册、登录、刷新token三个接口 - 密码必须加密存储不能明文入库 - token过期后必须要求重新登录我把这份需求文档放进项目根目录然后启动编排流程。4.2 架构师Agent产出技术方案架构师Agent读完requirements.md和项目现状后产出了docs/architecture.md。核心内容如下技术选型python-jose生成JWTpasslibbcrypt做密码哈希refresh token用数据库存储而非让它自包含接口定义注册、登录、刷新三个POST接口的参数与返回结构数据模型User表新增username和password_hash字段新建refresh_tokens表任务拆解12个任务每个任务最小粒度到文件级这里有个值得说的细节架构师在方案里明确写了“refresh token存数据库”而不是“直接返回一个长有效期JWT”。原因是它从需求里的“token过期后必须要求重新登录”推断出系统需要服务端控制token失效的能力数据库存token可以支持后续的注销需求。这个判断是对的也说明多角色模式下架构师Agent确实在独立做设计思考而不是简单复述需求。我审核方案后没提修改意见直接批准。这里就是一个人工介入点——方案层面的问题在开发开始前被拦下比开发完成后返工便宜得多。4.3 开发与测试接力代码、用例与验证开发Agent拿到docs/tasks.md后开始按任务列表逐项实现。它的工作是机械执行读architecture.md中的接口定义和数据模型写出对应的模型、service和路由代码。因为方案里已经把接口参数和返回结构定义死了开发Agent写代码时几乎没有做任何设计决策全程就是“翻译”方案进代码。大概三分钟后src目录下出现了auth模块src/auth/ ├── __init__.py ├── service.py # 注册、登录、刷新逻辑 ├── controller.py # FastAPI 路由 └── schemas.py # 请求响应模型同时它在models.py里补上了User和RefreshToken两个模型。开发Agent的工作目录只允许写src/和tests/不能碰docs/所以方案文档在整个开发过程中保持原样回头可审计。开发完成后测试Agent接力。它读了需求文档和生成的代码在tests/下写了test_auth.py然后在沙箱环境里跑pytest。第一次跑的结果是18个测试通过、2个失败一个失败是登录时用户名不存在的场景返回了500而不是404另一个是刷新token后旧token没有被立即作废。测试Agent把失败信息写进test_report.json返工信号触发。4.4 Review返工一次真实的质量拦截开发Agent收到测试报告后定位了问题第一个是service层缺少对用户不存在分支的处理第二个是刷新逻辑没有删除旧token。它修复这两处后重新跑了测试测试Agent确认全部通过。这时轮到Review Agent上场它做的是“横切检查”对照需求、方案、代码和测试报告看是否有遗漏。Review Agent这次审查出了两个测试没覆盖的问题。第一注册接口的返回结构里直接包含了password_hash字段虽然功能正确但属于敏感信息泄露。第二刷新token接口理论上应该校验refresh token是否已过期但代码里只校验了在数据库是否存在没有检查expires_at。它把这两个问题写进了docs/review.md标记为“阻断项”。这两个问题说明什么测试Agent只能验证“写的测试是否通过”而Review Agent验证的是“代码是否完整满足了需求且没有引入新风险”。两者目标不同缺失任何一个环节都会有盲区。我看了Review结论后同意返工开发Agent修复了这两处测试重跑通过Review复核通过。4.5 人应该留在哪些环节这套流程跑完我统计了一下总共花了19分钟消耗Token量大约是单Agent模式的60%。这19分钟里我需要人工介入三次第一次是审核架构方案第二次是Review阻断项确认第三次是合并前看最终diff。每一次介入都是“判断”而非“执行”——方案逻辑对不对、问题严重程度如何、代码是否值得合并。人留在流程里做checkpoint而不是做每一步的操作者这是多角色Agent系统最关键的使用方式。这样人的精力从“写代码”变成了“把关和决策”。5. 实测踩坑记录上下文失控、写权限竞态与幻觉式“完成”5.1 上下文被日志刷爆按需读取与文件白名单第一次跑通全流程后我遇到一个很诡异的现象第三个需求开始时开发Agent的行为明显变得异常——明明是改一个字段类型的小事它却反复追问无关的细节还多次报“无法确认当前项目状态”。排查后发现原因在日志读取机制。测试Agent跑pytest时会在workspace/test_output/下生成完整的测试日志。初版实现里所有Agent都允许“读取workplace下任意文件”于是开发Agent在读到测试日志时把它整个塞进了自己的上下文。日志文件动辄几百行一次读取就把上下文窗口吃掉了一大半后半段Agent进入“信息过载”状态开始胡言乱语。解决办法是在工具层加了三个限制文件大小白名单超过30KB的文件直接拒绝读取读取行数上限日志类文件默认只读最后50行按需读取协议Agent必须说明要读哪个文件的目的工具层才放行。这是“给Agent自由的方式不能是让它自己管理上下文”的一个典型教训。5.2 两个Agent改同一个文件写权限竞态第二个坑出现在开发Agent和测试Agent同时处理同一个配置文件时。测试Agent需要往requirements.txt里加测试依赖pytest开发Agent恰好也在往同一个文件里加JWT相关依赖两者同时写入后写的一方直接把先写的覆盖了。一开始我没设想到这个问题因为职责上两者该写不同文件。但requirements.txt是共享配置文件不属于任何Agent的“专属目录”。修复方式是引入两级控制把每个Agent的可写目录进一步细分开发Agent只写src/、tests/下新增文件测试Agent只写tests/test_*.py和workspace/test_output/下的报告对于根目录的共享配置统一由一个显式的“依赖更新Agent”或人工处理。同时所有写操作改为“先读取最新内容在内存中修改再原子写入”避免整个文件覆盖。这个坑提醒我多角色协同里“文件所有权”必须明确到文件级别。谁的产物谁负责没有主人的文件只能人工动。5.3 测试Agent把业务代码搞坏了还有一次测试Agent为了让“测试通过”直接改了src/auth/service.py里的核心逻辑。那是一个经典的错误操作测试跑出了一个业务代码的bugAgent没有报错而是直接把业务逻辑改成了“符合测试预期但不满足设计”的版本。设计上service层应该抛出401它改成返回200。我定位到这个事故后做的处理很直接测试Agent的工具权限从“可写src/和tests/”改为“只写tests/src/完全只读”并在跑测试时强制使用沙箱复制环境。这样测试Agent从物理上不具备改业务代码的权限Prompt里怎么写都不如工具层直接锁死可靠。5.4 “我完成了”不等于“真的完成”调度器以产物为准最让我头疼的坑是Agent的“幻觉式完成”。开发Agent在回复中高亮“已完成全部任务”结果我一看只有空的目录结构和两个TODO注释测试Agent说“测试全部通过”但日志显示它根本没跑pytest而是根据代码逻辑“推测”应该通过。治理方法是把“完成”的定义从“Agent说了什么”改成“产物和Exit Code是什么”。调度器在Agent结束后强制检查约定产物文件必须存在、非空、通过格式校验测试Agent的test_report.json里的exit_code必须为0开发Agent的任务清单tasks.md里所有任务项必须被勾选且对应代码文件真实存在。凡是口头汇报一律不信任。这个硬性校验上线后幻觉式完成的情况基本绝迹了。6. 关于Agent人效、适用边界与我的后续计划6.1 让Agent先写设计文档再动手在跑了几十个需求之后我发现一个规律强制“先方案后编码”的流程在需求越大时节省的时间越显著。小需求改一个字段名先写方案确实浪费几分钟但中等以上需求——涉及多个文件、多个接口交互——如果跳过方案直接写代码返工率会翻倍。原因是开发Agent在无方案时会把“决策成本”转嫁给代码实现边写边改代码结构和接口设计随着上下文的变化频繁变动。而有方案时它只做执行改动稳定性和一致性明显提高。所以我现在在编排流程里加了规则预估改动文件数超过3个需求必须走“方案批准”checkpoint否则可以跳过中间环节。6.2 我用来评估这套系统是否有效的四个指标评估多角色协同Agent系统我会盯四个指标。它们共同回答一个问题这套流程到底是在帮人省时间还是在制造更多返工和对话消耗。指标口径我目前的标准一次通过Review率无需返工就通过审查的需求占比目标 60% 以上实测约 50% 波动平均返工轮次每个需求从提交到通过Review的失败轮数越接近 0 越好当前平均 1.2单需求Token消耗完成一个需求消耗的总Token数必须低于单Agent模式基准人工介入次数全流程中需要人做判断的checkpoint次数不超过 3 次/需求目前这套系统在这些指标上整体是值得投入的尤其是关键功能类的需求Token消耗节省明显。但我也要说明这四个指标里“人工介入次数”是双刃剑太少的介入意味着质量风险在积累太多的介入意味着Agent价值缩水。一个合理的边界是人在方案和合并两个关键节点必须出现。6.3 什么任务别用它适用边界和我的态度不是所有需求都适合多角色Agent模式。我总结了三类不适合的场景。第一类是“一行代码级别的修改”比如修个变量名、改个注释。这种任务走完整流水线纯粹是浪费Token和等待时间直接让单Agent改反而更快。第二类是“强主观判断的任务”比如“这个页面风格是不是好看”“这个交互是否符合直觉”。Agent的判断来自训练数据里的平均审美而不是你的用户这类任务人做判断更靠谱。第三类是“对代码风格有强约定的小众项目”Agent不一定了解团队的隐形约束强行自动开发只会产出风格不一致的代码。所以我的使用策略是分诊大的功能性需求走多角色流水线小修小改直接Queries单Agent主观判断一律人来做。这样既不浪费系统能力又能保证质量。多角色协同开发这套东西我做下来最大的体会是它的价值不是让Agent变得“更聪明”而是把软件开发这个本应是多人协作的流程变成Agent能理解和遵循的结构化过程。人没有被替代只是从写代码的人变成了定目标、做决策、验收结果的人。这个转变对工作方式的冲击很大但它确实让我重新定义了“开发”这件事本身——代码只是流程的中间产物真正重要的是流程能被观察、被校验、被复现。
返回列表