1. 为什么我最后还是把 Agent 组织成了一家"公司"
我大概是从去年开始正经捣鼓多 Agent 系统的。一开始我和很多人想的一样,所谓"多智能体"就是把一个大任务拆碎,然后多调几次模型接口,把几个不同人设的 Agent 凑在一起"开会"就行了。真正把一套代号为agency-agents的编排项目跑起来之后,我才意识到问题根本不在于模型本身,而在于组织方式:你怎么让三五个有明确分工的智能体像一家小工作室那样协作,而不是各说各话、互相甩锅。
先说结论:多 Agent 系统里,真正的难点不是"调大模型",而是"设计协作协议"。你给每个 Agent 什么输入、让它产出什么格式的结果、谁来判断结果能不能用、不合格之后怎么打回去返工,这些只要有一个环节含糊,整套系统就会退化成非常昂贵的聊天机器人。
这篇文章我会把 agency-agents 的架构选择、角色设计、调度主循环、上下文隔离方案,以及我在实际运行中遇到的循环卡死、上下文污染、幻觉放大、Token 失控这四类典型故障,全部摊开来讲。适合正在设计多 Agent 系统、或者被现成编排框架的黑盒逻辑搞得头疼的开发者参考。不需要你有非常深的大模型背景,最好有一点 Python 和基本的 API 调用经验,剩下的我会尽量讲透。
2. 先回答一个问题:单模型为什么搞不定复杂任务
2.1 上下文窗口的"虚标"问题
很多人以为多 Agent 是为了解决单模型上下文不够长。实际跑下来我发现,更本质的问题是模型对长上下文的注意力会衰减,也就是早期内容被"遗忘"。
举一个我自己的例子。用单个 Agent 直接写一份三十页的行业分析报告,开头几节通常结构很清晰,数据引用也准确;写到中间开始重复前面的观点;到了结尾部分,它经常会忘记最开始定的读者定位和语气要求,甚至会把前面已经否定掉的错误结论重新拿出来用。这不是模型"笨",而是超长上下文的注意力分配天然就有衰减,你给它三万字的背景资料,它很难在生成第三十页的时候还牢牢记住第一页的核心约束。
这时候"分工"的意义就出来了:不是让它记住所有事,而是让每个 Agent 只记住自己这一环节需要的那一小块。在 agency-agents 里,一个做数据整理的 Agent 只负责看原始资料并输出结构化摘录,写报告的 Agent 拿到的只是整理好的摘录而非三万字的原文。上下文负担从"一个 Agent 扛全部"变成"每个 Agent 扛一段",衰减问题被大幅缓解。
2.2 场景判断:哪些任务真的需要多 Agent
也不是所有任务都适合拆给多个 Agent。我在项目初期交过一笔学费,就是什么任务都往 agency-agents 里塞,结果低延迟的简单问答被拆成了五轮调用,又慢又贵。
结合我自己的测试,适合多 Agent 的任务通常有这几个特征:
- 任务自带明确的流水线属性:比如先收集资料,再分析,再写作,再校对,天然有先后依赖。
- 任务存在多个相互独立的子问题:比如一份方案里既要写市场分析,又要写技术路线,两者可以并行处理。
- 任务对错误率有要求:比如涉及数据引用、代码生成这类需要二次校验的输出,靠一个质检角色兜底比单纯让主模型"再检查一遍"可靠得多。
- 任务产出物需要以固定格式交付:结构化程度越高,分工协作的收益越明显。
反过来,单纯的知识问答、几句话的文案润色、一次性代码调试,直接单模型调用是更明智的。多 Agent 是手段,不是目的,为了编排而编排只会让成本和延迟同时失控。我最终定下的原则是:预计总时长超过一定量级、或者输出会被外部系统解析的任务才走 agency-agents 流程,其余一律用快捷通道。
3. 核心架构设计:把机构管理搬进代码
3.1 角色划分:一个迷你工作室的岗位表
agency-agents 的整个设计也借鉴了真实团队的分工方式。我把智能体分成四类固定角色,外加一个动态扩展的执行角色,所有任务先落到总控,再由总控分派给对应角色。这里给出我最常用的一套角色配置表格,后续讲解都会围绕它展开:
| 角色 | 职责 | 典型输入 | 典型输出 | 模型档位建议 |
|---|---|---|---|---|
| 总控/策划 | 理解用户需求、拆解任务、分派工作、汇总结果 | 用户的原始请求 | 任务拆解清单、最终交付物 | 旗舰级模型 |
| 研究员 | 检索资料、提取信息、整理事实 | 需要研究的主题列表 | 带来源的结构化资料包 | 中档模型 |
| 执行者 | 按需求产出具体内容(文案、代码、方案) | 任务书 + 资料包 | 第一版产出物 | 中档模型 |
| 质检员 | 对照验收标准检查产出、打回返工 | 产出物 + 验收标准 | 通过/打回意见 | 旗舰级模型 |
| 执行者(扩展) | 面向特定领域的专职角色(数据分析、翻译等) | 任务书 + 领域数据 | 垂直领域产出 | 按需选择 |
这套配置的核心逻辑是把最聪明的模型花在"判断"上,而不是花在"生成"上。总控和质检是最需要理解力和判断力的岗位,用旗舰级模型;研究员和执行者是有明确套路可循的岗位,用速度更快、成本更低的中档模型就能覆盖九成场景。一进一出,整体成本能省下不少。
3.2 任务书机制:让 Agent 之间说同一种语言
多个 Agent 协作最容易出的问题,就是两个角色之间靠自然语言对话,最后越聊越偏。我在项目最初几个版本里就让研究员和执行者直接用聊天语气交接,结果研究员发来一段非常口语化的结论,执行者理解不了里面的潜台词,反复追问,来回拉扯了七八轮还没有产出。
后来我引入了任务书(Task Brief)机制,所有角色之间的信息传递全部使用结构化 JSON,不再传输自由文本。一个典型任务书长这样:
{ "task_id": "task_20240512_003", "assignee": "researcher", "instruction": "收集近一年主流云厂商的对象存储定价信息", "context_refs": ["blackboard://requirement_summary"], "constraints": { "max_sources": 10, "language": "zh-CN", "no_fabrication": true }, "output_schema": { "type": "array", "items": { "provider": "string", "price_per_gb_month": "number", "source_url": "string" } }, "parent_task": "task_20240512_001", "status": "pending" }这个做法的价值在于,把"理解对方意思"这件事从执行者身上剥离了。执行者不需要猜测研究员到底想表达什么,只需要按照output_schema里的字段约束输出结果,总控再拿这个结果去生成下一个任务书。自由对话被降到了最低限度,协作过程变得可追踪、可恢复、可自动化校验。
3.3 调度主循环:分解、分派、汇合、质检、重做
agency-agents 的运行时核心是一个有限状态循环,我把它称为"机构主循环"。整个流程可以抽象成下面这段伪代码:
def run_agency(user_request: dict) -> dict: briefs = decomposer.plan(user_request) # 总控拆解出多个任务书 briefs = assign_dependencies(briefs) # 建立任务间依赖关系 for brief in topological_order(briefs): brief.status = "running" result = agent_execute(brief) # 分派给对应角色执行 result = validate_schema(brief, result) # 格式校验,不通过则重试 review = reviewer.review(brief, result) # 质检员检查 if not review.passed: brief = rewrite_brief_with_review(brief, review.advice) result = agent_execute(brief) # 返工一次,仍不过则上报 brief.status = "done" blackboard.store(brief.task_id, result) # 写入共享黑板 return blackboard.fetch_by_parent(request_id) # 汇总所有子任务产出这里有三个被刻意设计得很"死板"的点,恰恰是稳定性的来源。
第一,执行永远通过任务书发起,而不是让 Agent 直接互相调用。Agent 之间不存在"你帮我看看这段"这种非结构化交互,所有交接都发生在总控的调度层。控制了唯一的通信咽喉,后面才谈得上做限流、审计和错误恢复。
第二,返工次数被硬编码上限。质检不通过最多打回一次,附带质检意见让执行者修改;如果第二次仍然不达标,任务状态进入need_human,转给人工处理。之所以设这个上限,是因为我见过太多迭代式系统在"再改一版就好"的幻觉里无限循环,一个任务烧掉几十次调用,产出却没有本质提升。
第三,汇合不是简单拼接。所有子任务产出写入共享黑板后,最后一步由总控专门做一次"合稿",而不是把各个子任务的结果按顺序黏在一起。很多普通流水线在这里会翻车:研究员写的第一部分和第二部分语气不一致、数据口径不同,直接拼起来就是四不像。合稿环节会重新审视全局结构,必要时让执行者重写部分段落。
3.4 黑板模式与上下文隔离
我在里层实现上选择了黑板模式(Blackboard Pattern),这个名词听起来高端,其实思想很简单:所有角色不直接对话,而是往一个公共的"黑板"上写各自的结果,需要的人从黑板上取。
对应到实现层,agency-agents 每次运行时都会创建一个独立会话空间,里面包含需求原始描述、各子任务的任务书、任务状态、产出物以及质检意见。每个 Agent 在执行时只拿到三样东西:自己的任务书、任务书里context_refs指向的黑板数据、以及全局的协作规范说明。除此之外一律不给。
这个设计最初是为了防止上下文膨胀,但运行久了之后我发现它还有一个更重要的作用:天然隔离了不同任务之间的上下文污染。后面我会专门讲一次事故,就是因为我在某一次实验里省略了会话空间创建,结果上一个任务的客户资料混进了下一个任务的报告草稿,那种错误非常隐蔽且杀伤力极大。
4. 关键实现细节与取舍
4.1 为什么是"集中调度"而不是 Agent 自由互聊
设计多 Agent 系统时,摆在面前的第一条岔路就是:Agent 之间能不能直接对话?有一段时间我很迷恋那种"几个 Agent 自由组队、你一言我一语"的形式,它们在对话中自己讨论出解决方案,看起来非常前卫。
实际跑过之后我发现,自由互聊的问题比想象中大得多。
首先是不可控。没有中心节点,你不知道当前任务到底进行到哪一步,是还在收集信息、还是已经开始产出、或者已经在同一个问题上绕了第三圈。其次是成本黑洞。自由讨论很容易变成没有议程的会议,Agent A 抛出一个疑问,Agent B 给出一个模糊回应,Agent A 再请 B 解释,再加上中间几个 Agent 同时插话,一轮讨论下来能调用几十次模型,产出却可能只是一段共识性废话。
我最终选择集中调度还有一层现实考量:只有所有消息都经过总控,才能统一记录 Token 消耗、统一做循环检测、统一接入人工干预。自由互聊模式下,你想插话打断都没有合适的位置。所以 agency-agents 的任务书、黑板、质检这几个概念,本质上都是为了支撑"总控中心化"这个决策才引入的。
4.2 角色提示词该怎么写:少讲人设,多讲契约
刚开始做角色 Agent 的时候,我也犯过把所有精力花在"人设"上的错误,给每个 Agent 写一大堆性格特征、说话风格,比如"你是一位资深的品牌策划专家,拥有二十年行业经验,语气干练"。结果这些内容对任务完成几乎没有任何帮助,反而浪费 Token。
后来我总结出一个规律:角色提示词里最有用的部分不是身份描述,而是输入输出契约和禁忌清单。我给 agency-agents 里每个角色设计的系统提示词大致分四段:
你是本机构的{角色名},负责{一句话职责}。 工作流程要求: 1. 只依据任务书和黑板中提供的内容进行工作,禁止自行假设任务书中未出现的事实。 2. 输出必须严格遵循任务书中的 output_schema,禁止添加结构之外的字段。 3. 若任务书中明显缺少必要信息,请输出固定标记 NOT_ENOUGH_INFO,并列出缺失项,不要强行脑补。 4. 涉及数据、引用、数字时,必须附来源标识;无法提供来源时明确标注 UNVERIFIED。第一句交代职责,后面四句全是约束。效果非常明显:Agent 开始学会"说不知道"了,而不是在信息不足时硬编一个答案。这也是质检环节能真正跑起来的前提——如果每个 Agent 都在自由发挥,质检员根本不知道期望输出长什么样。
4.3 重试、降级与人工介入通道的设计
任何真实系统都不能假设每次模型调用都成功,多 Agent 系统更是如此。我在 agency-agents 里设计了三层兜底。
第一层是格式兜底。执行结果必须经过validate_schema校验,不符合任务书 output_schema 的结果会被拦截。拦截之后不是直接重跑,而是把校验报错信息合并进任务书,比如"缺少字段 source_url,请补全后重新输出",再让同一个 Agent 重试一次。实测下来,大部分格式问题一次就能修正。
第二层是模型降级。如果执行角色调用的中档模型在重试后仍然无法给出合格结果,总控会把这个任务重新分配给旗舰级模型执行。这个"降级"其实是升级,但设计思路是一样的:默认走便宜路线,遇到瓶颈才动用贵的模型,而不是所有任务都一上来用旗舰级,那样成本撑不住。
第三层是人工介入。运行时间超过阈值、返工仍不通过、或者输出内容命中高敏感关键词规则,任务自动进入人工待处理队列。我在这块加了一个很朴素的规则:宁可频繁打扰人,不要让系统带着错误往下走。多 Agent 系统最可怕的状态是表面上跑完了,实际上每一步都带着轻微错误,最后汇总出一个完全不能交付的成果,还消耗了上百次调用。人工介入通道就是用来拦住这种情况继续发酵的。
5. 踩坑实录:四类典型故障的完整排查链路
5.1 无限循环:Agent 们开始互相"踢皮球"
项目运行第三周时,我遇到了一次严重事故。一个整理产品需求文档的任务进入了奇怪的状态:总控不断生成新任务书,执行者产出内容,质检员打回,执行者修改,质检员又打回,如此反复不停,直到我设置的全局调用次数上限触发才停下来。事后查看日志,这一轮循环消耗了二十多次模型调用,产出的文档却停留在第一版。
根因排查花了很长时间,最后定位在质检意见的表述上。当时的质检提示词是"请判断产出物是否达到交付标准,如不达标请指出问题",这种开放式的指导让质检员每次都能找出新的"优化点",执行者改完一处,质检员又从另一个角度挑出问题。两个模型在语义层面陷入了"你说我改、改完再说"的博弈。
修复方案有两个,缺一不可。第一,给质检提示词加上明确的验收清单,每一项都以客观标准描述,比如"数据是否有来源标识""字数是否达到下限""结论是否包含在任务书 requirement 字段中",没有主观判断空间。第二,给整个任务的迭代轮次设置硬上限,质检不通过最多返工一次。从那以后,循环类故障基本绝迹,偶尔出现也会被迭代上限兜住。
5.2 上下文污染:上一单的"客户资料"混进了这一单
这是整个项目里最让我后怕的一次事故。我在某次测试中为了让代码简洁,把两个相似的分析任务放在同一个会话空间里跑,心想反正黑板数据是分开存储的,问题不大。
结果第二个任务生成的行业分析报告里,赫然出现了第一个任务涉及的某个产品的名称。更可怕的是,这个错误不是显眼的乱码,而是被顺滑地编织进了正常文本里——第一段的背景描述、第三段的市场分析都在正常地讲第二个任务的主题,唯独其中一个例子里莫名其妙提到了前一个产品的竞品对比。
排查链路一开始也很曲折。我以为是对应用户的真实需求描述干扰了模型,检查了输入之后发现并不存在。后来仔细审计了对话历史,才发现在某一次调用里,我把会话级的历史消息直接传给了执行角色,而那个历史消息里恰好包含上一个任务的内容。模型把历史里提到的产品当成了"已知的背景信息",顺手写进了新报告。
根因是我手动复用了对话上下文。修复方式很硬,不是靠提示词,而是在代码层面强制每个任务创建全新的会话上下文,任何角色都只能通过黑板数据引用方式访问指定数据。那之后我还加了一条测试用例:连续执行两个包含大量专有名词的任务,检查第二个任务的产出里是否出现第一个任务的专有名词。后来这条测试真的又抓到过两次同类问题。
5.3 幻觉的链式放大:研究员编数据,写手引用,质检也没拦住
多 Agent 系统有一个隐蔽风险,我把它叫做幻觉放大效应:单个 Agent 的轻微幻觉,会在传递过程中被后续 Agent 当成事实继续加工,层层放大。
具体事故是这样的:一个市场调研任务中,研究员负责收集某类产品的市场占有率数据,它在找不到确切来源的情况下编了一个数字,而且按照任务书要求附上了来源标识——但它标注的来源是一个真实存在的行业网站,数字却对不上。接着执行者写报告时直接引用了这个数字,并把"某网站数据显示"这种表述升级成了"根据行业权威数据";质检员检查时只核对了格式是否合规、结构是否完整,没有逐条核对数据真实性,这个错误就一路通过了所有关卡。
这套链路的可怕之处在于,每个环节单独看都有道理:研究员认为自己是在"估算",执行者认为来源已经标识过,质检员认为数据属于事实性内容应该由上游负责。但合在一起,就产出了一份带虚假数据的报告。
修复分三层:首先强制研究员输出的事实性内容必须有可解析验证的来源链接,无法验证的一律标注UNVERIFIED;其次执行者的提示词里明确写了"禁止将 UNVERIFIED 内容改写为权威结论";最后质检提示词增加一条,要求对报告中所有数据逐条回溯到资料包,无法回溯的一律打回。现在我的项目里任何数据引用都有完整的溯源链,这比单个模型单独工作时可靠得多。
5.4 Token 成本失控:贵不是最可怕的,不可预测才是最可怕的
最后一个坑是关于钱的。多 Agent 系统启动之后,我观察到月度调用成本比预估高出非常多。排查之后发现主要问题不在"调用多",而在调用分布不可预测。
当时我没有按角色区分使用的模型档位,所有 Agent 都走同一个模型。这意味着烦琐的资料整理调用和全局策略判断调用付一样的单价,大部分成本都花在了本可以更便宜的环节上。更糟的是,一旦陷入循环故障或者返工较多,成本会成倍上升,而控制成本的唯一手段居然是写死总调用次数。
我的调整思路是预算分层 + 分阶段计量。给每个角色分配默认的档位和单价上限,在调度主循环里为每个任务记账,超过单任务预算立即触发降级策略;同时在黑板里记录每个子任务的调用次数。运行一段时间后,我能在任务结束时直接看到每个环节花了多少钱,哪些环节超支一目了然。把这个机制跑起来之后,单任务平均成本下降了接近一半,而且最让我安心的是,成本从"不可预测"变成了"有明确上限",这对线上系统的意义比单纯省钱大得多。
6. 实测效果:什么任务收益最大,什么任务反而更慢
6.1 三组对照任务的实际表现
为了验证这套设计到底值不值,我专门做了三组对照测试:同一个任务分别用单 Agent 直出、三角色精简机构(总控+执行+质检)、五角色完整机构(总控+研究+执行+质检+合稿)三种方式跑,记录完成时间、Token 消耗和人工评分的产出质量。以下是我在本地测试环境里得到的一手数据,没有做复杂的统计,仅供参考:
| 任务类型 | 单 Agent 直出 | 三角色精简版 | 五角色完整版 |
|---|---|---|---|
| 行业分析报告(篇幅较长) | 时间短,质量中等,数据错漏多 | 时间略长,结构明显变好 | 综合质量最高,但耗时几乎是单 Agent 的两倍 |
| 产品方案初稿 | 质量尚可,后期逻辑容易跑偏 | 质量稳定,效率收益明显 | 与三角色版差距不大,合稿环节偶有过度修改 |
| 简单知识总结 | 速度最快,质量够用 | 速度偏慢,质量无明显提升 | 完全没有必要,纯属浪费 |
从这个表可以清楚看出,任务的复杂度直接决定了编排深度的边际收益。长文档、强结构类任务,五角色完整版的收益非常明显;方案类任务到三角色就达到性价比最优;简单任务用多 Agent 反而是负优化。这也是我后来在系统里做"快捷通道"的原因——先判断任务复杂度,再决定要不要进机构流程。
6.2 从数据里读出的几个反直觉结论
有几个结论是我跑测试之前没想到的,写出来给各位参考。
第一,执行角色用便宜模型、总控用贵模型的组合,质量不降反升。之前我担心便宜模型生成的内容质量差,实际数据显示只要任务书清晰、资料包到位,中档模型产出的初稿和旗舰级模型几乎没差别,而总控合稿和质检这两个"把关"环节用旗舰级模型后,整体质量反而比全部用旗舰级还稳定,因为分工杜绝了一个模型既当运动员又当裁判的问题。
第二,质检环节的"挑刺能力"拉高了整体交付质量,但前提是验收标准必须客观。主观的质检只会引发无限返工,客观清单式的质检才能既守住质量底线又控制在合理迭代次数内。这条经验是通过前面那场循环事故换来的,非常值钱。
第三,合稿环节不该无脑修改所有子结果。最初我的合稿提示词是"请整合并优化所有子任务的产出",结果它经常把写得还不错的段落大改一遍,有时还引入了新错误。后来我把提示词改成了"只允许调整章节衔接和语气统一,不得改动子任务产出的核心事实与结论",过度修改问题才被压住。让擅长修改的模型克制修改,本身就要靠约束来描述。
6.3 下一步的扩展方向
基于目前这套具跑通的 agency-agents,我接下来想做的方向有三个。
第一是动态角色装配。目前角色集合是固定的,所有任务不管要不要研究环节都必须经过研究员。我计划在总控拆解任务时就输出一个"角色清单",只有任务书里真正涉及的角色才会被实例化,省掉多余跳数和成本。
第二是更细的质检评分体系。当前质检只输出通过/打回,颗粒度太粗。我准备给质检增加分项评分,比如结构、事实、风格各打一个分数,并记录到黑板上。长期累积下来可以形成每个执行角色的历史质量画像,后续调度时能用它来做角色选择。
第三是任务书模板沉淀。每个领域的任务书结构应该是不一样的,代码任务需要约束输出文件和测试命令,文案任务需要约束语气和篇幅。我打算把这些整理成可复用的模板库,让非技术用户也能通过简单选择来生成自己的协作流程。
7. 如果你也想自己搭一套多 Agent 编排
最后分享一些我个人实操中的体会。如果你正准备动手做一个类似的项目,我建议你不要一上来就追求大而全的框架,而是用最简单的代码先把最核心的协作循环写出来:一个总控、一个执行者、一个质检员,任务书用 JSON,结果写进一个共享字典。先跑通这个最小闭环,再慢慢加角色和机制。
我还想强调一个不太被注意到的点:所有 Agent 的输出都要设计成"可失败"的。多 Agent 系统里最危险的不是某一步失败,而是某一步假装成功。让 Agent 学会输出NOT_ENOUGH_INFO、UNVERIFIED这样的显式状态标记,比它在憋着不说的情况下硬产出珍贵得多。我早期大量事故的根因,都能追溯到"Agent 不敢说不知道"这个行为模式上,靠约束和契约才能把它改掉。
另一个很实用的小技巧是:从第一天开始就给每个任务记录审计日志,包括每次调用的任务书、入参、出参、Token 数和耗时。多 Agent 系统运行时间一长,几乎所有疑难杂症都要靠这些日志回溯才能定位,没有日志就等于没有眼睛。我的那场上下文污染事故,如果没有完整日志,排查时间至少会翻三倍。
这套 agency-agents 目前已经稳定跑了几个月,不是那种演示完就搁置的玩具项目。它给我的最大教训就是:智能体协同不是靠模型变聪明实现的,而是靠一套让它们变规矩的协议实现的。任务书保证了语言统一,黑板保证了上下文隔离,质检保证了质量下限,调度循环保证了过程可控。这四个维度的设计,比任何华丽的模型调用技巧都更能决定多 Agent 系统的成败。