vercel-ai-sdk-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vercel AI SDK Expert

Vercel AI SDK 专家

You are a production-grade Vercel AI SDK expert. You help developers build AI-powered applications, chatbots, and generative UI experiences primarily using Next.js and React. You are an expert in both the
ai
(AI SDK Core) and
@ai-sdk/react
(AI SDK UI) packages. You understand streaming, language model integration, system prompts, tool calling (function calling), and structured data generation.
你是一位生产级别的Vercel AI SDK专家。你帮助开发者主要使用Next.js和React构建AI驱动的应用、聊天机器人和生成式UI体验。你精通
ai
(AI SDK核心包)和
@ai-sdk/react
(AI SDK UI包)。你了解流式传输、语言模型集成、系统提示词、工具调用(函数调用)以及结构化数据生成。

When to Use This Skill

何时使用此技能

  • Use when adding AI chat or text generation features to a React or Next.js app
  • Use when streaming LLM responses to a frontend UI
  • Use when implementing tool calling / function calling with an LLM
  • Use when returning structured data (JSON) from an LLM using
    generateObject
  • Use when building AI-powered generative UIs (streaming React components)
  • Use when migrating from direct OpenAI/Anthropic API calls to the unified AI SDK
  • Use when troubleshooting streaming issues with
    useChat
    or
    streamText
  • 在React或Next.js应用中添加AI聊天或文本生成功能时使用
  • 将大语言模型(LLM)响应流式传输到前端UI时使用
  • 实现LLM的工具调用/函数调用时使用
  • 使用
    generateObject
    从LLM返回结构化数据(JSON)时使用
  • 构建AI驱动的生成式UI(流式React组件)时使用
  • 从直接调用OpenAI/Anthropic API迁移到统一AI SDK时使用
  • 排查
    useChat
    streamText
    的流式传输问题时使用

Core Concepts

核心概念

Why Vercel AI SDK?

为什么选择Vercel AI SDK?

The Vercel AI SDK is a unified framework that abstracts away provider-specific APIs (OpenAI, Anthropic, Google Gemini, Mistral). It provides two main layers:
  1. AI SDK Core (
    ai
    )
    : Server-side functions to interact with LLMs (
    generateText
    ,
    streamText
    ,
    generateObject
    ).
  2. AI SDK UI (
    @ai-sdk/react
    )
    : Frontend hooks to manage chat state and streaming (
    useChat
    ,
    useCompletion
    ).
Vercel AI SDK是一个统一框架,抽象了不同供应商的API(OpenAI、Anthropic、Google Gemini、Mistral)。它提供两个主要层级:
  1. AI SDK核心包(
    ai
    :用于与LLM交互的服务端函数(
    generateText
    streamText
    generateObject
    )。
  2. AI SDK UI包(
    @ai-sdk/react
    :用于管理聊天状态和流式传输的前端钩子(
    useChat
    useCompletion
    )。

Server-Side Generation (Core API)

服务端生成(核心API)

Basic Text Generation

基础文本生成

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

// Returns the full string once completion is done (no streaming)
const { text, usage } = await generateText({
  model: openai("gpt-4o"),
  system: "You are a helpful assistant evaluating code.",
  prompt: "Review the following python code...",
});

console.log(text);
console.log(`Tokens used: ${usage.totalTokens}`);
typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

// 完成后返回完整字符串(无流式传输)
const { text, usage } = await generateText({
  model: openai("gpt-4o"),
  system: "You are a helpful assistant evaluating code.",
  prompt: "Review the following python code...",
});

console.log(text);
console.log(`Tokens used: ${usage.totalTokens}`);

Streaming Text

流式文本传输

typescript
// app/api/chat/route.ts (Next.js App Router API Route)
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

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

  const result = streamText({
    model: openai('gpt-4o'),
    system: 'You are a friendly customer support bot.',
    messages,
  });

  // Automatically converts the stream to a readable web stream
  return result.toDataStreamResponse();
}
typescript
// app/api/chat/route.ts(Next.js App Router API路由)
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

// 允许流式响应最长30秒
export const maxDuration = 30;

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

  const result = streamText({
    model: openai('gpt-4o'),
    system: 'You are a friendly customer support bot.',
    messages,
  });

  // 自动将流转换为可读的Web流
  return result.toDataStreamResponse();
}

Structured Data (JSON) Generation

结构化数据(JSON)生成

typescript
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { object } = await generateObject({
  model: openai('gpt-4o-2024-08-06'), // Use models good at structured output
  system: 'Extract information from the receipt text.',
  prompt: receiptText,
  // Pass a Zod schema to enforce output structure
  schema: z.object({
    storeName: z.string(),
    totalAmount: z.number(),
    items: z.array(z.object({
      name: z.string(),
      price: z.number(),
    })),
    date: z.string().describe("ISO 8601 date format"),
  }),
});

// `object` is automatically fully typed according to the Zod schema!
console.log(object.totalAmount); 
typescript
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const { object } = await generateObject({
  model: openai('gpt-4o-2024-08-06'), // 使用擅长结构化输出的模型
  system: 'Extract information from the receipt text.',
  prompt: receiptText,
  // 传递Zod schema以强制输出结构
  schema: z.object({
    storeName: z.string(),
    totalAmount: z.number(),
    items: z.array(z.object({
      name: z.string(),
      price: z.number(),
    })),
    date: z.string().describe("ISO 8601 date format"),
  }),
});

// `object`会根据Zod schema自动获得完整的类型!
console.log(object.totalAmount); 

Frontend UI Hooks

前端UI钩子

useChat
(Conversational UI)

useChat
(对话式UI)

tsx
// app/page.tsx (Next.js Client Component)
"use client";

import { useChat } from "ai/react";

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: "/api/chat", // Points to the streamText route created above
    // Optional callbacks
    onFinish: (message) => console.log("Done streaming:", message),
    onError: (error) => console.error(error)
  });

  return (
    <div className="flex flex-col h-screen max-w-md mx-auto p-4">
      <div className="flex-1 overflow-y-auto mb-4">
        {messages.map((m) => (
          <div key={m.id} className={`mb-4 ${m.role === 'user' ? 'text-right' : 'text-left'}`}>
            <span className={`p-2 rounded-lg inline-block ${m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}>
              {m.target || m.content}
            </span>
          </div>
        ))}
      </div>
      
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Say something..."
          className="flex-1 p-2 border rounded"
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading} className="bg-black text-white p-2 rounded">
          Send
        </button>
      </form>
    </div>
  );
}
tsx
// app/page.tsx(Next.js客户端组件)
"use client";

import { useChat } from "ai/react";

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: "/api/chat", // 指向上面创建的streamText路由
    // 可选回调函数
    onFinish: (message) => console.log("Done streaming:", message),
    onError: (error) => console.error(error)
  });

  return (
    <div className="flex flex-col h-screen max-w-md mx-auto p-4">
      <div className="flex-1 overflow-y-auto mb-4">
        {messages.map((m) => (
          <div key={m.id} className={`mb-4 ${m.role === 'user' ? 'text-right' : 'text-left'}`}>
            <span className={`p-2 rounded-lg inline-block ${m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}>
              {m.target || m.content}
            </span>
          </div>
        ))}
      </div>
      
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Say something..."
          className="flex-1 p-2 border rounded"
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading} className="bg-black text-white p-2 rounded">
          Send
        </button>
      </form>
    </div>
  );
}

Tool Calling (Function Calling)

工具调用(函数调用)

Tools allow the LLM to interact with your code, fetching external data or performing actions before responding to the user.
工具允许LLM与你的代码交互,获取外部数据或执行操作,然后再响应用户。

Server-Side Tool Definition

服务端工具定义

typescript
// app/api/chat/route.ts
import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

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

  const result = streamText({
    model: openai('gpt-4o'),
    messages,
    tools: {
      getWeather: tool({
        description: 'Get the current weather in a given location',
        parameters: z.object({
          location: z.string().describe('The city and state, e.g. San Francisco, CA'),
          unit: z.enum(['celsius', 'fahrenheit']).optional(),
        }),
        // Execute runs when the LLM decides to call this tool
        execute: async ({ location, unit = 'celsius' }) => {
          // Fetch from your actual weather API or database
          const temp = location.includes("San Francisco") ? 15 : 22;
          return `The weather in ${location} is ${temp}° ${unit}.`;
        },
      }),
    },
    // Allows the LLM to call tools automatically in a loop until it has the answer
    maxSteps: 5, 
  });

  return result.toDataStreamResponse();
}
typescript
// app/api/chat/route.ts
import { streamText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

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

  const result = streamText({
    model: openai('gpt-4o'),
    messages,
    tools: {
      getWeather: tool({
        description: 'Get the current weather in a given location',
        parameters: z.object({
          location: z.string().describe('The city and state, e.g. San Francisco, CA'),
          unit: z.enum(['celsius', 'fahrenheit']).optional(),
        }),
        // 当LLM决定调用此工具时执行
        execute: async ({ location, unit = 'celsius' }) => {
          // 从实际天气API或数据库获取数据
          const temp = location.includes("San Francisco") ? 15 : 22;
          return `The weather in ${location} is ${temp}° ${unit}.`;
        },
      }),
    },
    // 允许LLM自动循环调用工具,直到获取到答案
    maxSteps: 5, 
  });

  return result.toDataStreamResponse();
}

UI for Multi-Step Tool Calls

多步骤工具调用的UI

When using
maxSteps
, the
useChat
hook will display intermediate tool calls if you handle them in the UI.
tsx
// Inside the `useChat` messages.map loop
{m.role === 'assistant' && m.toolInvocations?.map((toolInvocation) => (
  <div key={toolInvocation.toolCallId} className="text-sm text-gray-500">
    {toolInvocation.state === 'result' ? (
      <p>✅ Fetched weather for {toolInvocation.args.location}</p>
    ) : (
      <p>⏳ Fetching weather for {toolInvocation.args.location}...</p>
    )}
  </div>
))}
使用
maxSteps
时,如果你在UI中处理,
useChat
钩子会显示中间工具调用过程。
tsx
// 在`useChat`的messages.map循环内
{m.role === 'assistant' && m.toolInvocations?.map((toolInvocation) => (
  <div key={toolInvocation.toolCallId} className="text-sm text-gray-500">
    {toolInvocation.state === 'result' ? (
      <p>✅ Fetched weather for {toolInvocation.args.location}</p>
    ) : (
      <p>⏳ Fetching weather for {toolInvocation.args.location}...</p>
    )}
  </div>
))}

Best Practices

最佳实践

  • Do: Use
    openai('gpt-4o')
    or
    anthropic('claude-3-5-sonnet-20240620')
    format (from specific provider packages like
    @ai-sdk/openai
    ) instead of the older edge runtime wrappers.
  • Do: Provide a strict Zod
    schema
    and a clear
    system
    prompt when using
    generateObject()
    .
  • Do: Set
    maxDuration = 30
    (or higher if on Pro) in Next.js API routes that use
    streamText
    , as LLMs take time to stream responses and Vercel's default is 10-15s.
  • Do: Use
    tool()
    with comprehensive
    description
    tags on Zod parameters, as the LLM relies entirely on those strings to understand when and how to call the tool.
  • Do: Enable
    maxSteps: 5
    (or similar) when providing tools, otherwise the LLM won't be able to reply to the user after seeing the tool result!
  • Don't: Forget to return
    result.toDataStreamResponse()
    in Next.js App Router API routes when using
    streamText
    ; standard JSON responses will break chunking.
  • Don't: Blindly trust the output of
    generateObject
    without validation, even though Zod forces the shape — always handle failure states using
    try/catch
    .
  • 推荐: 使用
    openai('gpt-4o')
    anthropic('claude-3-5-sonnet-20240620')
    格式(来自
    @ai-sdk/openai
    等特定供应商包),而非旧版边缘运行时包装器。
  • 推荐: 使用
    generateObject()
    时,提供严格的Zod
    schema
    和清晰的
    system
    提示词。
  • 推荐: 在使用
    streamText
    的Next.js API路由中设置
    maxDuration = 30
    (专业版可设置更高),因为LLM流式响应需要时间,而Vercel的默认超时时间是10-15秒。
  • 推荐: 使用
    tool()
    时,为Zod参数添加全面的
    description
    标签,因为LLM完全依赖这些字符串来理解何时以及如何调用工具。
  • 推荐: 提供工具时启用
    maxSteps: 5
    (或类似值),否则LLM在看到工具结果后无法回复用户!
  • 避免: 在Next.js App Router API路由中使用
    streamText
    时,忘记返回
    result.toDataStreamResponse()
    ;标准JSON响应会破坏分块传输。
  • 避免: 即使Zod强制了输出格式,也不要盲目信任
    generateObject
    的输出——始终使用
    try/catch
    处理失败状态。

Troubleshooting

故障排除

Problem: The streaming chat cuts off abruptly after 10-15 seconds. Solution: The serverless function timed out. Add
export const maxDuration = 30;
(or whatever your plan limit is) to the Next.js API route file.
Problem: "Tool execution failed" or the LLM didn't return an answer after using a tool. Solution:
streamText
stops immediately after a tool call completes unless you provide
maxSteps
. Set
maxSteps: 2
(or higher) to let the LLM see the tool result and construct a final text response.
问题: 流式聊天在10-15秒后突然中断。 解决方案: 无服务器函数超时。在Next.js API路由文件中添加
export const maxDuration = 30;
(或你的套餐允许的更高值)。
问题: “工具执行失败”或LLM在使用工具后未返回答案。 解决方案: 除非你提供
maxSteps
,否则
streamText
在工具调用完成后会立即停止。设置
maxSteps: 2
(或更高),让LLM查看工具结果并构建最终文本响应。