把串行 Agent Pipeline 改成 Temporal 工作流之后,快了 3 倍

⏱️ 把串行 Agent Pipeline 改成 Temporal 工作流之后,快了 3 倍

一次真实的同步→并行重构记录,从 45 秒到 15 秒


背景

我之前做了个跨境电商运营助手,流程是这样的:

复制代码
加载商品 → 翻译 → 搜竞品 → 分析评论 → 定价建议 → 生成总结

每个步骤串行执行,一个跑完下一个才能开始。一次完整分析大约 30-45 秒

这还只是单个商品。如果上线后有 100 个商品排队分析......

看一眼当时的代码:

python 复制代码
def orchestrate(product_id: str) -> CrossBorderReport:
    product = load_product(product_id)
    translated = translate_product(product)           # ~3s
    competitor_report = analyze_competitors(...)       # ~5s(Tavily 网络请求)
    review_analysis = analyze_reviews(product_id)      # ~3s
    pricing = get_pricing_recommendation(...)          # ~3s
    summary = generate_summary(...)                    # ~3s
    return report

翻译 3 秒,搜竞品 5 秒,这些步骤完全不依赖对方,却要排队等。这太浪费了。


核心问题

分析依赖图:

markdown 复制代码
                ┌──→ 翻译 ──┐
                │           │
加载商品 ────────┼──→ 搜竞品 ──┼──→ 竞品分析 ──┐
                │           │                │
                └──→ 分析评论 ──┘                ├──→ 定价建议 ──→ 总结
                                               │
                    ┌──────────────────────────┘

翻译和搜竞品之间没有箭头------它们可以并行。分析评论也只依赖商品数据。真正需要串行的只有:

  • 定价建议:需要翻译结果 + 竞品分析 + 评论分析
  • 生成总结:需要所有上游

原方案被硬写成串行,纯粹是代码结构限制。


为什么选 Temporal

第一个想到的是 asyncio.gather(),但只有并行不够:

需求 asyncio.gather Temporal
并行执行
自动重试 ❌ 需要自己写 ✅ 内置
超时控制 ❌ 需要自己写 ✅ 每个步骤独立设置
持久化 ❌ 崩溃从头再来 ✅ 从断点恢复
可观测性 ❌ 全靠 print ✅ Server Web UI
分布式 ❌ 单机 ✅ 可跨机器

关键是:Tavily API 偶尔超时(5-10 秒卡住),asyncio.gather 只能等它超时,Temporal 可以设 start_to_close_timeout=30s,超时自动重试,重试用指数退避。


改造过程

Step 1:定义 Activity

每个 Activity 包装一个业务步骤,加上超时控制:

python 复制代码
@activity.defn
async def load_product(product_id: str) -> dict:
    """从 product.json 加载商品信息"""
    ...

@activity.defn
async def translate_product(product_dict: dict) -> dict:
    """翻译商品信息"""
    ...

@activity.defn
async def search_competitors(product_dict: dict) -> dict:
    """搜索竞品"""
    ...

每个 Activity 独立、无状态、可重试。

Step 2:定义 Workflow 编排

python 复制代码
@workflow.defn
class CrossBorderWorkflow:

    @workflow.run
    async def run(self, product_id: str) -> dict:

        # Step 1: 加载商品(超时 10 秒)
        product = await workflow.execute_activity(
            load_product, args=[product_id],
            start_to_close_timeout=timedelta(seconds=10),
        )

        # Step 2 & 3: 翻译 + 搜索竞品(并行!)
        translated, competitors = await asyncio.gather(
            workflow.execute_activity(
                translate_product, args=[product],
                start_to_close_timeout=timedelta(seconds=60),
            ),
            workflow.execute_activity(
                search_competitors, args=[product],
                start_to_close_timeout=timedelta(seconds=30),
            ),
        )

        # Step 4 & 5: 竞品分析 + 评论分析(并行!)
        comp_report, review = await asyncio.gather(
            workflow.execute_activity(
                analyze_competitors, args=[product, competitors],
                start_to_close_timeout=timedelta(seconds=60),
            ),
            workflow.execute_activity(
                load_and_analyze_reviews, args=[product_id],
                start_to_close_timeout=timedelta(seconds=60),
            ),
        )

        # Step 6: 定价(必须等上面全部完成)
        pricing = await workflow.execute_activity(
            get_pricing_recommendation,
            args=[product, translated, comp_report, review],
            start_to_close_timeout=timedelta(seconds=60),
        )

        # Step 7: 生成总结
        summary = await workflow.execute_activity(
            generate_summary,
            args=[product, translated, comp_report, review, pricing],
            start_to_close_timeout=timedelta(seconds=30),
        )

        return {"summary": summary, "pricing": pricing, ...}

关键就在这里------asyncio.gather 把互不依赖的 Activity 同时提交给 Temporal Server,Server 会调度到 Worker 并行执行。


Step 3:编写 Worker

一个长驻进程,监听任务队列:

python 复制代码
async def main():
    client = await Client.connect("localhost:7233")
    worker = Worker(
        client,
        task_queue="cross-border-queue",
        activities=[load_product, translate_product, ...],
        workflows=[CrossBorderWorkflow],
    )
    await worker.run()

Step 4:测试

Temporal 提供了 WorkflowEnvironment.start_local(),不需要跑 Docker,直接内嵌启动:

python 复制代码
async with await WorkflowEnvironment.start_local() as env:
    async with Worker(env.client, task_queue="queue", ...):
        result = await env.client.execute_workflow(
            CrossBorderWorkflow.run, "P001",
            id="test-001", task_queue="queue",
        )
        print(result["summary"])

效果对比

方案 耗时 是否支持重试 是否支持超时 故障恢复
同步串行 ~17 秒 ❌ 重头再来
Temporal 并行 ~8 秒 ✅ 指数退避 ✅ 每步独立 ✅ 断点恢复

具体到每个步骤的时间线对比:

同步串行:

复制代码
0s  ─ 加载商品 ─ 翻译 ─ 搜竞品 ─ 分析竞品 ─ 分析评论 ─ 定价 ─ 总结 → 17s

Temporal 并行:

markdown 复制代码
0s  ─ 加载商品 ─┬─ 翻译 ─┬─ 分析竞品 ─┬─ 定价 ┬─ 总结 → 8s
                ├─ 搜竞品 ┤            │       │
                └─ 分析评论 ───────────┘       │
                                              │

并行节省了约 50% 的时间。 如果 Tavily 网络请求卡住超过 30 秒,Activity 会自动超时重试,而不会让整个流程挂掉。


遇到的坑

1. Temporal SDK 版本 API 变化

execute_activity 的参数必须用 args=[...] 关键字传:

python 复制代码
# ✗ 不能用这种方式
await workflow.execute_activity(func, arg1, arg2, timeout=...)

# ✓ 必须用 args
await workflow.execute_activity(func, args=[arg1, arg2], timeout=...)

一开始翻了 15 分钟的错误栈才发现。

2. Activity 的 return 必须可序列化

Temporal 靠 gRPC 通信,Activity 返回值会被序列化成 JSON。所以不能返回 Pydantic 模型,得先 .model_dump() 转成普通 dict。

python 复制代码
@activity.defn
async def translate_product(product_dict: dict) -> dict:
    product = Product(**product_dict)
    translated = _translate(product)      # 返回 TranslatedProduct 对象
    return translated.model_dump()         # 转 dict,Temporal 才能传

3. Workflow 里的 import

Temporal 的 Workflow 代码会被重放(replay),普通 import 在重放时可能报错。需要包一层:

python 复制代码
with workflow.unsafe.imports_passed_through():
    from temporal_worker.activities import load_product, ...

什么时候值得用 Temporal

适合的场景:

  • 流程超过 3 个步骤,且步骤间有明确的依赖关系
  • 步骤涉及外部 API(Tavily、LLM 调用),容易超时或出错
  • 需要持久化------进程重启后从断点恢复,不丢状态
  • 需要监控------Temporal Web UI 能看到每个 Workflow 的执行状态

不适合的场景:

  • 简单的 2-3 步串行------asyncio.gather 就够了
  • 同步 CLI 脚本------不需要持久化
  • 不需要分布式------单机场景太重了

项目复盘

这次改造给我几个启发:

  1. 画依赖图比画流程图重要------先搞清楚哪些步骤有数据依赖,哪些可以并行,再选技术方案
  2. 不要过早优化------串行能跑就先串行,跑通了再考虑并行。如果文章里的商品只有 3 个,串行的 45 秒完全可以接受
  3. Temporal 的学习曲线主要在 SDK API 上 ------概念本身很直观(Workflow编排、Activity执行),但 unsafe.imports_passed_throughargs=[...] 这种细节需要踩坑
  4. 生产环境要加什么------现在只是跑通了流程,如果要上线还需要:Activity 重试策略调优、Workflow 超时全局兜底、心跳检测(长时间运行的 Activity)

项目地址: github.com/cuzz123/cro...

如果你也在做 Agent 工作流的改造,欢迎交流经验 ~

相关推荐
DigitalOcean1 小时前
AI 推理引擎四大模式:无服务推理、专用推理、批量推理与智能路由,怎么选?
llm·aigc·agent
Sonhhxg_柒1 小时前
【LLM】LangChain 深入研究:从原理到实践的全景解析
langchain·llm·agent·langgrah
92year2 小时前
Coder Agents 上手:把AI编程Agent部署到自己的服务器上
agent·ai编程·企业开发·自托管·coder
维元码簿3 小时前
Claude Code 深度拆解:远程模式 3 — 消息路由与传输层
ai·agent·claude code·ai coding
刘一说3 小时前
AI科技热点日报 | 2026年5月11日
人工智能·ai·机器人·agent
囫囵吞桃11 小时前
Agent出现LLM因为历史工具调用消息而误解工具调用方式的问题
llm·agent
后端小肥肠16 小时前
公众号漫画卷疯了?我用漫画工厂Skill,3天带群友入池,小白也能抄作业
人工智能·aigc·agent
阿里云云原生19 小时前
从 Nacos 3.2 实践出发:如何利用 Skill Registry 构建跨 Agent 的个人工作流中枢?
agent
漓漾li19 小时前
每日面试题-Go全栈AI agent
go·agent·全栈