Vercel AI SDK(Tool Calling)通用工具实践笔记

November, 4th 2025 12 min read
Vercel AI SDK Tool Calling 实践笔记

Vercel AI SDK(Tool Calling)通用工具实践笔记

这是一份基于 TypeScript/Next.jsVercel AI SDK v5/v6 的实践记录,整理了一个通用的 Tool Calling 接入示例,并在前端用 useChat 显示工具结果,形成最小闭环。
文中不特指某个具体工具,示例仅作演示(如文本总结 summarize);你可以替换为查询、检索、图片生成等任何符合需求的工具。


目录

  1. 核心概念速览
  2. 安装与准备
  3. 后端:定义工具 img-generate 并接入 streamText
  4. 前端:用 useChat 渲染文本与工具结果(含图片)
  5. Node 脚本版(不依赖 Next.js)
  6. 常用进阶:多步、强制工具、并行调用、错误处理
  7. 常见坑与排查清单
  8. 参考与扩展阅读

核心概念速览

  • Tool(工具):用 tool() 定义。包含 descriptioninputSchema(Zod/JSON Schema)、execute。模型在需要时会按 schema 产出参数并触发 execute。工具的参数/结果会被强类型化,且支持在流式对话中多轮调用。 oai_citation:0‡ai-sdk.dev
  • Tool Calling 工作流streamText/generateText 在生成回复时,模型可发起工具调用;服务端自动执行带 execute 的工具并把结果回流给前端,或由前端接管“交互型/客户端工具”。 oai_citation:1‡v6.ai-sdk.dev
  • OpenAI Provider:通过 @ai-sdk/openai 使用 OpenAI Responses API(AI SDK 5 起默认)。可在 providerOptions.openai 配置并行工具调用等细节。 oai_citation:2‡ai-sdk.dev
  • 生成图片:AI SDK 提供 experimental_generateImage 帮助方法;OpenAI 的图像生成模型为 gpt-image-1,支持 1024×1024、1024×1536、1536×1024 等尺寸。 oai_citation:3‡ai-sdk.dev
  • UIMessage/parts:前端 useChatmessage.parts(包括文本、工具调用、工具结果、文件等)来渲染消息,建议用 parts 而非旧的 contentoai_citation:4‡ai-sdk.dev

安装与准备

bash
123
      # 必装:
npm i ai zod @ai-sdk/openai
# 或 pnpm add ai zod @ai-sdk/openai
    

在环境变量中配置:

bash
12
      # .env / Vercel 项目环境变量
OPENAI_API_KEY=sk-...
    

@ai-sdk/openai 会默认走 OpenAI Responses API;也可用 createOpenAI 自定义 baseURL、headers、project 等。 oai_citation:5‡ai-sdk.dev


后端:定义工具 img-generate 并接入 streamText

下面的示例适用于 Next.js App Router,将接口放在 app/api/chat/route.ts。返回 UIMessage Stream,前端 useChat 可直接消费。 oai_citation:6‡AI SDK

ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
      // app/api/chat/route.ts
import { z } from 'zod';
import {
  streamText,
  convertToModelMessages,
  tool,
  stepCountIs,
} from 'ai';
import { openai } from '@ai-sdk/openai';
// Image 生成:实验 API,用法见官方“Image Generation”页面
import { experimental_generateImage as generateImage } from 'ai';

export const maxDuration = 30; // 可选:限制流式生成最长 30s

// 1) 定义一个可以被模型“自动调用”的服务端工具
const tools = {
  'img-generate': tool({
    description:
      '根据文字描述生成图片。仅在用户明确需要图片时调用。',
    inputSchema: z.object({
      prompt: z.string().describe('要生成内容的详细中文/英文描述'),
      size: z
        .enum(['1024x1024', '1024x1536', '1536x1024'])
        .default('1024x1024')
        .describe('输出分辨率'),
      n: z.number().int().min(1).max(4).default(1).describe('生成张数'),
    }),
    // execute 会在服务器上运行(安全、可保密 API Key)
    async execute({ prompt, size, n }, { abortSignal }) {
      const { images } = await generateImage({
        model: openai.image('gpt-image-1'),
        prompt,
        size,
        n,
        abortSignal,
      });
      // 返回精简后的结果,前端按需渲染
      return images.map((img) => ({
        base64: img.base64,
        mediaType: img.mediaType, // 如 'image/png'
      }));
    },
  }),
};

// 2) 接入 streamText(多轮 + 工具)
export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: openai('gpt-4o'), // 可换 gpt-4.1 / 其它 Responses 模型
    system:
      '你是图文助手。用户有明确“生成图/图片/海报/logo”等需求时,调用工具 img-generate,拿到图后再用一句话说明要点。',
    messages: convertToModelMessages(messages),

    tools,

    // 多步:允许“工具调用→拿结果→继续解释”后再停
    stopWhen: stepCountIs(2),

    // 进阶:也可以用 toolChoice 强制选用某个工具(见下方“常用进阶”)
  });

  // 返回 UIMessage 流式响应,前端 useChat 可直接消费
  return result.toUIMessageStreamResponse();
}
    
  • tool()inputSchema 会指导模型产出正确参数,并在运行期做严格校验。execute() 支持 AbortSignal、返回值或可迭代结果(可用于“处理中→成功”等状态)。 oai_citation:7‡ai-sdk.dev
  • experimental_generateImage 为 AI SDK 的图像生成辅助方法,建议与 openai.image('gpt-image-1') 搭配使用;OpenAI 对应模型说明见官方文档。 oai_citation:8‡ai-sdk.dev
  • streamText().toUIMessageStreamResponse() 会输出 UIMessage 流协议,天然配合 @ai-sdk/reactuseChatoai_citation:9‡ai-sdk.dev

前端:用 useChat 渲染文本与工具结果(含图片)

要点:渲染 message.parts;当遇到工具结果(类型名以 tool-<工具名>)时,将我们在 execute 返回的 base64 图片展示出来。官方建议使用 parts 渲染,并可结合“自动提交”等辅助函数。 oai_citation:10‡v6.ai-sdk.dev

tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
      // app/page.tsx
'use client';

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

export default function Page() {
  const { messages, input, setInput, sendMessage } = useChat({
    api: '/api/chat',
  });

  return (
    <div className="p-4 space-y-4">
      <div className="flex gap-2">
        <input
          className="border px-3 py-2 rounded w-full"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && sendMessage({ text: input })}
          placeholder="比如:生成一张赛博朋克风格的成都夜景 1024x1536"
        />
        <button
          className="px-3 py-2 rounded bg-black text-white"
          onClick={() => sendMessage({ text: input })}
        >
          发送
        </button>
      </div>

      {messages.map((m) => (
        <div key={m.id} className="space-y-3">
          <div className="text-xs opacity-70">{m.role}</div>

          {m.parts.map((p, i) => {
            // 1) 常规文本
            if (p.type === 'text') return <p key={i}>{p.text}</p>;

            // 2) 我们的“工具:img-generate”结果(AI SDK 会给出带工具名的类型标识)
            if (p.type === 'tool-img-generate') {
              // p.result 为 execute 返回值(此处是数组)
              return (
                <div key={i} className="grid grid-cols-2 gap-3">
                  {p.result.map(
                    (
                      img: { base64: string; mediaType: string },
                      idx: number,
                    ) => (
                      <img
                        key={idx}
                        className="rounded"
                        alt="generated"
                        src={`data:${img.mediaType};base64,${img.base64}`}
                      />
                    ),
                  )}
                </div>
              );
            }

            // 3) 其他类型(如文件/file、工具调用提示等)按需扩展
            return null;
          })}
        </div>
      ))}
    </div>
  );
}
    
  • 以上演示了“typed tool parts”:在前端 parts 里,你会看到类似 tool-img-generate 的片段名(官方示例中展示了 tool-askForConfirmation 的渲染方式)。 oai_citation:11‡v6.ai-sdk.dev
  • 如果你使用“图片文件(file part)”的方式回传,也可以检测 part.type === 'file' && part.mimeType.startsWith('image/') 直接 <img/> 渲染(AI SDK 4.2 博文示例)。 oai_citation:12‡Vercel

Node 脚本版(不依赖 Next.js)

ts
123456789101112131415161718192021222324252627282930313233
      import { z } from 'zod';
import { generateText, tool } from 'ai';
import { openai } from '@ai-sdk/openai';
import { experimental_generateImage as generateImage } from 'ai';

const imgGenerate = tool({
  description: '按文字描述生成图片(gpt-image-1)',
  inputSchema: z.object({
    prompt: z.string(),
    size: z.enum(['1024x1024', '1024x1536', '1536x1024']).default('1024x1024'),
    n: z.number().int().min(1).max(4).default(1),
  }),
  async execute({ prompt, size, n }) {
    const { images } = await generateImage({
      model: openai.image('gpt-image-1'),
      prompt,
      size,
      n,
    });
    return images.map((img) => ({ base64: img.base64, mediaType: img.mediaType }));
  },
});

const { text, toolCalls, toolResults } = await generateText({
  model: openai('gpt-4o'),
  tools: { 'img-generate': imgGenerate },
  prompt: '生成两张武侠风场景图,竖图 1024x1536',
  // 也可用 messages,多步、toolChoice 等见下节
});

console.log('LLM 总结:', text);
console.log('工具调用:', toolCalls);
console.log('工具结果:', toolResults);
    

@ai-sdk/openai 会自动选择 Responses API;也可通过 providerOptions.openai 配置 parallelToolCallsserviceTier 等选项。 oai_citation:13‡ai-sdk.dev


常用进阶:多步、强制工具、并行调用、错误处理

1) 多步(在拿到工具结果后继续总结)
streamText/generateText 里用 stopWhen: stepCountIs(n) 约束最大步数,例如:工具→总结两步。 oai_citation:14‡ai-sdk.dev

ts
12345678
      import { stepCountIs } from 'ai';

const result = streamText({
  model: openai('gpt-4o'),
  messages,
  tools,
  stopWhen: stepCountIs(2),
});
    

2) 强制工具选择(toolChoice)
可设为 'auto' | 'required' | 'none' | { type: 'tool', toolName: '...' }。例如强制调用 img-generateoai_citation:15‡ai-sdk.dev

ts
123456
      const result = await generateText({
  model: openai('gpt-4o'),
  tools,
  toolChoice: { type: 'tool', toolName: 'img-generate' },
  prompt: '来一张...',
});
    

3) 并行工具调用
OpenAI Responses API 在 AI SDK 中 默认开启并行工具调用,可通过 providerOptions.openai.parallelToolCalls 关闭或开启(默认 true)。 oai_citation:16‡ai-sdk.dev

ts
12345678910
      const result = await generateText({
  model: openai('gpt-4o'),
  tools,
  providerOptions: {
    openai: {
      parallelToolCalls: true, // 默认即为 true
      // serviceTier: 'flex' | 'priority' | 'auto' ...
    },
  },
});
    

4) 观测每一步(回调)
onStepFinish 可拿到每步的 toolCallstoolResultsprepareStep 可针对不同 step 切换模型/强制工具/限制 activeTools: oai_citation:17‡ai-sdk.dev

ts
12345678910111213
      const result = await generateText({
  model: openai('gpt-4o'),
  tools,
  stopWhen: stepCountIs(2),
  onStepFinish: ({ step, toolCalls, toolResults }) => {
    console.log('step:', step, toolCalls, toolResults);
  },
  prepareStep: async ({ stepNumber }) => {
    if (stepNumber === 0) {
      return { toolChoice: { type: 'tool', toolName: 'img-generate' } };
    }
  },
});
    

5) 服务端 → 前端流(UIMessage Stream)
后端用 toUIMessageStreamResponse() 返回;前端 useChat 自动解析 parts,无需手写 SSE 底层细节。 oai_citation:18‡ai-sdk.dev

6) 错误展示
默认会屏蔽具体错误,可在 toUIMessageStreamResponse 里自定义 onError 将错误文本返回(只在安全场景开启)。 oai_citation:19‡v6.ai-sdk.dev


常见坑与排查清单

  • 没用 convertToModelMessagesuseChat 的 UIMessage 与 Core 的 ModelMessage 结构不同,API 路由要转换。 oai_citation:20‡AI SDK
  • 前端仍渲染旧的 content 字段:请改为遍历 message.parts(文本、tool-...file 等都在这里)。 oai_citation:21‡ai-sdk.dev
  • 工具不触发:检查 description 是否清晰、inputSchema 是否贴合需求;必要时用 toolChoice: 'required' 或指定 { type: 'tool', toolName: '...' }oai_citation:22‡ai-sdk.dev
  • 图片无法显示:确认 mediaTypebase64 前缀拼接正确;或改为回传 file 类型的消息部件再渲染。 oai_citation:23‡Vercel
  • 多步没生效:确认设置了 stopWhen: stepCountIs(n),并让工具执行在服务器端(带 execute)。 oai_citation:24‡v6.ai-sdk.dev
  • 并发/速率:需要限制并发或关闭并行调用时,设置 providerOptions.openai.parallelToolCalls = falseoai_citation:25‡ai-sdk.dev

参考与扩展阅读


结语

以上就是一套“模型自动调用生成图片工具”的端到端实践:

  • tool() 定义 img-generateexecute 内部调用 gpt-image-1
  • streamText 中交给模型自由决定是否触发(或 toolChoice 强制)。
  • 前端用 useChat 渲染 parts,拿到 tool-img-generate 结果直接 <img/> 显示。

把这些拼起来,你的对话式“文生图”体验就齐活了 🚀