ByteFisher AI 工程化深度实战(十):多 Agent 编排工程化

第 07、08 篇分别剖析了 LangGraph 和 CrewAI 的源码,第 09 篇把博客工具封装成了 MCP。本篇是编排层的收官——把前面所有能力组合成一个生产级多 Agent 流水线。用一个真实案例贯穿:博客的钓鱼日记自动生成流水线(tools/douyin-blog/generate-blog.py)从单次调用升级为多 Agent 编排。

多 Agent 不是”为了 Agent 而 Agent”。它解决的问题是:复杂任务的每一步有不同质量要求、不同成本敏感度、可能失败、需要并行。单 Agent 一条 prompt 全包,面对这些必然顾此失彼。

一、先算账:单 Agent 的代价

generate-blog.py 当前是”一次 DeepSeek 调用生成完整日记”。它的隐性代价:

  1. 一次 prompt 塞了所有需求(素材、人设、结构、SEO),模型”捡了芝麻丢了西瓜”,输出经常某方面不合格(比如 front matter 格式错了、鱼获数据编错了)。
  2. 失败就是全盘重来:输出 2000 字之后发现格式错,整篇重生成,浪费 token 和时间。
  3. 质量不可分步审查:正文写得再烂也得等全文生成完才能发现。

这就是第 08 篇结尾说的”成本翻 4 倍但换来可审查”——多 Agent 的本质是用更多、更小的 LLM 调用,换取每步的可控性和可重试性。

二、流水线设计:把任务拆成有状态的步骤

1
2
3
4
5
6
7
           ┌────────────────────────────────────────────┐
│ LangGraph 流程图(外层:状态 + 恢复 + 观测) │
└────────────────────────────────────────────┘
素材整理 ──→ 策划大纲 ──→ 正文写作 ──→ SEO校对 ──→ 草稿落盘
(MCP: (小模型 (大模型 (中模型 (写文件 +
videos.json + 低成本) 高质量) 确定性) git-ready)
相册检索)

每步职责和资源配置:

步骤 角色 模型 成本等级 失败处理
素材整理 读 videos.json + 相册目录,出结构化清单 本地 Qwen 或 deepseek-chat 低 重试 2 次
策划大纲 出分节结构 + 亮点 deepseek-chat 低 重试 2 次
正文写作 完整日记正文 deepseek-chat(长输出) 高 重试 1 次
SEO 校对 校验 front matter、标签、违禁词 deepseek-chat(temperature 0.1) 中 校验失败回写

关键工程决策:

  • 每步一个小调用,而不是一个大调用——单步失败只重跑一步。
  • 不同步骤用不同 temperature:写作步骤创造性(0.7~0.8),校验步骤确定性(0.1)。
  • “策划”和”写作”分开:大纲是轻量任务(快、便宜),正文是重量任务(慢、贵)。大纲先行,正文才不跑偏——这也是内容生产的常识。

三、失败恢复:三种策略

3.1 步骤级重试(Retry)

第 02 篇的指数退避重试,在 Agent 层同样适用——但每步要有独立的 max_retries。写作步骤 token 大、失败代价高,重试次数应少于素材整理这种便宜步骤:

1
2
3
4
5
6
7
8
9
def run_with_retries(step_fn, step_name, max_retries, budget):
for attempt in range(max_retries):
try:
return step_fn()
except (LLMError, TimeoutError) as e:
if budget.exhausted():
raise # 总预算耗尽,不再重试
sleep(backoff(attempt))
raise RuntimeError(f"{step_name} 重试 {max_retries} 次后失败")

3.2 降级(Fallback)

写作步骤的模型挂了,降级到另一个模型(云端 → 本地,第 06 篇的链路在此复用)。降级优先级:重试 < 换模型 < 换策略(策略降级如”正文生成失败就输出大纲,人工补齐”)。

3.3 检查点续跑(Resume)

第 07 篇 LangGraph 的检查点是这个场景的杀手锏:thread_id 每篇日记一个,跑完正文后崩溃,续跑时只重跑 SEO 和落盘,正文结果已持久化。

1
2
3
config = {"configurable": {"thread_id": f"diary-{publish_date}"}}
# 上次跑到第3步崩了,这次从第3步继续
graph.invoke(None, config)

四、并行调度:什么时候值得并行

多 Agent 的第二个价值是并行。但并行不是免费的——并行同时占用多个模型并发额度,成本不变、延迟降低,但复杂度上升。判断标准:

1
2
3
4
任务之间是否有数据依赖?
├─ 强依赖(B 需要 A 的结果)→ 串行,别并行
├─ 无依赖且可独立失败 → 并行
└─ 弱依赖(B 可以先用 A 的部分结果)→ 流水线并行

ByteFisher 场景里值得并行的例子:多篇日记同时生成(一次钓一周的鱼,生成一周的日记)。LangGraph 超步天然支持并行节点,或用 asyncio.gather:

1
2
3
4
5
6
7
8
9
import asyncio

async def generate_all_diaries(dates):
# 每篇日记是一个独立子图,互不依赖,可并行
results = await asyncio.gather(
*(run_diary_pipeline(date) for date in dates),
return_exceptions=True # 单篇失败不影响其他篇
)
return results

注意:并行时每篇独立 thread_id、独立预算、失败互不影响。这又是一个”失败隔离”——一个坏素材拖垮不了整周的生产。

五、成本与延迟的平衡模型

多 Agent 流水线必须回答两个问题:跑一次多少钱?要等多久? 用第 02 篇的成本模型推演:

步骤 输入 token 输出 token 成本占比
素材整理 600 200 5%
策划大纲 500 300 6%
正文写作 1200 3000 45%
SEO 校对 4000 500 50%

(示例值,正文和校对是成本大头,因为校对要重读全文)

优化杠杆:

  1. 校对步骤的输入压缩(第 03 篇上下文工程):不用全文,用正文的关键段 + front matter。
  2. 模型路由(第 02/06 篇):素材整理这种简单任务下放给本地/小模型。
  3. 并行摊薄延迟:串行 E2E 延迟 = 4 步延迟之和;并行后 E2E = 最长链路延迟。
  4. 失败成本 = 已消耗 token:检查点 + 步骤级重试让失败只损失当前步,不损失全文。

工程红线:流水线的”单次成本上限”必须写进代码。用第 02 篇的预算模型给整个流水线设一个硬预算(比如单篇不超过 ¥0.5),预算耗尽直接落”大纲 + 标记人工补齐”,绝不让一次故障任务烧穿月度预算。

六、落地清单与小结

优先级 改造 收益
P0 把 generate-blog.py 拆成 4 步 + 独立重试 单步可控、失败只重试一步
P0 每步独立 temperature/模型路由 质量与成本双优
P1 LangGraph 检查点 + thread_id 崩溃续跑
P1 流水线总预算硬上限 成本红线
P2 多篇日记并行 + 失败隔离 批量产能
P2 校对输入压缩 省 30% 成本

核心结论:

  • 多 Agent 的本质是用更多更小的 LLM 调用换可控性与可重试性,不是炫技。
  • 拆分三原则:每步一个职责、每步独立重试、每步独立温度与模型。
  • 失败恢复三级:重试 → 换模型降级 → 检查点续跑;失败隔离让单篇故障不影响批量任务。
  • 并行判断:强依赖串行、无依赖并行、弱依赖流水线并行;并行必须配失败隔离。
  • 成本与延迟是设计输入而非事后遗憾:模型路由 + 输入压缩 + 总预算硬上限,三管齐下。

下一篇进入可观测性:给 ByteBot 和这条流水线装上 Langfuse 追踪,把第 02~10 篇的所有埋点变成可看的 trace 和指标。


本系列持续更新中,每周一篇。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: