开发者生态
morning
与运行时无关的 AI 工作流:一种兼顾生产环境稳定性和快速评估迭代的模式
2026-08-13
1 阅读
约10分钟阅读
作者:Mateus Moury
字号:
AI 工作流是一系列为完成某项任务而串联起来的步骤,其中一个或多个步骤包含对 大语言模型(LLM) "的调用。在此,我们将这些步骤组合在一起的逻辑(包括步骤的顺序和分支)称为工作流的协调逻辑。 对于这些工作流的生产要求,与过去十年间任何长期运行的分布式系统相同:它们需要能够经受住部署和崩溃的考验,能够以幂等的方式重试,并实现水平扩展。工作流引擎早在多年前就已经解决了这一类问题。 AI 工作流的独特之处在于,LLM 步骤的输出质量可能会随着每次提示词微调或模型变更而发生漂移。因此,必须通过评估(即在标注数据集上离线运行工作流并对输出进行评分)来对其进行验证。 反过来,这又要求经典工作流引擎具备其设计时未曾考虑的功能:一个成本足够低、能够重复运行数百次的快速评估循环。 这两项要求是相互矛盾的。生产环境的持久性需要一个重量级、分布式的持久化运行时环境;而评估迭代则需要一个轻量级、可在进程内运行的短暂循环,而且要能够在几秒钟内重新运行。大多数技术栈都是围绕其中一种运行时环境构建的。 虽然持久性优先的运行时确实提供了测试环境,但它们需要为一个根本不需要这些组件的评估循环搭建沙箱、任务队列和测试服务器。本文介绍了我们用来消除这种权衡的模式。 该模式源自 Brex 的 AI 工作流平台。该平台采用 TypeScript 编写,由五名工程师组成的团队负责维护。其工作进程运行在 Brex 的 Kubernetes 集群上,并连接到托管型 Temporal " 服务 Temporal Cloud " 来执行长期运行的代理。 代理通过 Vercel AI SDK " 访问大语言模型(LLM)。该 SDK 会将请求路由至内部 LLM 网关,该网关集中管理速率限制和身份验证。评估则在我们的自建平台上运行。 生产环境稳定性与快速离线评估之间的权衡 让我们先从工作流引擎的功能说起。 持久化执行 "要求在执行下一步之前,必须将每一步的结果持久化存储。如果流程发生崩溃、被重新部署,或者被重新调度到另一个工作节点上,引擎会回放历史记录,并从中断处精确地继续执行。状态的存续时间超过任何单个进程。这正是人们对一个 深度研究代理 "的期望——该代理需运行一小时,期间会调用数十次大语言模型(LLM)并调用各类工具:绝不能因为一个 Pod 被回收而损失四十分钟的工作成果。 现在来看评估迭代功能。你正在调整提示词或分支决策。你希望修改一行代码,加载包含几百个示例的数据集,并查看汇总分数。这个循环是局部的,在进程内,而且非常短暂。它不应该在集群范围内持久化或调度任何内容。任何内容都不应该在运行结束后继续存活。你需要模拟依赖项,使其输出保持稳定,从而将大语言模型(你实际正在评估的部分)隔离成不同运行之间唯一会变化的因素。这样,该循环的执行成本就会变得足够低,每小时可以运行数百次。 将评估代码通过工作流引擎运行会导致类别不匹配。你会继承持久化、任务队列、工作进程以及重放语义。这些开销与你所需要的紧凑循环相悖。反之,若将生产环境代码通过评估框架运行,则无法提供你那个运行一小时的代理所依赖的任何持久性保证。 它们是为解决不同问题而存在的不同的运行时。你的编排不应是被迫二选其一。但在实际中,情况几乎总是如此。 大多数技术栈都强迫你选择其一 团队最终会与某个运行时“捆绑”在一起,这是因为在主流工具的设计中,编排与运行时是紧耦合的。诸如 LangGraph " 和 Mastra " 之类的代理框架,会在其自身的 SDK 中直接表达编排逻辑。你的控制流会转化为图中的节点和边,或者转化为框架提供的领域特定语言(DSL)。编排逻辑与框架本身是同一个组件。要评估该逻辑,就需要运行框架;要部署该逻辑,同样需要运行框架。不存在独立于框架而存在的编排。 以下是一个具体的示例,展示了一个 Mastra 工作流 classifyBusinessAgent(即我们稍后将重写的那个): import { createWorkflow, createStep } from "@mastra/core/workflows"; import { z } from "zod"; const enrichWithWebData = createStep({ id: "enrich-with-web-data", inputSchema: z.object({ businessName: z.string(), website: z.string() }), outputSchema: z.object({ businessName: z.string(), webContext: z.string() }), execute: async ({ inputData }) => { // ... }, }); const classify = createStep({ id: "classify", inputSchema: z.object({ businessName: z.string(), webContext: z.string() }), outputSchema: z.object({ category: z.string() }), execute: async ({ inputData }) => // ... }); // 排序逻辑存在于 Mastra 的构建器而非普通的控制流中。 // 各个步骤是 Mastra 对象,且工作流只有在提交到其引擎后才会生效。 export const classifyBusinessWorkflow = createWorkflow({ id: "classify_business", inputSchema: z.object({ businessName: z.string(), website: z.string() }), outputSchema: z.object({ category: z.string() }), }) .then(enrichWithWebData) .then(classify) .commit(); 这里的一切都由 Mastra 掌控。在 .commit() 将图结构传递给 Mastra 的引擎之前,没有任何操作会执行。要评估这一逻辑,就需要启动该引擎。 像 Temporal 这样的工作流引擎则采取了相反的做法:它允许你使用通用语言编写编排代码,但会对编写方式施加限制。Temporal 的工作流代码必须是确定性的,因此你无法在编排代码内部直接调用 Date.now() 或执行 I/O 操作。所有这些操作都必须推入工作流步骤中。而且,跨工作流边界的数据对有效载荷的大小有限制。这些约束的存在是为了确保可重放性,但它们的存在也要求编排代码必须遵循引擎的规则。 Brex 的 开户代理 "运行在 Temporal 上的生产环境中,但其基于大语言模型(LLM)的决策需要持续调整。对于这些代理的评估,有一种简单粗暴的方法是在单独的评估运行时中重新实现每个代理,但这种方法会导致同一个逻辑存在两份副本,从而可能引发评估环境与生产环境之间的偏差,因为这两份副本可能会产生漂移。 评估与生产环境偏差正是该模式希望杜绝
这篇文章对您有帮助吗?
订阅66必读
每日精选科技资讯,直达你的邮箱