我做agency-agents这个项目时,最常被问到的一句话是:你写一个能搜资料的Agent不就够了吗,为什么非要搞出好几个Agent在一起工作?说实话,一开始我也是这么想的,但当我真把一个“超级Agent”丢到真实任务里跑了几轮之后,我才发现自己维护的根本不是一个智能助手,而是一台容易精神分裂的点唱机。同一个模型、同一段上下文,既要它深度调研,又要它严格校对,结果往往是两头都不讨好。
后来我把项目推倒重来,把单个大模型Agent拆成了一组各司其职的智能体团队:有人接单派活,有人埋头研究,有人专心产出,还有人专门负责挑毛病。项目名字agency-agents也表达了这层意思:agency既是“代理行为”,也是“代理机构”,agents就是机构里那些各司其职的成员。这篇文章会把我在这个项目里的设计思路、实际代码、选型理由和踩坑过程完整整理一遍,适合正准备做多智能体应用、或者正在被“单Agent越改越乱”折磨的同学参考。
1. 从超级Agent到数字团队:我为什么推倒重来
1.1 一个“全能选手”是怎么把任务搞砸的
最早我的方案特别简单:给一个Agent配上一堆工具,让它自己决定怎么用。比如给一个报告生成任务,我会这样写系统提示词:你是一个资深分析师,你负责搜索资料、整理信息、形成结论、校验事实,最终输出一份完整报告。
听起来没毛病,真跑起来全是问题。第一个问题是上下文长度被迅速吃满:搜索工具返回几十条网页摘要,Agent还没开始“分析”,上下文窗口的一半已经被塞满;第二个问题是职责互相干扰:它要完成任务,所以对搜到的资料照单全收,过滤性很差,摘要里明显过时的信息也被当成事实写进了报告;第三个问题是它几乎不会承认自己有错,在一次测试里我把一个明显矛盾的数据标出来要求它修改,它嘴上说“您提醒得对”,实际上把整段重写了一遍,矛盾处原封不动,只是换了种措辞。
这些现象其实都不是模型变笨了,而是角色边界模糊造成了自我一致性失效。让同一个模型在“开拓者”和“质检员”两种身份之间反复横跳,它往往优先维护“自己的产出”,而不是优先维护“任务的正确性”。我花了很多时间调提示词、换模型、加示例,效果提升很有限。
1.2 破局思路:不为一个超人,而为一家公司
我后来想明白了一个类比:真实公司里不会让一个人同时负责销售、研发、财务、法务,虽然理论上存在全能型员工,但把四个岗位压在一个人身上,出错概率一定暴涨。Agent也是一样——工具能力可以堆,但认知聚焦必须单一。
所以agency-agents的核心设计理念是:不为一个超人,而为一家公司。把任务的完整链路拆成几个不同角色,每个角色只负责一个维度的事情,角色之间用结构化消息协作,而不是在一个上下文里混成一锅粥。
这个“拆”不是随意拆。我在项目里定了一个原则:每个Agent必须有明确的决策边界。 Researcher可以决定搜什么、读哪些页面,但无权决定报告结论;Writer负责把资料组织成可读的文案,但无权自行编造数据;Editor只负责挑毛病,不负责重写内容。一旦某个Agent越界,编排层会把它拉回来。这个约束在单Agent方案里是永远做不到的——你没法让同一个模型在一个上下文里既当运动员又当裁判。
1.3 这个项目真正想解决的三件事
总结下来,agency-agents要解决的核心问题有三个。
第一,任务的拆解与分发自动化。用户只需提交一个自然语言需求,路由Agent自动判断需要哪些角色参与、以什么顺序执行。
第二,多Agent协作的一致性。所有角色通过统一的“任务单”(Task Ticket)交换信息,避免上下文污染和角色越权,并且整个流程可暂停、可恢复、可追溯。
第三,人工介入的窗口。真正有价值的场景不会全自动跑完,比如涉及金额、法务、对外发布的内容,必须有人确认。系统的设计里必须有明确的审批节点,而不是一股脑冲到底。
如果你也在做类似的东西,可以先拿这三条当验收标准,看自己的架构是否满足。不满足的话,大概率会掉进我在后面写的那些坑里。
2. 部门怎么分:四个角色的职责、工具与协作协议
2.1 四个角色的边界划分
这个项目落地时,我定义了四个Agent角色,你把它理解成一个微型内容公司的四个岗位就行。
| Agent角色 | 核心职责 | 工具集 | 输出物 |
|---|---|---|---|
| RouterAgent(前台) | 理解用户需求、拆解任务、分配合适角色、收集最终结果 | 意图识别、任务拆解模板 | TaskTicket |
| ResearcherAgent(研究员) | 按需求搜索资料、阅读总结、提取关键信息、标注来源 | 网页搜索、网页抓取、内容提取 | ResearchBrief |
| WriterAgent(写手) | 将调研简报加工为结构化内容、补全上下文、形成可交付初稿 | 内容模板、格式化工具 | DraftDocument |
| EditorAgent(质检) | 核查事实一致性、检查信息缺失、对照任务单给出修改清单 | 差异比对、引用核验 | ReviewReport |
我在实际项目里没有给WriterAgent配外部搜索工具,这是故意的。如果一个角色既要搜索又要写作,它就会忍不住在写作时引入“记忆之外的新信息”,而这些新信息往往没有经过Researcher的筛选,质量不可控。凡是没有出现在ResearchBrief里的数据,Writer必须标注为“存疑”,不得自行填充。
2.2 任务单协议:Agent之间只认数据结构
多Agent系统里最容易翻车的地方是“对话式协作”——A把结果用自然语言丢给B,B按自己的理解干活。这样做在小规模下勉强能跑,一旦任务变复杂,你根本不知道B到底理解了多少,也查不到A到底传递了什么。
我在项目里规定所有Agent之间只交换结构化任务单,也就是TaskTicket。它的核心结构长这样:
{ "task_id": "a3f2e9", "objective": "撰写一份关于边缘计算设备选型的对比报告", "constraints": [ "目标读者:技术负责人", "篇幅:2000字左右", "必须包含:性价比、功耗、生态成熟度" ], "assigned_agents": ["researcher", "writer", "editor"], "current_stage": "research", "context": { "research_brief": null, "draft": null, "review_report": null }, "status": "pending", "max_retry": 2 }为什么要用这种结构而不是自然语言?因为自然语言有歧义,而下游Agent需要的是确定性的字段。比如“constraints”是决策依据,“context”是各个阶段产物的挂载点,“status”是状态机流转的依据。整个团队的协作方式,就是对这张TaskTicket的分阶段填充和校验。
任务单的状态流转是核心生命周期,我把它限定为五态:
- pending:已创建,尚未分配
- in_progress:某角色正在处理
- review:产物等待质检
- confirmed:质检通过,等待汇总
- blocked:需要人工介入或者遇到无法解决的冲突
只有这五个状态,不允许Agent自己发明新状态。这样可以保证整个团队的行为是可控的,也方便在界面上随时看到“卡在哪一步”。
2.3 人工审批节点:什么时候必须停下来
我做这个项目之前听过一句话:全自动Agent系统的问题不是跑得太慢,而是跑得太快——它往往在你还没反应过来时,已经把错误方案执行完毕了。所以在设计编排层时,我特意在企业环境下不需要人工确认的环节,允许全自动;但到了高风险步骤必须停下来等人。
具体来说,有三个节点我设置了中断点:
- 预算超限:当Researcher的搜索调用次数超过预设阈值时,不继续加搜,而是生成“信息缺口”清单,交给用户决定是否追加。
- 多次返工:当Editor连续两次给出重大修改意见但Writer无法通过时,继续改下去大概率是徒劳,必须让人介入判断是需求理解有误还是资料确实不足。
- 对外产出:任何会被发送给真实用户或外部系统的最终文案,默认必须经过人工确认。
这个设计牺牲了一定的“自动化率”,但换来了整个系统的可靠性和信任度。我后面实测发现,真正让用户愿意长期使用的点,恰恰是这些“停下来等人”的节点,而不是那些跑得飞快的阶段。
3. 编排层选型:为什么是可恢复的状态机,而不是自由对话
3.1 三个主流方案的实测对比
选编排框架时,我重点比对了三个方向:基于图编排的状态机框架、基于自由对话的多Agent框架、基于轻量角色定义的快速框架。为了不引入过多品牌倾向,我统称它们为“方案A、B、C”,但只要你做过调研,基本一眼能认出来。
| 维度 | 方案A(图编排状态机) | 方案B(对话式多Agent) | 方案C(轻量角色定义) |
|---|---|---|---|
| 流程控制力 | 强,显式定义走向 | 弱,对话走向难预测 | 中,靠约定驱动 |
| 可暂停恢复 | 支持持久化检查点 | 较难,上下文难序列化 | 一般 |
| 调试体验 | 每个节点可单独验证 | 连排困难,需要回放对话 | 简单场景友好 |
| 适用团队规模 | 中大型,流程分支复杂 | 研究探索,流程不固定 | 快速验证原型 |
| 学习成本 | 较高 | 中 | 低 |
我一开始因为贪快选了方案C,结果项目到了需要精细化控制分支时就乏力了——角色之间的交流全靠提示词约定,一旦某个分支被漏念,整个流程就跑偏。后来我换到了方案A,核心原因只有一个:我需要整个团队的行为是可暂停、可序列化、可恢复的。真实任务会中断,用户会修改需求,模型会超时,这些情况都需要流程能从任意中间态继续,而不是从头再来。
3.2 最小闭环:一个能跑起来的双Agent流程
为了说明编排层的核心机制,这里给出一个极简双Agent示例:用户输入需求后,先由ResearcherAgent产出调研结果,再由WriterAgent基于调研结果产出最终文案,最后路由判断是否结束。
from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END class AgentState(TypedDict): objective: str research_brief: str draft: str retry_count: int def research_node(state: AgentState) -> dict: brief = run_researcher(state.get("objective", "")) # 调研Agent只负责产出简报,不负责产出最终内容 return {"research_brief": brief} def write_node(state: AgentState) -> dict: draft = run_writer(state["research_brief"], state["objective"]) # 写作Agent基于简报产出初稿,简报之外的来源一律标为存疑 return {"draft": draft} def should_continue(state: AgentState) -> str: if state.get("retry_count", 0) < 1: return "route_review" return END # 构建状态图 graph = StateGraph(AgentState) graph.add_node("researcher", research_node) graph.add_node("writer", write_node) graph.add_node("router", router_node) graph.add_edge("researcher", "writer") graph.add_edge("writer", "router") graph.add_conditional_edges( "router", should_continue, {"route_review": "researcher", "done": END} ) app = graph.compile()代码里最关键的不是某个函数,而是AgentState这个数据结构。它把“当前流程传到哪一步、每个角色的产物放在哪”统一管理起来,每个节点只依赖State里的字段,不依赖对方的原始输出。我在项目里甚至要求Researcher的原始搜索结果必须单独落盘,不进State——State里只放处理后的摘要,否则状态会膨胀到难以维护。
3.3 为什么状态管理决定了Agent团队的上限
多Agent系统的复杂度在于:Agent本身是不可靠的,必须用流程把它框起来。对话式方案的思维是“模型足够强,聊着聊着就能对齐”,而状态机方案的思维是“每一步做什么、谁来做、输出什么、出错了怎么办,都是提前确定好的”。
我自己的体会是,状态管理直接决定了排错效率。单Agent出错了,你只能翻对话日志;状态机出错了,你可以明确看到是哪个节点返回了异常数据、是哪条边把流程带到了错误分支,甚至可以从某个检查点恢复,重跑那个节点。
如果你现在还在用一长串Prompt管理多个Agent,请尽早把“上下文里的隐性状态”显性化——抽象成一个结构体,让每个Agent的输入输出都是这个结构体的某个字段。这是agency-agents项目里我认为最重要的一条架构经验。
4. 实测里最深的四个坑:返工风暴、循环失控、上下文污染和限流
4.1 坑一:EditorAgent把对的内容改成了错的
我加了EditorAgent之后,最开始觉得终于有人管着Writer了。但很快出现了一个诡异现象:Editor给出的修改意见是对的,Writer按意见改完后,原本正确的段落反而变错了。
排查过程是这样的:第一步,我先对比Editor意见和改后文本,发现Editor确实指出了三处“事实不一致”;第二步,我找到这三处“不一致”的原文出处,发现其中两处其实是Researcher的摘要表述过短,导致信息被误读;第三步,真正的问题浮出水面——Researcher产出的是摘要,不是原文。Editor对摘要做事实核查,等于拿二手信息当依据。Writer为了满足Editor的修改要求,把原本基于原文的准确描述改成了摘要里的表述,反而引入了错误。
修复方式有两层。第一层:在TaskTicket里新增了“evidence_sources”字段,把关键论断链接到原始来源,Editor只能依据原文链接做核验,不允许依赖摘要文本做判断。第二层:给Editor的ReviewReport增加“修改级别”——区分“必须修改”、“建议调整”和“可选优化”,只有“必须修改”项才会触发Writer返工,其他项记录在案但不强制重写。这一下把无效返工率降了一半以上。
4.2 坑二:Researcher钻进搜索死循环
跑长任务时,我遇到过一个非常典型的循环:Researcher为了寻找一个“关键数据”,连续进行了七八轮搜索,每轮都搜到一堆相关但不直接的文章,然后继续换关键词再搜,Token消耗翻了四倍,最终简报里真正可用的信息却没有增多。
这个问题的本质是Agent缺乏“停止”判断。模型不知道何时该收手,它倾向于继续挖掘以最大化“完成任务”的可能性。
我的处理方式是给搜索行为加了硬性约束:
- invocations:单任务最多调用N次搜索工具,超过后进入“信息缺口”流程。
- relevance:每轮搜索结果必须经过相关性评分,只有高于阈值的页面才被纳入摘要。
- diminishing_returns:当连续两轮没有新增高相关结果时,自动终止搜索行为。
我把这些约束写成一个ToolGate,挂在工具调用层,而不是依赖模型自觉。实测效果非常明显,搜索类任务的Token成本基本稳定在原来的四分之一到三分之一。所以我的经验是:工具调用类的治理,一定不要放在提示词里,要放在代码层强制执行。
4.3 坑三:共享上下文被污染,下游Agent拿到了不该看到的信息
多Agent系统最容易忽略的隐患是共享上下文泄漏。项目早期,我把所有中间产物都放在同一个Context对象里,Writer可以直接看到Researcher的原始搜索日志。结果有一次,Researcher在某次搜索里偶然看到一篇讨论竞品的内容,并在日志里随手写了一句话“这个竞品方案看着也不错”。Writer读到这句话后,以为这也是任务的一部分,硬生生在报告里加了一段竞品分析,完全偏离了主题。
排查这个问题时,我先以为是Writer提示词写得不够严格,试过加约束之后仍然偶发。后来才意识到,只要信息在上下文里,模型就有权引用它——你不能指望模型区分“这个字段只是日志,不是任务输入”。
修复方式是彻底隔离上下文:ResearchBrief只保留处理后的结构化信息,原始搜索日志单独存文件;Writer的输入只能是任务单里“ctx_research”字段的内容;所有Agent之间不共享自由格式的上下文。我还编写了一个上下文检查器,在每轮节点输出前扫描一遍,发现不属于该Agent职责范围的实体名或话题,自动截断并告警。
4.4 坑四:并行调用让API限流,整个团队互相拖后腿
为了提升吞吐,我给Editor和Researcher的若干子任务设置了并行执行。结果某一轮高负载测试中,多个Agent同时调用模型接口,直接把API配额打爆,报错之后所有节点都以为是自己失败,纷纷进入重试逻辑,重试又叠加了更多并发,形成限流雪崩。
后来我统一在编排层加了一个ConcurrencyController,用信号量把全局并发控制在API限制的70%左右,同时给每个Agent节点配置独立的超时和最大重试次数,避免集体重试。代码层面很简单:
import asyncio _semaphore = asyncio.Semaphore(3) # 全局最多3个并发调用 async def bounded_agent_call(fn, *args, **kwargs): async with _semaphore: try: return await asyncio.wait_for(fn(*args, **kwargs), timeout=30) except asyncio.TimeoutError: return {"error": "timeout", "retryable": False}加了这层之后,整个团队的稳定性提升非常明显,不再出现一个节点超时拖垮全队的情况。如果你的多Agent应用会上生产环境,强烈建议把并发治理放在架构设计里考虑,而不是出问题后再打补丁。
5. 这套架构到底值不值,以及下一步怎么优化
5.1 我用的评估口径
重构到第二个版本后,我给自己定了一套评估指标,每轮改动都基于数据判断而不是感觉。主要看四个数字:
- 任务完成率:成功交付符合约束的任务比例。
- 返工率:一次通过编辑审查的比例。
- 平均步骤数:一个任务从开始到结束经过的节点总数。
- Token成本:完成一个任务的总消耗。
我自己的一组对比数据:同一个报告类任务,单Agent方案完成率大概七成,返工率接近一半,平均步骤只有一步但一步耗时极长,Token消耗高且不可预测;多Agent方案完成率提升到九成一,返工率降到不足两成,平均步骤变多了,但每一步都短而清晰,整体等待时间和Token成本反而更可控。
5.2 如果重新开始,我会先做什么
如果我现在从头做类似项目,不会再急着写Agent代码。第一步会先把流程图在纸面上画清楚:谁接收任务、谁产出什么、什么情况需要返工、什么情况必须让人来拍板。等这张图经得起反问“为什么需要这个节点”之后,再动手写状态机和任务单结构。
Agent本身是最容易写的部分,也是最不重要的部分——提示词可以反复调,模型可以随时换,真正难的是流程设计。流程设计出错,换再强的模型都是白搭。
5.3 下一步:记忆、反思与动态编排
目前版本的系统还是“流程固定、角色固定”的,下一步我打算做两件事。第一件事是给每个角色加长期记忆模块,把过往任务中沉淀的高质量资料和常用结论保存下来,减少重复搜索;第二件事是加入一个独立的ReflectionAgent,它不参与当前流程,而是在任务结束后复盘整条链路,找出去重、合并、修改分支的机会,再反馈给编排层做优化。
这个方向能不能跑出实际价值,我还在验证中。但从目前的经验看,多Agent系统做得越久,越能体会到:真正的智能不只在模型推理里,更在流程的设计、状态的管理以及对错误的容忍机制里。agency-agents这个项目教会我的最大一件事就是:把Agent当成组织里的成员,用管理团队的方式管理它们,比把它们当成一个无所不能的超级智能体要可靠得多。