惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Security Blog
Microsoft Security Blog
量子位
大猫的无限游戏
大猫的无限游戏
酷 壳 – CoolShell
酷 壳 – CoolShell
IT之家
IT之家
博客园 - 三生石上(FineUI控件)
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - Franky
美团技术团队
Last Week in AI
Last Week in AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
罗磊的独立博客
Jina AI
Jina AI
小众软件
小众软件
S
SegmentFault 最新的问题
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
雷峰网
雷峰网
博客园 - 聂微东
博客园_首页
The Cloudflare Blog
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队

Workflow SDK Documentation

Patterns for Defining Tools Human-in-the-Loop Building Durable AI Agents Queueing User Messages Resumable Streams Sleep, Suspense, and Scheduling Streaming Updates from Tools API Reference Workflow Globals Changelog Resilient run start Cookbook Building a World Deploying Astro Express Fastify Hono Getting Started NestJS Next.js Nitro Nuxt Python SvelteKit Vite corrupted-event-log fetch-in-workflow hook-conflict Errors
Serializable Steps
2026-05-31 · via Workflow SDK Documentation

Wrap non-serializable third-party objects (like AI model providers) inside step factory functions so they can cross the workflow boundary.

This is an advanced guide. It dives into workflow internals and is not required reading to use workflow.

Workflow functions run inside a sandboxed VM where every value that crosses a function boundary must be serializable. There are two ways to get a non-serializable object across that boundary, depending on whether you own the class:

  • You own the class — implement the WORKFLOW_SERIALIZE / WORKFLOW_DESERIALIZE protocol. The instance becomes a first-class serializable value: you can pass it as a workflow input, return it from a step, and call "use step" instance methods on it directly. This is the right tool when the class is yours to modify.
  • You don't own the class — you can't add methods to openai("gpt-4o") from @ai-sdk/openai or new S3Client({...}) from @aws-sdk/client-s3. Instead, wrap construction in a "use step" factory function and pass the factory across the boundary. That's what this page covers.

AI SDK model providers — openai("gpt-4o"), anthropic("claude-sonnet-4-20250514"), etc. — return complex objects with methods, closures, and internal state. Passing one directly into a step causes a serialization error, and you can't bolt WORKFLOW_SERIALIZE onto a third-party class.

import { openai } from "@ai-sdk/openai";
import { DurableAgent } from "@workflow/ai/agent";
import { getWritable } from "workflow";
import type { UIMessageChunk } from "ai";

export async function brokenAgent(prompt: string) {
  "use workflow";

  const writable = getWritable<UIMessageChunk>();
  const agent = new DurableAgent({
    // This fails — the model object is not serializable
    model: openai("gpt-4o"),
  });

  await agent.stream({ messages: [{ role: "user", content: prompt }], writable });
}

Instead of passing the model object, pass a callback function that returns the model. Marking that callback with "use step" tells the compiler to serialize the function reference (which is just a string identifier) rather than its return value. The provider is only instantiated at execution time, inside the step's full Node.js runtime.

import { openai as openaiProvider } from "@ai-sdk/openai";

// Returns a step function, not a model object
export function openai(...args: Parameters<typeof openaiProvider>) {
  return async () => {
    "use step";
    return openaiProvider(...args); 
  };
}

The DurableAgent receives a function (() => Promise<LanguageModel>) instead of a model object. When the agent needs to call the LLM, it invokes the factory inside a step where the real provider can be constructed with full Node.js access.

The @workflow/ai package ships pre-wrapped providers for all major AI SDK backends. Each one follows the same pattern:

// packages/ai/src/providers/anthropic.ts
import { anthropic as anthropicProvider } from "@ai-sdk/anthropic";

export function anthropic(...args: Parameters<typeof anthropicProvider>) {
  return async () => {
    "use step";
    return anthropicProvider(...args); 
  };
}

This means you import from @workflow/ai instead of @ai-sdk/* directly:

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

export async function chatAgent(prompt: string) {
  "use workflow";

  const writable = getWritable<UIMessageChunk>();
  const agent = new DurableAgent({
    model: anthropic("claude-sonnet-4-20250514"), 
  });

  await agent.stream({ messages: [{ role: "user", content: prompt }], writable });
}

Apply the same pattern to any non-serializable dependency. The key rule: the outer function captures serializable arguments, and the inner "use step" function constructs the real object at runtime.

import type { S3Client as S3ClientType } from "@aws-sdk/client-s3";

// The arguments (region, bucket) are plain strings — serializable
export function createS3Client(region: string) {
  return async (): Promise<S3ClientType> => {
    "use step";
    const { S3Client } = await import("@aws-sdk/client-s3");
    return new S3Client({ region });
  };
}

// Usage in a workflow
export async function processUpload(region: string, key: string) {
  "use workflow";

  const getClient = createS3Client(region); 
  // getClient is a serializable step reference, not an S3Client
  await uploadFile(getClient, key);
}

async function uploadFile(
  getClient: () => Promise<S3ClientType>,
  key: string
) {
  "use step";
  const client = await getClient(); 
  // Now you have a real S3Client with full Node.js access
  await client.send(/* ... */);
}
  1. Compiler transformation: "use step" tells the SWC plugin to extract the function into a separate bundle. The workflow VM only sees a serializable reference (function ID + captured arguments).
  2. Closure tracking: The compiler tracks which variables the step function closes over. Only serializable values (strings, numbers, plain objects) can be captured.
  3. Deferred construction: The actual provider/client is only constructed when the step executes in the Node.js runtime — never in the sandboxed workflow VM.