使用 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/ 搜尋隨附的文件:
- 尋找文件:
glob "node_modules/workflow/docs/**/*.mdx" - 搜尋內容:
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。
官方資源
- Website: https://workflow-sdk.dev
- GitHub: https://github.com/vercel/workflow
快速參考
指令(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);
}
}
關鍵規則:
- 在類別主體內部定義 serde 方法,作為使用計算屬性語法的靜態方法(
static [WORKFLOW_SERIALIZE](...))。SWC 外掛會透過掃描類別來偵測它們。切勿在外部指派(例如(MyClass as any)[WORKFLOW_SERIALIZE] = ...)——編譯器將無法偵測到。 - Serde 方法必須僅傳回與 devalue 相容的型別(純物件、陣列、基本型別、Date、Map、Set、Uint8Array 等)。不得包含函式、類別實例或 Node.js 特有的物件。
- 為依賴 Node.js 的實例方法加上
"use step"。 SWC 外掛會從 workflow 打包檔中移除"use step"方法的主體內容。這就是將 Node.js 匯入(fs、crypto、child_process 等)排除在 workflow 沙盒之外的方法。包含 serde 方法的類別外殼仍會保留在 workflow 中。






