1. 为什么要有 Agency Agents:单智能体的三个天花板
Agency Agents 这个项目,算是我被单Agent逼疯之后的一次重构。如果你也调过那种既要联网搜索、又要汇总分析、还要按你风格输出的Agent,大概率遇到过这三个问题:第一,提示词越写越长,模型还是会在中途忘掉前文;第二,一个Agent又要找资料又要写结论,角色一旦混在一起,输出质量就很难稳定;第三,流程一旦出错,你根本不知道是哪一步出了问题,只能从头跑一遍。坦白说,单个大模型确实聪明,但让它“一个人干完一整条流水线”,并不是聪明的设计,而是把风险高度集中在一个黑盒里。
我真正需要的不是一个“全知全能”的Agent,而是一个可以互相分活、互相检查的小团队。Agency Agents 这个名字来自“数字乙方”的类比:把一条完整任务拆开,交给不同角色的智能体,由调度系统安排谁先做、谁后做、谁负责验收。有人负责搜集,有人负责分析,有人负责写初稿,还有人专门挑毛病。这套思路解决的不是单纯的模型能力问题,而是把复杂任务切成可控的小块,让每一次模型调用都专注于一件事,出错时也能精准定位。
如果你正在做自动化内容生产、行业调研、客服工单处理,或者只是想搞明白多智能体协作到底怎么落地,那这篇文章会比较对你的胃口。我会把项目从架构设计到核心实现,再到调试现场的东西都扒出来聊一遍,尽量让新手能找到能抄的代码,让老手能看到会踩的坑。
1.1 单Agent的上下文再宽,也装不下一条完整工作流
很多人以为上下文窗口是越大越好,实际用起来根本不是这个逻辑。我试过把一份三万字的资料直接塞给一个Agent,让它输出结构化总结,结果模型确实能“看见”全文,但回答到后面明显开始前后矛盾,前面说A方案最优,后面又把B方案说成首选。这不是模型变笨了,而是上下文里无关信息太多,模型在生成时无法聚焦。
更要命的是成本。每调用一次模型,你都要把所有历史消息重新发一遍。任务跑十分钟,token消耗可能比你想的高出好几倍。在线搜索工具的结果、临时文件内容、上一步的输出,全都堆在一个上下文里,不仅贵,而且慢。真正适合大模型处理的单一任务,反而是那种目标清楚、输入长度可控、输出格式明确的活。一个Agent负责找资料,另一个Agent负责判断,这样每个Agent拿到的上下文都能控制在一两千字以内,准确率和成本都更可控。
1.2 把“一个人”拆成“一个团队”
我这套思路不是从理论里推出来的,是被一个真实需求逼出来的。当时要做一个自动竞品调研系统,输入一个行业关键词,期望输出一份包含市场格局、竞品功能对比、差异化机会的报告。一开始我让一个Agent干所有事,结果它一边搜索一边写结论,经常把“我还没验证”的信息当成事实写进去。后来我索性把它拆开:A角色只负责收集事实,B角色只负责写报告,C角色只负责审核。效果立刻不一样,因为每个角色的提示词都很短,约束却非常明确。
这个项目之所以叫 Agency Agents,是因为它模仿的是一个数字外包公司。你给“项目经理”一个需求,项目经理把需求拆成任务卡,派给不同的“执行岗”;执行岗做完以后,“质检岗”再按验收标准打分,不合格就退回重做。类似公司里的部门协作,只是把“人”换成了“带有特定工具和提示词的智能体”。
1.3 这个项目具体影响什么场景
Agency Agents 的影响范围其实比很多人想的大。它不只适合会写代码的人,任何一个角色长期、重复地依赖大模型处理多步骤任务,都可以用到这套方案。内容团队可以用它来自动组织选题、搜集素材、初稿评审;研究团队可以用它来拆论文、对比观点、生成综述;客服团队可以用它来处理工单路由、草拟回复、记录闭环。
我自己目前主要把它用在两类场景:一是自动化报告生成,输入关键词,输出可交付的调研文档;二是内部的“AI质检员”,专门负责检查另外一个Agent的输出是否符合规范。这两类场景都有一个共同点:任务可以被明确拆解,质量可以被验收。如果你手上的任务也符合这个特征,Agency Agents 就很适合作为参考。
2. 整体架构设计:把一群Agent组织成一家虚拟公司
这个架构最核心的一点是:不要让Agent之间直接闲聊,而是让它们通过一个中央调度器交换任务卡。每个Agent只负责自己的那一小步,把结果写到共享区,再由调度器判断下一步应该触发谁。这会带来很多好处,最明显的是可控性:你可以在每个环节设置超时、重试、最大轮次,不会出现两个Agent在背后互相聊到天荒地老的失控局面。
2.1 角色不是拍脑袋,是按业务写出来的
最初我设计角色时犯过一个错,就是想让Agent“自由讨论”,结果它们经常把话题带偏。后来我把角色收敛成四类固定职能,每个职能只做一件事。项目里长期跑着的角色大致是这样:
| 角色 | 核心职责 | 典型工具 | 输出物 |
|---|---|---|---|
| 项目经理 Agent | 拆解需求、定义验收标准 | 任务模板、结构解析 | 任务卡列表 |
| 研究员 Agent | 搜索事实、收集资料 | 搜索API、文档解析 | 带来源的资料清单 |
| 写作者 Agent | 根据资料生成内容 | 文本模型、排版模板 | 初稿 |
| 审核 Agent | 校验内容、给出修改意见 | 规则引擎、评分模型 | 审核意见 |
这个组合不是随便定的。研究、写作、审核,这三种行为对提示词的要求完全不同。研究员需要的是“冷静、不评价”,写作者需要的是“结构化、可读”,审核需要的是“挑剔、按标准打分”。如果把它们塞进同一个系统提示词里,模型会非常困惑,因为你同时要求它兼具三种相反的特质。拆开以后,每个角色的行为边界反而更清晰。
2.2 协作协议:状态机加任务卡
Agency Agents 的调度逻辑,简单来说就是一个有限状态机。每个任务卡都带着自己的状态,调度器看到状态变化后,再决定投递到哪个Agent。常用状态包括:
created:项目经理刚拆解完任务卡。ready:任务已经准备好,可以派发给执行Agent。running:执行Agent正在处理。reviewing:任务已经产出,等待审核。revising:审核不通过,退回修改。done:审核通过,任务结束。
任务卡本身是一段结构化JSON,里面包含目标、输入、验收标准、产出路径。我之所以严格使用JSON而不是自然语言,是方便调度器程序直接读取,也方便后续审计和回放。模型只负责填写任务卡里的“产出”字段,调度器负责移动状态,两边各干各的,谁也不越界。
2.3 工具层与权限边界
要让Agent真正干成事,它必须有“手”。工具层我一开始是自己写装饰器,把搜索、读文件、写文件等能力注册进去,后来为了兼容生态,改成了标准化的工具协议。每个Agent能使用的工具由角色决定,而不是让所有Agent都能访问所有工具。这一点非常重要,因为它直接决定了风险边界。
举个例子,研究员Agent只需要搜索和读PDF,那就不要给它写文件的权限;写作者Agent可以写Markdown,但不能直接调用外部发送接口。这样即使某个Agent的提示词被用户输入干扰,它能造成的破坏也非常有限。权限控制是这套系统稳定运行的前提,宁可一开始多写几行权限配置,也不要让Agent“All in one”地乱调用。
3. 核心实现细节与实操要点
很多人低估了实现多智能体协作的工作量。其实模型调用本身很简单,难的是怎么把每个Agent的输入、输出、状态管清楚。这一章我挑几个最关键的实现细节讲,照着写就能跑通第一版。
3.1 Agent基类与统一接口
每个Agent我设计成非常薄的封装,核心就是一个run方法:输入任务卡,返回一段文本。外面再统一包一层日志和token统计。这样做的好处是,角色可以随便增删,只要实现同一个接口就行。
from typing import Dict, List, Callable, Optional class BaseAgent: def __init__(self, name: str, system_prompt: str, tools: Optional[List[Callable]] = None): self.name = name self.system_prompt = system_prompt self.tools = tools or [] def run(self, task: Dict, context: List[Dict]) -> str: messages = [ {"role": "system", "content": self.system_prompt}, *context, {"role": "user", "content": task["input"]}, ] response = call_llm( model=self.model, messages=messages, tools=[tool.schema for tool in self.tools], ) return self._format_response(response) def _format_response(self, response) -> str: # 这里统一把模型返回内容转成任务卡能落地的文本 return response["content"]这一段代码粗看起来很简单,但里面藏着一个关键细节:context不是全量历史,而是经过上下文管理器过滤过的“相关片段”。每个Agent只能看到自己当前任务需要的信息,而不是整个项目从头到尾的所有日志。
3.2 任务卡与上下文隔离
上下文隔离是我认为这套系统最优价值的设计。假设研究员做完搜索,产生了一份完整资料清单,里面包含来源、时效性、可信度评分。写作者并不需要看到研究员的所有中间过程,只需要看最终整理清楚的那份清单。如果调度器把研究员每一步的搜索日志都传给写作者,写作者反而会被噪音干扰。
我用任务卡来管理产出物。研究员的任务卡长这样:
{ "id": "task-0042", "from": "pm", "to": "researcher", "goal": "列出竞品在联系人管理、自动化营销、报表统计上的主要功能", "acceptance_criteria": [ "每个功能点必须有来源链接", "至少列出10条功能差异", "只描述事实,不做评价" ], "output": "researcher_output_0042.md" }写作者拿到的,是researcher_output_0042.md这个文件的内容,而不是任务卡本身。这样做还有一个附带好处:任何一步出错,你都能查看中间文件,寻找问题出在哪个环节。对于排查问题来说,这种可见性非常值钱。
3.3 提示词的工程化写法
给智能体写提示词,和给聊天机器人写提示词完全是两回事。聊天机器人的提示词可以含蓄一点,智能体的提示词必须像SOP一样精确。我总结了三个要点。
第一,职责明确。系统提示词开头就告诉它“你是谁、你只做什么、不做什么”。比如研究员角色:
你叫 researcher,只负责从资料中提取事实。 不要给出结论,不要评价。 如果找不到来源,就明确说“无法确认”。 每一条事实必须标注来源编号。第二,产出格式明确。不要让模型自由发挥格式,直接告诉它输出结构。比如审核Agent的输出必须是:
问题列表: - 位置:第3段 - 问题类型:事实错误 - 修改建议:... 评分:6/10 通过/退回:退回有了固定格式,调度器才能用正则或者JSON解析结果,才能程序化地决定下一步动作。如果审核Agent用散文式语言返回内容,你自己就得再调一个模型去解析它,平白多一层出错概率。
第三,给“不知道怎么办”的退路。我见过很多Agent为了保证“完成任务”而胡编乱造。所以每个角色我都会加一句:如果任务无法完成,直接写明失败原因,这也算有效结果。这让系统可以尽快进入失败处理流程,而不是在一个错误答案上反复打转。
3.4 参数配置与成本控制
一个好消息是,不是所有角色都要用大模型。项目跑起来以后,我发现研究员的活很多是重复性搜索,用小参数模型就能完成;项目经理拆解任务反而需要更强的推理能力,才需要用大模型。给每个Agent单独配置模型,是我省成本最有效的一招。
我常用的配置参考:
| Agent角色 | 模型档位 | temperature | 单次最大token |
|---|---|---|---|
| 项目经理 | 高推理档 | 0.2 | 1024 |
| 研究员 | 低成本档 | 0.0 | 512 |
| 写作者 | 中端档 | 0.7 | 2048 |
| 审核 | 高推理档 | 0.0 | 512 |
temperature的设置也是有讲究的。研究员和审核需要确定性,所以都用0;写作者需要一点自由度,才用0.7。这个参数看似不起眼,实际上决定了系统是“稳定执行”还是“发散创作”,做多智能体系统时,强烈建议不要一套参数打天下。
4. 完整实战:让Agent团队自动完成一次竞品调研
理论讲再多都不如跑一次真实流程。这一章我以“AI写作工具竞品调研”为例子,完整展示 Agency Agents 是怎么从一条需求变成一份报告的。你可以直接把这个流程换成自己的业务场景。
4.1 我们要交付的东西
输入只有一句话:调研市面上主流AI写作工具,输出一份十分钟能看完的竞品报告。项目经理Agent会先把这句话拆成三块:市场概况、核心功能对比、差异化机会。每块对应一个任务卡,交给研究员去执行。为了不过度开放,我要求报告里必须包含来源链接和发布时间,这也是验收的基本标准。
4.2 调度器的实际操作
调度器本身不需要很强的智能,它更像一个邮局。我从队列里取出一个任务卡,查看它的to字段,找到对应的Agent,调用run,拿到输出,把输出保存到指定路径,然后根据审核结果更新状态。核心循环大概长这样:
from collections import deque class AgencyOrchestrator: def __init__(self, agents: Dict[str, BaseAgent]): self.agents = agents self.queue: deque = deque() self.artifacts = {} def submit(self, task: Dict): self.queue.append(task) def run_until_done(self, max_steps: int = 12): step = 0 while self.queue and step < max_steps: step += 1 task = self.queue.popleft() agent = self.agents[task["to"]] result = agent.run(task, context=self._get_context(task)) self._save_output(task["id"], result) if task["to"] == "researcher": self.queue.append(self._create_writer_task(task["id"])) elif task["to"] == "writer": task["to"] = "reviewer" task["input"] = result self.queue.append(task) elif task["to"] == "reviewer": decision = self._parse_review(result) if decision["pass"]: print(f"任务 {task['id']} 完成") else: self.queue.append(self._create_revise_task(task, decision))第一步跑下来,研究员会搜索一批工具,整理成清单。写作者拿到清单以后,会按固定模板生成初稿。审核Agent再按验收标准把初稿过一遍,判断是直接交付还是退回修改。
4.3 一次典型的前后堆叠过程
我实际跑这个例子的过程中,审核Agent第一次退回是因为“缺少市场数据佐证”。研究员在第一轮只列出了功能表,没有包含定价、用户规模、增速之类的信息。审核意见其实很泛,如果没有系统兜底,写作者很容易改出一版差不多的东西。所以我在审核Agent的输出里强制要求给出“具体修改项”:哪里缺数据、大概需要什么来源、优先级是什么。
退回以后,调度器重新给研究员生成一个补充资料任务,只搜索价格和用户规模相关的信息。研究员补充完,写作者在对应段落插入数据,第二次审核直接通过。整个过程耗时大约六分钟,中间没有人工干预,唯一消耗的是调用模型和搜索API的费用。
4.4 产出示例与运行轨迹
最终报告的结构非常稳定,因为我给写作者定义了模板:概览、功能对比表、定价对比、差异化机会、风险提示。运行轨迹记录得也很清楚:
| 步骤 | 角色 | 结果 |
|---|---|---|
| 1 | 项目经理 | 拆解出4个任务卡 |
| 2 | 研究员 | 输出15条功能记录 |
| 3 | 写作者 | 生成初稿 |
| 4 | 审核 | 退回,缺少市场和定价数据 |
| 5 | 研究员 | 补充4条数据 |
| 6 | 写作者 | 修改对应段落 |
| 7 | 审核 | 通过 |
看到这个表格,你就能理解为什么我坚持用任务卡记录一切:它把模糊的“让Agent写个报告”变成了一段可回放、可复用的流程。
5. 调试与问题排查实录
写这种系统,最大的乐趣不是跑通一次,而是跑通一百次。这里的调试经验和单Agent完全不同,因为你不光要看模型输出对不对,还要看协作流程有没有跑歪。我把常见的几类问题整理成了排查表,基本覆盖了我踩过的大坑。
5.1 死循环和任务逃逸
最开始跑的时候,最头疼的问题是任务在“退回修改”这个状态里循环。审核Agent永远不满意,写作者改了两轮还是被退回来,最后直接把流程重启了。后来我把max_steps从全局控制加到了单个任务上,任务一旦超过三次修改,就强制降级为“人工审核待办”,并且把之前的修改记录整理成一份摘要,方便接手的人快速理解发生了什么。
提示:写Agent系统时,一定要给每个任务设定“有损退出”路径。不是所有任务都能成功,允许部分失败比死循环好一万倍。
5.2 角色错乱与协作者互相干扰
第二个高频问题是角色错乱。研究员Agent偶尔会在搜索结果里加入“我认为”,写作者Agent偶尔会直接修改资料原文而不是重新组织语言。原因是我最早把完整的项目背景塞给了所有Agent。现在我只会把角色需要的那部分上下文传给对应Agent,研究员看不到写作者的风格要求,写作者也看不到研究员的全量搜索日志。
5.3 工具调用的参数稳定性
工具调用的问题更隐蔽。研究员使用搜索工具时,偶尔会把参数顺序搞反,或者把关键词写成JSON对象而不是普通字符串。解决办法有两个:一是把工具的parameters定义得非常严格,能枚举的就枚举,必须的字段全部标记为 required;二是在工具调用失败后,把错误信息回传给模型,让它基于错误修正一次,而不是直接让流程崩溃。
我自己比较推荐的做法是:给每次工具调用设置“一次重试机会”。模型第一次参数写错不要紧,你把报错信息塞回去,它大概率能改对。这样比写一大堆校验规则省力得多。
5.4 质量兜底与人工介入
尽管审核Agent已经能挡掉一部分低质量输出,但它毕竟不是万能的。我在项目里保留了一个“人工验收”环节:凡是自动审核通过的任务,如果最终用户反馈不好,可以把结果标记为 bad,系统会自动记录这次模型输出和用户反馈之间的差异,用于后续调整提示词。
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 任务反复退回,无法结束 | 审核标准模糊,写作者不知道怎么改 | 审核输出必须给出具体修改项 |
| 多个Agent互相影响 | 全局上下文共享太多 | 按任务卡隔离上下文 |
| 工具参数频繁出错 | 工具schema定义太宽松 | 严格required,增加一轮重试 |
| 模型产出“正确的废话” | 验收标准偏主观 | 增加可量化指标,比如来源数量 |
6. 现在做 Agency Agents 的几个实操心得
前面聊的都是“怎么做”,最后聊几句只有亲自跑过才会明白的体会。第一,不要为了智能而智能,先把退出条件写进系统。Agent 的价值不在于它能自动跑多久,而在于你让它停下来的时候它能立刻停。第二,小模型也可以干很多活,关键是分工够细。你把一个任务切得足够小的时候,大多数环节甚至不需要动用顶级模型。
第三,日志和回放比功能本身更重要。项目初期最容易忽视的就是给每个任务建立完整轨迹。我第一次跑通全流程时非常兴奋,但第二天发现结果不理想,想回头研究为什么会出问题,结果发现根本没有日志。后来我强制每次Agent调用都记录输入摘要、输出摘要、token消耗和耗时,这套日志直接变成了后续调优的底本。
最后再分享一个小技巧:一开始不要追求“全自动无人值守”。我的第一版还有一个“人类停止按钮”,遇到不确定的地方会通过Fly.io发送通知。跑了一周以后我摸清了哪些环节稳定,哪些环节需要人工兜底,才逐步放开到现在的自动化程度。如果你打算实现自己的 Agency Agents,我的建议很简单:第一版先保证任务能在有限步里结束,再讨论怎么让Agent变得更聪明。把流程做对,永远比把模型换大更重要。