- 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
- 2.1 Generating Text ---
- 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 })。
三种使用方式:
agent.generate({ prompt })→result.text--- 一次性生成。agent.stream({ prompt })→result.textStream(for await迭代)--- 流式。createAgentUIStreamResponse({ agent, uiMessages })--- 在 API route 中产出 UI 消息流,供客户端useChat消费。
System Instructions(instructions):定义 Agent 行为、人格与约束,为所有交互设定语境。文档给出五类写法:基础角色设定、详细行为准则、约束边界、工具使用指引、格式与风格控制。
Loop Control 默认行为(关键规则) :默认 stopWhen: isStepCount(20)。每一步模型要么生成文本,要么调用工具;生成文本即完成,调用工具则执行后触发新一轮生成。循环终止于以下四者之一:
- 返回非
tool-calls的 finish reason; - 被调用的工具没有
execute函数; - 某次工具调用需要审批;
- 命中停止条件。
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》。
五种可组合模式:
- Sequential Processing (Chains) --- 上一步输出拼进下一步 prompt,并在链中插入一次 LLM 质检调用,用布尔/阈值决定是否重生成。
- Routing --- 第一次调用只做分类 (结构化输出),再用分类结果选取第二次调用的 model 与 instructions。
- Parallel Processing --- 同一输入分派给多个「专家角色」并发评审(
Promise.all),再用另一次模型调用聚合成摘要。 - Orchestrator-Worker --- 编排者产出文件清单计划,
Promise.all按changeType派生 worker 提示词执行。 - 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 });显式记忆 APIaddMemories(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;peerDependenciesai ^6.0.0、mongodb ^6.0.0、zod ^3.0.0。- Session 记忆两种模式 :tool-driven (LLM 自主决定读写,适合原型)与 hook-driven (运行时靠
prepareCall+onEnd钩子每轮强制持久化,生产推荐 )。其余四层(semantic / procedural / episodic / scratchpad)始终由 LLM 控制且默认选择性写入。
- Session 记忆两种模式 :tool-driven (LLM 自主决定读写,适合原型)与 hook-driven (运行时靠
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'。
三种配置形态:
- 按工具映射 (每工具一条简单策略):
toolApproval: { runCommand: 'user-approval' }。当runCommand被调用,agent 返回tool-approval-request而不执行该工具。 - 按工具输入的函数 :决策依赖解析后的工具入参。函数收到类型化 input 加
toolCallId、messages、toolContext、runtimeContext。示例:非 admin 自动拒绝、大额支付人工审批、小额 admin 支付正常执行。 - 一个函数管全部工具(
GenericToolApprovalFunction) :把函数直接作为toolApproval传入。收到:toolCall--- 完整调用(toolName、toolCallId、input、是否 dynamic)tools--- 模型可用的全部工具toolsContext--- 所有工具的上下文messages--- 产生该工具调用的那一步发给模型的 messagesruntimeContext--- 本次调用共享的运行时上下文
按请求配置 :toolApproval 是 agent 设置,因此也可以从 prepareCall 返回 ------ 适合审批策略依赖 call options、租户策略或用户权限的场景。
人工审批的完整流程(需要两次调用):
- 带
toolApproval调agent.generate()/agent.stream(); - 从结果或 UI 流中读出
tool-approval-request; - 向用户或审批系统征求决定;
- 往 messages 里追加
tool-approval-response; - 用更新后的 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、需要并行做独立调研、上下文可能超出模型上限、想按能力隔离工具访问。不适合 ------ 任务简单聚焦、顺序处理已足够、上下文仍在可控范围、所有工具可安全共存。
三大动机:
- 卸载上下文密集任务 ------ 起一个专用 agent 消耗数十万 token,只让它返回约 1000 token 的聚焦摘要,主 agent 上下文保持干净连贯(子 agent 干重活,主 agent 负责编排)。
- 并行独立工作 ------ 多个子 agent 同时调研代码库不同区域,各自返回摘要,主 agent 合成,而不用付出全部探索的上下文成本。
- 专业化编排 ------ 主 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:
- 子 agent 不能用工具审批 ------ subagent 工具不能用
toolApproval(或已废弃的needsApproval),所有工具必须无需用户确认自动执行。 - 子 agent 上下文是隔离的 ------ 每次调用从全新上下文窗口开始(这正是它能做重探索而不撑爆主对话的原因)。若确需让子 agent 看到对话历史,工具
execute里除abortSignal外也能拿到messages,但请慎用,传完整历史会削弱上下文隔离的收益。 - 流式增加复杂度 ------ 无流式的基础模式更简单、更好调试,只在需要实时展示进度时才加流式。
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):
- 改导入与类名 :
DurableAgent从workflow/ai导出;WorkflowAgent从@ai-sdk/workflow导出(连同 helpers),需与workflow一起安装。 - 写
ModelCallStreamPart而不是UIMessageChunk:DurableAgent直接把UIMessageChunk写进getWritable();WorkflowAgent写更底层的ModelCallStreamPart,把转换留给响应边界的 transform ------ 让持久流保持 provider 形态,不把 UI 协议烤进 workflow payload。 maxSteps→stopWhen:改用 AI SDK 共享的停止条件,使同一套停止逻辑跨ToolLoopAgent、generateText、streamText通用。experimental_output→output:返回值现在在result.output(原result.experimental_output)。- 人工审批改用
needsApproval:DurableAgent时代要在工具execute内部调 Hook;WorkflowAgent把审批变成一等工具属性 ------ agent 发出审批请求、挂起 workflow、用户响应后自动续跑。 uiMessages/collectUIMessages移除 :DurableAgent.stream()在collectUIMessages: true时返回累积uiMessages;WorkflowAgent.stream()改为在result.messages返回ModelMessage[]。持久化建议 :把UIMessage[]作为唯一真相源,传给 agent 前调convertToModelMessages(即 Chatbot Message Persistence 模式);没有内置的ModelMessage→UIMessage转换 ,若之后要在 UI 渲染对话,不要 只持久化result.messages。experimental_context→runtimeContext+toolsContext:不再接受experimental_context,拆成共享状态与按工具状态。- 其余 :
prepareStep、onStart、onStepStart、onStepEnd、onEnd、onError、toolChoice、activeTools、timeout、repairToolCall、experimental_sandbox及常规生成设置同名沿用 ;WorkflowAgent额外新增prepareCall与onToolExecutionStart/onToolExecutionEnd;experimental_onStart、experimental_onStepStart别名仍可用。