mcpField
V
vercel-ai-sdk

vercel-ai-sdk

github.com/op0ai开发17196 次安装41 周前更新
代码仓库

关于 vercel-ai-sdk

面向 TypeScript 应用开发者的 Vercel AI SDK v6 实践指南,涵盖聊天、Agent、工具、结构化输出、流式响应和 MCP 集成,并介绍当前 API 与验证流程。

主要功能

  1. 01聊天与流式响应模式
  2. 02使用 v6 API 调用工具
  3. 03ToolLoopAgent 实现指南
  4. 04使用 Output helpers 生成结构化输出
  5. 05嵌入与语义搜索指南
  6. 06MCP 服务集成指南

适用场景

  1. 01使用 useChat 实现聊天界面
  2. 02构建生成流式 AI 响应的 API 路由
  3. 03为 Agent 应用添加工具
  4. 04将结构化输出代码迁移至 v6

MCP 相关

指南介绍如何将 Model Context Protocol 服务集成到 Vercel AI SDK 应用中。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/op0ai/claude-skills --skill vercel-ai-sdk

安装后在 Claude Code 中输入/vercel-ai-sdk确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
agents.md
core-functions.md
mcp.md
middleware.md
tools.md
ui-hooks.md
workflows.md

详细介绍

Vercel AI SDK v6 实施指南

何时使用此技能

在以下情况下使用此技能:

  • 使用 useChat hook 实现 AI 聊天界面
  • 创建用于生成或流式传输 AI 响应的 API 路由
  • 使用 ToolLoopAgent 构建 Agent 应用
  • 添加工具调用/函数调用功能
  • 使用 Output.object()、Output.array() 等生成结构化输出
  • 生成用于语义搜索或 RAG 的文本嵌入
  • 从 AI SDK v5 迁移到 v6
  • 集成 Model Context Protocol(MCP)服务
  • 实现用于缓存、日志记录或防护机制的中间件
  • 构建工作流模式(顺序、并行、路由等)
  • 处理流式响应或消息持久化

结构化实施工作流

了解任务需求 - 确定需要哪些 AI 功能(聊天、生成、Agent、工具、嵌入) - 确定应采用客户端(useChat)还是服务端(API 路由)实现 - 检查是否需要流式或非流式响应 - 确认模型提供商(Anthropic、OpenAI 等) - 确定是否需要结构化输出(Output.object、Output.array 等) 如有疑问,核实当前 API 模式 - 如果 API 模式不明确,使用 WebFetch 查看 https://ai-sdk.dev/docs/ - 确认提供商的模型指定格式 - 核实复杂功能的函数签名 使用正确的 v6 模式实现 - 使用提供商函数指定模型:anthropic('claude-sonnet-4-5') - 对于聊天:使用 sendMessage(而非 append),并使用基于 parts 的消息 - 对于工具:必须从 'ai' 导入并使用 tool() 助手,必须使用 inputSchema(而非 parameters),必须使用 zod - 对于结构化输出:使用 Output.object()、Output.array()、Output.choice()、Output.json() - 对于流式传输:使用 toUIMessageStreamResponse() 或 toTextStreamResponse() - 对于 Agent:使用 ToolLoopAgent 类和 createAgentUIStreamResponse() - 对于嵌入:使用 provider.textEmbeddingModel() 确保 TypeScript 类型正确 - 检查是否从 'ai' 包正确导入 - 核实消息类型(useChat 使用 UIMessage) - 确保正确推断工具参数类型 - 为异步函数添加显式类型 使用正确的包管理器安装所有缺失的依赖项 - **关键:首先检测项目使用的包管理器** * 检查锁文件:pnpm-lock.yaml → 使用 pnpm,package-lock.json → 使用 npm,yarn.lock → 使用 yarn,bun.lockb → 使用 bun * 如果存在 pnpm-lock.yaml,则必须使用 pnpm(不能使用 npm!) - 检查是否已安装所有导入的包 - 如果构建失败并提示 "Module not found",请从错误中确定包名 - 将包添加到 package.json 的 dependencies 中 - 使用正确的包管理器安装: * 如果存在 pnpm-lock.yaml:`pnpm install [package]` 或 `pnpm add [package]` * 如果存在 package-lock.json:`npm install [package]` * 如果存在 yarn.lock:`yarn add [package]` * 如果存在 bun.lockb:`bun install [package]` 或 `bun add [package]` - 重新运行构建,验证安装是否成功 **绝对不要使用错误的包管理器!** - 项目使用 pnpm 时使用 npm,会在 pnpm-lock.yaml 旁边生成 package-lock.json - 这会导致依赖版本不匹配并破坏构建 - 始终检查现有锁文件,并使用与之匹配的包管理器
  NEVER accept "Module not found" errors as environment issues
  YOU must install the required packages with the CORRECT package manager

  Common packages needed:
  - ai (core AI SDK)
  - @ai-sdk/openai (OpenAI provider)
  - @ai-sdk/anthropic (Anthropic provider)
  - @ai-sdk/mcp (MCP integration)
  - @modelcontextprotocol/sdk (MCP client SDK)
  - zod (for tool schemas)
</critical>
运行构建并修复所有错误,直到构建通过 - 运行:npm run build(或 bun run build) - 如果构建失败,请仔细阅读完整错误消息 - 常见修复方法: * "Module not found" → 安装该包(返回步骤 5) * 类型错误 → 修复 TypeScript 类型 * 配置错误 → 检查 next.config.js/ts * 框架错误 → 调查错误并尝试不同的方法 - 应用修复并重新运行构建 - 重复以上步骤,直到构建通过 绝不要以“构建失败,但代码没问题”为由停止 未经调试,绝不要将问题归咎于“环境”或“框架错误” 持续迭代,直到构建通过 如果遇到框架错误,请调查原因、尝试变通方法并进行调试 在构建通过之前,不要放弃 运行测试并调试所有失败,直到测试通过 - 运行:npm run test(或 bun run test) - 如果测试失败,请仔细阅读完整测试错误消息 - 调试失败原因: * 检查测试预期的内容与你提供的内容是否一致 * 查看测试断言 * 验证实现是否符合要求 * 检查是否存在类型不匹配或缺少属性 - 修复实现 - 重新运行测试 - 重复以上步骤,直到所有测试通过 绝不要在未调试的情况下接受测试失败 未经调查,绝不要将失败斥为“测试框架错误” 每个测试失败都在告诉你一些事情——认真听取 持续调试,直到所有测试通过 测试失败不可接受——这意味着实现尚不完整 验证所有检查均已通过 - 运行:npm run build && npm run lint && npm run test - 三项都必须通过:✅ 构建、✅ Lint、✅ 测试 - 如果有任何一项失败,请返回并修复 - 所有检查都通过之前,不要认为任务已完成 满足以下条件后,任务才算完成: - ✅ 构建通过 - ✅ Lint 通过 - ✅ 测试通过
  "Code is correct" is NOT enough
  You must achieve FULL PASSING status
  This is what it means to be an autonomous agent
</critical>
## ⚠️ 自主 Agent 思维模式

你不只是在编写代码,而是在自主完成任务。

这意味着:

  1. ✅ 编写正确的实现
  2. ✅ 安装所有必需的依赖项
  3. ✅ 运行构建并修复所有错误
  4. ✅ 运行测试并调试所有失败项
  5. ✅ 反复迭代,直到所有检查都通过
  6. ✅ 绝不找借口或放弃
应避免的常见失败模式

❌ 错误:“代码没问题,只是没有安装软件包——这是环境问题” ✅ 正确:“构建因缺少软件包而失败——现在用 npm install [package] 安装”

❌ 错误:“测试通过了,但构建失败了——这不关我的事” ✅ 正确:“构建失败了——现在调试并修复错误”

❌ 错误:“这是框架的 bug,没法修复” ✅ 正确:“检测到框架错误——现在研究问题、尝试解决方法并持续调试,直到找到解决方案”

❌ 错误:“实现已完成”(但测试仍未通过) ✅ 正确:“测试失败了——现在调试并修复,直到全部通过”

依赖项安装工作流

遇到“Module not found”错误时:

  1. 先检测软件包管理器——检查锁文件:

    ls -la | grep -E "lock"
    # Look for: pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb
    
  2. 根据导入语句确定软件包

    Error: Cannot find module '@ai-sdk/anthropic'
    Import: import { anthropic } from '@ai-sdk/anthropic'
    Package needed: @ai-sdk/anthropic
    
  3. 使用正确的软件包管理器安装

    # If pnpm-lock.yaml exists (MOST COMMON for Next.js evals):
    pnpm install @ai-sdk/anthropic
    # or
    pnpm add @ai-sdk/anthropic
    
    # If package-lock.json exists:
    npm install @ai-sdk/anthropic
    
    # If yarn.lock exists:
    yarn add @ai-sdk/anthropic
    
    # If bun.lockb exists:
    bun install @ai-sdk/anthropic
    
  4. 重新运行构建进行验证

    npm run build
    # or pnpm run build, yarn build, bun run build
    
  5. 修复出现的任何新错误

⚠️ 严重警告: 使用错误的软件包管理器(例如,项目使用 pnpm,却使用 npm)会:

  • 创建第二个相冲突的锁文件
  • 安装不同版本的依赖项
  • 导致依赖项版本不匹配
  • 引发难以理解的错误,导致构建失败,例如“Cannot read properties of null”
构建错误调试工作流

构建失败时:

  1. 完整阅读错误消息——不要略读
  2. 找出根本原因:
    • 未找到模块 → 安装软件包
    • 类型错误 → 修复类型
    • 配置错误 → 检查配置文件
    • Next.js 错误 → 研究问题并尝试不同方法
  3. 应用修复
  4. 重新运行构建
  5. 重复以上步骤,直到构建通过
测试失败调试工作流

测试失败时:

  1. 完整阅读测试错误——弄清预期结果
  2. 比较预期行为与实际行为
  3. 根据测试断言检查实现
  4. 在代码中修复问题
  5. 重新运行测试
  6. 重复以上步骤,直到所有测试通过
成功标准

只有满足以下条件,任务才算完成:

  • ✅ 构建通过(npm run build 成功)
  • ✅ Lint 检查通过(npm run lint 成功)
  • ✅ 测试通过(npm run test 成功)

绝不要停留在“代码没问题”——务必确保所有检查全部通过!

⚠️ v6 关键变更:结构化输出

在 v6 中,generateObject 和 streamObject 已弃用。 请改用 generateText/streamText 和 Output 辅助函数。

❌ 错误——已弃用的 v5 模式
// DO NOT USE - DEPRECATED in v6
import { generateObject } from "ai";

const result = await generateObject({
  model: anthropic("claude-sonnet-4-5"),
  schema: z.object({
    sentiment: z.enum(["positive", "neutral", "negative"]),
  }),
  prompt: "Analyze sentiment",
});
✅ 正确——v6 Output 模式
import { generateText, Output } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const { output } = await generateText({
  model: anthropic("claude-sonnet-4-5"),
  output: Output.object({
    schema: z.object({
      sentiment: z.enum(["positive", "neutral", "negative"]),
      topics: z.array(z.string()),
    }),
  }),
  prompt: "Analyze this feedback...",
});

// Access typed output
console.log(output.sentiment); // 'positive' | 'neutral' | 'negative'
console.log(output.topics); // string[]
Output 辅助函数类型
辅助函数用途示例
Output.object()生成带类型的对象Output.object({ schema: z.object({...}) })
Output.array()生成带类型的数组Output.array({ schema: z.string() })
Output.choice()生成枚举值Output.choice({ choices: ['A', 'B', 'C'] })
Output.json()无结构的 JSONOutput.json()

⚠️ 关键:工具调用 API——必须使用 tool() 辅助函数

实现工具调用时,必须使用 'ai' 软件包中的 tool() 辅助函数。

❌ 错误——普通对象(会导致构建错误)
// DO NOT DO THIS - This pattern is INCORRECT
import { z } from 'zod';

tools: {
  myTool: {
    description: 'My tool',
    parameters: z.object({...}),  // ❌ WRONG - "parameters" doesn't exist in v6
    execute: async ({...}) => {...},
  }
}

这会导致以下错误: Type '{ description: string; parameters: ... }' is not assignable to type '{ inputSchema: FlexibleSchema<any>; ... }'

✅ 正确——使用 tool() 辅助函数(必需)
// ALWAYS DO THIS - This is the ONLY correct pattern
import { tool } from 'ai';  // ⚠️ MUST import tool
import { z } from 'zod';

tools: {
  myTool: tool({  // ⚠️ MUST wrap with tool()
    description: 'My tool',
    inputSchema: z.object({...}),  // ⚠️ MUST use "inputSchema" (not "parameters")
    execute: async ({...}) => {...},
  }),
}
工具调用清单

实现任何工具前,请确认:

  • [ ] 从 'ai' 包导入了 tool:import { tool } from 'ai';
  • [ ] 使用 tool({ ... }) 封装了工具定义
  • [ ] 使用了 inputSchema 属性(而非 parameters)
  • [ ] 使用了 zod 模式:z.object({ ... })
  • [ ] 使用异步回调定义了 execute 函数
  • [ ] 为工具添加了 description 字符串

⚠️ v6 新增:面向 Agent 应用的 ToolLoopAgent

Agent 定义
import { ToolLoopAgent, tool, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const myAgent = new ToolLoopAgent({
  model: anthropic("claude-sonnet-4-5"),
  instructions: "You are a helpful assistant that can search and analyze data.",
  tools: {
    getData: tool({
      description: "Fetch data from API",
      inputSchema: z.object({
        query: z.string(),
      }),
      execute: async ({ query }) => {
        // Implement data fetching
        return { result: "data for " + query };
      },
    }),
    analyzeData: tool({
      description: "Analyze fetched data",
      inputSchema: z.object({
        data: z.string(),
      }),
      execute: async ({ data }) => {
        return { analysis: "Analysis of " + data };
      },
    }),
  },
  stopWhen: stepCountIs(20), // Stop after 20 steps max
});

// Non-streaming execution
const { text, toolCalls } = await myAgent.generate({
  prompt: "Find and analyze user data",
});

// Streaming execution
const stream = myAgent.stream({ prompt: "Find and analyze user data" });
for await (const chunk of stream) {
  // Handle streaming chunks
}
Agent API 路由集成
// app/api/agent/route.ts
import { createAgentUIStreamResponse } from "ai";
import { myAgent } from "@/agents/my-agent";

export async function POST(request: Request) {
  const { messages } = await request.json();

  return createAgentUIStreamResponse({
    agent: myAgent,
    uiMessages: messages,
  });
}
Agent 配置选项
参数用途示例
model要使用的 AI 模型anthropic('claude-sonnet-4-5')
instructions系统提示词'You are a helpful assistant.'
tools可用工具{ toolName: tool({...}) }
stopWhen终止条件stepCountIs(20)
toolChoice工具使用模式'auto'、'required'、'none'
output结构化输出模式Output.object({...})
prepareStep每个步骤的动态调整返回步骤配置的函数
prepareCall运行时选项注入用于 RAG 等的异步函数

⚠️ 重要:v5 到 v6 的常见破坏性变更

1. useChat Hook 变更

❌ 错误(v5 模式):

const { messages, input, setInput, append } = useChat();

// Sending message
append({ content: text, role: "user" });

✅ 正确(v6 模式):

const { messages, sendMessage, status, addToolOutput } = useChat();
const [input, setInput] = useState('');

// Sending message
sendMessage({ text: input });

// New in v6: Handle tool outputs
addToolOutput({ toolCallId: 'xxx', result: { ... } });
2. 消息结构

❌ 错误(v5 简单内容):

<div>{message.content}</div>

✅ 正确(基于 v6 parts 的结构):

<div>
  {message.parts.map((part, index) =>
    part.type === 'text' ? <span key={index}>{part.text}</span> : null
  )}
</div>
3. 响应方法

❌ 错误(v5):

return result.toDataStreamResponse();

✅ 正确(v6):

return result.toUIMessageStreamResponse();
4. 模型指定
import { anthropic } from "@ai-sdk/anthropic";
import { openai } from "@ai-sdk/openai";

// Use provider functions (direct provider access)
model: anthropic("claude-sonnet-4-5");
model: anthropic("claude-opus-4-5");
model: anthropic("claude-haiku-4-5");
model: openai("gpt-4o");
model: openai("gpt-4o-mini");
5. Vercel AI Gateway

用途: 使用 Vercel AI Gateway,通过多个提供商统一访问模型,并实现速率限制、缓存和可观测性。

导入:

import { gateway } from "ai";

通过 Gateway 提供的 Anthropic 模型:

model: gateway("anthropic/claude-sonnet-4-5");
model: gateway("anthropic/claude-haiku-4-5");
model: gateway("anthropic/claude-opus-4-5");

适用 Gateway 的场景:

  • 需要速率限制和缓存的生产应用
  • 需要统一接口的多提供商应用
  • 需要可观测性和分析功能的应用
  • 需要自动重试和错误处理时

适用直接使用提供商的场景:

  • 开发/测试环境
  • 需要使用 Gateway 不支持的提供商专属功能时
  • 需要直接控制 API 调用时

示例:

import { generateText, gateway } from "ai";

const result = await generateText({
  model: gateway("anthropic/claude-sonnet-4-5"),
  prompt: "Hello, world!",
});

对比:

// Option 1: Direct provider
import { anthropic } from "@ai-sdk/anthropic";
model: anthropic("claude-sonnet-4-5");

// Option 2: Gateway (recommended for production)
import { gateway } from "ai";
model: gateway("anthropic/claude-sonnet-4-5");

核心 API 参考

1. generateText - 非流式文本生成

用途: 为非交互式场景生成文本(电子邮件草稿、摘要、使用工具的 Agent)。

签名:

import { generateText, Output } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const result = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  prompt: 'Your prompt here',
  system: 'Optional system message',
  tools?: { ... },
  maxSteps?: 5,
  output?: Output.object({ schema: z.object({...}) }),
});

返回值:

{
  text: string;              // Generated text output
  output?: T;                // Typed structured output (if Output specified)
  toolCalls: ToolCall[];     // Tool invocations made
  finishReason: string;      // Why generation stopped
  usage: TokenUsage;         // Token consumption
  response: RawResponse;     // Raw provider response
  warnings: Warning[];       // Provider-specific alerts
}

示例:

// app/api/generate/route.ts
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export async function GET() {
  const result = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    prompt: "Why is the sky blue?",
  });

  return Response.json({ text: result.text });
}
2. streamText - 流式文本生成

用途: 为交互式聊天应用流式传输响应。

签名:

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';

const result = streamText({
  model: anthropic('claude-sonnet-4-5'),
  prompt: 'Your prompt here',
  system: 'Optional system message',
  messages?: ModelMessage[],
  tools?: { ... },
  onChunk?: (chunk) => { ... },
  onStepFinish?: (step) => { ... },
  onFinish?: async (result) => { ... },
  onError?: async (error) => { ... },
});

返回方法:

// For chat applications with useChat hook
result.toUIMessageStreamResponse();

// For simple text streaming
result.toTextStreamResponse();

示例 - 聊天 API 路由:

// app/api/chat/route.ts
import { streamText, convertToModelMessages } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import type { UIMessage } from "ai";

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-5"),
    system: "You are a helpful assistant.",
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
3. useChat Hook - 客户端聊天界面

用途: 构建支持流式传输的交互式聊天 UI。

签名:

import { useChat } from '@ai-sdk/react';

const {
  messages,        // Array of UIMessage with parts-based structure
  sendMessage,     // Function to send messages (replaces append)
  status,          // 'submitted' | 'streaming' | 'ready' | 'error'
  stop,            // Abort current streaming
  regenerate,      // Reprocess last message
  setMessages,     // Manually modify history
  error,           // Error object if request fails
  clearError,      // Clear error state
  addToolOutput,   // Submit tool results (NEW in v6)
  resumeStream,    // Resume interrupted stream (NEW in v6)
} = useChat({
  api: '/api/chat',
  id?: 'chat-id',
  messages?: initialMessages,
  onToolCall?: async (toolCall) => { ... },
  onFinish?: (message) => { ... },
  onError?: (error) => { ... },
  sendAutomaticallyWhen?: (messages) => boolean,
  resume?: true,
});

完整示例:

'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function ChatPage() {
  const { messages, sendMessage, status, addToolOutput } = useChat({
    onToolCall: async ({ toolCall }) => {
      // Handle client-side tool execution
      if (toolCall.name === 'confirm') {
        const result = await showConfirmDialog(toolCall.args);
        addToolOutput({ toolCallId: toolCall.id, result });
      }
    },
  });
  const [input, setInput] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;

    sendMessage({ text: input });
    setInput('');
  };

  return (
    <div>
      <div>
        {messages.map((message) => (
          <div key={message.id}>
            <strong>{message.role}:</strong>
            {message.parts.map((part, index) => {
              switch (part.type) {
                case 'text':
                  return <span key={index}>{part.text}</span>;
                case 'tool-call':
                  return <div key={index}>Tool: {part.name}</div>;
                default:
                  return null;
              }
            })}
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message..."
          disabled={status === 'streaming'}
        />
        <button type="submit" disabled={status === 'streaming'}>
          Send
        </button>
      </form>
    </div>
  );
}
4. 工具调用 / 函数调用

用途: 让 AI 模型能够使用结构化参数调用函数。

定义工具:

import { tool } from "ai";
import { z } from "zod";

const weatherTool = tool({
  description: "Get the weather in a location",
  inputSchema: z.object({
    location: z.string().describe("The location to get the weather for"),
    unit: z.enum(["C", "F"]).describe("Temperature unit"),
  }),
  outputSchema: z.object({
    temperature: z.number(),
    condition: z.string(),
  }),
  execute: async ({ location, unit }) => {
    // Fetch or mock weather data
    return {
      temperature: 24,
      condition: "Sunny",
    };
  },
});

将工具与 generateText/streamText 配合使用:

// app/api/chat/route.ts
import { streamText, convertToModelMessages, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
import type { UIMessage } from "ai";

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-5"),
    messages: convertToModelMessages(messages),
    tools: {
      getWeather: tool({
        description: "Get the weather for a location",
        inputSchema: z.object({
          city: z.string().describe("The city to get the weather for"),
          unit: z
            .enum(["C", "F"])
            .describe("The unit to display the temperature in"),
        }),
        execute: async ({ city, unit }) => {
          // API call or mock data
          return `It is currently 24°${unit} and Sunny in ${city}!`;
        },
      }),
    },
    toolChoice: "auto", // 'auto' | 'required' | 'none' | { type: 'tool', toolName: 'xxx' }
  });

  return result.toUIMessageStreamResponse();
}

多步骤工具调用:

const result = await generateText({
  model: anthropic("claude-sonnet-4-5"),
  tools: {
    weather: weatherTool,
    search: searchTool,
  },
  prompt: "What is the weather in San Francisco and find hotels there?",
  maxSteps: 5, // Allow up to 5 tool call steps
});
5. 文本嵌入

用途: 将文本转换为数值向量,用于语义搜索、RAG 或相似度计算。

签名:

import { embed, embedMany } from "ai";
import { openai } from "@ai-sdk/openai";

// Single embedding
const result = await embed({
  model: openai.textEmbeddingModel("text-embedding-3-small"),
  value: "Text to embed",
});

// Batch embeddings
const batchResult = await embedMany({
  model: openai.textEmbeddingModel("text-embedding-3-small"),
  values: ["Text 1", "Text 2", "Text 3"],
});

返回值:

{
  embedding: number[];  // Numerical array representing the text
  usage: { tokens: number };  // Token consumption
  response: RawResponse;  // Raw provider response
}

示例 - 嵌入 API 路由:

// app/api/embed/route.ts
import { embed } from "ai";
import { openai } from "@ai-sdk/openai";

export async function POST(req: Request) {
  const { text } = await req.json();

  const { embedding, usage } = await embed({
    model: openai.textEmbeddingModel("text-embedding-3-small"),
    value: text,
  });

  return Response.json({ embedding, usage });
}
6. 中间件

用途: 拦截并修改模型行为,用于日志记录、缓存、防护措施、RAG 等。

内置中间件:

import {
  extractReasoningMiddleware,
  simulateStreamingMiddleware,
  defaultSettingsMiddleware,
  wrapLanguageModel,
} from "ai";

// Extract reasoning from models like Claude
const modelWithReasoning = wrapLanguageModel({
  model: anthropic("claude-sonnet-4-5"),
  middleware: extractReasoningMiddleware({ tagName: "thinking" }),
});

// Apply default settings
const modelWithDefaults = wrapLanguageModel({
  model: anthropic("claude-sonnet-4-5"),
  middleware: defaultSettingsMiddleware({
    temperature: 0.7,
    maxOutputTokens: 1000,
  }),
});

自定义中间件:

import { LanguageModelMiddleware, wrapLanguageModel } from "ai";

// Logging middleware
const loggingMiddleware: LanguageModelMiddleware = {
  transformParams: async ({ params }) => {
    console.log("Request params:", params);
    return params;
  },
  wrapGenerate: async ({ doGenerate, params }) => {
    const result = await doGenerate();
    console.log("Response:", result);
    return result;
  },
};

// Caching middleware
const cache = new Map<string, string>();
const cachingMiddleware: LanguageModelMiddleware = {
  wrapGenerate: async ({ doGenerate, params }) => {
    const cacheKey = JSON.stringify(params.prompt);
    if (cache.has(cacheKey)) {
      return { text: cache.get(cacheKey)! };
    }
    const result = await doGenerate();
    cache.set(cacheKey, result.text);
    return result;
  },
};

// RAG middleware
const ragMiddleware: LanguageModelMiddleware = {
  transformParams: async ({ params }) => {
    const relevantDocs = await vectorSearch(params.prompt);
    return {
      ...params,
      prompt: `Context: ${relevantDocs}\n\nQuery: ${params.prompt}`,
    };
  },
};

// Apply multiple middleware
const enhancedModel = wrapLanguageModel({
  model: anthropic("claude-sonnet-4-5"),
  middleware: [loggingMiddleware, cachingMiddleware, ragMiddleware],
});
7. Model Context Protocol(MCP)集成

**用途:**连接外部 MCP 服务,以动态访问工具。

安装:

bun add @ai-sdk/mcp @modelcontextprotocol/sdk

HTTP 传输(生产环境):

import { createMCPClient } from "@ai-sdk/mcp";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const httpTransport = new StreamableHTTPClientTransport(
    new URL("https://mcp-server.example.com/mcp"),
    { headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` } }
  );

  const mcpClient = await createMCPClient({ transport: httpTransport });

  try {
    const tools = await mcpClient.tools();

    const response = streamText({
      model: anthropic("claude-sonnet-4-5"),
      tools,
      prompt,
      onFinish: async () => {
        await mcpClient.close();
      },
      onError: async () => {
        await mcpClient.close();
      },
    });

    return response.toTextStreamResponse();
  } catch (error) {
    await mcpClient.close();
    return new Response("Internal Server Error", { status: 500 });
  }
}

Stdio 传输(开发环境):

import { createMCPClient } from "@ai-sdk/mcp";
import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp";

const stdioTransport = new Experimental_StdioMCPTransport({
  command: "npx",
  args: [
    "-y",
    "@modelcontextprotocol/server-filesystem",
    "/path/to/allowed/dir",
  ],
});

const mcpClient = await createMCPClient({ transport: stdioTransport });

要点:

  • 始终在 onFinish 和 onError 中关闭客户端
  • 使用 mcpClient.tools() 动态获取工具
  • 生产环境使用 HTTP/SSE,开发环境使用 Stdio
  • 可以合并工具集合,以组合多个 MCP 客户端
8. 消息实用工具

convertToModelMessages: 将 useChat 中的 UI 消息转换为供 AI 函数使用的 ModelMessage 对象。

import { convertToModelMessages } from "ai";
import type { UIMessage } from "ai";

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-5"),
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}

工作流模式

1. 顺序(链式)模式
async function sequentialWorkflow(input: string) {
  // Step 1: Generate initial content
  const { text: draft } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    prompt: `Write marketing copy for: ${input}`,
  });

  // Step 2: Evaluate quality
  const { output: evaluation } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    output: Output.object({
      schema: z.object({
        score: z.number().min(1).max(10),
        feedback: z.string(),
      }),
    }),
    prompt: `Evaluate this copy: ${draft}`,
  });

  // Step 3: Improve if needed
  if (evaluation.score < 7) {
    const { text: improved } = await generateText({
      model: anthropic("claude-sonnet-4-5"),
      prompt: `Improve this copy based on feedback:\n\nCopy: ${draft}\n\nFeedback: ${evaluation.feedback}`,
    });
    return improved;
  }

  return draft;
}
2. 并行模式
async function parallelReview(code: string) {
  const [securityReview, performanceReview, maintainabilityReview] =
    await Promise.all([
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Review for security issues:\n\n${code}`,
      }),
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Review for performance issues:\n\n${code}`,
      }),
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Review for maintainability:\n\n${code}`,
      }),
    ]);

  return {
    security: securityReview.text,
    performance: performanceReview.text,
    maintainability: maintainabilityReview.text,
  };
}
3. 路由模式
async function routeQuery(query: string) {
  // Classify the query
  const { output: classification } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    output: Output.choice({
      choices: ["technical", "billing", "general"] as const,
    }),
    prompt: `Classify this customer query: ${query}`,
  });

  // Route to appropriate handler
  switch (classification) {
    case "technical":
      return handleTechnicalQuery(query);
    case "billing":
      return handleBillingQuery(query);
    default:
      return handleGeneralQuery(query);
  }
}
4. 编排器-工作者模式
async function implementFeature(requirement: string) {
  // Orchestrator: Break down the task
  const { output: plan } = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    output: Output.object({
      schema: z.object({
        tasks: z.array(
          z.object({
            type: z.enum(["frontend", "backend", "database"]),
            description: z.string(),
          })
        ),
      }),
    }),
    prompt: `Break down this feature into tasks: ${requirement}`,
  });

  // Workers: Execute tasks in parallel
  const results = await Promise.all(
    plan.tasks.map((task) =>
      generateText({
        model: anthropic("claude-sonnet-4-5"),
        prompt: `Implement this ${task.type} task: ${task.description}`,
      })
    )
  );

  return results.map((r) => r.text);
}
5. 评估器-优化器模式
async function optimizeOutput(input: string, maxIterations = 3) {
  let output = await generateText({
    model: anthropic("claude-sonnet-4-5"),
    prompt: input,
  });

  for (let i = 0; i < maxIterations; i++) {
    const { output: evaluation } = await generateText({
      model: anthropic("claude-sonnet-4-5"),
      output: Output.object({
        schema: z.object({
          isGood: z.boolean(),
          improvements: z.array(z.string()),
        }),
      }),
      prompt: `Evaluate this output: ${output.text}`,
    });

    if (evaluation.isGood) break;

    output = await generateText({
      model: anthropic("claude-sonnet-4-5"),
      prompt: `Improve based on: ${evaluation.improvements.join(", ")}\n\nOriginal: ${output.text}`,
    });
  }

  return output.text;
}

消息部分类型(v6)

| 部分类型 | 描述 | 属性 | | ----------- | -------------------- | ------------------------------------------ | ---------- | -------- | --------------- | | text | 文本内容 | text、isStreaming | | tool-call | 工具调用 | name、args、state('input-streaming' | 'invoking' | 'output' | 'output-error') | | reasoning | 模型思考 | text、isStreaming | | file | 文件附件 | mediaType、url 或 data | | source | RAG 来源引用 | url 或 documentId、title | | step | 工作流边界 | 标记步骤边界 | | data | 自定义数据 | 任意自定义负载 |

TypeScript 最佳实践

类型导入
import type {
  UIMessage, // Message type from useChat
  ModelMessage, // Message type for model functions
  ToolCall, // Tool call information
  TokenUsage, // Token consumption data
} from "ai";
Agent 类型安全
import type { InferAgentUIMessage } from "ai";

// Type-safe messages from agent
type MyAgentMessage = InferAgentUIMessage<typeof myAgent>;
强类型工具
import { tool } from "ai";
import { z } from "zod";

// Tool helper infers execute parameter types
const myTool = tool({
  description: "My tool",
  inputSchema: z.object({
    param1: z.string(),
    param2: z.number(),
  }),
  outputSchema: z.object({
    result: z.string(),
  }),
  execute: async ({ param1, param2 }) => {
    // param1 is inferred as string
    // param2 is inferred as number
    return { result: "success" };
  },
});

常见模式

模式 1:简单聊天应用

客户端(app/page.tsx):

'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Chat() {
  const { messages, sendMessage, status } = useChat();
  const [input, setInput] = useState('');

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>
          <strong>{m.role}:</strong>
          {m.parts.map((part, i) =>
            part.type === 'text' ? <span key={i}>{part.text}</span> : null
          )}
        </div>
      ))}
      <form onSubmit={(e) => {
        e.preventDefault();
        sendMessage({ text: input });
        setInput('');
      }}>
        <input value={input} onChange={(e) => setInput(e.target.value)} />
        <button disabled={status === 'streaming'}>Send</button>
      </form>
    </div>
  );
}

服务端(app/api/chat/route.ts):

import { streamText, convertToModelMessages } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import type { UIMessage } from "ai";

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-5"),
    system: "You are a helpful assistant.",
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
模式 2:具有结构化输出的聊天
import { streamText, convertToModelMessages, Output } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
import type { UIMessage } from "ai";

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-5"),
    messages: convertToModelMessages(messages),
    output: Output.object({
      schema: z.object({
        response: z.string(),
        sentiment: z.enum(["positive", "neutral", "negative"]),
        confidence: z.number().min(0).max(1),
      }),
    }),
  });

  return result.toUIMessageStreamResponse();
}
模式 3:使用多个工具的 Agent
import {
  ToolLoopAgent,
  tool,
  stepCountIs,
  createAgentUIStreamResponse,
} from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";

const researchAgent = new ToolLoopAgent({
  model: anthropic("claude-sonnet-4-5"),
  instructions:
    "You are a research assistant that can search and analyze information.",
  tools: {
    webSearch: tool({
      description: "Search the web for information",
      inputSchema: z.object({
        query: z.string().describe("Search query"),
      }),
      execute: async ({ query }) => {
        // Implement web search
        return { results: ["..."] };
      },
    }),
    analyze: tool({
      description: "Analyze collected information",
      inputSchema: z.object({
        data: z.string().describe("Data to analyze"),
      }),
      execute: async ({ data }) => {
        return { analysis: "..." };
      },
    }),
    summarize: tool({
      description: "Summarize findings",
      inputSchema: z.object({
        findings: z.array(z.string()),
      }),
      execute: async ({ findings }) => {
        return { summary: "..." };
      },
    }),
  },
  stopWhen: stepCountIs(10),
});

// API Route
export async function POST(request: Request) {
  const { messages } = await request.json();
  return createAgentUIStreamResponse({
    agent: researchAgent,
    uiMessages: messages,
  });
}
模式 4:使用嵌入的语义搜索
// app/api/search/route.ts
import { embed } from "ai";
import { openai } from "@ai-sdk/openai";

export async function POST(req: Request) {
  const { query } = await req.json();

  // Generate embedding for search query
  const { embedding } = await embed({
    model: openai.textEmbeddingModel("text-embedding-3-small"),
    value: query,
  });

  // Use embedding for similarity search in vector database
  // const results = await vectorDB.search(embedding);

  return Response.json({ embedding, results: [] });
}

常见陷阱及解决方案

陷阱 1:使用已弃用的 generateObject/streamObject
// ❌ WRONG - Deprecated in v6
import { generateObject } from 'ai';
const result = await generateObject({
  schema: z.object({...}),
  prompt: '...',
});

// ✅ CORRECT - Use Output with generateText
import { generateText, Output } from 'ai';
const { output } = await generateText({
  output: Output.object({ schema: z.object({...}) }),
  prompt: '...',
});
陷阱 2:未使用 tool() 辅助函数定义工具
// ❌ WRONG - Plain object (WILL CAUSE BUILD FAILURE)
tools: {
  myTool: {
    description: 'My tool',
    parameters: z.object({...}),  // ❌ Wrong property name
    execute: async ({...}) => {...},
  },
}

// ✅ CORRECT - Use tool() helper (REQUIRED)
import { tool } from 'ai';
tools: {
  myTool: tool({
    description: 'My tool',
    inputSchema: z.object({...}),  // ⚠️ Use inputSchema
    execute: async ({...}) => {...},
  }),
}
陷阱 3:在 v6 中使用 v5 的 useChat API
// ❌ WRONG - v5 pattern
const { input, setInput, append } = useChat();
append({ content: "Hello", role: "user" });

// ✅ CORRECT - v6 pattern
const { sendMessage } = useChat();
const [input, setInput] = useState("");
sendMessage({ text: "Hello" });
陷阱 4:访问 message.content 而非 message.parts
// ❌ WRONG - v5 pattern
<div>{message.content}</div>

// ✅ CORRECT - v6 parts-based
<div>
  {message.parts.map((part, i) =>
    part.type === 'text' ? <span key={i}>{part.text}</span> : null
  )}
</div>
陷阱 5:使用错误的响应方法
// ❌ WRONG - v5 method
return result.toDataStreamResponse();

// ✅ CORRECT - v6 method
return result.toUIMessageStreamResponse();
陷阱 6:忘记清理 MCP 客户端
// ❌ WRONG - no cleanup
const mcpClient = await createMCPClient({ transport });
const tools = await mcpClient.tools();
const response = streamText({ model, tools, prompt });
return response.toTextStreamResponse();

// ✅ CORRECT - cleanup in callbacks
const response = streamText({
  model,
  tools,
  prompt,
  onFinish: async () => {
    await mcpClient.close();
  },
  onError: async () => {
    await mcpClient.close();
  },
});

迁移检查清单(v5 → v6)

从 v5 迁移到 v6 时,请更新:

  • [ ] 将 generateObject/streamObject 替换为 generateText/streamText + Output
  • [ ] 在 useChat 中将 append 替换为 sendMessage
  • [ ] 从 useChat 解构中移除 input、setInput、handleInputChange
  • [ ] 为输入添加本地状态管理:const [input, setInput] = useState('')
  • [ ] 将消息渲染从 message.content 更新为 message.parts.map(...)
  • [ ] 将 sendMessage 调用更新为使用 { text: input } 结构
  • [ ] 将 toDataStreamResponse() 替换为 toUIMessageStreamResponse()
  • [ ] 将工具定义更新为使用带有 inputSchema 的 tool() 辅助函数
  • [ ] 更新模型 ID(例如,claude-sonnet-4-5)
  • [ ] 考虑在 Agent 应用中使用 ToolLoopAgent
  • [ ] 更新 TypeScript 类型(UIMessage、ModelMessage)
  • [ ] 如果使用客户端工具,添加 addToolOutput 处理逻辑
  • [ ] 考虑实现中间件,用于日志记录、缓存或护栏控制

决策指南

实现 AI SDK 功能时,请思考:

  1. 这是客户端还是服务端功能?

    • 客户端:使用 useChat hook
    • 服务端:使用 generateText 或 streamText
    • Agent:使用 ToolLoopAgent 和 createAgentUIStreamResponse
  2. 我需要流式还是非流式处理?

    • 流式聊天:streamText + toUIMessageStreamResponse()
    • 非流式:generateText
    • 简单文本流:streamText + toTextStreamResponse()
  3. 我需要结构化输出吗?

    • 是:使用 Output.object()、Output.array()、Output.choice() 或 Output.json()
    • 通过 output 参数传递给 generateText 或 streamText
  4. 我需要调用工具吗?

    • 是:使用 tool() 助手函数和 inputSchema(zod)定义工具
    • 将工具对象传递给 generateText、streamText 或 ToolLoopAgent
  5. 我是在构建 Agent 吗?

    • 是:使用 ToolLoopAgent 类
    • 根据需要配置 stopWhen、toolChoice、prepareStep
    • 在 API 路由中使用 createAgentUIStreamResponse
  6. 我使用的消息格式正确吗?

    • 客户端(useChat):返回包含 parts 属性的 UIMessage[]
    • 服务端:使用 convertToModelMessages() 转换为 ModelMessage[]
    • 使用 message.parts.map(...) 渲染消息
  7. 我的模型指定正确吗?

    • 直接使用提供商:anthropic('claude-sonnet-4-5')
    • Gateway(生产环境):gateway('anthropic/claude-sonnet-4-5')
    • Embeddings:openai.textEmbeddingModel('text-embedding-3-small')
  8. 我需要 Embeddings 吗?

    • 单个值使用 embed
    • 批量处理使用 embedMany
    • 使用 textEmbeddingModel() 方法
  9. 我需要中间件吗?

    • 日志记录:使用 transformParams/wrapGenerate 编写自定义中间件
    • 缓存:编写可存储结果的自定义中间件
    • RAG:编写用于注入上下文的自定义中间件
    • 护栏:编写用于过滤输出的自定义中间件

快速参考

任务函数关键参数
生成文本generateText()model、prompt、system、tools、output
流式生成文本streamText()model、messages、tools、output、onFinish
聊天 UIuseChat()api、onToolCall、onFinish、onError
构建 AgentToolLoopAgentmodel、instructions、tools、stopWhen
工具调用tool()description、inputSchema、outputSchema、execute
结构化输出Output.object()schema(zod)
文本 Embeddingembed()model、value
批量 EmbeddingembedMany()model、values
消息转换convertToModelMessages()messages(UIMessage[])
MCP 集成createMCPClient()transport
添加中间件wrapLanguageModel()model、middleware
Gateway 模型gateway()"provider/model-name"(例如:"anthropic/claude-sonnet-4-5")

其他资源

如有疑问,请查阅官方文档:

**请记住:**AI SDK v6 使用提供商函数模型规范(或在生产环境中使用 gateway())、基于 parts 的消息、使用 sendMessage 代替 append、使用 Output 助手代替 generateObject、使用 toUIMessageStreamResponse 代替 toDataStreamResponse,并且要求在 API 路由中使用 convertToModelMessages。

评论

支持 Markdown