AI SDK 概念全景总结(v7)

  • 0. 总体心智模型(分层表 + Agent 定义)
  • 1. Agents 板块 ------ ToolLoopAgent 及其生态
    • 1.1 Overview --- Agent 的组成与选型
    • 1.2 Building Agents --- ToolLoopAgent 完整用法
    • 1.3 Workflow Patterns --- 结构化工作流(非 Agent 循环)
    • 1.4 Loop Control --- stopWhen 与 prepareStep
    • 1.5 Configuring Call Options --- 类型安全的调用选项
    • 1.6 Memory --- 持久记忆
    • 1.7 Tool Approvals --- 工具调用审批
    • 1.8 Policy-Based Tool Approvals --- @ai-sdk/policy-opa
    • 1.9 Subagents --- 子 Agent
    • 1.10 WorkflowAgent --- 持久化、可续跑的 Agent
    • 1.11 Terminal UI --- @ai-sdk/tui
  • 2. AI SDK Core 板块
    • 2.1 Generating Text --- generateText / streamText
    • 2.2 Generating Structured Data --- Output 家族
    • 2.3 Settings --- 模型调用与请求选项
    • 2.4 Prompt Engineering --- 提示词开发与调试
    • 2.5 Reasoning --- 顶层 reasoning 参数
    • 2.6 Runtime and Tool Context --- 两种上下文与遥测过滤
    • 2.7 Tool Calling --- 工具调用与多步执行
    • 2.8 MCP 三件套(Tools / Apps / Events)
    • 2.9 Code Mode --- 用沙箱 JS/TS 编排工具
    • 2.10 Tool Search --- 按需发现工具
    • 2.11 Language Model Middleware
    • 2.12 Batch --- 异步批处理
    • 2.13 Provider & Model Management
    • 2.14 Error Handling
    • 2.15 Testing --- mock providers
    • 2.16 Embeddings
    • 2.17 Reranking
    • 2.18 Decisions --- experimental_decide
    • 2.19 Image Generation
    • 2.20 Realtime --- 实时语音对话
    • 2.21 Transcription / Translation / Speech
    • 2.22 Video Generation
    • 2.23 File Uploads 与 Skill Uploads
    • 2.24 Telemetry --- OpenTelemetry 集成
    • 2.25 DevTools
    • 2.26 Lifecycle Callbacks
  • 3. AI SDK Harnesses 板块
    • 3.1 HarnessAgent
    • 3.2 Harness Tools
    • 3.3 Harness Skills
    • 3.4 Harness Adapters(含能力矩阵)
    • 3.5 Workflow Utilities --- 把 harness 轮次跑成持久 workflow
    • 3.6 Harness UI(useChat 路由)
    • 3.7 Harness Terminal UI
    • 3.8 Harness Tools 逐节细节(3.2 的展开)
  • 4. 选型速查(决策表 + 贯穿全篇的硬规则)

0. 总体心智模型

AI SDK 由几条互相解耦但原语兼容的抽象层组成:

层 面向对象 核心入口 说明
Provider / Model 语言模型 createOpenAI 等 provider 工厂、模型字符串 "anthropic/claude-sonnet-5.5" 把各家模型暴露给 Core 函数
AI SDK Core 单次模型调用 generateText / streamText / Output / tool 标准化 prompt、settings、tool calling、结构化输出
Agents 自主循环 ToolLoopAgent(ai 包)、WorkflowAgent(@ai-sdk/workflow) 把「LLM + Tools + Loop」封装成可复用组件
Harnesses 现成 Agent 运行时 HarnessAgent + harness adapter + sandbox provider + session 直接跑 Claude Code / Codex / Pi 等完整 harness
UI / RSC 前端交互 useChat、createAgentUIStreamResponse、toUIMessageStream、ChatTransport 消费统一的 UI 消息流原语

一句话区分两条 Agent 路线:

  • ToolLoopAgent:你自己围绕语言模型搭循环(模型、工具、停止条件、上下文都由你配)。
  • HarnessAgent :你跑一个已经搭好循环的成熟 harness(Claude Code、Codex、Pi),只在外层做指令/工具/权限/沙箱定制。

Agent 的本质定义:Agent = 使用工具的大语言模型,在一个循环里完成任务。三个部件:

  • LLM --- 处理输入并决定下一步动作。
  • Tools --- 扩展文本生成之外的能力(读文件、调 API、写数据库)。
  • Loop --- 编排执行,内部包含两个职责:
    • Context management:维护消息历史,决定每一步模型看到什么输入。
    • Stopping conditions:判断循环(任务)何时完成。

1. Agents 板块(ToolLoopAgent 及其生态)

1.1 Overview --- Agent 的组成与选型

定位:给出 Agents 板块的顶层心智模型与三种抽象的选型入口。

ToolLoopAgent 类:把上面三个部件收拢到一个类里。

ts 复制代码
import { ToolLoopAgent, tool } from 'ai';
import { z } from 'zod';

const weatherAgent = new ToolLoopAgent({
  model: 'anthropic/claude-sonnet-5.5',
  tools: {
    weather: tool({ description, inputSchema: z.object({ location: z.string() }), execute: async ({ location }) => ... }),
    convertFahrenheitToCelsius: tool({ ... }),
  },
});

const result = await weatherAgent.generate({ prompt: 'What is the weather in San Francisco in celsius?' });
result.text;   // agent 的最终回答
result.steps;  // agent 走过的步骤

该 agent 自动完成「调 weather → 调 convertFahrenheitToCelsius → 生成最终文本」的多步链路;循环、上下文管理、停止条件全部由 ToolLoopAgent 代管。

为什么推荐 ToolLoopAgent :减少样板(托管循环与 messages 数组)、提升复用(定义一次全应用使用)、便于维护(单点更新配置)。需要显式控制每一步的复杂结构化工作流 时,才下沉到 core 函数 generateText / streamText。

HarnessAgent 类 :当你要跑的是预配置好的成熟 harness (Claude Code、Codex、Pi)而不是自己搭循环时使用。Harness 是与 provider、model 不同层的抽象,但输出仍流入 AI SDK 兼容的 result 与 UI 原语。

Agent State 与 Context(重要) :Agent 常需要不该直接塞进 prompt 的服务端状态 ------ tenant 配置、request ID、feature flag、凭据、任务进度。

  • runtimeContext:Agent 的共享运行时状态 。贯穿整个 agent loop,可在 prepareStep 与生命周期回调中读取,并可在步骤之间更新。
  • toolsContext:按工具粒度 的值(API key、作用域权限)。每个工具只拿到依据自身 contextSchema 校验过的、属于自己那一份类型化 context。

Terminal UI :@ai-sdk/tui 可在交互式终端里跑 ToolLoopAgent(见 1.11)。

ts 复制代码
import { runAgentTUI } from '@ai-sdk/tui';
await runAgentTUI({ title: 'Weather Agent', agent: weatherAgent });

Structured Workflows :Agent 灵活但非确定性。需要可靠、可重复、显式控制流时,用 core 函数 + 结构化工作流模式组合:条件分支、普通函数复用、错误处理、显式流程控制(见 1.3)。


1.2 Building Agents --- ToolLoopAgent 完整用法

定位 :把 LLM 配置、工具、行为封装成可复用组件的完整指南。核心价值:Reuse configurations / Maintain consistency / Simplify API routes / Type safety。

构造函数选项 (ToolLoopAgent 接受与 generateText、streamText 完全相同的设置集):

model、instructions、tools、runtimeContext、toolsContext、prepareStep、stopWhen、toolChoice、output、toolApproval、experimental_sandbox、callOptionsSchema、prepareCall,以及全部生命周期回调。

工具定义 :tool({ description, inputSchema, contextSchema, execute });execute 签名为 execute(input, { context, abortSignal, experimental_sandbox })。

三种使用方式:

  1. agent.generate({ prompt }) → result.text --- 一次性生成。
  2. agent.stream({ prompt }) → result.textStream(for await 迭代)--- 流式。
  3. createAgentUIStreamResponse({ agent, uiMessages }) --- 在 API route 中产出 UI 消息流,供客户端 useChat 消费。

System Instructions(instructions):定义 Agent 行为、人格与约束,为所有交互设定语境。文档给出五类写法:基础角色设定、详细行为准则、约束边界、工具使用指引、格式与风格控制。

Loop Control 默认行为(关键规则) :默认 stopWhen: isStepCount(20)。每一步模型要么生成文本,要么调用工具;生成文本即完成,调用工具则执行后触发新一轮生成。循环终止于以下四者之一:

  1. 返回非 tool-calls 的 finish reason;
  2. 被调用的工具没有 execute 函数;
  3. 某次工具调用需要审批;
  4. 命中停止条件。

toolChoice 取值 :'none' | 'auto' | 'required' | { type: 'tool', toolName }(最后一项可强制指定某个工具)。

Structured Output :output: Output.object({ schema }),结果取 result.output。

生命周期回调(6 个,顺序固定):

回调 触发时机 收到的数据
onStart 操作开始时、任何 LLM 调用之前(仅一次) model 信息(含 modelId)、messages、settings、runtimeContext
onStepStart 每一步(每次 LLM 调用)之前 stepNumber、model、将要发送的 messages、tools、此前各步
onToolExecutionStart 工具 execute 运行前一刻 toolCall 对象、messages、toolContext
onToolExecutionEnd 工具 execute 完成或报错后 toolCall、toolExecutionMs、toolOutput 判别联合(type: 'tool-result' 带 output / type: 'tool-error' 带 error)
onStepEnd 每一步结束 usage.inputTokens/outputTokens、performance.effectiveOutputTokensPerSecond、performance.stepTimeMs、finishReason、toolCalls
onEnd 全部步骤完成、响应结束 所有 steps、总 usage、runtimeContext

构造级 vs 调用级回调 :所有回调既可写在构造函数 (agent 全局追踪)也可写在 generate()/stream() (单次调用追踪),两者可同时存在 ------ 都会执行,先构造级、后调用级 。ToolLoopAgent 与 core 使用同一批 generation lifecycle 事件类型(见 2.32)。

端到端类型安全 :type MyAgentUIMessage = InferAgentUIMessage<typeof myAgent>,客户端 useChat<MyAgentUIMessage>() 直接复用。

注意事项:

  • prepareStep 返回的模型调用参数(如 temperature)只作用于当前步,后续步骤回落到 agent 顶层设置。
  • experimental_sandbox 是 per-call 值 ,必须传给 generate() / stream() / UI stream helper。
  • sandbox 的 description 不会自动进入 prompt,需要模型了解环境时得自己写进 instructions。
  • 传入 sandbox ≠ 工具被沙箱化 :工具必须显式把操作委派给 experimental_sandbox.run(...)。
  • 每个工具的服务端凭据走 toolsContext + contextSchema,不要用顶层字段。

1.3 Workflow Patterns --- 结构化工作流(非 Agent 循环)

定位 :用 core 函数搭确定性、可重复的流程,替代自由 Agent 循环;模式源自 Anthropic《Building Effective Agents》。

五种可组合模式:

  1. Sequential Processing (Chains) --- 上一步输出拼进下一步 prompt,并在链中插入一次 LLM 质检调用,用布尔/阈值决定是否重生成。
  2. Routing --- 第一次调用只做分类 (结构化输出),再用分类结果选取第二次调用的 model 与 instructions。
  3. Parallel Processing --- 同一输入分派给多个「专家角色」并发评审(Promise.all),再用另一次模型调用聚合成摘要。
  4. Orchestrator-Worker --- 编排者产出文件清单计划,Promise.all 按 changeType 派生 worker 提示词执行。
  5. Evaluator-Optimizer(反馈循环) --- while (iterations < MAX_ITERATIONS),评估结构化输出达标则 break,否则带着 specificIssues / improvementSuggestions 重生成。

选型四维:Flexibility vs Control、Error Tolerance、Cost Considerations、Maintenance。

涉及的 API :generateText({ model, prompt, instructions, output })、Output.object({ schema: z.object({...}) })、解构 const { text } = ... / const { output } = ...、Promise.all。分类字段用 zod 枚举承载,例如 type: z.enum(['general','refund','technical'])、complexity: z.enum(['simple','complex'])、changeType: z.enum(['create','modify','delete'])、riskLevel/impact: z.enum(['low','medium','high'])、qualityScore: z.number().min(1).max(10)。

示例取值 :"anthropic/claude-sonnet-5.5";简单任务 'openai/gpt-6-luna'、复杂任务 'openai/gpt-6-astra';手写循环上限 MAX_ITERATIONS = 3;达标判据示例 qualityScore >= 8 && preservesTone && preservesNuance && culturallyAccurate。

注意事项:

  • 从最简方案开始,复杂度只应因四件事引入:拆清晰步骤、加工具、加质检反馈、最后才多 Agent。层级越多 → LLM 调用数与成本越高、调试越难。
  • 评估类步骤一定要用 Output.object 拿到可比对的数值/布尔字段,而不是解析自由文本。
  • 每个自写循环都要自己设死迭代上限,否则成本失控。

1.4 Loop Control --- stopWhen 与 prepareStep

定位:精细控制 Agent 循环「何时停」与「每步怎么跑」的两个参数。

stopWhen(停止条件)

控制「最后一步存在工具结果时」是否继续执行。

  • 内置条件(三个):
    • isStepCount(count) --- 跑满指定步数后停止。
    • hasToolCall(...toolNames) --- 调用其中任意一个工具即停止。
    • isLoopFinished() --- 永不触发,让循环跑到自然结束。
  • ToolLoopAgent 默认 isStepCount(20),这是防止跑飞导致超额 API 费用的安全措施。
  • 若因该默认上限被截断,SDK 会打 warning 并提示配置 stopWhen;显式设置 stopWhen(即使仍是 isStepCount(20))就不会产生该 warning。
  • WorkflowAgent 不套用默认步数上限 ,需要自己配 isStepCount(20)。
  • 传数组 时,任一条件命中即停止。
  • 自定义条件类型 StopCondition<typeof tools>,签名 ({ steps }) => boolean,可基于全步骤信息判断,例:steps.some(s => s.text?.includes('ANSWER:')),或累加 step.usage.inputTokens / outputTokens 估算成本、超 $0.50 就停。

prepareStep(逐步改写)

在每一步之前运行;不返回任何改动则沿用初始设置。

入参 :model、stepNumber(0-based )、steps(历史各步的 toolCalls/toolResults/text/usage)、messages、initialMessages、responseMessages、runtimeContext。

可返回覆盖项 :model、instructions、messages、activeTools、toolChoice、experimental_sandbox,以及模型调用设置 maxOutputTokens、temperature、topP、topK、presencePenalty、frequencyPenalty、stopSequences、seed、reasoning。

规则:

  • 这些模型调用覆盖只作用于当前步 ;省略或 undefined → 用顶层值。
  • 显式假值会被保留 (temperature: 0、seed: 0、空 stopSequences 数组)。
  • 返回的 messages 覆盖会持久成为后续步骤的基础 ,新的 assistant/tool 响应消息追加其后。若要从离散片段重建,用 initialMessages(原始输入)与 responseMessages(已累积的模型/工具响应)。
  • 用途:动态模型选择、上下文压缩(compaction)、按阶段收窄 activeTools、消息改写、逐步切换 sandbox。

pruneMessages 辅助函数(内置裁剪,适合简单压缩):

ts 复制代码
pruneMessages({ messages, reasoning: 'all', toolCalls: 'before-last-3-messages', emptyMessages: 'remove' })

prepareStep 返回 experimental_sandbox 的特殊性 :仅作用于该步的工具执行 ,后续步骤回落顶层 ------ 这一点与 runtimeContext/toolsContext 的行为不同。

Forced Tool Calling(强制工具调用范式)

toolChoice: 'required' + 一个没有 execute 的 done 工具:

  • toolChoice: 'required' 强制模型每步都调工具而非直接生成文本 → 保证结构化流程。
  • 无 execute 的 done 工具充当终止信号,调用它即停循环。
  • 最终答案从 result.staticToolCalls[0].input.answer 取(staticToolCalls = 未被执行的 tool calls)。

Manual Loop Control

用 core 函数自己写循环以获得完全控制:

ts 复制代码
while (step < maxSteps) {
  const result = await generateText({ model, messages, tools });
  messages.push(...result.responseMessages);
  if (result.text) break;
  step++;
}

可完全掌控:消息历史管理、逐步决策、自定义停止条件、动态工具与模型选择、错误处理与恢复。

注意事项 :isLoopFinished() 有无限运行/烧钱风险;分阶段限工具靠 activeTools + toolChoice: 'required' 组合;压缩时机由自己决定(示例用 JSON.stringify(messages).length / 4 粗估 token,阈值 100_000);同一套 prepareStep 压缩写法也适用于 generateText / streamText。


1.5 Configuring Call Options --- 类型安全的调用选项

定位 :让同一段 Agent 定义在每次调用时接收类型安全的结构化输入,从而动态改写任意 Agent 设置;免去为不同场景创建多个 Agent,或在 Agent 外另做配置逻辑。

两个新选项:

  • callOptionsSchema(zod schema):声明每次调用的参数形状,例如 z.object({ userId: z.string(), accountType: z.enum(['free','pro','enterprise']) })。
  • prepareCall: ({ options, ...settings }) => ({ ...settings, /* 覆盖项 */ }):循环开始前调用一次,可以是 async。

三步流程 :定义 schema → prepareCall 用 options 生成设置 → 调用时传 options:

ts 复制代码
agent.generate({ prompt, options: { userId, accountType } });
agent.stream({ prompt, options: { ... } });
createAgentUIStreamResponse({ agent, messages, options: { userId, accountType } });

可改写项 (文档中出现):instructions、model、tools、activeTools、providerOptions,以及其余任意 agent 设置。

典型用法:

  • 按 complexity 切模型:simple → 'openai/gpt-6-luna',complex → 'openai/gpt-6-astra'。
  • 按 taskDifficulty(low|medium|high)喂 providerOptions.openai.reasoningEffort,并用 satisfies OpenAILanguageModelResponsesOptions(来自 @ai-sdk/openai)保持类型。
  • 动态启用 provider 自带工具:openai.tools.webSearch({ searchContextSize: 'low', userLocation: { type: 'approximate', city, region, country } })。
  • 按 userRole 收窄工具面:admin 得 ['readDatabase','writeDatabase'],普通用户仅 ['readDatabase'],同时改写 model 与 instructions。
  • RAG:prepareCall 内先 vectorSearch 再把检索结果拼进 instructions。

注意事项与坑:

  • 一旦声明 callOptionsSchema,options 变为必填且受类型检查,漏传或类型错误直接 TS 报错。
  • prepareCall 的返回对象会替换调用设置 ,因此必须先展开 ...settings 再覆盖个别字段,否则其余配置全部丢失 ------ 这是最常见错误。
  • 「不升级」分支用 settings.model 以保留默认。
  • createAgentUIStreamResponse 在本文用 messages 字段、Building Agents 用 uiMessages,两处示例字段名不一致,落地前按当前版本参考页确认。
  • 权限裁剪要 activeTools + instructions 双管,别只靠提示词。

1.6 Memory --- 持久记忆

定位:让 Agent 跨会话保存与召回信息,避免每次从零开始。三种路线及权衡:

方案 Effort Flexibility Provider Lock-in
Provider-Defined Tools 低 中 是
Memory Providers 低 低 取决于记忆服务
Custom Tool 高 高 否

Provider-Defined Tools :provider 定义 inputSchema 与 description,你只提供 execute。模型已被训练适配这些工具,性能通常优于自定义工具。

  • Anthropic Memory Tool :anthropic.tools.memory_20250818({ execute })。工具接收结构化命令 view / create / str_replace / insert / delete / rename ,每个命令的 path 限定在 /memories 下;execute 把命令映射到你的存储后端(文件系统/数据库),返回字符串。示例模型 'anthropic/claude-haiku-5.5'。只对 Claude 有效(硬锁定)。

Memory Providers(记忆存/取/注入全透明,你不定义任何工具):

  • Letta (包 @letta-ai/vercel-ai-sdk-provider):lettaCloud() 作 model;providerOptions: { letta: { agent: { id } } };内置记忆工具工厂 lettaCloud.tool(name),名称 core_memory_append、memory_insert、memory_replace;平台侧负责 core / archival / recall memory,可云或自托管。

  • Mem0 (包 @mem0/vercel-ai-provider):createMem0({ provider, mem0ApiKey, apiKey });model: mem0('gpt-6-astra', { user_id });显式记忆 API addMemories(messages, { user_id })、retrieveMemories(prompt, { user_id });跨 OpenAI / Anthropic / Google / Groq / Cohere。

  • Supermemory (包 @supermemory/tools,入口 /ai-sdk):tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY!);暴露 addMemory、searchMemories,靠语义检索;可与任意 AI SDK provider 搭配。

  • Hindsight (包 @vectorize-io/hindsight-ai-sdk + @vectorize-io/hindsight-client):new HindsightClient({ baseUrl });createHindsightTools({ client, bankId });五个工具 retain、recall、reflect、getMentalModel、getDocument ;可 Docker 自托管或云。bankId 是记忆库标识(通常是 user id);多用户场景必须在请求处理函数内 调用 createHindsightTools,保证每请求对应正确的 bank。

  • MongoDB (包 @mongodb-developer/vercel-ai-memory):createMongoDBMemory({ uri, embedder })(如 embedder: openai.embedding('text-embedding-3-small'));tools: mongodbMemory({ userId, sessionId });stopWhen: isLoopFinished()。五层记忆:Session、Semantic、Procedural、Episodic、Scratchpad ;Atlas Vector Search + 自动建索引 + 按类型的 retention policy;peerDependencies ai ^6.0.0、mongodb ^6.0.0、zod ^3.0.0。

    • Session 记忆两种模式 :tool-driven (LLM 自主决定读写,适合原型)与 hook-driven (运行时靠 prepareCall + onEnd 钩子每轮强制持久化,生产推荐 )。其余四层(semantic / procedural / episodic / scratchpad)始终由 LLM 控制且默认选择性写入。

Custom Tool(最灵活、无锁定)两种范式:

  • Structured actions :自定义 view / create / update / search 等显式操作,每个操作自控,天然安全。
  • Bash-backed :给模型沙箱 bash,用 cat / grep / sed / echo 组合访问。能力强但必须做命令校验(参考配方含 AST 校验 + 文件系统持久化)。

注意事项 :memory provider 由服务方掌控记忆行为,对「存了什么、怎么检索」可见度低 ,且引入外部服务依赖。MongoDB 集成面向 AI SDK v6(依赖 ModelMessage、ToolLoopAgent、isLoopFinished()),且 createMongoDBMemory 应在模块/服务级只创建一次 ,按请求只做 {userId, sessionId} 作用域。记忆工具若需先读后写,默认 isStepCount(20) 可能提前截断,示例改用 isLoopFinished()。Bash-backed 不做命令校验即为安全漏洞。


1.7 Tool Approvals --- 工具调用审批

定位 :默认情况下,带 execute 的工具在模型调用时自动执行 。toolApproval(配在 ToolLoopAgent 上,也可配在 generateText/streamText)让你在执行前 审查、批准或拒绝特定工具调用。适用于会改数据、花钱、执行代码、发消息、访问私有数据等敏感操作的工具。

toolApproval 只作用于 AI SDK 执行的工具 ;provider-executed tools 在 provider 侧运行,不走 AI SDK 的审批机制。

四种审批状态 (审批规则返回其中之一,可以是字符串,也可以是带 type 字段的对象):

状态 含义
'not-applicable' 正常执行,不带审批元数据(默认)
'approved' 记录一次自动批准,然后执行工具
'denied' 记录一次自动拒绝,返回 denied 的工具输出
'user-approval' 发出审批请求,等待显式响应

自动批准/拒绝时,若要附带理由,用对象形式 。审批函数返回 undefined 视同 'not-applicable'。

三种配置形态:

  1. 按工具映射 (每工具一条简单策略):toolApproval: { runCommand: 'user-approval' }。当 runCommand 被调用,agent 返回 tool-approval-request 而不执行该工具。
  2. 按工具输入的函数 :决策依赖解析后的工具入参。函数收到类型化 input 加 toolCallId、messages、toolContext、runtimeContext。示例:非 admin 自动拒绝、大额支付人工审批、小额 admin 支付正常执行。
  3. 一个函数管全部工具(GenericToolApprovalFunction) :把函数直接作为 toolApproval 传入。收到:
    • toolCall --- 完整调用(toolName、toolCallId、input、是否 dynamic)
    • tools --- 模型可用的全部工具
    • toolsContext --- 所有工具的上下文
    • messages --- 产生该工具调用的那一步发给模型的 messages
    • runtimeContext --- 本次调用共享的运行时上下文

按请求配置 :toolApproval 是 agent 设置,因此也可以从 prepareCall 返回 ------ 适合审批策略依赖 call options、租户策略或用户权限的场景。

人工审批的完整流程(需要两次调用):

  1. 带 toolApproval 调 agent.generate() / agent.stream();
  2. 从结果或 UI 流中读出 tool-approval-request;
  3. 向用户或审批系统征求决定;
  4. 往 messages 里追加 tool-approval-response;
  5. 用更新后的 messages 再调一次 agent。

批准 → 工具在第二次调用中执行;拒绝 → 模型收到拒绝结果,可在无工具输出的情况下继续回应。建议在 instructions 里加一句「工具执行未获批准时不要重试」,避免对同一动作反复发审批请求。

与 useChat 集成 :审批请求以 state: 'approval-requested' 的 tool part 出现在流里,客户端用 addToolApprovalResponse 回应。只对人工审批调用它 ------ 自动批准/拒绝的状态已包含在流中。带理由的人工审批可从 part.approval.requestReason 读取;通过 addToolApprovalResponse 提供的理由单独存为 part.approval.reason。

Schema 变换与持久化审批 :当审批所依据的输入与 schema 原始输入不同时,审批请求会把原始 schema 输入保留在 inputSchemaInput 。持久化 responseMessages 或 UI 消息时必须保留该字段 (UI 消息转换函数会自动保留)。续跑时 SDK 会重建变换后的输入并校验它与被批准的输入一致,绝不 用不同值替换已批准的值。若旧历史/投影历史缺少原始输入、且重新校验失败或会改变批准值,则被判为非法工具输入 而拒绝。原始输入会进入持久化消息与 UI 审批流,包括被 schema 变换删掉的字段 ------ 变换删字段≠从审批元数据中脱敏。

安全模型(务必读) :标准 useChat 模式下,服务端每轮根据客户端发来的消息重建对话 ,不在请求之间持久化会话状态 → 消息历史是客户端可控输入 。从该历史重建的审批虽然会重新校验(工具入参过 schema、审批策略重新求值),但没有额外保护时,客户端可以伪造一个「看起来合法」的审批绕过人工环节 。若工具做敏感操作,用 experimental_toolApprovalSecret 把审批密码学绑定到签发它的服务端。

experimental_toolApprovalSecret 行为:

  • 提供服务端密钥后,服务器在签发时对每条审批请求做 HMAC 签名 ,审批被重放时验签;伪造或被篡改的审批在工具执行前被拒绝。
  • 签名绑定精确的工具名、tool call ID 与输入参数,三者任一改动即失效。
  • 配置方式:给 ToolLoopAgent(或直接给 generateText / streamText)传 experimental_toolApprovalSecret。
  • 密钥准备:openssl rand -base64 32 生成高熵串(≥32 字节)→ 存进环境变量(如 TOOL_APPROVAL_SECRET)→ 让所有服务端实例共享。
  • 行为要点:无有效签名的审批请求一律拒绝(fail-closed) ;未配置密钥则与既往行为一致(向后兼容);密钥永不 发给客户端、永不出现在流里。
  • WorkflowAgent 也支持它,在 workflow step 内签名后再写入持久化审批请求;应传环境变量引用 ({ environmentVariable: 'TOOL_APPROVAL_SECRET' }),使原始密钥只在签名/验签步骤内读取;只有签名被持久化并送到客户端。

关联 API 边界 :toolApproval 适用于 ToolLoopAgent、generateText、streamText;@ai-sdk/policy-opa 把规则写成代码策略(见 1.8);needsApproval 只用于 WorkflowAgent (审批会挂起并续跑持久化 workflow);subagent 工具不能用 toolApproval(见 1.9)。


1.8 Policy-Based Tool Approvals --- @ai-sdk/policy-opa

定位 :把 toolApproval 里的判断从代码搬到 OPA 的 .rego 策略 里。收益:策略是可单独评审的产物、可在 CI 用 opa test 独立测试、可在不重新部署代码的情况下修改(由运行中的 OPA 提供时)。

它完全建立在公开 toolApproval 回调之上,线上协议不变 :仍然是同样的 tool-approval-request / tool-approval-response 流程。

可 enforcement 的类别:安全与访问(scope、角色、权限、租户隔离、工具/主机/路径 allowlist);业务规则(谁能在什么条件下对哪些资源做什么,例如超阈值支付需审批);成本与用量(超预算、token 上限、每用户配额则拒绝或要求审批);合规与变更控制(破坏性或受监管动作绑定到正确的审批人/环境/时间窗)。

策略输入不止当前调用参数 :input 还携带 messages(本次运行的完整模型与工具调用历史),因此规则可以依赖「已经发生过什么」:先前工具调用、动作序列、跨对话累计值。例如:已执行 N 次写入后要求审批;同一对话中的第二个不可逆动作(push、delete)直接拒绝;跨步骤累计花费/ token 到顶即停。

最佳适配:确定性检查 ------ 策略从字段里取值比较,答案每次相同。强适配:scope、权限、数值阈值、allowlist、时间窗,以及计数/序列/累计值等历史感知检查。弱适配 :基于内容或语义的过滤("不要用脏话"、"拦截毒性内容")------ 这类尽力而为、难验证、易绕过,应交给专门的 moderation/classification 步骤,策略只做其外围的确定性闸门。经验法则 :若能指着 input 里某个字段写出精确检查,就用策略;若规则依赖判断自由文本语义,不要只靠策略。

决策映射(每次工具派发前咨询):

策略决策 效果
allow 执行工具
deny 返回模型可读的 denied 结果(无需人工)
requires-approval 挂起运行,等待人工 tool-approval-response
无规则命中 归一化为 not-applicable,SDK 视为 allow。想默认拒绝请在策略里写 default decision := { "decision": "deny" }

Rego 与输入形状 :策略输出 decision 对象;reason 可选,会回灌给模型(deny)或人工审批者(requires-approval)------ 在 core 结果里暴露为 reason,在 UI 消息里暴露为 approval.requestReason。兼容旧的布尔形状 { "allow": true | false, "reason": "..." },存量规则无需重写。默认 input 形状是 { tool: { name }, args, messages, runtimeContext },可用 toInput 覆写。测试:opa test policy.rego policy_test.rego(无需 SDK)。

错误一律 fail closed :后端出错(服务不可达、WASM 故障、bundle 构建错误)→ opaPolicy 返回 denied,并把错误信息作为 reason;后端返回了但决策无法识别(未知 decision 值、非布尔的旧 allow)→ 同样 denied。错误永不 从回调抛出、永不 中止运行。后端宕机会阻塞 受影响的调用而不是静默放行。注意这与「规则未命中」不同:null/undefined 归一化为 not-applicable(放行);要让未命中也拒绝,就写 default ... deny 规则。

两种策略加载方式:

  • Option A:WASM(进程内) ------ 提前把 .rego 编译成 WASM,wasmPolicyClient({ wasm, data? })(async,进程内加载)。每次决策无网络调用;适合与 app 一起发布策略或启动时从对象存储拉取;热更新需重建 WASM 并重新实例化。
  • Option B:HTTP(运行中的 OPA server) ------ httpPolicyClient({ url, headers? })(sync)。每次决策一次 HTTP 往返;适合策略频繁变更、想不重新部署就热加载,或多个服务共用一个 OPA。传 headers 做 Styra DAS / EOPA 鉴权。

外部数据与集成 :策略可基于角色→权限映射、IDP 组成员、entitlement 列表、allowlist 等外部数据决策,且这些数据可无需重新部署你的 app 地更新。数据如何进入 OPA 是 OPA 的事:data(wasmPolicyClient({ wasm, data }) 交给 bundle 的 setData,策略以 data.* 读取)、bundle(HTTP 后端的 OPA server 指向 bundle 服务定期拉取新策略与数据)、求值时外部查找(策略在评估过程中回调外部服务)。本包只负责把最终决策映射成 SDK 的审批状态。

Shadow mode 安全上线 :第一版策略几乎总会误杀。shadow(approval, opts) 会评估并通过 onDecision 上报决策,但在 enforce: true 之前告诉 SDK 一切批准 。推荐流程:写策略 + opa test → 用 shadow(...)(enforce: false 为默认)包住并把 onDecision 接到日志/指标 → 在真实环境跑,检查 decision.type 为 denied / user-approval 的事件(就是策略本会改变行为的调用)→ 修策略迭代 → 只剩你想要的 denied/user-approval 事件时设 enforce: true。遥测语义 :onDecision 是 fire-and-forget,慢或抛错的 logger 不会阻塞或破坏 enforcement,抛出的错误被吞;契约是enforcement 优先、observability 其次 。若要反过来(enforcement 等审计日志),直接在底层 toolApproval 里写日志而不是走 shadow。每条 PolicyDecisionEvent 带工具调用、策略 decision(type 与 reason)、enforced、effective(SDK 实际执行的动作)------ 对比 decision 与 effective 可发现漂移。用 HTTP 后端时 OPA 还能原生发 decision logs。

模型边界的 capability scoping :opaCapabilityMiddleware({ client, path, toInput? }) 比 toolApproval 更早 执行策略 ------ 在模型被告知某工具存在之前 就把工具面收窄(纵深防御 + 省 token + 提升越狱拒绝率)。它是一个 LanguageModelV4Middleware,把 params.tools 收窄为 allowlist:path 处的规则返回允许的工具名 string[] 或 { tools: string[] };不在 allowlist 的工具在模型看到之前就被丢弃;fail closed ------ 响应畸形或求值器出错时把 params.tools 置为 undefined(模型被告知自己没有工具),想 fail-open 请在 Rego 里写兜底。

已发现工具面的收口 :工具来自 MCP discovery 或插件注册表时无法预先写每工具规则,且漏写的工具会被静默放行 。wrapMcpTools(tools, approval, opts?) 把审批在发现的整个工具集上变成全函数 ,未覆盖的工具走可配置默认值:'user-approval'(默认,要求人工)、'denied'(硬 allowlist,策略列举允许项其余拒绝)、'approved'(放行,仅在来源完全可信时)。尽管叫 Mcp,它对任意 Record<string, Tool> 有效。

无策略时全放行 :optionalOpaPolicy({ client, path, toInput? }) 在 client 为 undefined 时返回 undefined,等价于没传 toolApproval(SDK 批准一切)。适合本地开发/CI:POLICY_WASM_PATH 未设 → 全放行且 OPA 模块根本不加载;设了 → 加载并 enforcement。想要更严格(没策略就拒绝启动)就直接用 opaPolicy,让缺失的字节在启动时抛错。

传递式 enforcement(组合/派发工具) :toolApproval 只在模型直接调用工具时触发 。粗粒度派发工具(能跑 git push 的 bash 工具、HTTP 工具、MCP proxy)会让模型绕过单动作规则 。修复办法写在派发器自己的 toolApproval 条目里:把派发器输入解析成一个逻辑 (name, args) 对,再用与直接工具相同的规则评估。指导:匹配逻辑集中在一处 (共享 Rego helper 规则,两边都带同一 path 调 opaPolicy;或共享 TypeScript 谓词);无法归约为干净调用的输入一律 deny (shell 输入天生难解析,"证明不了安全"就等于拒绝)。诚实的局限 :这只在模型的调用边界做闸门,无法阻止 一个工具在获批后做出输入未描述的额外副作用 ------ 那要靠带外沙箱(Vercel Sandbox、Firecracker、容器)承载不可信执行,把沙箱当信任边界。SQL/HTTP/MCP/browser/shell 派发器的完整示例见包 README;ai-sdk-slackbot 示例展示端到端接线(域名限定的 web search、城市 allowlist 的天气工具、只读命令 allowlist 的 bash 工具,用 POLICY_MODE 一个环境变量切换 WASM/HTTP 后端)。

API 清单(全部从包根 @ai-sdk/policy-opa 导出):

  • 引擎无关核心:shadow(approval, opts?)、wrapMcpTools(tools, approval, opts?)、PolicyClient(每个后端实现 evaluate(path, input),也是接入非 OPA 引擎的缝隙)、辅助类型 PolicyDecision、WrappedMcpTools、PolicyDecisionEvent。
  • OPA 后端与适配器:wasmPolicyClient({ wasm, data? })、httpPolicyClient({ url, headers? })、opaPolicy({ client, path, toInput? })(返回 toolApproval 配置,fail closed)、optionalOpaPolicy({ client, path, toInput? })、opaCapabilityMiddleware({ client, path, toInput? })、normalizeOpaDecision(result)(自己调 OPA 时可独立使用的结果归一化)。

1.9 Subagents --- 子 Agent

定位 :subagent 是父 agent 可以调用的 agent 。父 agent 通过一个工具委派工作,子 agent 自主执行后返回结果。核心价值是把上下文消耗巨大的任务隔离出去,让主 agent 保持聚焦。

工作流程 :① 用独立的 model / instructions / tools 定义子 agent;② 为主 agent 创建一个在 execute 里调用它的工具;③ 子 agent 在自己的上下文窗口 内独立运行;④ 返回结果(可选地把进度流式送到 UI);⑤ 用 toModelOutput 控制模型实际看到什么。

何时用(权衡表):适合 ------ 任务需要探索海量 token、需要并行做独立调研、上下文可能超出模型上限、想按能力隔离工具访问。不适合 ------ 任务简单聚焦、顺序处理已足够、上下文仍在可控范围、所有工具可安全共存。

三大动机:

  1. 卸载上下文密集任务 ------ 起一个专用 agent 消耗数十万 token,只让它返回约 1000 token 的聚焦摘要,主 agent 上下文保持干净连贯(子 agent 干重活,主 agent 负责编排)。
  2. 并行独立工作 ------ 多个子 agent 同时调研代码库不同区域,各自返回摘要,主 agent 合成,而不用付出全部探索的上下文成本。
  3. 专业化编排 ------ 主 agent 纯做编排:只读工具的 exploration 子 agent、带文件编辑的 coding 子 agent、对接特定平台的 integration 子 agent,形成清晰职责分离(较少见,但合法)。

基础用法(无流式) :execute 里直接调另一个 agent,工具调用阻塞直到子 agent 完成,返回最终文本。

取消处理 :用户取消时 abortSignal 会传播到子 agent,务必透传 以保证清理。abort 后子 agent 停止执行并抛 AbortError,主 agent 的工具执行失败,从而停住主循环。为避免后续消息里出现「不完整的工具调用」报错,用 convertToModelMessages 的 ignoreIncompleteToolCalls 过滤掉没有对应结果的工具调用。

流式子 Agent 进度(preliminary tool results) :把 execute 从普通函数改成 async generator(async function*) ,每个 yield 向前端发送一次初步结果 。关键规则:每次 yield 完整替换前一次输出(不是追加) ,所以需要一个不断增长的完整消息 ------ 用 readUIMessageStream 读取流的每个 chunk,累积成一个包含到目前为止所有 parts 的 UIMessage。于是每次 yield 的 message 都是含子 agent 迄今所有 parts(text、tool call、tool result)的完整 UIMessage,前端只需用新消息替换显示。

控制模型看到的内容:toModelOutput ------ 完整 UIMessage 存入消息历史并在 UI 展示,但 toModelOutput 把工具输出映射成发给模型的 token:用户看到子 agent 的每一步执行,模型只看到最终摘要文本。子 agent 可能花 10 万 token 探索推理,主 agent 只消耗摘要。

必须为子 agent 写摘要指令 :若不在子 agent instructions 里明确要求「产出全面摘要」,它可能只回一句 "Done",toModelOutput 就无内容可提取。

UI 渲染(配合 useChat) :检查工具 part 的 state 与 preliminary 标志。工具 part 状态:

state 含义
input-streaming 工具输入正在生成
input-available 工具已就绪待执行
output-available 工具已产出输出(看 preliminary 判断是否仍在流)
output-error 工具执行失败

类型安全:与 agent 一起导出类型(如 InferAgentUIMessage),供 UI 组件同时渲染主 agent 消息与子 agent 流式输出。

三条 Caveats:

  1. 子 agent 不能用工具审批 ------ subagent 工具不能用 toolApproval(或已废弃的 needsApproval),所有工具必须无需用户确认自动执行。
  2. 子 agent 上下文是隔离的 ------ 每次调用从全新上下文窗口开始(这正是它能做重探索而不撑爆主对话的原因)。若确需让子 agent 看到对话历史,工具 execute 里除 abortSignal 外也能拿到 messages,但请慎用,传完整历史会削弱上下文隔离的收益。
  3. 流式增加复杂度 ------ 无流式的基础模式更简单、更好调试,只在需要实时展示进度时才加流式。

1.10 WorkflowAgent --- 持久化、可续跑的 Agent

定位 :@ai-sdk/workflow 的 WorkflowAgent 用于在 workflow 内构建持久化、可续跑 的 agent。它提供与 ToolLoopAgent 相同的 agent 循环 ,但额外带来自动状态持久化、工具 schema 序列化、能跨越 workflow step 边界的内置工具审批。

为什么需要持久化 agent :标准 ToolLoopAgent 全在内存里跑,进程崩溃则进度尽失。生产级多次工具调用的 agent 会遇到:Statefulness (长循环需跨进程边界持久化)、Resumability (step 失败要从最后 checkpoint 重试而非从头开始)、Human-in-the-loop (需审批的工具要能暂停 agent 稍后续跑)、Observability (每次工具调用作为离散 workflow step 出现在看板)。WorkflowAgent 让每次工具执行成为带自动重试的持久 step 来解决这些。

与 ToolLoopAgent 对比:

ToolLoopAgent WorkflowAgent
包 ai @ai-sdk/workflow
运行时 内存 Workflow
持久性 崩溃即丢失 跨重启存活
工具重试 手动 自动(经 workflow steps)
人工审批 内置 内置 + 跨挂起存活
generate() 可用 可用(持久化非流式模型调用)
stream() 可用 主 API
流输出 streamText 返回值 writable 参数 + ModelCallStreamPart

更简单、不需要持久化的场景仍用 ai 包的 ToolLoopAgent。

安装依赖 :@ai-sdk/workflow 需要 Workflow 5 ,以及 ai 与 zod 的 peer 依赖;workflow 包提供 Workflow DevKit 运行时(getWritable、'use workflow'、'use step')。

在 workflow 中集成的三个要点 :① 函数标记 'use workflow';② 把 getWritable() 传给 agent 的 stream();③ 从 API route 启动 workflow。model 参数接受两种形态(预构建模型实例与模型字符串)。

消息转换 :WorkflowAgent.stream() 期望 ModelMessage[] ,不是 UIMessage[];从客户端 useChat 收到的消息要先转换(convertToModelMessages)。

Writable 流 :与 ToolLoopAgent 消费返回的流不同,WorkflowAgent 把原始 ModelCallStreamPart chunk 写入 workflow 运行时提供的 writable(getWritable())。在响应边界用 createModelCallToUIChunkTransform() 转成 UIMessageChunk 发给客户端。该 transform 还会转发 WorkflowAgent 在重试时发出的 reset-step 事件;客户端在处理重试输出前会移除失败的模型调用 step 的 partial parts。

可续跑流式:WorkflowChatTransport ------ workflow 函数可能超时或被网络故障中断。WorkflowChatTransport 是一个 ChatTransport 实现,检测流在没有 finish 事件的情况下结束 并自动重连续跑。要求 POST endpoint 返回 x-workflow-run-id 响应头,并提供 {api}/{runId}/stream 的 GET endpoint 供重连。

索引一致性:WorkflowChatTransport 统计的是 UIMessageChunk 对象,而持久化的 WorkflowAgent 流存的是原始 ModelCallStreamPart。要从索引 0 重放原始流,并把非负 UI cursor 传给 createModelCallToUIChunkTransform()。负数起始索引 要求持久流已经存的是 UIMessageChunk,不能与这种 raw→UI 转换混用。

Tools as Workflow Steps :给工具 execute 函数标记 'use step' 即成为持久 step,获得自动重试(默认 3 次尝试) 、结果跨进程重启持久化 、看板里作为离散 step 的可观测性 。不加 'use step' 的工具仍可运行,但只是普通内存函数、没有持久性保证。

工具审批:needsApproval(此处与 ToolLoopAgent 不同) ------ WorkflowAgent 的人工审批配在工具定义 上的 needsApproval(needsApproval 是 WorkflowAgent 专属 ;generateText/streamText/ToolLoopAgent 要用 toolApproval)。设置后 agent 暂停并把审批请求发到 writable 流,workflow 挂起 直到用户批准或拒绝。因为 workflow 是持久的,审批请求跨进程重启存活 ------ 用户几小时后再批准 agent 仍会续跑。needsApproval 也接受 async 函数以按输入决定是否需要审批。

签名审批 ------ 客户端提供的消息历史可能在重放给 workflow 前被篡改。敏感工具请配 experimental_toolApprovalSecret:agent 对审批 ID、tool call ID、工具名、已校验输入 做 HMAC 签名;重放已审批历史时签名缺失或非法则阻止工具执行 。签名会贯穿持久模型调用流、createModelCallToUIChunkTransform()、addToolApprovalResponse()、convertToModelMessages()。要求:高熵密钥 ≥32 字节;每个能签发或续跑审批的 worker 都要能拿到同一密钥 ;待批审批未决期间要保留旧密钥 (换密钥会使在途审批失效)。WorkflowAgent 只把环境变量名 传入签名与验证步骤,各步骤在本地环境读取,原始值永不 进入 step 参数、持久流 parts、回调或遥测事件。因此环境变量要在每个 worker 上配好,不要把密钥值放进 runtimeContext 或 toolsContext 。也可以在 agent.stream() 传 experimental_toolApprovalSecret,流级值覆盖构造默认值。

Loop Control 差异 :WorkflowAgent 不套用默认步数上限 ;省略 stopWhen 时它会跑到模型不再调用工具或其他自然终止条件。反复调用工具的模型可产生无限次模型调用 ,需要限制运行时长与成本时必须配显式条件(如 isStepCount(20));isLoopFinished() 与「省略 stopWhen」对 WorkflowAgent 等价,同样需谨慎。

Structured Output :用 Output 把响应解析成类型化对象,结果在 result.output。

配置项 :接受与 ToolLoopAgent 相同的生成设置(temperature、maxOutputTokens、topP 等),另加 workflow 专属选项。

  • runtimeContext / toolsContext (取代旧的 experimental_context):runtimeContext 是贯穿 prepareStep、生命周期回调、onEnd 的共享状态,视为不可变 ,要更新就从 prepareStep 返回新值以作用于当前及其后步骤;toolsContext 是按工具名为键的映射,每个工具 execute 只看到自己那一份(声明了 contextSchema 的工具会在执行前校验)。两者也可按调用 传给 stream() 并覆盖构造默认值。序列化硬性要求 :context 值可能被持久化并在 workflow / step 边界之间重放,因此 runtimeContext、toolsContext 以及 prepareStep 返回的任何 context 必须可序列化 ------ 用字符串、数字、布尔、数组、普通对象、Date、URL、Map、Set 及其他 Workflow 支持的结构化数据;不要 放函数、类实例、symbol、WeakMap、WeakSet、数据库客户端或 SDK 客户端,改为传标识符/配置数据,在 step 函数内部重建不可序列化资源。这与内存中运行的 ToolLoopAgent(单进程存活期间可携带更丰富的 JS 值)不同。
  • experimental_sandbox :工具需要执行环境时传 sandbox session;它在工具的 description 函数与 execute 中作为 experimental_sandbox 可用,也可在 prepareStep 中按当前 step 覆写。它是活的运行时句柄、不是持久化 context :不要存进 runtimeContext/toolsContext;若工具作为独立 workflow step 运行,传可序列化的 sandbox 标识符或配置,在该 step 内重新 attach。
  • prepareCall:循环开始前调用一次,可基于 runtime context 转换 model、instructions 或其他设置。
  • prepareStep:每一步(每次 LLM 调用)前调用,可改设置、管理 context、动态注入消息。
  • prepareCall 与 prepareStep 都可按调用 传给 stream()。
  • toolCallConcurrency :限制一个 step 内并发执行的本地工具调用数 ,设 1 表示必须按模型顺序串行。该限制同时作用于 generate() 与 stream()(包括从消息历史续跑过来的已批准工具调用),必须是正整数 ;默认所有本地调用并发,provider-executed 工具不受影响 。也可从 prepareCall 返回或传给 generate()/stream(),优先级:显式调用选项 > prepareCall > 构造默认值。

生命周期回调 :与 ToolLoopAgent 同样可在构造器(全局)或 stream()(单次)定义,两者都存在则都触发(先构造级)。细节:

  • 对具体工具集,WorkflowAgentToolExecutionStartEvent / WorkflowAgentToolExecutionEndEvent 保留了每个工具名与其 input、context、output 类型的关系;TS 可直接收窄嵌套 toolCall,但要按工具名收窄相关联的 toolContext 或 output 需用 Extract 或自定义类型守卫。
  • 工具输入回调 onInputStart / onInputDelta / onInputAvailable 也被保留 。模型调用在持久 step 内运行,而回调函数留在 workflow 上下文(任意函数不能跨越 step 边界),所以 WorkflowAgent 在模型 step 期间记录回调事件 ,在该 step 完成后、工具执行与 step 生命周期回调之前按序重放 。它们不与模型生成并发 ,不能提供在途取消或背压 。每个回调收到该工具 toolsContext 条目(已过 contextSchema 校验)。
  • 对高度碎片化的工具输入,onInputDelta 的重放数据属于持久模型 step 结果的一部分 ------ 只在确实需要每个 delta 时才配 onInputDelta,否则不要配以免保留 delta 重放数据。
  • 已废弃别名 experimental_onStart、experimental_onStepStart 仍可用(向后兼容);同一构造器或同一次 stream() 里同时提供稳定名与实验名时,使用稳定回调。

类型推断 :同 InferAgentUIMessage 推出 UI message 类型供类型安全客户端组件使用。

非流式生成 agent.generate() :需要完整结果而不流式时使用。走 provider 的 doGenerate(),与流式相同的持久工具循环。默认最多 20 步 (注意与 stream() 不同 ------ stream() 除非设 stopWhen 否则无上限)。生成失败会 reject;流式模型错误保持既有 result.error 行为。结果使用 core 的「最终步 text + 全步骤 content、tool results、warnings、usage」语义;responseMessages 只含生成的消息;output 默认为 text,并支持构造或单次调用的 output 规格;End 回调在完整 output 解析之前运行 。 审批往返(无流式) :generate() 与 stream() 即使没提供 writable 也会返回审批请求 。生成场景下保留 result.responseMessages、把请求的工具与输入展示给用户、再把用户决定追加到下一次调用。使用返回的 tool-approval-request 里的 approvalId 与用户的 approved 决定;存储对话时要保留请求签名与原始工具调用输入 ;两次调用都要用同一环境变量引用 配置 experimental_toolApprovalSecret(密钥在持久签名/验证步骤内解析)。本地工具仅在审批校验通过后执行;被拒绝的工具产生 execution-denied 结果 ;provider-executed 审批转发给 provider;等待 Workflow hook 的工具在生成期间同样可以挂起与续跑。 Deadline、取消与重放 :workflow 内 timeout 为模型调用设绝对 deadline 。每个模型 step 在派发前、SDK 重试尝试前、provider 返回后各检查一次;工具挂起并续跑后同一 deadline 仍有效 。SDK maxRetries 管模型重试,持久模型 step 不再叠加一层重试 。派发前过期 → 以 TimeoutError reject;SDK 重试退避期间过期 → 以 AbortError reject;两者都不会启动新的重试 。模型 deadline 不会取消 正在等待 Workflow hook 的工具,运行可能在该 deadline 之后仍保持挂起;一旦 hook 续跑,其后任何模型调用若 deadline 已过则在派发前失败。要终止挂起的运行,用 Workflow 运行时的 run.cancel() ;取消不会撤销已发生的外部副作用 。Workflow 重放时会复用已完成的持久模型与工具 step,因此把外部副作用放进 step 函数,并在必要时使用外部服务的幂等机制(已完成 step 的重放不保证 被中断尝试的 exactly-once 效果)。 返回值边界 :生成文件与 result getter 只在 workflow 代码内可用;要回传的数据自行返回(例如通过 run.returnValue 选择可序列化字段 )。整个 result 类不是受支持的 run.returnValue 形状 。请求/响应 body 除非用 include 显式开启否则被省略;可用的响应头、时间戳、ID 与 provider 元数据会被保留。Workflow 使用既有回调形状与准备规则,且上报的是模型响应耗时 而非完整的 tool/step 计时;它不实现 ToolLoopAgent 的 callOptionsSchema 抽象,也没有 core 的实时流读取器与 HTTP 响应 helper。

从 DurableAgent 迁移 (WorkflowAgent 取代 Workflow DevKit 的 DurableAgent):

  1. 改导入与类名 :DurableAgent 从 workflow/ai 导出;WorkflowAgent 从 @ai-sdk/workflow 导出(连同 helpers),需与 workflow 一起安装。
  2. 写 ModelCallStreamPart 而不是 UIMessageChunk :DurableAgent 直接把 UIMessageChunk 写进 getWritable();WorkflowAgent 写更底层的 ModelCallStreamPart,把转换留给响应边界的 transform ------ 让持久流保持 provider 形态,不把 UI 协议烤进 workflow payload。
  3. maxSteps → stopWhen :改用 AI SDK 共享的停止条件,使同一套停止逻辑跨 ToolLoopAgent、generateText、streamText 通用。
  4. experimental_output → output :返回值现在在 result.output(原 result.experimental_output)。
  5. 人工审批改用 needsApproval :DurableAgent 时代要在工具 execute 内部调 Hook;WorkflowAgent 把审批变成一等工具属性 ------ agent 发出审批请求、挂起 workflow、用户响应后自动续跑。
  6. uiMessages / collectUIMessages 移除 :DurableAgent.stream() 在 collectUIMessages: true 时返回累积 uiMessages;WorkflowAgent.stream() 改为在 result.messages 返回 ModelMessage[]。持久化建议 :把 UIMessage[] 作为唯一真相源,传给 agent 前调 convertToModelMessages(即 Chatbot Message Persistence 模式);没有内置的 ModelMessage → UIMessage 转换 ,若之后要在 UI 渲染对话,不要 只持久化 result.messages。
  7. experimental_context → runtimeContext + toolsContext :不再接受 experimental_context,拆成共享状态与按工具状态。
  8. 其余 :prepareStep、onStart、onStepStart、onStepEnd、onEnd、onError、toolChoice、activeTools、timeout、repairToolCall、experimental_sandbox 及常规生成设置同名沿用 ;WorkflowAgent 额外新增 prepareCall 与 onToolExecutionStart / onToolExecutionEnd;experimental_onStart、experimental_onStepStart 别名仍可用。

相关推荐
imDwAaY1 小时前
SQL 查询突然变慢了?一篇讲清慢 SQL 排查思路
后端·mysql
小羊没烦恼!1 小时前
T-SQL查询高级—SQL Server索引中的碎片和填充因子
开发语言·前端·javascript·数据库·后端·python·web安全
the局外人1 小时前
学会 SQLAlchemy 搭建 Python 与 MySQL 之间的数据访问桥梁 🚀
后端·python·sql
金銀銅鐵2 小时前
[Java] 用 GUI 展示 ArrayList 扩容的过程
java·后端·ai编程
霸道流氓气质2 小时前
基于 Spring @Profile 的 MQ 消费者按需开关设计
java·后端·spring
逆风优雅3 小时前
nestjs middleware 接口请求日志
后端·js
是枚小菜鸡儿吖3 小时前
听歌截图太普通?我把MusicCard部署到极空间,把歌曲做成随时可开的音乐海报页
服务器·人工智能·后端·ai·大模型