mcpField
V
vercel-ai-sdk

vercel-ai-sdk

github.com/wsimmonds开发109339 次安装53 周前更新
代码仓库

关于 vercel-ai-sdk

面向使用 Vercel AI SDK v5 的开发者,提供实现聊天、文本生成、工具调用及 MCP 集成的编码指南。重点说明 v5 API 模式、依赖管理,以及反复构建和测试验证。

主要功能

  1. 01聊天与流式响应实现
  2. 02工具调用与嵌入生成
  3. 03v4 到 v5 迁移指导
  4. 04依赖安装与包管理器检查
  5. 05构建、Lint 和测试排错

适用场景

  1. 01实现 useChat 聊天界面
  2. 02创建生成或流式响应 API
  3. 03为语义搜索生成嵌入
  4. 04将项目迁移至 AI SDK v5

MCP 相关

指南涵盖集成 MCP 服务端的实现。未说明该技能本身提供 MCP 服务。

安装

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

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

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

详细介绍

Vercel AI SDK v5 实现指南

何时使用此技能

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

  • 使用 useChat hook 实现 AI 聊天界面
  • 创建用于生成或流式传输 AI 响应的 API 路由
  • 添加工具调用 / 函数调用功能
  • 为语义搜索或 RAG 生成文本嵌入
  • 从 AI SDK v4 迁移到 v5
  • 集成 Model Context Protocol (MCP) 服务
  • 处理流式响应或消息持久化

结构化实现工作流

了解任务需求 - 确定所需的 AI 功能(聊天、生成、工具、嵌入) - 确定采用客户端(useChat)还是服务端(API 路由)实现 - 检查是否需要流式或非流式响应 - 确认模型提供方(OpenAI、Anthropic 等) 如有疑问,确认当前 API 模式 - 如果不确定 API 模式,请使用 WebFetch 检查 https://ai-sdk.dev/docs/ - 确认该提供方的模型指定格式 - 验证复杂功能的函数签名 使用正确的 v5 模式进行实现 - 使用基于字符串的模型指定方式('provider/model-id') - 对于聊天:使用 sendMessage(不要使用 append),并采用基于 parts 的消息 - 对于工具:必须从 'ai' 导入并使用 tool() 辅助函数,必须使用 inputSchema(不要使用 parameters),必须使用 zod - 对于流式传输:使用 toUIMessageStreamResponse() 或 toTextStreamResponse() - 对于嵌入:使用 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 - 这会导致依赖版本不匹配并使构建失败 - 务必检查现有锁文件,并使用对应的包管理器
  绝不要将 "Module not found" 错误视为环境问题
  你必须使用正确的包管理器安装所需的包

  常用所需包:
  - ai(核心 AI SDK)
  - @ai-sdk/openai(OpenAI 提供方)
  - @ai-sdk/anthropic(Anthropic 提供方)
  - @modelcontextprotocol/sdk(MCP 集成)
  - zod(用于工具模式)
</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 通过 - ✅ 测试通过
  “代码是正确的”还不够
  必须达到全部通过的状态
  这就是成为自主 Agent 的意义
</critical>

⚠️ 自主 Agent 心态

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

这意味着:

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

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

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

❌ 错误:“框架有问题,修不了” ✅ 正确:“检测到框架错误——调查问题、尝试变通方案并持续调试,直到找到解决办法”

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

依赖安装工作流

遇到“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/openai'
    Import: import { openai } from '@ai-sdk/openai'
    Package needed: @ai-sdk/openai
    
  3. 使用正确的包管理器安装

    # If pnpm-lock.yaml exists (MOST COMMON for Next.js evals):
    pnpm install @ai-sdk/openai
    # or
    pnpm add @ai-sdk/openai
    
    # If package-lock.json exists:
    npm install @ai-sdk/openai
    
    # If yarn.lock exists:
    yarn add @ai-sdk/openai
    
    # If bun.lockb exists:
    bun install @ai-sdk/openai
    
  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 成功)

绝不要停留在“代码正确”——务必达到全部通过状态!

⚠️ 严重提示:工具调用 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 v5
    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 schema:z.object({ ... })
  • 使用异步回调定义 execute 函数
  • 为工具添加 description 字符串

⚠️ 严重提示:v4 到 v5 的常见破坏性变更

1. useChat Hook 变更

❌ 错误(v4 模式):

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

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

✅ 正确(v5 模式):

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

// Sending message
sendMessage({ text: input });
2. 消息结构

❌ 错误(v4 简单内容):

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

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

<div>
  {message.parts.map((part, index) =>
    part.type === 'text' ? <span key={index}>{part.text}</span> : null
  )}
</div>
3. 模型指定方式

✅ 首选:基于字符串(v5 推荐):

import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-4o',  // String format
  prompt: 'Hello',
});

✅ 也可使用:基于函数(兼容旧版):

import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';

const result = await generateText({
  model: openai('gpt-4o'),  // Function format
  prompt: 'Hello',
});

核心 API 参考

1. generateText——非流式文本生成

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

签名:

import { generateText } from 'ai';

const result = await generateText({
  model: 'openai/gpt-4o',           // String format: 'provider/model-id'
  prompt: 'Your prompt here',        // User input
  system: 'Optional system message', // Optional system instructions
  tools?: { ... },                   // Optional tool calling
  maxSteps?: 5,                      // For multi-step tool calling
});

返回值:

{
  text: string;              // Generated text output
  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';

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

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

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

签名:

import { streamText } from 'ai';

const result = streamText({
  model: 'openai/gpt-4o',
  prompt: 'Your prompt here',
  system: 'Optional system message',
  messages?: ModelMessage[],  // For chat history
  tools?: { ... },
  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 type { UIMessage } from 'ai';

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

  const result = streamText({
    model: 'openai/gpt-4o',
    system: 'You are a helpful assistant.',
    messages: convertToModelMessages(messages),
  });

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

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

签名:

import { useChat } from 'ai/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
  reload,          // Retry after error
} = useChat({
  api: '/api/chat',  // API endpoint
  onFinish?: (message) => { ... },
  onError?: (error) => { ... },
});

完整示例:

'use client';

import { useChat } from 'ai/react';
import { useState } from 'react';

export default function ChatPage() {
  const { messages, sendMessage, status } = useChat();
  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) =>
              part.type === 'text' ? (
                <span key={index}>{part.text}</span>
              ) : 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'),
  }),
  execute: async ({ location, unit }) => {
    // Fetch or mock weather data
    return {
      location,
      temperature: 24,
      unit,
      condition: 'Sunny',
    };
  },
});

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

// app/api/chat/route.ts
import { streamText, convertToModelMessages, tool } from 'ai';
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: 'openai/gpt-4o',
    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 }) => {
          // Mock response
          return `It is currently 24°${unit} and Sunny in ${city}!`;
        },
      }),
    },
  });

  return result.toUIMessageStreamResponse();
}

多步工具调用:

const result = await generateText({
  model: 'openai/gpt-4o',
  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 } from 'ai';
import { openai } from '@ai-sdk/openai';

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

返回值:

{
  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 GET() {
  const { embedding, usage } = await embed({
    model: openai.textEmbeddingModel('text-embedding-3-small'),
    value: 'sunny day at the beach',
  });

  return Response.json({ embedding, usage });
}

批量嵌入:

import { embedMany } from 'ai';

const { embeddings, usage } = await embedMany({
  model: openai.textEmbeddingModel('text-embedding-3-small'),
  values: [
    'sunny day at the beach',
    'rainy afternoon in the city',
    'snowy mountain landscape',
  ],
});
6. 消息工具

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: 'openai/gpt-4o',
    messages: convertToModelMessages(messages),  // Convert for model
  });

  return result.toUIMessageStreamResponse();
}
7. 模型上下文协议(MCP)集成

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

示例:

// app/api/chat/route.ts
import { experimental_createMCPClient, streamText } from 'ai';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

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

  try {
    // Connect to MCP server
    const httpTransport = new StreamableHTTPClientTransport(
      new URL('http://localhost:3000/mcp')
    );

    const httpClient = await experimental_createMCPClient({
      transport: httpTransport,
    });

    // Fetch tools from MCP server
    const tools = await httpClient.tools();

    const response = streamText({
      model: 'openai/gpt-4o',
      tools,
      prompt,
      onFinish: async () => {
        await httpClient.close();  // Clean up
      },
      onError: async () => {
        await httpClient.close();  // Clean up on error
      },
    });

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

要点:

  • 使用 experimental_createMCPClient(注意:这是实验性 API)
  • 始终在 onFinish 和 onError 中关闭客户端
  • 使用 httpClient.tools() 动态获取工具
  • 需要 @modelcontextprotocol/sdk 包

模型指定模式

基于字符串(v5 推荐)
// Format: 'provider/model-id'
model: 'openai/gpt-4o'
model: 'anthropic/claude-4-sonnet'
model: 'google/gemini-2.0-flash'
基于函数(兼容旧版)
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';

model: openai('gpt-4o')
model: anthropic('claude-4-sonnet')
嵌入模型
import { openai } from '@ai-sdk/openai';

// Text embeddings use a different method
openai.textEmbeddingModel('text-embedding-3-small')
openai.textEmbeddingModel('text-embedding-3-large')

TypeScript 最佳实践

类型导入
import type {
  UIMessage,           // Message type from useChat
  ModelMessage,        // Message type for model functions
  ToolCall,            // Tool call information
  TokenUsage,          // Token consumption data
} from 'ai';
强类型工具
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(),
  }),
  execute: async ({ param1, param2 }) => {
    // param1 is inferred as string
    // param2 is inferred as number
    return { result: 'success' };
  },
});
API 路由类型
// app/api/chat/route.ts
import type { UIMessage } from 'ai';

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

  // ... implementation
}

常见模式

模式 1:简单聊天应用

客户端(app/page.tsx):

'use client';

import { useChat } from 'ai/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 type { UIMessage } from 'ai';

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

  const result = streamText({
    model: 'openai/gpt-4o',
    system: 'You are a helpful assistant.',
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
模式 2:使用工具的聊天

带工具调用的服务端:

import { streamText, convertToModelMessages, tool } from 'ai';
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: 'openai/gpt-4o',
    messages: convertToModelMessages(messages),
    tools: {
      getWeather: tool({
        description: 'Get weather for a city',
        inputSchema: z.object({
          city: z.string(),
        }),
        execute: async ({ city }) => {
          // API call or mock data
          return { city, temp: 72, condition: 'Sunny' };
        },
      }),
      searchWeb: tool({
        description: 'Search the web',
        inputSchema: z.object({
          query: z.string(),
        }),
        execute: async ({ query }) => {
          // Search implementation
          return { results: ['...'] };
        },
      }),
    },
  });

  return result.toUIMessageStreamResponse();
}
模式 3:非交互式生成
// app/api/summarize/route.ts
import { generateText } from 'ai';

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

  const result = await generateText({
    model: 'anthropic/claude-4-sonnet',
    system: 'You are a summarization expert.',
    prompt: `Summarize this text:\n\n${text}`,
  });

  return Response.json({ summary: result.text });
}
模式 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:工具未使用 tool() 辅助函数 — ⚠️ 严重

这是最常见也最严重的错误。请务必使用 tool() 辅助函数!

// ❌ WRONG - Plain object (WILL CAUSE BUILD FAILURE)
import { z } from 'zod';

tools: {
  myTool: {
    description: 'My tool',
    parameters: z.object({      // ❌ Wrong property name
      city: z.string(),
    }),
    execute: async ({ city }) => { ... },
  },
}
// Build error: Type '{ description: string; parameters: ... }' is not assignable

// ✅ CORRECT - Use tool() helper (REQUIRED)
import { tool } from 'ai';      // ⚠️ MUST import tool
import { z } from 'zod';

tools: {
  myTool: tool({                // ⚠️ MUST use tool() wrapper
    description: 'My tool',
    inputSchema: z.object({     // ⚠️ Use inputSchema (not parameters)
      city: z.string(),
    }),
    execute: async ({ city }) => { ... },
  }),
}
问题 2:在 v5 中使用 v4 的 useChat API
// ❌ WRONG - v4 pattern
const { input, setInput, append } = useChat();
append({ content: 'Hello', role: 'user' });

// ✅ CORRECT - v5 pattern
const { sendMessage } = useChat();
const [input, setInput] = useState('');
sendMessage({ text: 'Hello' });
问题 3:访问 message.content 而不是 message.parts
// ❌ WRONG - v4 pattern
<div>{message.content}</div>

// ✅ CORRECT - v5 parts-based
<div>
  {message.parts.map((part, i) =>
    part.type === 'text' ? <span key={i}>{part.text}</span> : null
  )}
</div>
问题 4:未为模型转换 UIMessages
// ❌ WRONG - passing UIMessages directly
const result = streamText({
  model: 'openai/gpt-4o',
  messages: messages,  // UIMessage[] - type error
});

// ✅ CORRECT - convert to ModelMessage[]
const result = streamText({
  model: 'openai/gpt-4o',
  messages: convertToModelMessages(messages),
});
问题 5:忘记清理 MCP 客户端
// ❌ WRONG - no cleanup
const httpClient = await experimental_createMCPClient({
  transport: httpTransport,
});
const tools = await httpClient.tools();
const response = streamText({ model, tools, prompt });
return response.toTextStreamResponse();

// ✅ CORRECT - cleanup in callbacks
const response = streamText({
  model,
  tools,
  prompt,
  onFinish: async () => {
    await httpClient.close();
  },
  onError: async () => {
    await httpClient.close();
  },
});
问题 6:使用了错误的响应方法
// ❌ WRONG - using text stream for useChat
return result.toTextStreamResponse();  // Won't work with useChat hook

// ✅ CORRECT - use UI message stream for useChat
return result.toUIMessageStreamResponse();

// ✅ ALSO CORRECT - text stream for non-chat scenarios
// For simple text streaming (not using useChat hook)
return result.toTextStreamResponse();
问题 7:使用了错误的嵌入模型方法
// ❌ WRONG - using regular model method
const { embedding } = await embed({
  model: openai('text-embedding-3-small'),  // Wrong method
  value: 'text',
});

// ✅ CORRECT - use textEmbeddingModel
const { embedding } = await embed({
  model: openai.textEmbeddingModel('text-embedding-3-small'),
  value: 'text',
});

迁移检查清单(v4 → v5)

从 v4 迁移到 v5 时,请更新以下内容:

  • 在 useChat 中将 append 替换为 sendMessage
  • 从 useChat 的解构赋值中移除 input、setInput、handleInputChange
  • 为 input 添加本地状态管理:const [input, setInput] = useState('')
  • 将消息渲染从 message.content 更新为 message.parts.map(...)
  • 更新 sendMessage 调用,使用 { text: input } 结构
  • 确认 API 路由中使用了 convertToModelMessages
  • 确认使用了 toUIMessageStreamResponse()(而非 v4 流式方法)
  • 更新工具定义,使用带有 inputSchema 的 tool() 辅助函数
  • 考虑使用基于字符串的模型指定方式('provider/model-id')
  • 更新 TypeScript 类型(UIMessage、ModelMessage)

决策指南

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

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

    • 客户端:使用 useChat hook
    • 服务端:使用 generateText 或 streamText
  2. 需要流式还是非流式?

    • 流式聊天:streamText + toUIMessageStreamResponse()
    • 非流式:generateText
    • 简单文本流:streamText + toTextStreamResponse()
  3. 需要调用工具吗?

    • 需要:使用 tool() 辅助函数和 inputSchema(zod)定义工具
    • 将工具对象传给 generateText 或 streamText
  4. 使用的消息格式正确吗?

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

    • 优先使用字符串格式:'openai/gpt-4o'
    • 函数格式也可用:openai('gpt-4o')
    • 嵌入:openai.textEmbeddingModel('text-embedding-3-small')
  6. 需要嵌入吗?

    • 单个值使用 embed
    • 批量值使用 embedMany
    • 使用 textEmbeddingModel() 方法

快速参考

任务函数关键参数
生成文本generateText()model、prompt、system、tools
流式生成文本streamText()model、messages、tools、onFinish
聊天 UIuseChat()api、onFinish、onError
工具调用tool()description、inputSchema、execute
文本嵌入embed()model、value
批量嵌入embedMany()model、values
消息转换convertToModelMessages()messages(UIMessage[])
MCP 集成experimental_createMCPClient()transport

更多资源

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

请记住: AI SDK v5 使用基于字符串的模型指定方式和基于 parts 的消息,使用 sendMessage 替代 append,并要求在 API 路由中使用 convertToModelMessages。

评论

支持 Markdown