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

推荐订阅源

B
Blog RSS Feed
J
Java Code Geeks
C
Check Point Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
Engineering at Meta
Engineering at Meta
Blog — PlanetScale
Blog — PlanetScale
D
Docker
H
Hackread – Cybersecurity News, Data Breaches, AI and More
月光博客
月光博客
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
A
About on SuperTechFans
L
LangChain Blog
腾讯CDC
Y
Y Combinator Blog
MongoDB | Blog
MongoDB | Blog
Vercel News
Vercel News
MyScale Blog
MyScale Blog
博客园 - Franky
IT之家
IT之家
博客园_首页

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.