workflow

workflow

熱門

使用 Vercel 的 Workflow SDK 建立具備持久化(durable)與可復原(resumable)特性的工作流程。適合用於需要跨重啟存活、暫停等待外部事件、失敗自動重試,或需要長期協調多步驟操作的工作流程建置。當提及 "workflow"、"durable functions"、"resumable"、"workflow sdk"、"queue"、"event"、"push"、"subscribe" 或基於步驟的編排(step-based orchestration)時觸發。

2236星標
308分支
更新於 2026/7/23
SKILL.md
唯讀
名稱
workflow
描述

使用 Vercel 的 Workflow SDK 建立具備持久化(durable)與可復原(resumable)特性的工作流程。適合用於需要跨重啟存活、暫停等待外部事件、失敗自動重試,或需要長期協調多步驟操作的工作流程建置。當提及 "workflow"、"durable functions"、"resumable"、"workflow sdk"、"queue"、"event"、"push"、"subscribe" 或基於步驟的編排(step-based orchestration)時觸發。

重要提示:請務必使用正確的 workflow 文件

您對 workflow 的瞭解可能已經過時。

下方列出的 workflow 文件內容與已安裝的 Workflow SDK 版本一致。
在開始處理任何與 workflow 相關的任務前,請先遵循以下指示:

在 node_modules/workflow/docs/ 搜尋隨附的文件:

  1. 尋找文件:glob "node_modules/workflow/docs/**/*.mdx"
  2. 搜尋內容:grep "您的查詢關鍵字" node_modules/workflow/docs/

node_modules/workflow/docs/ 中的文件結構:

  • getting-started/ - 框架設定(next.mdx、express.mdx、hono.mdx 等)
  • foundations/ - 核心概念(workflows-and-steps.mdx、hooks.mdx、streaming.mdx 等)
  • api-reference/workflow/ - API 文件(sleep.mdx、create-hook.mdx、fatal-error.mdx 等)
  • api-reference/workflow-api/ - 客戶端 API(start.mdx、get-run.mdx、resume-hook.mdx 等)
  • api-reference/workflow-runtime/ - 執行階段 API(get-world.mdx)與 world/ World SDK(storage.mdx、streams.mdx、queue.mdx)
  • api-reference/workflow-observability/ - 資料復原(Hydration)與名稱解析工具(hydrate-resource-io.mdx、parse-workflow-name.mdx 等)
  • ai/ - AI SDK 整合文件
  • errors/ - 錯誤代碼文件

相關套件亦包含隨附文件:

  • @workflow/ai: node_modules/@workflow/ai/docs/ - DurableAgent 與 AI 整合
  • @workflow/core: node_modules/@workflow/core/docs/ - 核心執行階段(基礎概念、運作原理)
  • @workflow/next: node_modules/@workflow/next/docs/ - Next.js 整合

如有疑問,請更新至最新版本的 Workflow SDK。

官方資源

快速參考

指令(Directives):

"use workflow";  // 第一行 - 將 async 函式轉為持久化(durable)
"use step";      // 第一行 - 將函式轉為可快取、可重試的獨立單元

核心匯入(Essential imports):

// Workflow 基礎元件
import { sleep, fetch, createHook, createWebhook, getWritable } from "workflow";
import { FatalError, RetryableError } from "workflow";
import { getWorkflowMetadata, getStepMetadata } from "workflow";

// API 操作
import { start, getRun, resumeHook, resumeWebhook } from "workflow/api";

// 可觀測性與資料復原
import { hydrateResourceIO, observabilityRevivers, parseStepName, parseWorkflowName } from "workflow/observability";

// 框架整合
import { withWorkflow } from "workflow/next";
import { workflow } from "workflow/vite";
import { workflow } from "workflow/astro";
// 或使用 modules: ["workflow/nitro"] 搭配 Nitro/Nuxt

// AI Agent
import { DurableAgent } from "@workflow/ai/agent";

優先使用 Step 函式以避免沙盒錯誤

"use workflow" 函式是在沙盒化的虛擬機器(VM)中執行。而 "use step" 函式則擁有完整的 Node.js 存取權限。請將商業邏輯放在 step 中,並將 workflow 函式純粹作為編排用途。

// Step 擁有完整的 Node.js 及 npm 存取權限
async function fetchUserData(userId: string) {
  "use step";
  const response = await fetch(`https://api.example.com/users/${userId}`);
  return response.json();
}

async function processWithAI(data: any) {
  "use step";
  // AI SDK 可直接在 step 中運作,無需額外變通方法
  return await generateText({
    model: openai("gpt-4"),
    prompt: `Process: ${JSON.stringify(data)}`,
  });
}

// Workflow 用於編排 step - 無沙盒限制問題
export async function dataProcessingWorkflow(userId: string) {
  "use workflow";
  const data = await fetchUserData(userId);
  const processed = await processWithAI(data);
  return { success: true, processed };
}

優點: Step 具備自動重試機制、執行結果會被持久化儲存以利重播(replay),且不受沙盒限制。

Workflow 沙盒限制

當你需要直接在 workflow 函式中編寫邏輯(而非放在 step 中)時,需注意以下限制:

限制 變通方法
無法使用 fetch() 先 import { fetch } from "workflow",再執行 globalThis.fetch = fetch
無法使用 setTimeout/setInterval 使用來自 "workflow" 的 sleep("5s")
無法使用 Node.js 模組(fs、crypto 等) 移至 step 函式中執行

範例 - 在 workflow 上下文中呼叫 fetch:

import { fetch } from "workflow";

export async function myWorkflow() {
  "use workflow";
  globalThis.fetch = fetch;  // AI SDK 與 HTTP 套件運作所需
  // 現在 generateText() 及其他函式庫皆可正常運作
}

注意: 來自 @workflow/ai 的 DurableAgent 會自動處理 fetch 的賦值。

DurableAgent — Workflow 中的 AI Agent

使用 DurableAgent 來建立可維護狀態並跨中斷存活的 AI Agent。它會自動處理 workflow 沙盒限制(無需手動設置 globalThis.fetch)。

import { DurableAgent } from "@workflow/ai/agent";
import { getWritable } from "workflow";
import { z } from "zod";
import type { UIMessageChunk } from "ai";

async function lookupData({ query }: { query: string }) {
  "use step";
  // Step 函式擁有完整的 Node.js 存取權限
  return `Results for "${query}"`;
}

export async function myAgentWorkflow(userMessage: string) {
  "use workflow";

  const agent = new DurableAgent({
    model: "anthropic/claude-sonnet-4-5",
    system: "You are a helpful assistant.",
    tools: {
      lookupData: {
        description: "Search for information",
        inputSchema: z.object({ query: z.string() }),
        execute: lookupData,
      },
    },
  });

  const result = await agent.stream({
    messages: [{ role: "user", content: userMessage }],
    writable: getWritable<UIMessageChunk>(),
    maxSteps: 10,
  });

  return result.messages;
}

重點說明:

  • getWritable<UIMessageChunk>() 會將輸出串流傳送至 workflow 執行的預設串流
  • 需要 Node.js/npm 存取權限的工具 execute 函式應加入 "use step"
  • 使用 workflow 基礎元件(sleep()、createHook())的工具 execute 函式不應使用 "use step" — 它們會在 workflow 層級執行
  • maxSteps 用於限制 LLM 呼叫次數(預設為無限制)
  • 多輪對話:將 result.messages 加上新的使用者訊息傳入後續的 agent.stream() 呼叫中

關於 DurableAgent 的更多細節,請參閱 node_modules/@workflow/ai/docs/ 中的 AI 文件。

啟動 Workflow 與子 Workflow

使用 start() 從 API 路由中啟動 workflow。start() 無法直接在 workflow 上下文中呼叫 — 需將其包裝在 step 函式中。

import { start } from "workflow/api";

// 在 API 路由中 — 可直接使用
export async function POST() {
  const run = await start(myWorkflow, [arg1, arg2]);
  return Response.json({ runId: run.runId });
}

// 無引數的 workflow
const run = await start(noArgWorkflow);

從 workflow 內部啟動子 workflow — 必須使用 step:

import { start } from "workflow/api";

// 將 start() 包裝在 step 函式中
async function triggerChild(data: string) {
  "use step";
  const run = await start(childWorkflow, [data]);
  return run.runId;
}

export async function parentWorkflow() {
  "use workflow";
  const childRunId = await triggerChild("some data");  // 透過 step 觸發(觸發後即不管)
  await sleep("1h");
}

start() 會立即傳回 — 不會等待 workflow 完成。若需等待完成請使用 run.returnValue。

Hooks — 暫停與透過外部事件復原

Hook 能讓 workflow 等待外部資料。在 workflow 內部使用 createHook(),並在 API 路由中使用 resumeHook()。確定性 Token(Deterministic tokens)僅適用於 createHook() + resumeHook()(伺服器端)。createWebhook() 總是會產生隨機 Token — 切勿傳遞 token 選項給 createWebhook()。

單一事件

import { createHook } from "workflow";

export async function approvalWorkflow() {
  "use workflow";

  const hook = createHook<{ approved: boolean }>({
    token: "approval-123",  // 供外部系統使用的確定性 Token
  });

  const result = await hook;  // Workflow 會在此暫停
  return result.approved;
}

多個事件(可疊代 Hooks)

Hook 實作了 AsyncIterable — 可使用 for await...of 來接收多個事件:

import { createHook } from "workflow";

export async function chatWorkflow(channelId: string) {
  "use workflow";

  const hook = createHook<{ text: string; done?: boolean }>({
    token: `chat-${channelId}`,
  });

  for await (const event of hook) {
    await processMessage(event.text);
    if (event.done) break;
  }
}

每一次呼叫 resumeHook(token, payload) 都會將下一個值傳送至迴圈中。

從 API 路由進行復原

import { resumeHook } from "workflow/api";

export async function POST(req: Request) {
  const { token, data } = await req.json();
  await resumeHook(token, data);
  return new Response("ok");
}

錯誤處理

對於永久性故障(不進行重試)使用 FatalError;對於暫時性故障使用 RetryableError:

import { FatalError, RetryableError } from "workflow";

if (res.status >= 400 && res.status < 500) {
  throw new FatalError(`Client error: ${res.status}`);
}
if (res.status === 429) {
  throw new RetryableError("Rate limited", { retryAfter: "5m" });
}

序列化

所有傳入/傳出 workflow 和 step 的資料都必須是可序列化的。

支援的內建型別: string、number、boolean、null、undefined、bigint、純物件(plain objects)、陣列、Date、RegExp、URL、URLSearchParams、Map、Set、Headers、ArrayBuffer、typed arrays、Request、Response、ReadableStream、WritableStream。

不支援: Functions、Symbols、WeakMap/WeakSet。請傳遞資料而非回呼函式(callbacks)。

自訂類別序列化

類別實例(Class instances)可以透過實作 @workflow/serde 協定來跨越 workflow/step 的邊界進行序列化。當類別包含帶有 "use step" 的實例方法,或需要在不同 step 之間傳遞類別實例時,這是不可或缺的。

安裝: @workflow/serde 必須是包含該類別之套件的依賴項(dependency)。

模式: 使用計算屬性語法在類別主體(class body)內部新增兩個靜態方法:

import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";

export class Point {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  // 序列化:傳回純資料(僅能包含與 devalue 相容的型別)
  static [WORKFLOW_SERIALIZE](instance: Point) {
    return { x: instance.x, y: instance.y };
  }

  // 反序列化:從純資料重建物件
  static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) {
    return new Point(data.x, data.y);
  }

  async computeDistance(other: Point) {
    "use step";
    return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
  }
}

關鍵規則:

  1. 在類別主體內部定義 serde 方法,作為使用計算屬性語法的靜態方法(static [WORKFLOW_SERIALIZE](...))。SWC 外掛會透過掃描類別來偵測它們。切勿在外部指派(例如 (MyClass as any)[WORKFLOW_SERIALIZE] = ...)——編譯器將無法偵測到。
  2. Serde 方法必須僅傳回與 devalue 相容的型別(純物件、陣列、基本型別、Date、Map、Set、Uint8Array 等)。不得包含函式、類別實例或 Node.js 特有的物件。
  3. 為依賴 Node.js 的實例方法加上 "use step"。 SWC 外掛會從 workflow 打包檔中移除 "use step" 方法的主體內容。這就是將 Node.js 匯入(fs、crypto、child_process 等)排除在 workflow 沙盒之外的方法。包含 serde 方法的類別外殼仍會保留在 workflow 中。