最近大半年,我一直在折腾一个圈子里叫“Agency-Agents”的东西。翻译成人话,就是搭一个由多个AI Agent组成的“虚拟机构”,让不同角色像公司里的产品、开发、测试、运营那样协作干活,而不是让一个超级Agent包打天下。
我为什么从单Agent转向这个模式?因为之前做了太多单Agent自动化流程,在短任务上确实好用,但一遇到跨步骤的长任务就开始失控:上下文越堆越长,模型开始“忘记”最开始的数据;干到一半它还会自己改主意,输出格式翻来覆去对不上;最离谱的一次,它为了凑一份行业分析报告的篇幅,编了几个不存在的竞品数据。这些事让我意识到,靠“单Agent+长提示词”硬扛复杂流程,是走不通的。
后来我换成了多Agent协作的架构:把大任务拆开,交给不同角色的Agent,让它们之间有明确分工、接口和检查点。折腾了几个月,踩了无数坑之后,这套架构已经能稳定跑完以前单Agent根本搞不定的任务。这篇文章就把我的架构方案、代码骨架、还有几次差点让我崩溃的翻车现场,一次性讲清楚。
1. 为什么单Agent撑不起复杂任务:三个必须上多Agent的信号
1.1 上下文窗口是单Agent的“天花板”
很多朋友一听到Agency-Agents,第一反应都是:为什么要搞这么多Agent?一个Agent不够用吗?说实话,任务量小的时候,一个Agent确实够。但做复杂任务,单Agent最大的物理瓶颈就是上下文窗口。
拿我真实经历过的一个例子说。我需要一个Agent把销售数据全部读一遍,然后按月、按区域、按产品线分别写小结,最后汇总成一份经营分析报告。这个任务本身不算难,但数据文件加起来有几十页,中间还要经历好几轮“先分析、再提炼、再对比”的递归流程。结果就是:上下文越塞越满,模型开始丢信息,引用某个数字的时候张冠李戴。到后面,我不得不反复在提示词里强调“请先回忆输入数据,再回答”,但效果依然不理想。
这就是上下文窗口的“天花板效应”:模型能力是固定的,上下文越长,注意力越分散,越容易丢掉早前信息。多Agent架构的第一个价值,就是把这个“什么都要记住”的大模型,拆成多个只管一小段上下文的角色。每个Agent只面对一个子任务,上下文长度和复杂度急剧下降,稳定度自然就上来了。
1.2 长流程中的“目标漂移”与“幻觉放大”
比上下文更隐蔽的问题,是“目标漂移”。单Agent在长流程里,特别容易在执行到中间步骤时,突然决定“换个它认为更好的实现方式”。比如我让它按固定报告模板输出,它干到第五步,忽然自作主张加了一个“行业对比”模块,理由是它觉得加上更有价值。这个行为在实际项目中非常致命——你很难从输出上判断哪些是按规范干的,哪些是它的临时起意,尤其是当它生成了一个看起来完全合理、但事实上是编造的数据时。
这个现象业内叫“幻觉放大”。单个Agent在短回答里,幻觉概率可能不高;但一旦它已经输出了几段内容,后续输出会基于前面的内容继续扩展,错误就像滚雪球一样越滚越大。我在生产环境吃过一次很大的亏:一个自动生成行业周报的流程,连续跑了一个多月,某天抽查时发现,近三周的周报里所有“竞品公司动态”段落,都是Agent基于两年前的一条旧新闻脑补出来的,时间、公司名全是假的。
多Agent架构里,校验角色的价值在这里就体现出来了——它专门负责盯着执行角色的输出,对照任务要求和原始数据源做核查。就算执行层偶尔“飘”了,校验层也能把它拉回来,而不是让错误一路往下游传。
1.3 需要一个“判断谁的输出更可信”的独立角色
第三个信号,从前两个推导出来:一旦你的任务里需要“对输出结果做评判”——比如判断报告是否覆盖全部要点、摘要是否忠于原文、代码是否能通过测试——你就需要一个跟“执行者”不同角色的“审查者”。
这里说的不是简单的if-else规则,而是让另一个Agent专门做评判。为什么必须是不同的Agent?因为同一个模型、同一套提示词,让它既当运动员又当裁判,它天然会偏向自己的输出。我做过一个对照实验:让同一个Agent先写一篇文章再自我评分,它给的分几乎没有低于9分的;但换一个独立的Agent当裁判,同样质量的输出经常只给7分,而且能指出具体问题。这个差异非常明显。
所以,我判断一个项目要不要上多Agent,主要就看三个信号:上下文是不是已经压到挤牙膏?流程里是不是有多步决策容易目标漂移?需不需要对产出做独立的质量判断?三条里占了一条,建议直接考虑Agency结构;占了两条以上,基本就是必选项了。
2. 架构拆解:一个能满足80%场景的三层多Agent结构
2.1 编排层(Orchestrator):任务拆解与调度
我最初的失败教训是:把所有Agent放在一个“大而全的提示词”里,然后祈祷它们各司其职。结果完全失控,每个Agent都试图“帮忙”做别人的事。后来我改用三层结构:编排层、执行层、校验层,才真正稳定下来。
编排层是整个团队的“项目经理”。它不干活,只干三件事:拆解任务、派发任务、汇总结果。具体到实现上,它读取任务的初始输入,把大目标拆成一组有依赖关系的子任务,决定每个子任务交给哪个执行Agent,最后收集所有执行结果,再按任务目标重新组织成一份完整输出。
这里有个容易被忽视的设计要点:编排层不应该直接改动执行层的产出物。一开始我图省事,让编排层在汇总时“顺手润色”执行层的文字,后来发现只要这么一润色,中间过程就不可审计了——你不知道哪里被改了、为什么改。现在的做法是:编排层只做“拼接”和“引用”,任何内容修改都必须由专门的执行或润色Agent完成,且留下操作记录。
还有一个实操细节:给编排层的提示词里,一定要写清楚“每个子任务必须返回结构化状态码,如success、failed、skipped,以及依赖项”。因为Agent的对话是流式的,如果子任务状态不明确,编排层经常会把失败的任务当成成功任务继续派发下去,流到校验层才发现整体出错,浪费大量时间和token。
2.2 执行层(Workers):专业角色分工
执行层就是那几个干活的Agent。在设计角色时,我的原则是:宁可角色多一点,也不要让一个角色同时承担两种以上的核心职责。
举个例子,我的自动行业分析项目里有五个执行Agent:数据抓取Agent、数据清洗Agent、图表生成Agent、初稿撰写Agent、润色排版Agent。每个Agent的系统提示词里都会明确写出:我是谁(角色定位)、我的输入来自哪里(上游数据源格式)、我的输出交给谁(下游数据格式要求)、我绝对不做哪些事(防止越界)。
第三点“绝对不做哪些事”,实际比“做什么”更重要。比如数据抓取Agent的提示词里我会写“本阶段不要分析数据含义,不要剔除异常值,只负责抓取和原样输出原始数据”;数据清洗Agent的提示词里则写“只处理上游提供的数据文件,禁止引入任何外部知识或自行补充数据”。这一条帮我挡住了大部分“好心办坏事”的情况。
执行层用什么模型,也值得单独说。我的经验是:编排层和校验层用能力更强的模型,执行层反而可以适当用更轻量、更便宜的模型。因为执行层的任务非常聚焦,对模型综合能力要求不高,但对速度和成本敏感。如果每个执行Agent都用旗舰模型,一次任务下来费用会非常惊人。
2.3 校验层(Critic):防幻觉的最后一道闸门
校验层是我在这个架构里认为最具价值、但也最容易被低估的一层。
它的职责可以概括为:拿执行层的输出,回到“初始任务”和“原始数据”面前,做三件事:
- 完整性检查:任务要求的所有要点是否都覆盖到了。
- 一致性检查:输出的数据是否与原始数据源一致,有没有数字对不上、日期错乱的问题。
- 合规性检查:输出格式是否符合下游使用要求,比如JSON结构是否合法、Markdown标题层级是否正确。
这里有个关键设计:校验层必须能看到“原始输入”,而不只是看执行层的汇报。我第一版架构里,校验Agent只接收执行层的输出,结果它只能发现格式问题,发现不了数据造假。后来我把原始数据源也放进校验层的上下文里,它才开始真正发挥作用——比如能指出“这个月销售数据里华东区是1.2万,但你的报告里写成了2.1万”。
校验层的输出同样要结构化:它需要给出每个检查项的结果(pass或fail)、问题定位(哪一段出了问题)、修改建议(具体怎么改)。然后由编排层决定:是直接打回给某个执行Agent重做,还是接受当前结果进入下一步。
我测试过一种改良思路,让校验层不只跑一轮,而是做“多轮对抗式校验”——第一轮发现问题后,让执行Agent根据建议修改,再让校验Agent复检,如果还有问题再打回。这样最多三轮,输出质量提升非常明显。但要注意设定最大轮数,否则会陷入后面说的死循环问题。
2.4 通信协议:Agent之间如何“说话”
三层结构决定了,Agent之间本质上是在“传递数据”,而不是“聊天”。这一点是我跟很多初做多Agent的朋友反复强调的。
有些框架默认让Agent之间用自然语言对话,看起来很好玩——一个Agent说“我需要你的数据”,另一个回“好的,给你”。但在工程化场景里,这种自由对话就是灾难。首先是不稳定,自然语言表达同一个意思的方式太多,下游Agent解析时经常出错;更麻烦的是不可审计,对话记录里根本没有清晰的“数据字段”概念,出了问题都不知道该从哪里排查。
我的做法是,所有Agent之间的交互统一走结构化数据,具体来说:
- 每个子任务的输入和输出,都是一个JSON对象。
- JSON里至少包含几个固定字段:task_id(任务唯一标识)、status(任务状态)、input_data(引用或内联的输入数据)、output_data(结构化产出)、error(错误信息,没有则为null)、meta(元信息,如生成时间、模型版本)。
这样做的两个直接好处:第一,任何下游Agent都能用确定性的代码做校验,而不是依赖模型去“猜”上游想做什么;第二,全程可审计,出问题时打开JSON记录,一眼就能看到是哪个环节断的。
我在实际项目里还为每个Agent加了“输入Schema”和“输出Schema”的定义,用JSON Schema描述清楚字段类型和必填项。执行Agent在返回输出前,会先用代码校验一下结构,不合法就直接重试一次。这个做法把“格式错误”类问题几乎彻底消灭了。
3. 基于LangGraph的落地实现:核心代码与关键配置
3.1 为什么选LangGraph而不是AutoGen或CrewAI
聊完架构,说说实现。市面上的多Agent框架不少,主流的有AutoGen、CrewAI、LangGraph,我全都试过,最终在正式项目里选择的是LangGraph,原因有三:
第一,LangGraph把“图”作为一等公民,状态管理是显式的。它适合这种有清晰编排逻辑的业务流;AutoGen更偏研究性质,自由度太高,实际业务里要写很多胶水代码;CrewAI上手快,但抽象程度高,出问题时不容易定位底层到底发生了什么。
第二,LangGraph的“状态-节点-边”模型,跟我上面讲的三层架构天然对齐:每个Agent就是一个节点,边就是数据流和路由条件,状态就是那个贯穿全程的JSON对象。调试的时候能用现成的可视化面板看每个节点的状态流转,这对排错帮助巨大。
第三,LangGraph对生产环境的支持更成熟,支持检查点持久化、断点恢复、流式输出。多Agent任务动不动就是几分钟的长流程,如果没有断点恢复,一次网络抖动整个任务就要重来。
需要说明的是,这是我个人的选型经验,不是否定另外两个框架。如果你只想快速跑个Demo验证想法,CrewAI确实更简单;如果你在搞研究、需要高度自定义Agent对话逻辑,AutoGen也值得深入。但面向“可靠交付”的生产项目,我推荐LangGraph。
3.2 状态定义与节点函数
下面这段是一个极简但完整的LangGraph多Agent流程骨架,只演示核心逻辑:一个任务进来,编排Agent拆解,一个执行Agent干活,一个校验Agent检查,然后根据结果决定重做或完成。
from typing import TypedDict, Literal from langgraph.graph import StateGraph, END class AgentState(TypedDict): # 贯穿整个流程的共享状态 task_id: str original_task: str # 初始任务描述 split_result: str # 编排层输出的子任务 worker_output: str # 执行层产出 critic_result: str # 校验层结论 status: Literal["pending", "running", "needs_retry", "done"] def orchestrator_node(state: AgentState) -> AgentState: # 编排层:调用模型拆解任务,并设定执行顺序 # 实际项目中这里会请求大模型,并解析返回的JSON state["split_result"] = json.dumps({ "tasks": [ {"task_id": "t1", "type": "fetch", "deps": []}, {"task_id": "t2", "type": "analyze", "deps": ["t1"]}, ] }) state["status"] = "running" return state def worker_node(state: AgentState) -> AgentState: # 执行层:根据split_result执行一个具体子任务 # 关键是:只处理自己相关的部分,不要越界 state["worker_output"] = "这里是执行Agent的产出" return state def critic_node(state: AgentState) -> AgentState: # 校验层:检查worker_output是否满足要求 # 若需要重做,将status置为needs_retry;否则置为done state["critic_result"] = "通过" state["status"] = "done" return state节点函数的设计有一个重要原则:每个节点函数必须有幂等性,也就是说,给它同样的输入,它应该能够稳定地产生同样性质的输出。我为此给每个节点都加了简单的错误捕捉和超时处理,不能让节点内部的偶发异常直接拖垮整个图。
3.3 条件路由与控制流
LangGraph里最能体现工程价值的,是“条件边”。在上面的流程中,校验层返回结论后,我要根据结论决定走“重做”还是“结束”分支,代码如下:
def route_after_critic(state: AgentState) -> Literal["worker_node", "end"]: if state["status"] == "needs_retry": return "worker_node" return "end" builder = StateGraph(AgentState) builder.add_node("orchestrator", orchestrator_node) builder.add_node("worker", worker_node) builder.add_node("critic", critic_node) builder.add_edge("start", "orchestrator") builder.add_edge("orchestrator", "worker") builder.add_edge("worker", "critic") builder.add_conditional_edges("critic", route_after_critic, {"worker_node": "worker", "end": END})这里有个坑想提醒大家:条件路由的返回值必须严格匹配节点名,而且我建议给每条边都配上显式的最大重试次数记录。我在项目里给Worker节点的最大重试次数设的是3,超过3次就不再进入校验节点,而是直接返回一个“needs_human_review”状态,触发人工检查。这样能有效避免死循环烧钱。
3.4 关键参数:温度、模型选择、最大迭代次数
有了骨架,接下来是几个直接影响稳定性和费用的参数,我逐个说配置经验。
温度(temperature):在需要确定性输出的执行层和校验层,我统一设成0或接近0,比如0.05。很多人觉得设为0就一定不随机,实际上API层面的采样还是会有微小波动,但足够稳定了。编排层我留一点温度,比如0.2到0.3,因为步骤编排的路径不同时,适度变化反而是好事。记住一个原则:越靠近“执行和检查”的角色,温度越低;越靠近“规划和调整”的角色,温度可以稍微高一点。
模型选择:我用的是分层配模型。编排层用带长上下文、推理能力强的模型;执行层尽量选便宜且速度快的模型,只要在具体领域上够用就行;校验层我的建议是“别省钱”,因为校验质量是整个流程的最后一道防线,比执行层更值得用好模型。成本和稳定性的平衡点需要实测调整,我一般会跑50次基准任务,对比不同配置下的通过率。
最大迭代次数(recursion_limit):为整个图设一个全局上限。例如执行时设置recursion_limit=50,防止因为路由条件写错或者校验层永远不通过导致无限循环。这属于保险丝,平时碰不到;一旦碰到,就说明你的路由逻辑或提示词设计出了问题,需要人工介入,而不是让系统无限跑下去。
4. 实测翻车现场:上下文污染、死循环、费用失控和结果不可复现
每次看到有人晒“全自动多Agent流水线”,我第一反应都是:你一定还没遇到真正的长尾问题。多Agent系统的坑,绝不是架构图上看得到的,而是你跑了几百次任务之后才慢慢浮现的。
4.1 上下文污染:Agent A把Agent B的活也干了
上下文污染是我踩的第一个大坑。第一版多Agent流程里,为了让每个Agent“了解全局”,我把初始任务、中间结果、最终要求全塞到每个Agent的上下文里。结果很讽刺:Agent之间越来越“同质化”——数据抓取Agent开始顺手分析数据,撰写Agent开始怀疑数据准确性并自行修正数字,校验Agent则因为只看到了二手信息,把错误当作标准。
我后来总结了根因:上下文越丰富,Agent的主动性越强,越容易跨出边界。解决方法是做“信息隔离”:每个Agent的上下文只包含完成它自己职责所必需的最小信息集。执行Agent不需要知道任务的全部背景,校验Agent则需要看到原始数据和任务目标。如果一个Agent的上下文里出现了与它职责无关的信息,那这段信息就该被裁剪掉。
4.2 死循环:Critic和Worker互相“杠”
死循环是我遇到的第二个坑,也是最烧钱的坑。现象是:校验Agent打回执行Agent的结果,执行Agent修改后再交给校验Agent,校验Agent又因为另一个小问题打回,来来回回对话几十轮,费用噌噌往上涨,结果输出越改越复杂,最后跟初始版本相比几乎没进展。
这个问题的本质是:校验Agent在打回建议时,没有区分“阻塞性问题”和“非阻塞性问题”。我后来在提示词里明确要求校验层给问题标级别:
| 级别 | 含义 | 处理方式 |
|---|---|---|
| P0 | 必须修,不修不能交付,比如数据不一致、结构错误 | 打回重做 |
| P1 | 建议修,交付前能修就修 | 打回重做 |
| P2 | 可选,不影响整体质量 | 记录即可,不阻断流程 |
同时,我给校验层设定了一个上限:单次任务最多打回三次,超过三次直接进入“需要人工审查”分支。这个简单的熔断机制,让死循环问题几乎绝迹。可以把“打回+重做”想象成写文章时的审校:无限改稿当然能改得更好,但你不可能让写手和编辑无限对峙下去,必须有截止时间。
4.3 费用失控:一次任务烧掉几十万token
费用失控往往是死循环的伴生问题,但即使没有死循环,费用也容易失控。我做过一次统计:一个拆成6个子任务的项目,如果每个Agent都用旗舰模型、上下文不压缩、还跑两轮校验,一次任务的token消耗能到20万以上。一天跑20次,一个月下来费用非常可观。
控制费用的三板斧,是我在实际项目里验证过的:
- 第一,能不用旗舰模型就不用。执行层尽量用小参数模型,很多任务用小模型完全够用。
- 第二,做上下文压缩。中间结果能摘要就摘要,不要总把全量数据往下游传。可以关注一下各框架对“滑动窗口摘要”或“上下文精简压缩”的支持。
- 第三,给token上预算。在代码或配置里给每个任务设定最大token上限,超过上限的任务直接标记为“成本异常”,停止继续执行,人工介入。
我个人的经验是:把这三板斧都落实之后,同样的一批任务,每月费用至少降了一半以上,而输出质量几乎没有变化。
4.4 结果不可复现:随机性如何管理
最后一个坑,是多Agent系统里被讨论得最少的——不可复现性。单Agent的随机性已经够烦了,多Agent连续叠加几个随机环节之后,结果可能差得离谱。
有一次我连续跑三次相同的任务,第一次输出的报告结构是“背景-现状-建议”,第二次变成“现状-问题-趋势-建议”,第三次干脆把建议部分写成了图表说明。原因不复杂:编排层拆解的步骤每次都有细微差异,执行层每次的温度波动又被后面的Agent放大了。
现在我的做法是:在Agent的输入里塞一个session_id和一个random_seed,所有Agent都使用同一个seed作为抽样种子,部分模型API支持seed参数;同时把每个环节的关键参数(模型版本、温度、seed)都记录到meta字段里。这样即使某次输出出了问题,也能完整复现当时的运行条件来排查。
5. 让Agency跑得又稳又省的几条实战经验
5.1 任务拆细、输出写死、JSON为主
多Agent系统里,任务拆分的粒度决定了系统稳定性的上限。我见过不少失败案例,共同特征就是“任务拆得太粗”。比如让执行Agent“分析销售数据并给出建议”——这看起来是一个任务,实际上包含了数据理解、指标计算、趋势解读、建议生成四件事。Agent做这种复合任务时,大概率会在某个环节出错。
正确的做法是拆到“单个Agent单次只做一件事”。具体到销售数据分析这个场景,应该拆成:
- 筛选并清洗近三个月的销售数据。
- 按区域汇总销售额和环比。
- 根据任务2的结果,生成趋势简述。
- 基于趋势简述生成建议。
每一步的输出都是明确的结构化数据,下游只需要做确定性处理或读取。
另外一个经验是:让Agent输出JSON,而不是自由文本。自由文本适合给人看,不适合给程序或另一个Agent消费。我在每个执行Agent的提示词里都写了一句:“请用JSON格式返回结果,不要输出任何多余的文字或Markdown代码块标记。”
5.2 缓存与上下文压缩
多Agent系统最容易出现的一个误区,是“每次任务都从头跑”。实际上,很多子任务是可以被缓存的。比如数据抓取Agent的原始抓取结果,如果没有数据源变化,完全可以直接读缓存,不用重新让模型生成一遍——这类确定性操作甚至可以用普通程序实现,不需要Agent参与。
我的做法是:给每个子任务生成一个内容哈希作为缓存键,比如把“任务描述+输入数据摘要+模型版本+温度”拼起来哈希。下次相同参数进来,如果缓存命中,直接跳到下游节点。这个优化在大批量、重复性任务中效果拔群。
上下文压缩方面,当中间结果特别长时,我会在传给下游之前先做一步“摘要转换”——由编排层调用一个摘要Agent,把上游的长文本压成下游真正需要的关键信息。这一步单独花一次调用很值,因为长文本往下游传的钱,往往比这一次摘要调用还贵。
5.3 人机协同检查点
我知道很多人做多Agent的初衷,是要“全自动”。但我的真实经历是:在复杂业务里追求100%全自动,最后都会变成人肉救火。更稳妥的做法,是在关键节点上设置人机协同的检查点。
比如在最终报告要发给客户的场景里,我会让流程停在校验层通过之后、正式输出之前,把结果推到一个预览页,由业务同学一键确认或修改。这个人工步骤表面上是增加了干预,实际上节省了大量后期返工——因为Agent偶尔会犯“看起来合理但用时会翻车”的错误,比如把月度数据错当成季度数据、把附注表格里的口径搞混。
还有一个比“最终人审”更轻量的方式:异常触发人审。前面说的P0问题、成本超限、重试超限,这些情况下流程自动转人工,而不是硬着头皮继续跑。用这种机制,90%的情况下流程可以全自动跑完,剩下10%的疑难杂症由人处理,我认为这是健康的多Agent系统该有的姿态。
5.4 从“全自动”到“半自动”的渐进路线
最后想聊一下落地路径。如果你还没在生产环境里跑过多Agent,我的建议是:不要一开始就设计一个10个Agent的宏大系统,而是从一个3-Agent的最小闭环起步。
选一个真实存在痛点、且波及面可控的任务,比如“自动生成周报”或“自动做数据质量巡检”,用“编排层+一个执行层+一个校验层”这个最小组合先跑起来。先盯一周,记录所有翻车场景;接着在翻车最频繁的环节上加新Agent或新规则;稳定一个周期后,再加下一个功能模块。
我自己的第一版只有3个Agent,现在是6个,这个扩张速度是刻意控制的——每加一个Agent,系统的不确定性都会上升一截,你得有足够的时间驯服它。
同时,为每个Agent配上日志、指标和告警。日志记录每次调用的输入输出;指标是耗时、token、重试次数、校验通过率;告警是超过阈值时通知你。没有可观测性的多Agent系统,就像一个没有仪表台的发动机,你敢开它上路吗?反正我不敢。
最后再分享一点个人体会:Agency-Agents模式最大的价值,不是让AI看起来更“聪明”,而是让工程上更“可控”。单个Agent像一位才华横溢但脾气不定的自由艺术家;一个组织良好的Agent团队,则像一家流程规范、各司其职、质量有保障的成熟机构。把随机性分散到多个小而明确的环节里,用结构化数据串起来,再配上熔断和人工检查点——这套组合拳,是当前阶段让多Agent系统真正交付价值的可行路径。如果你也在折腾类似的东西,希望这篇文章里的坑和方案,能让你少走几天弯路。