ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

使用 AI SDK WorkflowAgent 构建可恢复、可断点续跑的持久化聊天 Agent:next-workflow 示例全解析

使用 AI SDK WorkflowAgent 构建可恢复、可断点续跑的持久化聊天 Agent:next-workflow 示例全解析 使用 AI SDK WorkflowAgent 构建可恢复、可断点续跑的持久化聊天 Agentnext-workflow 示例全解析【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai导读本指南基于 AI SDK 仓库中的 examples/next-workflow 示例系统讲解如何用ai-sdk/workflow的WorkflowAgent搭配 Workflow DevKit 构建一个**持久化durable、可恢复resumable**的聊天 Agent。示例不仅覆盖了带工具调用天气查询、计算器、文件删除的完整聊天链路还演示了toModelOutput双视角输出、runtimeContext/toolsContext双上下文、工具审批、流式传输与断线重连以及基于 webhook 的异步视频生成工作流。读完本文你将掌握WorkflowAgent从「定义工具」到「部署运行」的完整实战路径并能直接复刻这套架构到自己的 Next.js 项目中。一、示例定位与核心特性examples/next-workflow是一个 Next.js 应用演示用 AI SDK 的WorkflowAgent来自ai-sdk/workflow包构建容错、可恢复的 AI Agent 执行。与普通的generateText/streamText调用不同WorkflowAgent把一次 Agent 运行编排成一组可持久化的步骤step每个步骤的状态都可以被记录和恢复因此运行中途进程重启也不会丢失进度。该示例在 README.md 中明确列出以下核心能力持久化 Agent基于ai-sdk/workflow的WorkflowAgent提供容错的 Agent 执行工具调用天气查询getWeather与计算器calculate作为持久化步骤实现toModelOutputgetWeather工具给模型发送紧凑的一行摘要而 UI 保留完整结构化结果流式传输通过getWritable()与createUIMessageStreamResponse实现实时流式响应可恢复Workflow 运行在重启后依然存活且可以被重新连接Telemetry E2E 测试台访问/telemetry运行确定性的 WorkflowAgent 遥测场景覆盖生命周期事件、工具执行、上下文过滤、审批、错误与重连Sandbox E2E 测试台访问/sandbox运行确定性的沙箱工具执行场景异步视频工作流访问/async-apis查找近期仓库维护者并把他们的 GitHub 头像转成 FAL 短视频进度实时流式推送到浏览器。从 package.json 可以看到项目的依赖组合ai-sdk/workflow、ai-sdk/anthropic、ai-sdk/fal、ai-sdk/react、ai、workflow5.0.0-beta.42以及zod4.4.3其中workflow包Workflow DevKit提供了getWritable、start/getRun、createWebhook等运行时能力。二、快速开始安装、环境变量与启动按照 README 的「Running」一节运行该示例只需四步# 1. 安装依赖仓库使用 pnpm workspace pnpm install # 2. 创建 .env.local 并填写目标页面所需的 API Key # ANTHROPIC_API_KEY... # FAL_API_KEY... # GITHUB_TOKEN... # 3. 启动开发服务器 pnpm dev # 4. 打开浏览器 # http://localhost:3000关于环境变量的说明README 原话要点三个 Key 按需配置跑聊天页需要ANTHROPIC_API_KEY跑异步视频页需要FAL_API_KEYGITHUB_TOKEN需要对你提交的仓库有读取权限公共仓库的公共访问权限即可满足在「Async APIs」一节README 还额外提及WORKFLOW_LOCAL_BASE_URL当本地无法被 FAL 回拨 webhook 时可把它设置为一个能转发到本地服务器的公共 HTTPS 地址来启用 webhook 路径详见后文第七节。主聊天页 app/page.tsx 使用ai-sdk/react的useChat并配置了WorkflowChatTransport作为传输层同时提供跳转到/telemetry、/sandbox、/async-apis三个测试台的入口。三、核心实现WorkflowAgent 聊天示例聊天工作流的全部逻辑位于 workflow/agent-chat.ts入口函数chat声明为use workflow其中use step标记的异步函数则成为可持久化的步骤export async function chat(messages: UIMessage[], request: ChatRequestContext) { use workflow; const modelMessages await convertToModelMessages(messages, { tools }); const agent new WorkflowAgent({ model: anthropic(claude-sonnet-4-20250514), instructions: You are a helpful assistant with access to weather, calculator, and file deletion tools. ..., tools, runtimeContext: { tenantId, requestId, plan }, toolsContext: { getWeather: {...}, deleteFile: {...} }, prepareStep: ({ runtimeContext }) { ... }, onEnd: ({ messages }) { ... }, }); const result await agent.stream({ messages: modelMessages, writable: getWritableModelCallStreamPart(), repairToolCall: repairToolCall as any, }); return { messages: result.messages }; }几个关键点模型示例使用anthropic(claude-sonnet-4-20250514)ai-sdk/anthropic来自仓库 workspace消息转换convertToModelMessages(messages, { tools })会把 UI 侧的UIMessage历史转换成模型消息。注意传入tools的目的——让历史轮次中遗留的工具结果也通过各工具的toModelOutput钩子重建与 WorkflowAgent 对新鲜工具结果应用的转换保持一致如果不传早前轮次的工具结果会回退到默认的json/text序列化导致跨轮次结果不一致源码注释对此有明确说明指令明确要求 Agent「该动手就调用工具不要只说要做」保持回复简洁容错repairToolCall回调接收ToolCallRepairFunctiontypeof tools类型示例中直接原样返回工具调用。3.1 工具即持久化步骤示例定义了两个核心工具每个工具的execute都以use step开头表示这是一个可以被持久化、暂停与恢复的工作流步骤async function getWeather(input: { city: string }, options: { context: { defaultUnit: ... } }) { use step; // 用城市名的字符码哈希生成确定性的温度与天气便于端到端演示 ... return { city, temperature, unit, condition }; } async function calculate(input: { expression: string }) { use step; const translated input.expression.replace(/\s/g, ).replace(/\^/g, **); if (!/^[0-9\-*/().]$/.test(translated)) throw new Error(Invalid expression: ${input.expression}); return { expression, result: new Function(return (${translated}))() as number }; }工具对象通过inputSchemazod校验输入通过可选的contextSchema校验每个工具的专属上下文并通过execute绑定步骤函数const tools { getWeather: { description: Get the current weather for a city., inputSchema: z.object({ city: z.string().describe(The city name) }), contextSchema: z.object({ defaultUnit: z.enum([celsius, fahrenheit]) }), execute: getWeather, toModelOutput: ..., }, calculate: { description: Evaluate a math expression., inputSchema: z.object({ expression: z.string() }), execute: calculate, }, deleteFile: { description: Delete a file from the filesystem., inputSchema: z.object({ path: z.string() }), contextSchema: z.object({ rootDir: z.string() }), execute: deleteFileStep, needsApproval: true as const, // 高风险操作需要人工审批 }, };deleteFile工具同时演示了两件重要的事上下文约束execute内部检查input.path必须以toolsContext.deleteFile.rootDir本示例为/tmp/workflow-sandbox开头否则直接抛错拒绝防止模型删除任意路径——这是「沙箱化文件操作」的最小实现agent-chat.ts工具审批needsApproval: true as const标记该工具必须经过用户审批后才执行审批流在 UI 侧由addToolApprovalResponse完成详见第六节。四、toModelOutput模型视角与 UI 视角分离这是 README 用专门一节「TestingtoModelOutput」讲解的重点。WorkflowAgent与generateText、streamText、ToolLoopAgent一样会尊重工具的可选toModelOutput钩子该钩子决定「模型看到的工具结果」与「应用/UI 收到的结果」相互独立。getWeather的演示如下toModelOutput: ({ output }) ({ type: text as const, value: ${output.city}: ${output.temperature}°${output.unit celsius ? C : F}, ${output.condition}., }),即UI 收到的仍是execute返回的完整对象{ city, temperature, unit, condition }而模型收到的是压缩成一行的人类可读摘要如Boston: 22°C, sunny.。这样既节省模型上下文 token又避免把结构化 JSON 塞进提示词同时 UI 还能渲染富信息卡片。README 给出了可复现的验证步骤运行应用并提问Whats the weather in Boston?浏览器中渲染的工具结果展示完整 JSON 对象来自execute的原始返回值在 dev server 终端中onEnd回调会打印模型视角的工具结果例如{ type: tool-result, toolName: getWeather, output: { type: text, value: Boston: 22°C, sunny. } }为了对照calculate工具没有定义toModelOutput因此其模型视角输出保持默认的json序列化。4.1 底层实现逐结果转换工具输出从源码结构看WorkflowAgent与generateText/streamText在提示词组装上有个关键差异它不是一次性把整段ModelMessage[]通过convertToLanguageModelPrompt整体转换而是增量地、一次追加一个工具结果来拼装LanguageModelV4提示词。为此ai-sdk/workflow在 create-language-model-tool-result-output.ts 中提供了一个逐结果的等价转换 helper其注释明确了三条处理流水线createToolModelOutput—— 应用tool.toModelOutput或 text/json/error 兜底downloadAssets—— 对content类型的输出下载其中的文件/图片资源把 URL 变成 provider 可消费的字节mapToolResultOutput—— 把 AI 层的ToolResultOutput映射为 provider 层输出并转换遗留文件类型。这一实现细节解释了为什么toModelOutput返回{ type: text, value: ... }而非任意自定义对象——它最终要落为LanguageModelV4ToolResultOutput。仓库的单元测试 workflow-agent.test.ts 也验证了「本地工具结果使用toModelOutput的同时保留原始输出」以及 provider 执行工具结果、审批后工具结果同样走该钩子的行为。4.2 端到端可观测onEnd示例把toModelOutput变成可端到端观察的手段是onEnd回调——它从最终的 model messages 里筛出role tool的消息扁平化其 content 并打印onEnd: ({ messages }) { const modelFacingToolResults messages .filter(message message.role tool) .flatMap(message (Array.isArray(message.content) ? message.content : [])); console.log( [WorkflowAgent] model-facing tool results (post toModelOutput):, JSON.stringify(modelFacingToolResults, null, 2), ); },这里的 tool-role 消息携带的正是模型视角的工具结果而 UI 渲染的是原始工具输出——README 描述的两侧差异由此可被直接观测验证。五、双上下文 APIruntimeContext与toolsContextChatRequestContext接口agent-chat.ts定义了路由层解析后传入工作流的每请求上下文它被拆分为互补的两套 APIruntimeContext运行时上下文共享的 Agent 状态会流经prepareStep、生命周期回调和onEnd但不会加入提示词toolsContext工具上下文按工具隔离、经 schema 校验的状态。每个工具的execute只能看到属于自己那一份校验过的条目作为context。示例演示了两者的典型用法runtimeContext: { tenantId: request.tenantId, requestId: request.requestId, plan: request.userPlan, }, toolsContext: { getWeather: { defaultUnit: request.preferredUnit }, deleteFile: { rootDir: request.fileRootDir }, },sensitive values like rootDir never leak across tools——源码注释强调像rootDir这样的敏感值不会跨工具泄露deleteFile有自己的rootDirgetWeather根本拿不到它。prepareStep则可在每步执行前读取runtimeContext并微调设置。示例做了一个「企业版更确定性」的演示企业套餐把采样温度调到0.2其他套餐不改动prepareStep: ({ runtimeContext }) { if (runtimeContext.plan enterprise) { return { temperature: 0.2 }; } return {}; },源码注释特别提醒runtimeContext应视为不可变的——需要更新时应在prepareStep中返回新值。在 app/api/chat/route.ts 中路由层从请求头解析这些上下文x-tenant-id、x-request-id、x-user-plan、x-unit未提供时使用默认值如tenant_demo、crypto.randomUUID()、free、celsiusfileRootDir固定为/tmp/workflow-sandbox然后调用start(chat, [messages, requestContext])启动工作流。六、流式传输、断线重连与工具审批6.1 流式输出链路README 强调「实时流式响应」通过getWritable()与createUIMessageStreamResponse实现。完整的链路是agent.stream({ ..., writable: getWritableModelCallStreamPart() })—— WorkflowAgent 把模型调用流部件ModelCallStreamPart写入由 Workflow DevKit 提供的可写流路由层把start()返回的run.readable通过createModelCallToUIChunkTransform()转换为 UI 消息流const run await start(chat, [messages, requestContext]); return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), headers: { x-workflow-run-id: run.runId, x-request-id: requestContext.requestId }, });客户端WorkflowChatTransportapp/page.tsx配合useChat消费该流maxConsecutiveErrors: 5提供连续错误上限保护。6.2 断线重连getRun「Workflow 运行在重启后依然存活且可被重新连接」的关键路由是 app/api/chat/[runId]/stream/route.tsconst run await getRun(runId); const readable run .getReadable({ startIndex: 0 }) .pipeThrough(createModelCallToUIChunkTransform({ uiStartIndex: startIndex })); return createUIMessageStreamResponse({ stream: readable, headers: { x-workflow-run-id: runId } });它通过workflow/api的getRun(runId)按运行 ID 取回持久化的运行用getReadable({ startIndex: 0 })从存储中重放已产生的流部件并支持startIndex查询参数从指定位置续传校验其为非负安全整数否则返回 400。这正是「可恢复 可重连」的落地实现客户端拿到首次响应头里的x-workflow-run-id后可以随时重新连接到同一运行。6.3 工具审批 UI由于deleteFile声明了needsApproval: true聊天 UI 需要处理审批状态机。app/page.tsx 中useChat配置sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses确保带审批响应的消息在合适时机自动发送对approval-requested状态的 tool part渲染琥珀色审批卡片展示工具名、输入 JSON以及Approve / Deny两个按钮分别调用addToolApprovalResponse({ id, approved: true })与addToolApprovalResponse({ id, approved: false, reason: User denied the operation. })对approval-responded状态展示绿色「approved — executing...」或红色「denied」反馈普通工具结果则渲染output-available状态的完整 JSON。结合 workflow-agent.test.ts 的测试用例可以确认审批通过后的工具结果同样走toModelOutput钩子同时保留原始流输出。七、三个 E2E 测试台与异步视频工作流README 描述了三个可独立访问的页面它们分别对应 WorkflowAgent 三个进阶能力的确定性验证。7.1/telemetryTelemetry E2E 测试台打开 http://localhost:3000/telemetry 可运行确定性的 WorkflowAgent 遥测场景。测试台会记录稳定的 AI SDK 遥测集成事件覆盖生命周期回调、模型调用、chunk、工具执行、上下文过滤、审批恢复、错误处理、重连行为。实现位于 workflow/telemetry-agent.ts其要点使用mockSequenceModelworkflow/mock-model.ts按场景脚本化输出tool-call、text、error三种描述符保证确定性该 mock 模型实现了WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE静态方法因此是可持久化/反序列化的模型——这正是「运行可恢复」对模型对象本身的要求通过TelemetryOptions配置functionId、includeRuntimeContext仅挑选telemetryRunId/requestId/tenantId排除secretToken等敏感字段和includeToolsContext同样按工具白名单过滤——这就是 README 提到的「上下文过滤」注册两类遥测来源integrations自研的createTelemetryIntegration DevTools 桥接devToolsTelemetry以及 agent 构造器/stream上的显式回调experimental_onStart、experimental_onStepStart、onToolExecutionStart、onToolExecutionEnd、onEnd、onError场景由getResponses(scenario)决定approvaldeleteFile 审批、tool-errorfailTool 抛错、model-error模型流抛错、默认天气 计算器双工具调用。7.2/sandboxSandbox E2E 测试台打开 http://localhost:3000/sandbox 可运行确定性的experimental_sandbox场景验证「传给agent.stream的沙箱会话在工具执行期间可用」。workflow/sandbox-agent.ts 的实现要点createSandbox构造一个最小Experimental_SandboxSession仅实现run返回{ exitCode: 0, stdout: sandbox:label:command }其余文件读写、spawn 方法抛出「Only sandbox.run is implemented」工具runSandboxCommand通过tool({...})的execute第二参数解构出experimental_sandbox为空则抛Sandbox is not available否则调用experimental_sandbox.run({ command, workingDirectory: /workspace, env: { SCENARIO: sandbox } })并连同sandboxDescription一起返回agent.stream({ ..., experimental_sandbox: streamSandbox })把会话注入执行环境同文件还导出了toUIMessageStreamhelperreadable.pipeThrough(createModelCallToUIChunkTransform({ uiStartIndex }))把模型调用流部件转换为 UI chunk 流供各测试台复用。7.3/async-apis基于 webhook 的异步视频工作流这是示例中「含金量」最高的部分。打开 http://localhost:3000/async-apis 并提交一个 GitHub 仓库 URL工作流会通过 GitHub GraphQL API 查询该仓库最近 30 天合并的 pull request分页上限MAX_GITHUB_SEARCH_PAGES 10每页 100 条统计合并者的合并次数排序后取前 3 位维护者MAX_MAINTAINERS 3同分按 login 字典序逐个下载头像限制 HTTPS 受信域名如github.com、avatars.githubusercontent.com、*.githubusercontent.com且大小不超过 10 MB用 FAL 的luma-dream-machine/ray-2/image-to-video模型为每位维护者生成 5 秒、9:16 竖屏540p的图生视频。全程通过writeProgress内部getWritableAsyncApisProgressUpdate().getWriter()向浏览器实时推送status/maintainers/avatar/video/complete/error各阶段进度。webhook 方案的细节README 专段说明工作流把新的webhook选项传给experimental_generateVideo用 Workflow DevKit 的createWebhook()给 FAL 一个持久化的回调 URL。工作流会挂起suspend直到 FAL 回调该 URL随后检查已完成的任务并把结果流式推回页面——全程无需轮询。实现要点using webhook useWebhook ? createWebhook() : undefined; await generateVideo({ model: createDurableFalVideoModel({ useWebhook }), ... maxRetries: 0, poll: { delay: useWebhook ? waitWithoutSchedulingTimeout : sleep, // webhook 模式放弃竞争的 sleep timeoutMs: FAL_WEBHOOK_TIMEOUT_MS, // 10 分钟 }, webhook: webhook null ? undefined : async () ({ url: webhook.url, received: webhook.then(request ({ body: null, headers: Object.fromEntries(request.headers) })), }), });几个值得注意的工程细节createDurableFalVideoModel把 FAL 模型的doStart/doStatus包装成use step的持久化步骤并仅在启用 webhook 时附加handleWebhookOption把webhookFactory返回的{ url, received }转成 provider 需要的{ webhookUrl, received }waitWithoutSchedulingTimeout返回一个永不 resolve 的 Promise——注释解释可持久化的 webhook 自己拥有等待权若同时调度一个 Workflow sleep会在 webhook 赢得竞态时留下未提交uncommitted的 sleep生成完成后从result.providerMetadata.fal.videos[0].url提取视频地址并格式化result.warnings。本地运行的限制与应对README 明确说明FAL不能向私有回环地址loopback回调 webhook。因此当示例跑在普通localhost上时自动回退为异步 start/status API 持久化轮询poll.delay用sleep同样能完成任务若要在本地走通 webhook 路径需要部署到 Vercel或把WORKFLOW_LOCAL_BASE_URL设置为一个能转发到本地服务器的公共 HTTPS 地址canReceiveFalWebhook()的判定逻辑async-apis.ts未设置WORKFLOW_LOCAL_BASE_URL时看VERCEL 1设置了则校验协议为https:且主机名不是localhost/127.0.0.1/::1/*.localhost否则视为不可接收 webhook。八、总结这套示例教会你的架构模式综合 README 与源码examples/next-workflow实际演示了一套可复用的「生产级 Agent 工作流」模式关注点方案代码位置持久化执行use workflow入口 use step工具步骤workflow/agent-chat.ts可恢复运行start/getRunx-workflow-run-id重连app/api/chat/route.ts、app/api/chat/[runId]/stream/route.ts模型/UI 输出解耦工具toModelOutput钩子create-language-model-tool-result-output.ts上下文隔离runtimeContext共享、不进提示词 toolsContext按工具校验隔离agent-chat.ts高风险操作needsApproval UI 审批流app/page.tsx可观测性telemetry集成 生命周期回调workflow/telemetry-agent.ts沙箱化工具执行experimental_sandbox注入agent.streamworkflow/sandbox-agent.ts异步长任务createWebhook挂起等待回调回退轮询workflow/async-apis.ts确定性测试可序列化 mock 模型 脚本化响应workflow/mock-model.ts如果你正在为 AI 应用设计「多步骤、长耗时、需要审批、必须抗崩溃」的 Agent 后端这套示例是直接从仓库里可以照搬的完整蓝本先跑通pnpm dev看三个测试台的效果再对照本文各小节逐步拆解workflow/目录下的实现最后把start/getRun的持久化路由接入你自己的 Next.js 项目即可。【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表