当你将大语言模型接入一个需要通过电子邮件进行人工确认的工作流时,出问题的往往不是模型本身。故障发生在代码结束与收件箱开始的交界处。一次自动运行触发了一个请求,随后在第一个请求完成前,另一个运行又开始了。一个共享收件箱收集了来自不同进程的邮件线程。有人点击批准了一条延迟了 12 小时才到达的消息。现在你有了输出,也有了决策。但你无法证明是哪次运行产生了什么结果,甚至无法证明该审批是否真的针对这次生成。我清理过足够多的内部自动化流水线,深知这种模式。它从混乱演变为事故的速度,比大多数团队预期的都要快。

运营边界

你的编排器(orchestrator)与电子邮件提供商之间的边界不仅仅是一个网络跳跃,它是一个状态边界。当 LLM 完成草稿生成时,该次运行(run)仍然处于活跃状态。它在等待。如果你的系统将“发送”视为一个“触发后即不管”(fire-and-forget)的事件,那么你已经失去了对流程的掌控。

我见过这样的流水线:由于重试策略过于激进,单次运行生成了两个独立的审批请求。我也见过另一次运行回收了一个仍保留着上周邮件的邮箱。人类审批者看不到 run_id。他们看到的只是主题行和一个按钮。如果没有结构化管理,他们只能在那个混杂着营销新闻稿和监控告警的收件箱里进行盲猜。

被忽视的步骤

团队会花数周时间调整提示词、添加护栏并对输出进行基准测试。然后,他们将审批步骤接入 Slack 频道或共享支持收件箱,便认为大功告成了。这会造成三种可以预见的损伤:

  • 共享收件箱变成了多个运行实例事件的垃圾场。 上下文坍塌。如果不手动打开邮件线程并解析时间戳,你无法重建哪条消息属于哪笔业务交易。
  • 重试会覆盖证据。 如果一次运行重新发送了审批请求,原始消息可能会被过度积极的邮件客户端埋没、删除或标记为重复邮件。审计追踪(audit trail)因此变得支离破碎。
  • 人类决策游离在系统之外。 有人在工单或私信中回复“看起来不错”。这种情绪从未转化为工作流内部的结构化数据。代理(agent)无法验证是谁在何时说了什么。

当问题发生需要调查时,你得到的只能是传闻。“我觉得那是正确的邮件。”记忆并不等同于可追溯性。审计日志无法消化直觉。

从交付细节到检查点

修复这个问题需要设计思维的转变。不要再把电子邮件仅仅视为一种交付细节,而要开始将其视为系统检查点(checkpoint)。这意味着每条消息都是一次状态转换,而每一次状态转换都需要身份、授权和证据。

当你采用这种思维方式时,问题也会随之改变。你不再询问邮件是否发送成功,而是开始询问是哪次运行发送了它、它留下了什么证据,以及哪条规则授权了工作流的继续执行。LLM 完全可以编写邮件正文,但你的平台必须强制执行身份和验证路径。LLM 是撰稿人,而基础设施则是公证人。

最简设计

你不需要花费巨资来构建这一切。我的最简可行版本包含五个刻意的组成部分:

  • 编排器在工作流启动的瞬间生成一个 run_id 这个标识符是后续所有操作的脊梁。它永不改变,也永不复用。
  • 每项邮件操作都携带三个字段: run_id、一个消息类型标签(例如 approval_requestevidence_notification),以及一个标识当前生效治理规则的 policy_version 字符串。这把一条普通消息变成了一个类型化的事件。
  • 证据保存在由 run 隔离的收件箱中。 这并不一定意味着为每次运行都创建一个单独的邮箱账户。它可以意味着一个专门的标签、一个子文件夹,或者一种路由规则,通过对邮件线程进行分段,确保一次运行的通信不会与另一次运行混淆。
  • 审批响应必须是一个结构化事件,而不是一段自由文本“ok”。 人类仍然是点击或回复,但系统会将该动作转化为机器可读的负载(payload),其中包含 run_id、决策结果和时间戳。
  • 只有当证据与决策匹配时,流程才会继续。 工作流不会孤立地信任审批结果。在允许 LLM 的输出进入生产环境之前,它会根据原始请求验证审批负载。

有效检查点所验证的内容

一个有效的检查点在接受人工决策前会强制执行四个条件。

  • 接收者必须属于运行上下文。如果审批者不是该特定工作流实例的指定审核员,系统将拒绝该信号。
  • 主题或路由元数据必须与当前流程状态匹配。对第三步的批准不能绕过第二步。
  • 时间戳必须落在预期的窗口期内。超时后到达的决策应当触发重新审核,而非自动通过。
  • 证据不得被另一个运行实例重复使用。如果同一个消息 ID 或令牌出现在两个不同的审批请求中,则视为冲突,系统应当停止。

真正的代价

这种模式并非没有代价。你需要存储更多的元数据。你增加了一个必须由人维护的策略层。你强制要求团队将人工决策记录为结构化数据,而不是随意的评论。这看起来像是官僚主义。但在实践中,这是一个极佳的权衡。

你是在用速度换取清晰度。