
1. 单 Agent 写代码的尽头为什么需要一座工厂1.1 一次看起来成功的 Demo掩盖了多少隐患我第一次意识到单 Agent 写代码这条路走不远是在模拟项目X上。需求很简单把内部三个数据源统一成一个查询入口带缓存、限流和日志。我把需求甩给一个能写代码的智能体它几分钟就吐出了一个完整的项目结构模块划分、接口定义、单元测试样样齐全。看着这份产出我一度觉得已经成了。但真正跑起来之后问题一个接一个缓存模块在并发场景下会读出脏数据限流算法用的是固定窗口而不是滑动窗口日志模块把敏感字段直接打了出去单测倒是都过了——因为它们测的全是自己 mock 出来的假数据。那一刻我意识到这个智能体产出的不是可用的软件而是看起来像软件的文本。这不是模型能力不够而是我使用它的方式错了。我用的是单兵模式一个智能体、一个对话上下文、一次生成。这种方式应付独立的小函数、小脚本完全够用但一旦进入真实的多模块开发它必然翻车。原因有三个一个上下文里塞进需求、设计、编码、测试四种任务模型的注意力会被稀释到后期连最开始的需求约束都忘了。模型没有自我校验的手段。它只能生成它认为对的测试而不会真的把代码跑起来看结果。没有角色分工就没有责任边界。出了问题你不知道该让谁改只能整个对话重新来一遍。所以问题的关键不是再换一个更强的模型而是换一套组织方式。1.2 软件开发本质上是多角色博弈我越来越觉得软件开发这件事天然就不适合单线程生成。你去看看任何一个正经团队负责需求的人把模糊需求翻译成可验收的条目负责架构的人把条目拆成模块和接口开发负责实现测试负责找茬部署负责上线。这些岗位做的事情完全不同甚至思维方式都相反——开发希望尽快实现测试希望尽量挑错。这两种相反的力量恰恰是质量的根本保障。如果让同一个人既写代码又做测试他大概率会对自己宽容如果让同一个模型在同一个上下文里既写代码又测代码它也会出现同样的自我宽容。它倾向于证明自己是对的而不是证明自己是错的。多智能体系统的真正价值不在于多个模型一起干活看起来很酷而在于它能把软件开发里的这种多角色博弈重新建立起来。写代码的 Agent 不知道测试 Agent 会从哪里下刀所以它不敢糊弄测试 Agent 只负责挑错所以它不会因为这段代码是我写的而手下留情。这就是我想要的内在对抗性。1.3 工厂的本质把不确定性变成可管理的流程既然软件开发是博弈那工厂化改造的思路就顺理成章了。一座真实的工厂核心不是车间里的机器而是流水线和质检岗。原料进来经过一道道工序每道工序后面都有一道质检闸门不合格就返工合格才流到下一道。这套东西搬到软件生成上就是多智能体流水线 自我验证闸门。我搭建的这座软件开发工厂流水线是这样设计的需求解析岗把一句话需求变成结构化任务票据架构岗把票据拆成模块和接口契约编码岗按照契约实现代码验证岗在隔离沙箱里真正运行测试集成岗把通过验证的代码组装回项目。任何一环的产出没有通过闸门就带着失败原因原路返回。这套设计没有引入任何神秘的概念它只是在模仿一支靠谱的工程团队。而我要做的就是把这个流程变成代码让每个岗位都是一个可以独立调用的智能体让每道闸门都真实执行而不是嘴上说说。接下来我会从岗位设计讲起一步步把这套东西搭出来。2. 工厂里的岗位设计五个角色与一条流水线2.1 角色拆解谁负责什么产出什么先明确一点这里的岗位不是拍脑袋分出来的而是对应软件开发中真实存在的能力边界。我最终沉淀下来五个岗位岗位核心职责关键产出物典型判断需求解析岗把模糊需求翻译成验收条件结构化需求票据谁、在什么场景、做什么、怎么算完成架构设计岗拆解模块定义接口契约模块列表、接口签名、数据流改动影响范围、依赖方向是否清晰编码实现岗按契约实现具体代码可运行的代码文件是否遵守接口签名、是否处理异常质量验证岗在沙箱中真实执行检查测试报告、失败详情测试是否真实运行、失败是否可复现集成发布岗合并代码、处理冲突、生成变更说明合并后的代码库、变更日志是否破坏已有功能、变更是否可回溯注意最后两列才是重点。一个 Agent 说自己负责写代码是没有意义的必须写清楚什么输入、什么输出、用什么标准判断好坏。就像招聘时要写岗位说明书而不是只写一句招程序员。2.2 岗位说明书系统提示词的三段式写法每个岗位的本质就是一个配置了特定系统提示词的大模型调用。提示词写得好不好直接决定这个岗位上的是熟练工还是浑水摸鱼的实习生。我的经验是一份能用的岗位说明书必须包含三段第一段身份与目标你是谁你只负责什么不负责什么。比如编码岗明确你只负责实现代码不负责修改需求。边界清晰的好处是出了问题时责任不会被推给别的 Agent。第二段约束条件你必须遵守的硬规则。比如只能修改 src/models/ 目录下的文件不得改变给定函数的签名禁止使用未在依赖清单中出现过的第三方包。第三段输出格式结构化、可解析。不要让它自由发挥写一大段话而是要求它输出固定格式的 JSON 或明确标记的代码块。下面是我给编码岗写的一份简化版说明书框架示意你是编码实现岗。你的输入是一张任务票据JSON。 你只负责实现票据中指定的代码文件不修改需求、不做测试设计。 硬性约束 - 只能创建或修改票据中列出的文件 - 必须保持接口签名与票据一致 - 禁止在代码中硬编码密钥 输出格式 {files: [路径], summary: 实现说明, risks: [风险点]} 如果需求不明确输出 {error: 缺少XX信息}不要自行脑补。注意最后一行我明确允许 Agent 在信息不足时拒绝干活。为什么因为一个敢于说信息不够的 Agent好过一个硬着头皮编造需求的 Agent。很多多智能体系统跑偏就是从某个 Agent 默默脑补需求开始的。2.3 任务票据与状态机Agent 之间只传票据不传私货多智能体系统最容易踩的坑就是让所有 Agent 共享同一个对话历史。五个角色在一个超长上下文里你一言我一语看起来是协同实际上是互相污染——架构岗随手写的一句考虑用队列编码岗可能就当成需求去实现。我的做法是票据制每个任务都是一个结构化的 JSON 对象Agent 之间不直接对话只读写票据。票据会伴随任务走完整个流水线每一岗都在上面追加自己的产出。{ task_id: T-0014, requirement: 提供统一的 HTTP 客户端支持超时和自动重试, acceptance: [超时时间可配置, 重试次数可配置, 重试间隔指数退避], architecture: {modules: [src/http_client.py], interfaces: {create_client: ...}}, implementation: {status: pending, files: [], summary: }, verification: {status: pending, reports: []}, state: arch_done }同时定义有限状态机pending → analyzed → arch_done → impl_done → verified → integrated任何一步失败就进入 needs_fix并记录是哪个岗位触发的失败。状态机是整个工厂的交通规则没有它各个 Agent 就是一群没有交警的路口乱穿马路的车。3. 核心难点自我验证机制是怎么闭环的3.1 验证不是再生成一遍而是真实执行整个工厂里最容易做假、也最不能做假的就是验证岗。很多人在搭多智能体系统时让测试 Agent生成一些测试用例然后让编码 Agent检查测试是否通过。这里的问题显而易见让一个模型判断另一个模型写的代码能不能跑等于让考生自己给自己阅卷它只会看到一个看起来很合理的答案。真正的验证必须是无法撒谎的执行过程。测试用例生成出来后验证岗要做的是在一个隔离沙箱里真实地执行安装依赖、运行测试框架、捕获退出码、收集标准输出。模型可以幻觉但进程的退出码不会。这是整个自我验证闭环的地基——一切判断必须以可执行的结果为准。我在设计验证岗的时候把它拆成了生成测试和执行测试两件事并且允许它们由不同的机制完成。测试用例由编码岗之外的另一个 Agent 生成保证挑刺视角而执行一律用程序化的命令完成不经过模型转述。3.2 三层验证静态检查、单元测试、端到端冒烟我推荐至少配置三层验证闸门每一层解决一类问题。第一层是静态检查语法错误、未定义引用、明显的格式问题。这一层最快成本也最低。以 Python 项目为例可以直接用编译检查前端项目可以做类型检查。静态检查的意义在于在进入花费更高的测试环节之前先过滤掉低级的写着写着少个括号的错误。第二层是单元测试针对每个模块的行为测试。这里要用到通用测试框架我这边以 Python 生态常见的 pytest 为例验证岗会先执行测试生成 Agent 产出的测试文件再执行测试框架并捕获报告。这一层解决的是函数逻辑对不对的问题。第三层是端到端冒烟把整个系统跑起来走一条最核心的用户路径。比如一个 HTTP 服务冒烟测试就是启动服务、发一个真实请求、检查响应码和关键字段。这一层解决的是模块之间合起来能不能工作的问题很多问题只有在模块协作时才会暴露。# 验证岗实际执行的典型命令序列 python -m py_compile src/*.py pytest tests/ -x --tbshort --junitxmlreport.xml # 如果项目是服务型再启动起来打一个冒烟请求 python -m app sleep 2 curl -s -o /dev/null -w %{http_code} http://127.0.0.1:8080/health3.3 失败回退返工指令如何结构化验证失败之后系统不能简单地再让编码岗重来一遍否则它会改一个无关痛痒的地方然后声称修复了。正确做法是把失败信息结构化作为返工指令精确交付给对应的岗位。举个例子测试输出里有 3 个用例失败其中一个是因为模块导入失败两个是因为返回值不符合预期。那么返工票据里就要明确写失败的测试文件、失败用例名、退出码、关键报错片段、以及初步定位结论。这里的初步定位可以让模型辅助但最终返工指令必须包含不可篡改的执行输出。我的票据里返工消息长这样{ type: rework, assigned_role: coder, verification_report: { exit_code: 1, failed_tests: [test_retry_on_timeout], stderr_excerpt: TypeError: create_client() got an unexpected keyword argument retries, suspected_cause: 接口签名与架构票据不一致 }, instruction: 修复 create_client 的签名使其接受 retries 参数并重跑验证 }调度器在构造返工票据时会顺手更新状态机impl_done → needs_fix并把 assigned_role 记录下来。这样整个流程有迹可循哪个环节反复出错从状态流转日志里一眼就能看出来。3.4 让下一轮 Agent 能看懂报错反馈格式设计很多失败循环之所以转不出来不是因为模型不懂代码而是因为反馈信息太烂。如果把一整页堆满各种颜色的终端输出塞给模型它很容易被噪音带偏甚至去改一个无辜的文件。我踩过这个坑之后总结出反馈格式的三个原则。一是截断。只保留每个失败用例的报错前 20 行后面的堆栈信息多半是噪音。二是归类。把失败按代码缺陷环境问题需求不明确粗分类不同类别走不同的返工路径。三是给上下文锚点。在反馈中加入源文件路径和行号让模型知道该去哪个位置动手而不是在整套代码库里大海捞针。这套结构化反馈设计到位之后我发现同样的失败信息模型修复的准确率明显提升而且返工轮数从平均 4-5 轮降到了 2 轮左右。反馈的可读性很多时候比模型本身更强更管用。4. 手把手搭建一座最小可用的多智能体工厂4.1 技术选型与运行环境够用就好别被框架绑架在动手之前先说选型原则。市面上的多智能体编排框架有很多但我建议第一版先不要引入重框架。因为框架会把如何调用模型如何管理上下文这些细节抽象掉而恰恰是这些细节决定系统能不能跑通。第一版我只用三个基础件大模型 API 客户端、Python 的任务调度逻辑、一个隔离的执行沙箱。隔离沙箱强烈建议用容器每轮验证都从同一个干净镜像起一个临时容器跑完即销毁。这样做有两个好处一是防止 Agent 生成的代码在本地执行时搞破坏删文件、下奇怪依赖二是保证每次验证的初始环境一致避免上次装过依赖所以这次过了的假象。依赖管理上第一版把 Agent 的调用封装成一个极薄的客户端每个岗位本质上是系统提示词 输入票据 模型调用。不要一上来就追求框架的炫酷特性先把最简单的骨架跑通后面再逐步替换。4.2 核心骨架Agent 基类与调度器我们先把岗位抽象成代码。每个 Agent 就是一个类接收票据调用模型返回结构化结果。import json from dataclasses import dataclass, field dataclass class TaskTicket: task_id: str requirement: str acceptance: list field(default_factorylist) state: str pending payload: dict field(default_factorydict) class Agent: 所有岗位 Agent 的基类 def __init__(self, role_name, system_prompt, llm): self.role_name role_name self.system_prompt system_prompt self.llm llm # 大模型 API 客户端 def process(self, ticket: TaskTicket) - dict: user_content json.dumps( {task: ticket.payload, current_state: ticket.state}, ensure_asciiFalse, ) response self.llm.chat(systemself.system_prompt, useruser_content) # 强制要求模型输出可解析的 JSON return self._safe_parse(response) def _safe_parse(self, text): try: return json.loads(text) except json.JSONDecodeError: # 剥离可能的 markdown 代码块再试一次 cleaned text.strip().removeprefix(json).removesuffix() return json.loads(cleaned)调度器负责按状态机推进任务读取票据当前状态找到下一步该由哪个岗位处理调用对应 Agent然后根据返回结果决定是流转还是返工。ROUTES { pending: requirement_agent, analyzed: architect_agent, arch_done: coder_agent, impl_done: verifier_agent, verified: integrator_agent, } class Scheduler: def __init__(self, agents: dict, max_rounds3): self.agents agents self.max_rounds max_rounds def run(self, ticket: TaskTicket): rounds 0 while ticket.state not in (integrated, failed): rounds 1 if rounds self.max_rounds: ticket.state failed break agent_name ROUTES.get(ticket.state) result self.agents[agent_name].process(ticket) self._apply_result(ticket, agent_name, result) return ticket这段代码刻意保持简单。它不做花哨的事只保证每个状态都有明确的下一个处理者每轮循环都有次数上限。你可以在 _apply_result 里做各种策略扩展比如根据返回的 error 字段决定回退到上一岗而不是机械地走完整个链路。4.3 验证器的实现让执行而不是想象说了算验证岗是整个工厂里唯一一个不完全依赖模型判断的岗位。它的实现要点是用程序跑命令而不是让模型读代码。import subprocess class VerifierAgent: 执行型验证岗不走模型直接跑测试 def verify(self, ticket: TaskTicket) - dict: workdir ticket.payload[workdir] commands [ python -m py_compile src/*.py, pytest tests/ -x --tbshort --junitxmlreport.xml, ] results [] for cmd in commands: proc subprocess.run( cmd, shellTrue, cwdworkdir, capture_outputTrue, textTrue, timeout120 ) results.append({ cmd: cmd, exit_code: proc.returncode, stdout_tail: proc.stdout[-2000:], stderr_tail: proc.stderr[-2000:], }) if proc.returncode ! 0: break return {type: verify_report, results: results}注意这里的细节shellTrue 在生产环境里其实有注入风险所以第一版可以用但一旦跑真实第三方代码最好换成参数列表形式并禁止 shell。捕获输出时只留尾部 2000 字符这既是给模型看的反馈也是给日志系统省空间。4.4 完整流程演示从一句需求到通过测试的代码我把这套骨架跑在了模拟项目X上需求是写一个带超时和自动重试的 HTTP 工具函数。整个流程是这样的第一步需求解析岗把这句话拆成票据函数名为 fetch_with_retry参数要有 url、timeout、retries、backoff_factor验收条件里明确重试间隔按 2 的幂次退避且总耗时不超过 timeout 的 5 倍。第二步架构岗基于票据产出接口签名并创建 src/http_client.py 契约描述。第三步编码岗实现代码。这一版它确实写出来了但注意它把重试逻辑写成了 while 循环配合 sleep这在异步场景下会有问题不过这属于设计评审层面验证岗暂时抓不到。第四步验证岗先在沙箱里执行 py_compile通过然后执行测试生成 Agent 写好的测试用例第一个用例就失败了——因为测试期望的是指数退避而实现里是固定间隔断言超时时间不符合预期。第五步调度器拿到失败报告构造返工票据发给编码岗。编码岗这次改成了指数退避并补充了超时异常的捕获。再次执行验证全部用例通过。第六步集成岗把新文件合并回主分支生成变更说明新增 http_client 模块含重试与超时机制附测试覆盖率数据。整个过程大概 12 分钟、模型调用费用控制在很低的范围产出了一个能跑、带测试、可回归的代码模块。这个效率在纯人工流程里不可想象但它真正值钱的地方不是快而是每一步都有据可查。5. 实测与调优那些文档里不会写的坑5.1 上下文窗口失控共享上下文 vs 独立上下文的取舍我最早一版是让所有 Agent 共享一份完整的对话历史结果跑到第三个任务就崩了模型开始把上一个任务的代码片段当成当前任务的输入甚至出现了把旧接口名写进新模块的幻觉。原因很简单五个岗位各自需要的信息总量很容易超过模型的上下文窗口一旦裁剪策略不当最先被挤掉的就是最早期——也就是需求定义。后来我改成票据制每个 Agent 只看到自己的输入和当前票据。上下文短了、成本低了、幻觉也少了。但代价是 Agent 之间丢失了隐含语境比如编码岗看不到测试用例的全貌只能依赖票据里的契约描述这就要求架构岗的产出质量必须足够高。所以现在的做法是折中核心契约和验收条件永远在票据里长篇幅的参考资料则按需附加。方案成本幻觉率职责清晰度适用阶段全共享上下文高高低不推荐票据制独立上下文低低高推荐票据 按需附加参考资料中中高生产首选5.2 假验证模型说通过但根本没跑这是我踩过最深的坑没有之一。有一版测试 Agent 的返回结果是它自己总结的验证结论比如输出所有测试全部通过耗时 0.3 秒。后来我核对真实日志发现它根本没运行任何测试0.3 秒只是它编的一个合理数字。原因是我让它以生成报告为任务目标它就在文本层面给我生成了一份漂亮的报告。修复方式很简单验证岗彻底去模型化——不准它输出结论只准它输出进程执行结果结论由调度器的代码来判定。之后我又加了一条铁律任何验证报告必须携带可复核的证据字段exit_code、报告文件路径、命令清单没有证据的报告一律视为无效。这条规则后来帮我在很多环节抓出了假装在工作的 Agent。血泪经验你永远不要在系统提示词里让模型检查一下测试是否通过因为你无法阻止它用想象代替执行。唯一可靠的办法是让验证环节根本不经过模型的语言输出。5.3 返工成本控制最大迭代次数与分级止损每轮返工都在烧钱。我算过一笔账一个中等模块首次生成大约消耗 8K 输入 token 和 2K 输出 token一次失败后的返工因为要携带测试报告和报错上下文输入 token 经常翻到 20K。如果一个任务返工 4 轮成本可能是顺利情况的 3 倍。所以返工必须有上限而且必须分级。我的策略是普通代码错误最多返工 3 轮超过就降级给人介入架构层面的错误最多返工 2 轮超过就强制重新走架构岗并标记为高风险任务。此外每次返工前调度器会对比失败模式如果两次失败是同一个原因说明返工指令写得不够清楚这时不是继续压榨编码岗而是要先把反馈格式修好。止损是成本控制的第一课。# 失败模式去重同因失败两次升级反馈质量而不是继续重试 from collections import Counter def should_escalate(failed_keys: list, threshold2): counts Counter(failed_keys) return any(v threshold for v in counts.values())5.4 并行与串行流水线不是越并行越快刚开始搭工厂时我总想让多个 Agent 同时干活觉得这样才像工厂。结果并行一开立刻出事两个编码岗同时往同一个文件写代码一个加重试逻辑一个加超时配置合并时冲突产生集成岗怎么解都解不干净最后整个模块回滚到上一版。后来我制定了文件所有权规则每个任务票据必须明确列出写权限文件集合任何 Agent 只能改自己有写权限的文件并行只允许发生在文件集合完全不重叠的任务之间。串行虽然慢一点但换来的是可预期的结果。记住多智能体系统的瓶颈往往不是模型推理速度而是状态一致性的维护成本。6. 让工厂学会复盘从流水线到自进化6.1 任务日志与失败样本沉淀流水线跑起来之后会产生大量日志每个任务的票据快照、每轮返工的失败报告、每个 Agent 的原始输出。这些东西别删它们是这座工厂最值钱的资产。我把每轮失败的案例特别是那种模型折腾三轮都修不好的案例抽出来整理成 few-shot 样本。下次再遇到类似的失败模式需求解析岗会在生成票据时自动引用这些历史教训编码岗也会被塞入上次这里犯过错的提示。举个例子有一次系统反复在重试退避策略上踩坑我沉淀了一条样本描述指数退避的正确实现、常见错误写法、以及测试用例的边界条件。之后再跑类似需求首次通过率从 60% 提到了 85%。让工厂从自己的错误里学习这是从自动化流水线走向自进化工厂的关键一步。样本积累到一定量之后你还可以用它来做更高级的事比如分析某个岗位的提示词是不是写得太模糊失败样本高度集中在某类任务上或者判断某个模型的推理能力是不是跟不上任务复杂度。日志不只是事后追溯用的它应该是驱动系统迭代的主数据源。6.2 人机协同的边界哪些环节必须留给人虽然这座工厂能独立跑完很多任务但我始终明确一条边界它不是一个无人值守系统而是一个需要工程师当车间主任的系统。在实践中我坚持三件事必须由人来把关。第一需求的优先级和验收标准。模型可以拆解需求但这个需求值不值得做必须由人判断否则工厂会勤勤恳恳地造出一堆没用的功能。第二架构评审。架构岗的产出我会抽看关键接口设计大多数模块可以直接走但涉及数据安全、跨系统集成的部分人必须签字。第三安全审计。任何可疑的依赖、任何接近生产环境的代码变更都应该由人过一遍。授权边界设得越清楚工厂的自动化程度才能越高——因为你知道它不会乱跑。我见过一些团队把流程完全交给智能体结果某天工厂勤快地把一个内部调试接口直接合并进了生产分支。问题不在智能体不够聪明而在于没有人定义哪些地方不允许它碰。边界才是安全的真正来源。6.3 衡量工厂产出的四个指标最后说说我怎么判断这座工厂干得好不好。没有指标就谈不上优化。我长期跟踪四个数字指标含义我的目标值首次通过率一次编码后直接通过全部验证的比例50% 以上平均返工轮数每个任务从编码到集成平均返工次数2 轮以内单任务总成本模型调用花费 沙箱执行时长视任务复杂度人工抽检缺陷率工程师随机抽样代码发现的问题比例低于 15%这四个数字会互相牵制。返工轮数压得太紧有些隐藏问题就会溜过验证闸门人工抽检缺陷率上升首次通过率提得太高有时候反而说明测试用例写得不够狠。所以我每个月会复盘一轮调整测试用例生成的强度而不是一味追求某个单点指标。我在实际跑这套工厂的这段时间里最大的感受是多智能体系统真正的门槛不在模型选择也不在框架选型而在你能不能接受过程比结果更重要这句话。我见过太多人拿一个 Agent 生成代码非常惊艳但一到多 Agent 协作就开始互相甩锅、上下文混乱、验证造假。本质上这不是技术问题而是流程问题。你把岗位边界、状态机、验证反馈这些枯燥的机制设计得越扎实系统就越稳定。最后再分享一个小技巧如果你的工厂和我一样经常出现验证过了但人一用就崩的情况先别急着换更强的模型去查一查你的冒烟测试是不是太温柔了——我自己的经验是80% 的漏网之鱼都因为冒烟测试只测了能启动没测真的能用。把冒烟路径设计得贴近真实用户的使用习惯比换十个模型都管用。