Part 7 多智能体与编排:一个不够怎么办
墨叔:老哥,前面六 Part 你已把”单个 agent 怎么转”嚼透了,这一 Part 咱们聊一个特别现实的问题——当一件事大到一个 agent 扛不住,或者杂到它顾不过来,怎么办。别急着想”上五个 agent 一起聊”,咱们先把”要不要上、怎么上、上了的代价”这三笔账算明白。
7.1 什么时候需要多个 agent——单 agent 的天花板
老哥,先问你一个扎心的问题:你写了五年 Go,什么时候会觉得”一个 goroutine 不够,得上 goroutine 池甚至多台机器”?
你说:任务太多单核跑不过来、或者某个任务崩了不想拖垮主流程、或者不同类型任务需要不同资源(比如有的要 GPU 有的要 IO)。好,那我再问你:这些东西,放到 agent 身上,是不是一模一样的逻辑?
你愣了一下。对,这就是这一节的核心论点:多 agent 不是什么新范式,它解决的是”单 agent 的算力 / 上下文 / 职责过载”这种老问题——只是这里的”算力”换成了”上下文窗口”和”模型注意力”,“职责”换成了”任务类型”。你后端那套”什么时候该拆分”的直觉,平移过来几乎原封不动。
但墨叔要先泼一盆冷水:绝大多数你以为”需要多 agent”的场景,其实单 agent 加一点工程技巧就够了。我见过太多人被”多智能体”这个词忽悠,上来就造五个 agent 互相聊天,结果 token 烧得飞起、bug 多三倍、调试熬通宵。所以这一节,我先帮你划清楚”单 agent 的天花板到底在哪”,再告诉你”什么情况下才值得越线”。
先说单 agent 的天花板。你回头看 Part 1 学的那个循环——一个模型、一张纸条(上下文窗口)、一堆工具。它干活时,所有信息都堆在同一张工作台上。天花板有四条,我一个一个掰开。
天花板一:上下文窗口被撑爆。 这是最直观的。你让一个 agent 同时读十个文件、查五张表、跟用户聊了二十轮——这些全塞进同一张纸条。转眼窗口满了,模型开始忘事、开始胡说、开始把中间的关键约束弄丢。你想”那我把它读到的东西塞进长期记忆啊”?塞进去是能缓解,但你得不断在”抽屉里翻”和”工作台摆着”之间权衡,复杂度蹭蹭涨。当一个任务天然需要”同时摆在眼前”的素材太多,单个工作台就不够用了。这和你后端”一个进程的内存装不下所有热数据”是同一个困境。
天花板二:任务异构。 这是更本质的。想象你让一个 agent 既写代码、又做法律合同审查、又跑数据分析。这三个活,对”system prompt 该怎么写、该给什么工具、该用哪种模型”的要求完全不同。写代码的 agent 需要文件系统、shell、代码库索引;做合同审查的可能需要法规知识库、严谨的推理约束;数据分析的需要 SQL 和图表工具。你想把这些都塞进同一个 system prompt?那 prompt 会变成一锅粥,模型注意力被撕成三瓣,哪个活都干不精。这就像你让同一个后端服务既当 Web 网关、又当计算引擎、又当存储——不是不能,是拧巴,每个角色都演不到位。
天花板三:需要并行。 单 agent 的循环是串行的——“想一步、做一步、再看”。但很多任务里,子任务之间根本不依赖彼此。比如”帮我调研三个竞品,各写一份报告”,三个调研互相独立。单 agent 只能一个一个串行跑,慢。你作为后端一眼就知道:这该并发啊!但单 agent 的”一张纸条”天然是串行的——它一次只能往纸条上写一件事。要并行,就得有”多个纸条、多个循环”同时转。
天花板四:需要隔离避免互相干扰。 这个最容易被忽略。有些子任务”脏”——比如让 agent 去试一堆可能有毒的输入、去探索一个未知代码库可能走错路、去跑一个可能崩溃的实验。如果这些”脏操作”和”主任务”共用同一个上下文,脏东西会污染主任务的判断。你写 Go 的时候懂这个:一个会 panic 的 goroutine 你用 recover 单独兜住,别让它炸了整个进程。agent 也一样——有些活得放到”隔离舱”里跑,炸了也不影响主线。
我再给你一个更具体的场景,帮你看清这四条天花板是怎么”先后敲门”的,而不是”同时炸”。假设你让 agent 给一个 50 万行代码的老仓库写迁移方案。最开始你用一个单 agent,它读几个核心文件、做点分析,窗口还撑得住——天花板一还没敲。但随着它越读越多,窗口开始吃紧,这是天花板一在敲门。同时你发现它既要当架构师(定方案)又要当码农(逐处读调用点),角色打架、注意力被撕开——这是天花板二。你又想”让它并行分析十个模块不是更快吗”——这是天花板三在招手。最后你担心它为了探索一个可疑的脏依赖,把主上下文搅浑——这是天花板四。关键认知是:这四条不是一起爆的,是先后敲门;你要在哪一条敲门时决定”还能撑”还是”该拆”,正是架构判断力的体现。
这和你决定”一个 Go 服务要不要拆成多个”的节奏一模一样。你不会因为”服务有点忙”就立刻拆微服务——你先看能不能加 CPU、能不能加缓存、能不能优化慢查询。只有当你发现”加机器也救不了、因为瓶颈在单实例职责过重或状态耦合”,才动手拆。agent 也一样:先尝试单 agent 的”加 CPU / 加缓存 / 优化”等价物——上下文裁剪、工具返回截断、长对话摘要——这些就是 agent 界的”性能优化”;优化到极限还顶不住,才上多 agent 这剂”拆分药”。 这个先后顺序,你后端肌肉记忆里就有,现在只是换了个对象。
老哥,你发现没有?这四条天花板,每一条在你的后端生涯里都有对应物:窗口爆了等于内存爆了;任务异构等于单一服务职责过重;需要并行等于 CPU 密集型串行太慢;需要隔离等于故障域要拆分。你早就在用多进程 / 微服务 / 线程池解决同样的问题,只是老板没管这叫”多智能体”。
那”能单不强上多”的原则,到底怎么落地判断?墨叔给你一把尺:
- 先问:单个循环加上下文裁剪(Part 4 的摘要 / 滑动窗口)能不能扛住? 能,就别上多 agent。这是 90% 场景的答案。
- 再问:任务是不是真的异构到”一套 system prompt + 一套工具”伺候不过来? 是,才考虑拆。
- 再问:子任务之间真的互不依赖、能并行省时间吗? 真的能,并行才有意义;否则”伪并行”只是把串行变复杂。
- 最后问:有没有”脏操作”必须隔离,避免污染主上下文? 有,才为隔离单独开 agent。
我给你一个真实的反面例子。有个团队做”客服 agent”,非要把”意图识别""查订单""写回复”拆成三个 agent 串行聊天。结果呢?意图识别的 agent 产出一句话丢给查订单的 agent,查订单的又产出丢给写回复的——每一步都在重新组装上下文、每一步都可能传错信息、每一步都多花一轮 token。这三件事明明可以在一个循环里顺顺序做掉,拆开纯属自找麻烦。这就是典型的”为多而多”。
反过来,一个正面的例子。你让 agent “重构一个大型仓库里所有重复的 util 函数”。这个任务的天然结构就是:先扫描找出所有候选点(一个检索型 agent 擅长),再对每个点分别做重构(多个互相独立的编码 agent 并行),最后由一个总控把所有 diff 汇总、解决冲突、跑测试。这里”扫描”和”逐个重构”异构、“逐个重构”之间独立可并行、且每个重构的失败该局部隔离——四条天花板同时命中,多 agent 才名正言顺。顺便说,这个例子里”扫描”和”逐个重构”如果用单 agent 串行做,不仅慢,还会因为扫描阶段把整个仓库读进窗口、重构阶段又需要读细节,导致窗口反复在”全量”和”细节”之间撕裂——这正是窗口天花板与任务异构天花板叠加发作的信号,单 agent 越优化越别扭,这时候拆才是顺水推舟。
所以老哥,记住墨叔这句话:多 agent 是药,不是饭。单 agent 够用的时候,强行上多 agent,副作用比原来的病还大。 下两节我们才讲”真要上的时候,怎么上才对”。
【思考题】
- 你回头看自己项目里”单 agent 够用却差点被你拆多”的冲动,最可能是被哪个天花板误导的?为什么那个天花板其实能靠单 agent 工程手段解决?
- “任务异构”这个天花板,和你在后端说的”单一职责原则(SRP)“有什么深层的共同点?用微服务 / 单体服务的取舍类比一下 agent 拆不拆。
- 如果一个任务你判断”应该并行”,但子任务之间其实有隐藏的依赖(比如 B 要用到 A 的中间结果),伪并行会带来什么具体灾难?你作为后端怎么避免?
【参考答案】
- 最常被误导的是”上下文窗口被撑爆”。很多人一看到窗口要满,第一反应就是”拆多个 agent 各自带窗口”。但窗口爆的本质是”工作台摆的东西太多且没裁剪”,正确的单 agent 解法在 Part 4 已经埋了伏笔:用滑动窗口丢掉旧无关内容、用摘要压缩长对话、让工具层返回时就做截断。这就像你后端”内存不够”时,第一反应应该是优化数据结构、加缓存、做对象复用,而不是立刻加机器。只有当你发现”即使裁剪到极致,任务天然需要同时摆在眼前的素材仍超窗口”,才算真正触到这条天花板。判断顺序永远是:先裁剪,后拆分。
- 深层共同点是”关注点分离带来可靠性与可优化性”。后端里,一个服务既管用户又管支付又管报表,代码耦合、改一处崩一片,所以才拆微服务;agent 里,一个循环的 system prompt 既要它当码农又要它当律师,注意力被撕碎、哪个角色都演不好,所以才拆 agent。但微服务也有代价(网络、一致性、运维),所以后端铁律是”能单体就别微服务”;agent 同理”能单就别多”。两者的取舍尺完全一致:拆分的收益要大于拆分引入的通信 / 协调 / 调试成本,才值得拆。你当年纠结”这个服务要不要独立出去”,用的就是同一把尺。
- 隐藏依赖下的伪并行会带来”竞态 + 错误传播放大”。B 拿到 A 还没产出的中间结果(或根本没拿到),B 基于残缺信息干活,产出的东西是错的,而你还以为”并行跑完了”——等到汇总时才发现 B 全错,时间和 token 全白烧。后端里你用依赖图(DAG)/ Future/WaitGroup 的协调来避免:先
go A()拿到futureA,B 真正开始执行前await futureA拿结果再算。多 agent 也是一样:orchestrator 必须先把任务的依赖关系理成一张图,无依赖的才并发,有依赖的必须等前置完成。这直接引出下一节的编排问题——并行不是”同时启动 N 个”,而是”按依赖图调度”。
小结:单 agent 有四条天花板——窗口爆、任务异构、需并行、需隔离,但每一条在后端都有对应物,且多数能靠单 agent 工程手段先顶住。多 agent 是药不是饭,“能单不强上多”;只有当异构 / 并行 / 隔离同时或显著命中,拆分才划算。
7.2 Orchestrator-Worker 模式——总控与工人的分工
老哥,假设我们真的决定上多 agent 了。下一步就是你得选个”编排模式”。今天市面上花里胡哨的名字很多,但最经典、最像你后端日常的那个,叫 Orchestrator-Worker(总控-工人)。
先问你:你后端里”网关 + 微服务”是怎么配合的?网关(gateway)不干活,它只做三件事:接请求、把请求拆 / 路由到对应的微服务、把各服务的返回拼成一个响应给客户端。微服务们各干各的,互相不知道对方存在,也不共享内存,只通过网关传话。
Orchestrator-Worker 就是这套结构在 agent 世界的翻版。我把角色拆给你看:
Orchestrator(总控 / 编排者):它是”网关”。自己不亲自干具体活,职责是——① 理解总目标;② 把总目标拆解成若干子任务;③ 把每个子任务派给合适的 worker;④ 回收每个 worker 的结果;⑤ 把结果汇总成最终答案;⑥ 处理 worker 失败、决定重试还是绕过。注意,orchestrator 自己也有一个上下文窗口,但它窗口里装的不是”干活细节”,而是”任务清单 + 各 worker 的摘要结果”——所以它不容易被脏细节撑爆。
Worker(工人):它是”微服务”。每个 worker 是一个独立的 agent 循环,有自己的 system prompt、自己的工具集、自己的上下文窗口。它只关心”派给我的这一个子任务”,干完把结果(最好是一段摘要,而不是满屏原始输出)回给 orchestrator。worker 之间互相隔离,A 崩了不影响 B,B 的脏上下文也污染不到 C。
老哥,你品这个隔离的价值。在单 agent 里,所有事共享一张纸条,一个子任务走错路,整张纸条都脏了。在多 agent 的 Orchestrator-Worker 里,每个 worker 是独立的纸条——故障域被天然拆分,这正是你后端做微服务时梦寐以求的东西。你用 recover 兜 goroutine、用熔断兜下游服务,本质都是在拆故障域;orchestrator 把 worker 隔离,做的是同一件事,只是粒度到了”一个子任务的上下文”。
我给你一个狠点的生活类比。Orchestrator 像包工头,Worker 像一个个专业工种:水电工、泥瓦工、油漆工。包工头不自己刷墙也不自己接管线,他只做:看图纸(总目标)→ 拆出”先水电、再泥瓦、后油漆”的顺序(拆解)→ 派活给对应工种(派发)→ 各工种在自己工位干(隔离,水电工搞砸了不会把油漆弄花)→ 包工头验收每段活、拼成整屋(汇总)。你作为业主(用户),只跟包工头对接,不用认识每个工种。这就是编排的核心价值:把”对用户复杂的协调”,收敛到一个简洁的接口后面。
那么 orchestrator 怎么”派活”给 worker?技术上,最朴素的做法就是:orchestrator 这个循环,把”子任务描述”作为目标,去启动(或调用)一个子 agent 循环,等它返回,再把返回结果写回自己的纸条。下面给个示意——注意,这不是可运行工程,重点在看”这个调度结构长什么样”。
# Orchestrator-Worker 示意(非可运行工程,逻辑自洽即可)
def worker_run(worker_name: str, subtask: str, tools: dict) -> str:
# 每个 worker 是独立的 agent 循环,自带 system prompt 与工具集
# 它只看到"派给我的这一件子任务",看不到别的 worker 的事
messages = [
{"role": "system", "content": f"你是{worker_name},只解决交给你的子任务,返回简洁摘要。"},
{"role": "user", "content": subtask},
]
for _ in range(15):
resp = model_chat(messages, tools=tools)
if resp.is_final_answer:
return resp.text # 只回摘要,不让原始细节污染 orchestrator
result = tools[resp.tool_call.name](**resp.tool_call.args)
messages.append({"role": "tool", "content": str(result)})
return "该子任务超时未完成"
def orchestrator(goal: str):
# 1. 总控先拆解:让模型把目标切成若干子任务(这里用一次模型调用产出清单)
plan = model_chat([
{"role": "system", "content": "把用户目标拆成有序子任务清单,每项标注是否需要并行。"},
{"role": "user", "content": goal},
]).text
subtasks = parse_plan(plan) # 解析出 [{"name","desc","parallel"}...]
results = {}
# 2. 派活 + 回收:可并行的并发启动,需串行的按顺序
for task in subtasks:
if task["parallel"]:
results[task["name"]] = run_in_parallel( # 类比 goroutine/WaitGroup
lambda: worker_run(task["worker"], task["desc"], pick_tools(task["worker"]))
)
else:
results[task["name"]] = worker_run(task["worker"], task["desc"], pick_tools(task["worker"]))
# 3. 汇总:把各 worker 的摘要拼回 orchestrator 自己的纸条,让模型出最终答案
summary = "\n".join(f"[{k}] {v}" for k, v in results.items())
return model_chat([
{"role": "system", "content": "你是总控,根据各工人结果汇总成最终回答。"},
{"role": "user", "content": f"目标: {goal}\n各工人结果:\n{summary}"},
]).text老哥,你逐行看这个 orchestrator 函数,是不是处处是你熟的东西?parse_plan 像”请求路由解析”,run_in_parallel 像你 go func() + sync.WaitGroup,worker 的循环和 Part 1 那个最简循环一模一样。唯一新东西是:循环里套了循环,外层循环负责”调度内层循环”。就这么点事。
我再点破一个你容易忽略的点:orchestrator 自己那张纸条里装的”任务清单”,长什么样直接决定了它聪不聪明。一个写得好的 orchestrator,它的纸条里不是”去把这件事办了”这种一句话,而是一份结构化的任务看板:每个子任务有编号、有目标、有状态(待派 / 进行中 / 已完成 / 失败)、有对应 worker 类型、有依赖关系。orchestrator 每轮决策时,扫一眼看板就知道”现在该派 3 号、该等 1 号回、该重试 5 号”。这其实就是你后端”调度器 / 任务队列”的翻版——celery 的 task 状态机、k8s 的 controller 在不断 reconcile 实际状态与期望状态,干的是同一件事:把”一堆待办”变成”一份可观测、可恢复的状态表”。你一旦把 orchestrator 想成”带 AI 的调度器”,它的设计就该往”状态清晰、可重入、可恢复”方向走,而不是往”聪明地聊”走。
还有个实战里特别容易栽的反模式,我提前给你标个雷:把 orchestrator 写成”伪总控”——也就是它名义上是总控,实际却把大半业务逻辑硬编码在总控的 prompt 或代码里,worker 退化成只会调工具的傻执行器。这违背了”总控聪明在调度、worker 聪明在干活”的分工。反过来另一种反模式是”无总控”——几个 worker 直接互发消息,谁也不统筹,最后产出凑不到一块。两种都偏离了 Orchestrator-Worker 的本意。本意的甜区是:总控轻、worker 重、信息经总控显式流转。
但这里有个你作为后端要立刻警觉的设计点:worker 返回给 orchestrator 的,应该是”摘要”而不是”原始细节”。就像微服务返回给网关的是”处理结果摘要 + 必要字段”,不是把整个内部日志甩给网关。如果 worker 把满屏工具输出直接丢回 orchestrator,那 orchestrator 的窗口立刻又被撑爆——你费劲拆隔离,结果在汇总环节又汇到一起爆了,等于白拆。所以 Orchestrator-Worker 能不能成立,一半看”拆得开”,一半看”汇得简”。
还有一个常被忽视的分工哲学:orchestrator 要”聪明在调度”,worker 要”聪明在干活”。很多新手把 orchestrator 写成一个超复杂的状态机,试图在总控里硬编码所有业务逻辑——这就背离了 agent 的初衷。总控该做的判断是”谁该干、现在干还是并行干、干砸了怎么办”,而不是”这个子任务具体怎么实现”。具体实现交给 worker 里的模型去想。这和你后端”网关只做路由不做业务、业务下沉到服务”是同一句格言。
还有一层你作为后端一定能共鸣的隔离——工具集的隔离。不同 worker 该配不同工具,而不是所有人共享一个巨型工具箱。编码 worker 给文件系统加 shell,检索 worker 给搜索加向量库,验证 worker 给测试运行器。为什么?一是减少 system prompt 体积(工具 schema 也占窗口),二是限制每个 worker 的”能力面”能降低它瞎调工具的概率——给检索 worker 一个 shell 工具,它就可能跑去执行命令而不是老老实实搜索。这和你后端”最小权限原则(least privilege)“一模一样:每个服务只给它能用的账号和接口,不给多余的。把 worker 当成”最小权限的服务”,它的工具集就该按职责裁剪,而不是一股脑全塞给它。
最后给你一个把 OW 落到实处的案例,看看这套结构怎么呼吸。假设总目标是”把一个 Python 2 的老服务迁移到 Python 3”。orchestrator 先拆出子任务:① 扫描仓库列出所有 .py 文件与依赖(检索型 worker);② 对每个模块做语法 / API 差异分析(分析型 worker,可并行);③ 逐个模块改写(编码型 worker,可并行但彼此隔离);④ 跑测试并修红(验证型 worker);⑤ 汇总成迁移报告与风险清单(orchestrator 自己干)。你看,这里 ① 是总控的”侦察”,②③④ 是并行的工人各扫门前雪,⑤ 是总控的”收口”。注意 ④ 验证型 worker 如果挂了,orchestrator 标记它为失败、跳过该模块并在报告里标注风险,而不是让整个迁移崩盘——这正是隔离在”容错”上的直接回报:一个工人的崩溃,被关在它自己的舱里。每个 worker 只看到自己的模块,互不污染;orchestrator 只看清单和摘要,不被细节撑爆。这就是 OW 在真实任务里长什么样——不是抽象图,是”先侦察、再分头干、最后收口”的节奏。
但墨叔也得点一句边界:OW 不是多 agent 的唯一姿势。还有”辩论 / 投票模式”(多个 agent 对同一问题各自给答案,总控投票取优,适合需要多视角校验的判断类任务)、“黑板模式”(多个 agent 共享一块公共记忆互相拾遗,适合探索性发现)。但 OW 是最通用、最像你后端日常的那个,所以这一节只把它讲透。你掌握 OW 之后,别的模式都是它的变体,触类旁通。
【思考题】
- Orchestrator-Worker 里,如果某个 worker 彻底卡死(既不返回也不报错),orchestrator 该怎么兜底?类比你后端的什么机制?
- 为什么”worker 返回摘要而非原始细节”这么关键?如果偷懒直接返回原始输出,会在哪个环节爆雷?
- 你觉得自己最容易犯的错,是把 orchestrator 写成”超复杂总控”还是”太弱导致 worker 各自为政”?两种分别会带来什么问题?
【参考答案】
- orchestrator 必须给每个 worker 调用加超时(timeout)与失败兜底,超时或异常就标记为”该子任务失败”,然后决定:是重试(换 prompt/换模型)、还是跳过用部分结果汇总、还是整体降级返回”部分完成”。这完全对应你后端的超时 + 熔断 + 降级:调下游服务设
context.WithTimeout,超时就cancel并返回兜底值,不能让一个下游把网关拖死。多 agent 里一个 worker 卡死,和微服务里一个下游无响应,性质一模一样,防御手法也一模一样——别信任任何”可能不还手”的调用。 - 关键在”隔离的成果不能在汇总处重新汇爆”。worker 的价值是”在自己的窗口里消化细节”;如果它把几百行原始输出甩回 orchestrator,orchestrator 的窗口就会膨胀到和单 agent 一样大,隔离带来的窗口红利瞬间归零。爆雷环节是汇总:orchestrator 本来窗口只装”任务清单+摘要”,现在被灌满原始输出,于是它也开始忘事、开始基于噪声决策,最终汇总质量崩塌。所以”worker 出摘要”不是礼貌问题,是架构能否成立的硬约束——对应微服务的”接口契约要精简、别把内部脏数据外泄”。
- 两种极端都常见,但后果不同:把 orchestrator 写成超复杂总控,会让它变成一个”硬编码所有逻辑”的脆弱单体,worker 退化成纯执行器,agent 的灵活性丧失,且总控一改全崩(耦合);把 orchestrator 写得太弱(比如只是机械地把一句话同时发给所有 worker),则失去”拆解与依赖调度”的能力,worker 各自为政、产出无法拼合、依赖关系混乱。正确的度是:orchestrator 负责”调度智能”(拆、派、汇、兜底),worker 负责”执行智能”(具体怎么干)。这和你”网关轻、服务重”的分层哲学一致。判断自己偏哪边,就看代码里业务逻辑到底堆在哪一层。
小结:Orchestrator-Worker 是”网关+微服务”在 agent 界的翻版——总控拆解 / 派活 / 汇总 / 兜底,worker 隔离各干各的。隔离拆故障域、摘要回流防爆窗口,二者缺一不可。它本质是”把对用户复杂的协调,收敛到一个简洁接口后”,你后端的分层直觉直接平移。
7.3 主流框架对比:LangGraph / AutoGen / 自研
老哥,我们聊完模式,你肯定要问:那我直接用什么框架?市面上名字一抓一大把,但这节课墨叔只拎三个有代表性的:LangGraph(图编排派)、AutoGen(会话式多智能体派)、以及自己写循环(自研派)。我把它们的”世界观”讲清楚,你才能判断哪个顺手,而不是被营销词带跑。
先说 LangGraph。它的核心理念是:把 agent 的多步流程显式画成一张”图”(graph)。节点(node)是某段逻辑(比如”调一次模型""跑一个工具""做一次判断”),边(edge)是流转条件(比如”如果模型说要调工具,走这条边;如果说完了,走那条边”)。多 agent 在你眼里是什么?在 LangGraph 眼里,每个 agent 就是图里的一个节点(或一个子图),节点之间通过边传状态。它的卖点是”可控、可持久化、可回放”——因为流程是图,你能清楚看到现在在图的哪个节点、能 checkpoint 任意节点、能从中间恢复。
老哥,你作为后端,一眼就懂它的隐喻:LangGraph 像把 agent 流程写成了一个”状态机 + 工作流引擎”,类似你用过的 CI/CD pipeline 把构建画成 stage 和 job。它的强项是”流程复杂但结构清晰、需要严格可控”的场景。坑在哪?坑在**“图”这套抽象有学习成本和过度设计诱惑**——很多人把本可以一个循环解决的事,硬画成一张复杂图,结果 debug 时要顺着图追状态,心智负担重。而且图的”显式”是把双刃剑:灵活是真灵活,但”每个分支都要你画”也意味着”每个边界你都得自己兜”。
再说 AutoGen。它的理念截然不同:多 agent 是”一群会聊天的角色”,靠对话来协作。你定义几个 agent(比如一个”程序员”、一个”评审员”、一个”产品经理”),它们通过互相发消息来推进任务——程序员写出代码发给评审员,评审员挑刺发回给程序员,如此往复。它的卖点是”对话即编排”,特别适合”几个角色吵一架把事办成”的开放式协作,原型(prototype)速度极快,几行就能拉起一群 agent 聊天。
它的坑,老哥你作为工程师一听就明白:“靠聊天来编排”是隐式控制流,极其难调试和保证终止。两个 agent 聊嗨了停不下来、或者互相甩锅来回重复、或者关键信息在闲聊里丢失——你没有一个显式的”图”去卡住它。这就像你后端如果不用状态机、不写明确路由,而是让两个微服务”自由对话”来决定流程,最后谁也说不清现在到哪了。AutoGen 适合”探索性、容错高、不在乎严格 SLA”的实验,不太适合”生产级、要稳定可观测”的系统。
第三种,自研——也就是你在 Part 1 学的那个循环,自己包一层调度。理念最简单:我不信任何框架的抽象,我自己写 orchestrator 的拆解 / 派活 / 汇总逻辑。它在 BRIEF 里被反复强调”本教程不教 SDK”也是这意思——你的底层功力够了,框架只是螺丝刀。
自研的强项是完全可控、零黑盒、和你的 Go 系统无缝融合、没有框架版本升级的烦恼。坑是所有脏活累活自己扛:状态持久化、并发调度、错误兜底、可观测性,全得自己写。框架帮你省的这些,你都得补。所以自研不是”更酷”,是”你付出了工程成本,换来了掌控力和与现有系统的贴合度”。
我特别想把这个”自研的成本”说透,因为很多 Go 后端一听”我自己写循环”就兴奋,低估了要补的工程。你以为自研只是”写个 orchestrator 函数”,实际上框架白送你的那些东西,你都得自己造:① 状态持久化——agent 跑到一半进程挂了,怎么从断点续跑?LangGraph 的 checkpoint 帮你存了每个节点的状态,你自己写就得搞一套状态存储加恢复协议;② 并发调度——多 worker 并发你怎么管?自己写协程池、限流、取消传播(context cancel),框架已经封装好;③ 可观测性——框架通常自带 trace,你自己得接一套日志 / span;④ 流式与中断——用户中途打断、改需求,你怎么干净地停掉在飞的 worker?这些不是”写个循环”能覆盖的。所以墨叔说”自研不是更酷,是付出工程成本换掌控力”——你得诚实地问自己:这些成本你团队扛不扛得住?扛得住、且确实需要,自研才值;否则框架帮你的那部分,远比你想象的金贵。
说个真实的反面教材。一个团队做”代码评审 agent”,选了 AutoGen 让”作者 agent”和”评审 agent”自由对话来改代码。原型阶段爽翻天,两天就跑通。但上线后噩梦来了:两个 agent 经常就一个 styling 问题来回扯十几轮停不下来,用户等到超时;更糟的是出 bug 时,没人说得清”为什么最后采纳了那版”——因为决策散落在对话流里,没有显式状态可查。他们最后推倒重来,用 LangGraph 把”改到评到通过 / 打回”画成显式图,才稳住。这个教训就是:选框架看的是”你的控制流需要多显式、多可观测”,不是看”原型多快”。原型快的东西,往往是因为把复杂度藏在了隐式里,而生产环境最恨隐式。
老哥,你肯定想要一把”什么时候用框架、什么时候自己写”的尺。墨叔给你:
- 用框架(LangGraph 类)的信号:你的流程分支多、需要 checkpoint/恢复、需要严格可控可回放、团队不熟悉底层循环想快速搭——框架的”图”能帮你把复杂流程理清。
- 用 AutoGen 类会话框架的信号:你在做探索性原型、需要多个角色快速碰撞想法、对终止和 SLA 不敏感——它的聊天范式能让你几行拉起多 agent。
- 自研的信号:你要嵌入已有 Go 生产系统、需要极致可控和可观测、流程其实没那么复杂(一个 orchestrator + 几个 worker 就够)、或者你就是想完全理解每一行——这时候框架的抽象反而碍事,自己写更干净。
- 一个反信号(别用框架):如果你的任务”单 agent 加裁剪就够”(回到 7.1 那把尺),那无论什么框架都别上,直接单循环。
我再补一条血泪经验:框架解决的是”编排的机械部分”,解决不了”agent 到底聪不聪明、工具设计得好不好”。很多人以为”上了 LangGraph 我的多 agent 就丝滑了”,结果卡点根本不在编排,而在每个 worker 的 system prompt 写得太烂、工具 schema 设计得模棱两可。框架是脚手架,地基(心智模型、工具、上下文)还是得你按前面几章打。这就好比你用了最先进的微服务框架,但每个服务的业务逻辑是屎山,整体照样崩。
最后落到一个很实际的维度:团队成熟度与可维护性。如果你团队里没人深度理解 agent loop,上来就自研,等于让一群没写过 Web 框架的人手写一个网关——能跑但全是坑,出事没人能修。这时候 LangGraph 这种”把最佳实践固化进框架”的东西,反而帮你少踩坑。反过来,如果你团队已经对循环、状态机、调度门儿清,还硬套框架,会被框架的约定绑手绑脚,明明三行能写清的逻辑,非要绕进它的节点定义里。所以选型还有一条隐藏尺:框架是用来补团队短板的,不是用来秀技术肌肉的。短板在”不懂编排”就上框架,短板在”被框架束缚”就来自研。
三者的选择,未必是”三选一”。实战里常见的是”混合”:用 LangGraph 画主干流程(保证可控可恢复),其中某个节点内部用自研的 worker 循环(因为那个 worker 要深度嵌进你的 Go 系统),而探索性子任务临时用会话式 agent 撞想法。框架是工具不是宗教,关键看你这条链路上”哪里要显式、哪里要灵活、哪里要掌控”。
墨叔给你一句收口的建议:新手先拿 LangGraph 类的图框架练手,因为它逼你把流程显式化,这个过程本身就是训练”编排思维”;等你对循环和调度烂熟于心,再把高频、简单、嵌生产的那部分自研掉,享受掌控力;会话式框架留给做原型、做探索的那 10% 场景。 别一上来就自研(容易把坑全踩一遍),也别永远依赖框架(会丢了对底层的手感)。这和你当年”先用成熟 Web 框架、再自己写中间件”的成长路径,一模一样。
【思考题】
- LangGraph 的”图”和 AutoGen 的”对话”,在”控制流是否显式”上有什么本质区别?这对你 debug 意味着什么?
- 如果让你把 Part 1 那个最简循环扩成多 agent,你倾向”自己写 orchestrator”还是”套 LangGraph”?依据是什么?
- 框架”解决不了 agent 聪不聪明”这句话,落到你后续设计上,提醒你优先投入精力在哪一层?
【参考答案】
- 本质区别:LangGraph 的控制流是显式声明(declarative)的——你画了图,流转条件写在边上,当前在哪个节点、下一步走哪条边,都是可查的状态;AutoGen 的控制流是隐式涌现(emergent)的——没有显式图,流程靠 agent 之间互发的消息自然推进,走到哪全凭对话内容。对 debug 的影响天差地别:显式图让你能”断点”在任意节点、能回放、能问”为什么走这条边”;隐式对话让你只能”看完整个聊天记录反推”,而且容易进入”两人反复说同一件事”的死循环还不知道为什么。这对应你后端”状态机 vs 自由消息驱动”的差别:要可观测可恢复,显式状态机是底线。
- 我的倾向(也给老哥当参考):如果你的多 agent 流程是”清晰可预设的拆解+派活+汇总”(这正是 7.2 那种),而且你要嵌进现有 Go 系统、要可控可观测,那自己写 orchestrator 更干净——因为 7.2 那个
orchestrator函数其实就是你要的全部,套 LangGraph 反而多一层图抽象要去学去调。但如果你要做”复杂条件分支、需要任意节点 checkpoint 恢复、且团队想可视化流程”,LangGraph 的图能省你大量自研状态持久化的功夫。判断依据就一条:框架省下的工程成本,是否大于它引入的抽象 / 学习成本。流程简单就自研,流程复杂且需持久化就框架。 - 它提醒你:优先投入在”每个 worker 的个体能力”这一层,而不是”编排层”。具体来说,资源该花在——写好每个 worker 的 system prompt(行为约束清晰)、设计精准的工具 schema(名字 / 描述 / 参数让模型一次调对)、做好每个 worker 的上下文裁剪。编排再花哨,worker 是个笨蛋,整体还是笨。这和你后端”微服务架构再漂亮,每个服务的业务逻辑烂就全完”一模一样。框架是脚手架,地基(心智模型 + 工具 + 上下文)才是你该死磕的。
小结:LangGraph=显式图编排(可控可回放,但易过度设计)、AutoGen=隐式会话协作(原型快,但难终止难观测)、自研=完全掌控但全靠自己。框架只解决机械编排,解决不了 worker 聪不聪明。选型尺:流程简单嵌生产就自研,复杂需持久化就框架,探索原型可用会话式——但永远先问”单 agent 够不够”。
7.4 真实经验:多 agent 的协作成本——别只看收益,先看账单
老哥,前面三节我把多 agent 的”该上”和”怎么上”都讲了,这一节墨叔要给你泼最猛的一盆冷水:多 agent 的协作成本,往往被严重低估。很多人上多 agent 时只算了”并行省时间”的账,没算”N 个 agent 各自的隐性开销”的账。等账单来了,才发现省下的时间全贴进去了,还搭上睡眠质量。
我把四大成本掰开,每一条都配真实踩坑。
成本一:token 爆炸——每个 agent 都是一张独立的嘴。 你回想 1.4 学的:单 agent 一张纸条,转一圈烧一份 token。多 agent 呢?orchestrator 一张、每个 worker 各一张——N 个 agent 就是 N 份上下文同时在烧。更狠的是,每个 worker 的 system prompt、工具清单、子任务描述,都是独立喂进去的,重复内容极多。一个任务用单 agent 可能 5 万 token 搞定,拆成 5 个 worker 可能变成 30 万——因为每个 worker 都要把自己的”我是谁、我能调什么、我要干啥”完整铺一遍。这就像你后端”加一台机器分担负载”,结果每台机器都要加载全套依赖、缓存预热,总资源消耗反而涨了。多 agent 的”并行省时间”常常用”token 费暴涨”来买单,你老板看账单时未必笑得出来。
成本二:错误传播——worker 错了,orchestrator 跟着错,而且错得理直气壮。 单 agent 里,模型自己调工具、自己看结果、自己纠错,误差在一个循环内闭环。多 agent 里,错误会顺着”worker→orchestrator→最终答案”的链路放大传播。一个 worker 基于错误前提给了个错摘要,orchestrator 不知道它是错的(它只看到摘要),就把错摘要当事实汇入最终答案。更阴险的是:orchestrator 自己也可能”误解”了 worker 的摘要,二次加工出更离谱的结论。这像你后端里的”上游返回脏数据,下游照单全收,最后整个响应都是错的”——而且因为中间经过多手,错误被”合法化”了,看起来每个 agent 都”说得通”。解法不是没有(后面给忠告),但成本实实在在。
我给你一个错误传播的具体画面。假设你要 agent “整理一份季度汇报,含三个部门的业绩”。worker A 负责销售部,它因为 prompt 里一句歧义,把”环比增长”算成了”同比增长”,产出一个错数字,但它在摘要里写得理直气壮。orchestrator 拿到摘要,不知道 A 算错了,把这个错数字当已知事实,在汇总时还基于它推导出”销售部是增长主力”的结论。最终用户看到一份从头错到尾的汇报,而每个 agent 的本地输出单独看都”合理”。更可怕的是:你事后查,发现 orchestrator 的汇总逻辑没问题、worker B/C 也没问题,问题藏在 worker A 的一个数字里——错误被多层加工后,根因反而被埋住了。这就是为什么多 agent 的”错”比单 agent 更难定位:单 agent 错在当场,多 agent 错在传递链。
成本三:调试地狱——N 个黑盒,你不知道问题出在哪个。 单 agent 出问题,你盯一个循环、一段纸条,就能定位。多 agent 呢?用户说”最终答案错了”,你面对的是 orchestrator + 五个 worker 各自的上下文,每个都是概率性的黑盒。是 orchestrator 拆解错了?是某个 worker 干活错了?是 worker 返回摘要时丢了关键信息?还是汇总时 orchestrator 理解偏了?你得像查一个跨五个微服务的分布式 bug 一样,把每条链路的输入输出都捞出来看。而 agent 的输入输出是自由文本,不像微服务有结构化日志好过滤——debug 难度是乘法级的。我见过一个团队,为了查一个多 agent 的错,把六个 agent 的全程对话全 dump 出来人工读,读了半天才发现是某个 worker 的 system prompt 里一句话写歧义了。
成本四:上下文隔离带来的信息丢失——拆开了,就拼不回去了。 这是 7.2 讲的”隔离”的反面代价。隔离让你故障域干净,但也意味着worker A 知道的细节,worker B 天然不知道,除非 orchestrator 主动搬运。而 orchestrator 只收到”摘要”——摘要必然丢信息。结果常出现:A 干活时发现了个关键线索,但只在 A 的窗口里,没进摘要;B 因为没看到这个线索,做出了错误假设;orchestrator 汇总时更是一无所知。这就像你后端拆微服务,服务 A 有个上下文状态,但接口只返回了部分字段,服务 B 拿不到完整信息就只能猜——经典的”分布式信息碎片化”。隔离是优点也是枷锁:你用隔离换来了干净,就得用”显式传递关键信息”来补回丢失。
我再补一个特别阴间的成本,叫**“协调轮次税”**。你以为 orchestrator 派个活、worker 回个摘要就完事了?现实里,worker 回的摘要常常”不够好”——orchestrator 看完发现”这没回答我的子问题”,于是得再派一轮、再问一遍、worker 再回。来回几轮,每个 worker 又被多烧几份 token,orchestrator 也多转几圈。这叫”澄清税”:因为 agent 之间用自由文本沟通,没有结构化契约,误解要靠多轮对话来消化,而每轮都是真金白银的 token。你后端里两个服务用强类型接口(protobuf / json schema)通信,字段不对编译期或校验期就报错,几乎不靠”多聊几轮”来对齐;而 agent 之间目前大多还在”用自然语言聊天”,所以协调成本天生更高。这提醒你:给 worker 之间的”接口”也尽量结构化(让 worker 返回固定字段的 JSON 而非自由散文),能大幅砍掉这笔税。
老哥,你现在应该品出来了:多 agent 的四大成本,本质上就是你后端”分布式系统的经典难题”换了个皮——token 爆炸等于资源放大、错误传播等于故障扩散、调试地狱等于分布式追踪难、信息丢失等于服务间上下文传递损耗。你当年踩过的分布式的坑,在 agent 界换身衣服又来了。所以墨叔说”你不是不会,是功力平移”——连坑都是同一个坑。
那墨叔给你几条踩坑忠告,都是血换来的:
忠告一:给每个 worker 的 system prompt 做”最小必要集”,别把通用大 prompt 复制 N 份。并且,在 orchestrator 派活时,把”这个子任务需要的前置上下文”显式写进 worker 的初始消息——别指望 worker 自己猜。对应你后端”接口入参要带齐,别让下游自己查”。
忠告二:worker 之间不要依赖”心领神会”,要依赖 orchestrator 显式传递。 如果 B 需要 A 的某个产物,让 orchestrator 把 A 的结果(必要时精炼后)明确塞给 B,而不是让 B 去”问问 A”。口头协调在黑盒间最不可靠。
忠告三:加全链路追踪。 每个 agent 的”输入摘要、输出摘要、调用的工具、工具返回”都结构化落日志(类比你后端的 trace/span)。没有这个,调试地狱你逃不掉。这是 Part 5 可观测性在多 agent 上的硬要求。
忠告四:设”可信度校验”关卡。 别让 orchestrator 盲信 worker 摘要。可以让 orchestrator 在汇总前,对关键结论做一次”这个结论是哪来的、有依据吗”的复核,甚至引入一个独立的 critic agent 专门挑刺(呼应 Part 3 的反思循环)。用确定性校验去框概率性输出,老套路了。
忠告五:永远保留”退回单 agent”的退路。 如果多 agent 实测成本 / 复杂度压不住收益,果断降级。架构是手段不是目的。
忠告六:上线前先用”小样本”量化成本,别等账单教你做人。 在决定上多 agent 之前,挑 10 个真实任务,分别用”单 agent”和”你设计的多 agent”各跑一遍,记下每份方案的 token 消耗、总延迟、成功率。如果多 agent 的 token 是单 agent 的 4 倍、延迟只降了 20%、成功率没明显提升——那这多 agent 就是负优化,趁早砍。这就像你后端做架构选型前会先压测、先算 TCO,绝不会凭”感觉更快”就上。把”多 agent 值不值”从玄学变成可测量的数字,是你作为工程师的底线。
这四大成本不是均摊的,不同任务主导成本不同:IO / 检索密集的任务,token 爆炸最狠;判断 / 推理密集的任务,错误传播最致命;探索 / 长链路的任务,调试地狱最磨人。所以你设计多 agent 时,要先判断”我的任务最怕哪一种成本”,再针对性下忠告。这也是为什么没有”万能多 agent 模板”——和你后端”没有万能架构,只有适配场景的架构”同一道理。
老哥,这一节你记住一句话就够了:多 agent 的账,要连本带利一起算。省下的墙皮(时间),可能不够付物业费(token+调试)。 但只要你用后端的分布式功力去管它——隔离、追踪、显式传递、校验——它就不是妖怪,是你能驯服的工具。
【思考题】
- 多 agent 的”错误传播”和单 agent 里的”工具报错被模型自救”相比,为什么更难兜?你后端对应”故障扩散”用什么手段,能平移过来吗?
- “上下文隔离带来的信息丢失”是隔离的必然代价吗?有没有办法既隔离、又不丢关键信息?类比你后端怎么在微服务间传上下文。
- 如果你老板问”咱们上多 agent 到底值不值”,你除了说”能并行”,还必须把哪几项成本摆上桌,才算诚实的架构评估?
【参考答案】
- 单 agent 里工具报错,模型在同一个循环、同一张纸条里立刻看到报错并自救——误差就地闭环,不扩散。多 agent 里,错误发生在 worker 内部,但 worker 只回”摘要”给 orchestrator,摘要可能把错误粉饰掉或漏报;orchestrator 拿到的是”已被加工过的信息”,失去了原始报错信号,无法就地纠正,只能带着错往下汇总——错误被合法化、扩散化了。后端对应”故障扩散”的手段可平移:一是熔断 / 降级(worker 错就标记失败,别让它污染下游);二是重试 + 幂等(worker 错了 orchestrator 重试);三是契约校验(orchestrator 收到 worker 结果先校验结构 / 可信度再采用)。核心心法是”不要让上游的脏数据被下游当事实默收”——加一道显式校验闸门。
- 信息丢失不是”隔离”的必然代价,而是”摘要传递”的代价。隔离只是物理分开窗口,丢信息是你在传递时只传了摘要。解法:① 结构化传递而非自由文本摘要——worker 返回时带”关键产物字段 + 关键证据指针”,orchestrator 按需再把原始细节取回(类比微服务返回 DTO,但保留”详情查询接口”);② 让 orchestrator 主动搬运必要上下文——派活时把 B 需要的 A 产物显式塞给 B;③ 共享只读存储——把各 worker 发现的关键线索写进一个公共黑板(blackboard)/ 共享记忆,worker 需要时去查,而非全塞进对话。这完全对应你后端”微服务间用共享缓存 / 事件总线传上下文,而不是靠口头协调”——隔离的是执行,共享的是必要状态。
- 诚实的架构评估必须把四类成本摆上桌:① token / 算力成本(N 个 agent 同时烧,总量放大多少);② 延迟(并行省的时间,是否被 orchestrator 调度开销和汇总轮次吃掉一部分);③ 可靠性与错误传播风险(一个 worker 错是否拖垮整体,有无兜底);④ 可观测性与调试成本(有没有全链路追踪,出事能不能定位)。只说”能并行”是片面的——真正的架构师会给老板一张”收益 vs 这四项成本”的对照表,并说明在哪种负载下净收益为正。这和你做技术选型时”不能只讲优点,要讲 TCO(总拥有成本)“是一模一样的职业素养。
小结:多 agent 的四大隐性成本——token 爆炸、错误传播、调试地狱、隔离致信息丢失——本质就是分布式系统的经典难题换皮。驯服它的手段也来自你后端功力:最小必要上下文、显式传递、全链路追踪、可信度校验、保留退回单 agent 的退路。上多 agent 前,连本带利算清账。