建立单步骤的 AI 操作很简单:接收输入、呼叫 LLM、回传输出。但建立一个支援分支、回圈、平行执行、人工审批关卡和自动重试的多步骤工作流引擎,则是完全不同层次的问题。
本文将介绍 JieGou 工作流引擎的架构 — 执行模型、8 种步骤类型、步骤间的资料流动方式、审批如何暂停和恢复执行,以及确保一切可靠运作的安全机制。
执行模型
工作流是一个由步骤组成的有向图。执行从 executeWorkflow() 开始,它会建立 WorkflowRun 记录、建立共享的 StepExecutionContext,并呼叫 executeStepList() 来依序处理步骤。
执行环境(context)携带一个 previousStepOutputs Map — 这是一个键值对储存空间,每个完成的步骤会将其输出存放在这里,供下游步骤参考。这是资料流动的主干。步骤 B 可以使用像 {{step.stepA.fieldName}} 这样的范本语法来参考步骤 A 的输出。
每个工作流都有可配置的逾时设定(预设 5 分钟),透过执行环境中的 deadlineMs 栏位来强制执行。个别步骤也有自己的逾时设定(预设 60 秒),由 withStepTimeout() 包装器强制执行。
8 种步骤类型
Recipe 步骤
主力步骤。透过 executeRecipe() 执行可重复使用的提示词范本,并将解析后的输出储存在 previousStepOutputs 中。输入映射会解析对工作流输入、先前步骤输出、静态值或回圈项目的参考。
条件步骤
评估布林表达式,并递回执行 thenSteps 或 elseSteps。这是真正的分支 — 两条路径都可以包含任何步骤类型,包括巢状条件。引擎会对选中的分支递回呼叫 executeStepList()。
回圈步骤
遍历一个集合,并为每个项目执行一系列子步骤。集合可以来自 4 个来源:工作流中定义的静态阵列、先前步骤的输出、工作流输入栏位,或父回圈项目。
每次迭代都有自己的 loopContext Map,因此子步骤可以透过 {{loop_item.fieldName}} 参考当前项目。迭代结果会以阵列形式储存,并包含巢状的 stepRuns 以提供可观测性。
平行步骤
透过 Promise.allSettled() 同时执行多个分支。每个分支都是独立的步骤列表,拥有自己的 stepRuns 阵列。当多个独立操作可以同时执行时,这非常有用 — 例如,同时从三个不同的资料来源丰富潜在客户资料。
审批步骤
架构上最有趣的步骤类型。当执行到达审批步骤时,会抛出一个 ApprovalPauseError。这是一个受控异常 — 不是崩溃。
这个错误会在顶层被捕获,WorkflowRun 会以 pending_approval 状态持久化,并透过电子邮件通知合格的审批者。执行完全停止。不会占用任何资源。
当审批者采取行动(透过 API 批准或拒绝)时,resumeWorkflowFromApproval() 会载入已持久化的执行记录,呼叫 reconstructPreviousOutputs() 从已储存的 stepRuns 重建 previousStepOutputs Map,并从审批关卡后的下一步恢复执行。
重建是递回的 — 它会遍历 thenStepRuns、elseStepRuns、iterations[] 和 branchStepRuns[] 来恢复每个巢状输出。这意味着即使审批位于条件分支或回圈迭代内部,也能正确运作。
写入知识库步骤
捕获步骤的输出并将其写入知识库文件。这使得工作流能够建立组织知识 — 例如,客户支援分流工作流可能会将解决方案摘要写入知识库,供未来执行透过 RAG 参考。
移交步骤
透过电子邮件和应用程式内通知来通知目标部门的使用者。对于执行来说这是一个空操作 — 它不产生输出也不阻塞流程。对于需要提醒人工介入但工作流应该继续执行的升级流程很有用。
浏览器动作步骤
透过浏览器扩充功能执行 MCP 工具呼叫。从 MCP 连线池中获取客户端,解析范本参数(工作流输入栏位和先前步骤输出),并回传工具结果。
资料流动:范本解析
步骤透过在执行时解析的范本语法相互参考:
{{workflow_input.fieldName}}— 参考工作流的输入资料{{step.stepId.path.to.value}}— 使用点号表示法参考先前步骤的输出{{loop_item.fieldName}}— 参考回圈迭代中的当前项目
解析器使用 getNestedValue() 进行点号表示法的路径遍历,处理阵列和巢状物件。输入映射会明确宣告其来源:workflowInput、previousStep、static 或 loopItem。
重试与错误处理
并非所有失败都是永久性的。速率限制、暂时性 5xx 错误、逾时和连线失败都是可重试的。客户端验证错误(4xx)则不可重试。
重试策略使用带抖动的指数退避:Math.min(30000, 2000 * 2^attempt + random jitter)。大约是 2 秒、4 秒、8 秒、16 秒,上限为 30 秒,并加上随机抖动以避免惊群问题。预设最大尝试次数为 3 次,每个步骤可配置。
每次重试都会记录尝试索引和错误详情。如果所有尝试都失败,步骤会被标记为失败,工作流会终止(除非工作流层级的错误处理另有规定)。
并发控制
每个帐户透过 Redis 信号量限制为 10 个并发 LLM 呼叫。这可以防止批次工作流垄断供应商连线。
MCP 连线使用带 LRU 淘汰的连线池(最多 20 个连线,60 秒闲置逾时)。连线池处理连线设定、保持活动和清理。
两个系统在 Redis 错误时都使用开放失败语义 — 降级的快取层绝不应阻止工作流执行。
可观测性
三层可观测性同时运作:
Prometheus 指标追踪按状态分类的 workflowExecutionsTotal 和 workflowDuration。这为成功率和延迟百分位数提供操作仪表板。
OpenTelemetry spans 建立具有工作流层级和步骤层级 span 的追踪阶层,携带工作流 ID、名称和步骤中继资料。这些可以与任何相容 OTel 的后端整合。
执行追踪是一个自订的阶层式 span 树,透过 fire-and-forget 方式持久化到 Firestore。这些为 UI 中的详细执行检查器提供动力,精确显示每个步骤发生的事情,包括输入、输出、计时和重试尝试。
执行前检查
在执行开始之前,引擎会执行验证:
- 输入架构验证 — 工作流输入会根据宣告的架构进行检查
- 映射验证 — 验证所有步骤输入映射(参考的步骤存在,路径合理)
- MCP 验证 — 如果任何步骤需要浏览器动作,引擎会预先验证 MCP 客户端连线,而不是在执行中途失败
- 自动上下文解析 — 从工作流、部门和明确 ID 来源解析知识库文件,因此在第一步执行之前 RAG 上下文就已准备就绪
经验教训
**将审批关卡视为异常简化了架构。**我们的第一个设计使用带有明确暂停/恢复状态的状态机。基于异常的方法更简洁 — 工作流向前执行直到遇到审批,抛出异常,持久化,然后停止。恢复时重建状态并继续。无需管理复杂的状态转换。
**递回输出重建值得其复杂性。**从持久化的步骤执行记录重建 previousStepOutputs 需要遍历每个巢状层级 — 条件、回圈、平行分支。这是复杂的程式码,但这意味着审批在任何巢状深度都能正确运作,无需特殊处理。
**每个步骤的逾时防止级联延迟。**单个缓慢的 LLM 呼叫不应该耗尽整个工作流的逾时时间。预设的 60 秒单步逾时能及早捕获挂起的请求,而工作流层级的逾时则作为最后防线。
**Fire-and-forget 可观测性保持热路径的快速。**执行追踪、webhook 传递和目标输出都在主执行完成后非同步分派。使用者无需等待可观测性写入完成就能获得结果。