第21章:Temporal 工作流:Agent 系统的“时光宝石”

21 阅读6分钟

第21章:Temporal 工作流:Agent 系统的“时光宝石”

0001页.png

如果说 Go/Rust/Python 是 Agent 的躯体,那么 Temporal 就是它的“灵魂容器”。它赋予了代码一种超能力——“永生”。即使服务器灰飞烟灭,任务状态依然永存。

想象这样一个场景: 你的 Agent 正在执行一个“深度行业调研”任务,预计耗时 30 分钟。 它已经勤勤恳恳地搜了 20 分钟,整理了 50 个网页,就在准备写报告的瞬间—— 机房的保洁阿姨拔掉了服务器的电源插头。

普通系统的结局: 一切归零。用户看到报错,愤怒地重试,然后你的公司为此浪费了 20 分钟的 Token 钱。

Temporal 系统的结局: 电源插回,服务器重启。Agent 就像什么都没发生一样,从第 20 分钟的断点继续执行,哪怕它换了一台服务器,哪怕中间隔了一天。

这就是 Temporal 的魔力。它把“代码执行”这件事,变成了“可持久化的数据”。

01. 为什么我们需要“工作流引擎”?

在写简单的脚本时,我们假设“程序会一直跑到底”。但在生产环境,崩溃是常态

为了处理长时任务(Long-running Process),传统做法有三种:

  1. 听天由命流:崩了就崩了,让用户重试。(用户体验极差)
  2. 数据库状态机流:在 MySQL 里建一张 tasks 表,每做一步更新一下 status。(代码写得像意大利面,维护噩梦)
  3. 消息队列流:用 Kafka/RabbitMQ 串联。(难以处理复杂的依赖和超时)

Temporal 提出了一种全新的范式:Fault-Oblivious Stateful Code(对故障无感知的有状态代码) 。 简单说:你只管写逻辑,我负责“存档”和“读档”。

02. 核心概念:大脑(Workflow)与手脚(Activity)

Temporal 的世界观里,有两个绝对不能混淆的概念:

1. Workflow(大脑 / 导演)

  • 职责:负责编排。决定先做什么,后做什么,如果错了怎么办。
  • 特性必须是确定性的(Deterministic)
  • 限制严禁使用 time.Now()rand()Thread.sleep() 或直接发起 HTTP 请求。
  • 为什么? 因为 Workflow 恢复状态靠的是 Replay(回放) 。如果你的代码里有随机数,每次回放结果都不一样,时空就错乱了。

2. Activity(手脚 / 演员)

  • 职责:负责干活。调用 GPT、查数据库、发邮件。
  • 特性可以有副作用
  • 机制:Activity 的结果会被 Temporal 记录在案。回放时,不会真的再执行一遍发邮件,而是直接从数据库里拿出“上次发送成功”的记录。

比喻: Workflow 是剧本。剧本里写着“主角看了看表”。 Activity 是拍摄。演员看表,发现是 12:00。这个“12:00”被胶片(History)记录下来了。 下次重看电影(Replay)时,主角看到的永远是 12:00,而不是你手表上的当前时间。

03. 核心机制:时光倒流与断点续传

Temporal 是怎么实现“永生”的? 它维护了一个 Event History(事件历史)

正常执行时:

  1. Workflow 启动。 -> 记录:WorkflowStarted
  2. 调用搜索 Activity。 -> 记录:ActivityTaskScheduled
  3. 搜索完成,结果"Tesla"。 -> 记录:ActivityTaskCompleted: "Tesla"
  4. (此时服务器断电)

重启恢复时(Replay):

  1. Temporal 读取历史。
  2. 看到 WorkflowStarted,启动代码。
  3. 代码走到“调用搜索”,Temporal 拦截: “停!看历史记录,这个活已经干过了,结果是 Tesla。”
  4. 直接把 "Tesla" 塞给变量,跳过执行。
  5. 代码继续往下走……

这就像玩游戏时的 S/L 大法(Save/Load) ,但它是自动的,而且是毫秒级的。 0003页.png

04. 版本门控(Versioning):给飞行中的飞机换引擎

这是 Temporal 最容易踩的坑,也是它最强大的地方。

假设你的 Agent 已经在生产环境跑了。你修改了 Workflow 的代码:

  • 旧代码:先搜 Google,再写报告。
  • 新代码:先搜 Google,再搜 Bing,再写报告。

此时,有一个旧任务刚搜完 Google,正在休眠。如果你发布了新代码,它醒来继续跑(Replay),发现代码里多了一步“搜 Bing”,但历史记录里直接是“写报告”。 BOOM!Non-Deterministic Error(非确定性错误)。 Workflow 会直接死掉。 0007页.png 解决方案:GetVersion 你必须告诉 Temporal:“如果是老玩家,走老路;如果是新玩家,走新路。”

// 伪代码:版本门控
version = workflow.GetVersion("add_bing_search", DefaultVersion, 1)

if version == DefaultVersion {
    // 老任务:保持原样
    SearchGoogle()
} else {
    // 新任务:执行新逻辑
    SearchGoogle()
    SearchBing()
}

这就像 忒修斯之船,你在航行中替换了零件,但船还得是那艘船。

05. 信号(Signal)与查询(Query):与正在跑的任务对话

有些任务一跑就是好几天(比如“写一本小说”),我们不能干等着。

信号(Signal):插手干预

用户说:“别写了,暂停一下,我改个大纲。” 通过 Signal,你可以向正在运行的 Workflow 发送指令,改变它的内部状态。

查询(Query):偷看进度

用户问:“写多少字了?” 通过 Query,你可以实时获取 Workflow 内部变量的快照,而不会干扰它的运行。

06. 避坑指南:架构师的血泪史

坑 1:在 Workflow 里用 time.Now()

后果:本地跑没问题,上线一 Replay 就报错。因为回放时的时间和第一次执行时不一致。 解法:必须用 workflow.Now(ctx)。这是 Temporal 提供的“冻结时间”。

坑 2:Activity 返回超大包

后果:Agent 搜索了一篇 10MB 的论文,直接作为 Activity Result 返回。这会把 Temporal 的数据库撑爆,且不仅慢,还可能导致 Workflow 超时。 解法传引用。把大文件存 S3,只返回 S3 URL。

坑 3:无限循环

后果:一个 for 循环跑了 1 万次 Activity。Event History 变得巨大无比,每次 Replay 都要花几分钟加载历史。 解法:使用 Continue-As-New。就像分页一样,把当前状态传给一个新的 Workflow 实例,清空历史包袱,轻装上阵。

总结

Temporal 是 Agent 系统架构中 最重 的组件,但也是 最值 的组件。

  • 它让你的 Agent 具备了 容错性(崩溃恢复)。
  • 它让你的 Agent 具备了 可交互性(暂停/恢复)。
  • 它让你的 Agent 具备了 可观测性(每一步都有记录)。

掌握了 Temporal,你就从“写脚本的人”进化成了“掌控时间的人”。

下一章预告

系统跑起来了,也摔不死了。但还有一个问题:它到底跑得怎么样? 用户说“太慢了”,是卡在 LLM 上,还是卡在数据库上? Agent 说“出错了”,到底是 Prompt 没写好,还是网络超时?

下一章,我们将进入 Part 7 的终章 —— 可观测性(Observability) 。 不仅是看日志,我们要聊 Tracing(链路追踪)、Metrics(指标监控)和 Cost Analysis(成本分析) ,给你的 Agent 装上“X 光机”。

0015页.png