Vercel AI SDK 深度实战:Tool Calling、Schema 流式输出与多模型路由

深入 Vercel AI SDK 三大核心包(ai / @ai-sdk/openai / @ai-sdk/react),覆盖 generateObject 结构化输出、streamText 工具调用流、多模型路由与回退、Server Action 集成等高级场景,提供端到端 TypeScript 实现。

前置阅读:建议先阅读 Vercel AI SDK 指南 了解基础概念。

关键概念:Vercel AI SDK 3.0+ 将核心拆分为 ai(通用接口)、@ai-sdk/provider(提供商协议)和 @ai-sdk/react(前端 Hooks),实现了模型无关的 AI 应用开发。

  1. ² 核心架构与包结构

    ┌─────────────────┐
    │   @ai-sdk/react │  ← useChat / useCompletion / useObject (前端 Hooks)
    ├─────────────────┤
    │       ai        │  ← streamText / generateObject / embed (核心运行时)
    ├─────────────────┤
    │ @ai-sdk/openai  │  ← OpenAI 提供商适配
    │ @ai-sdk/anthropic│ ← Anthropic 适配
    │ @ai-sdk/google  │  ← Google Gemini 适配
    │ @ai-sdk/mistral │  ← Mistral 适配
    │ ...             │
    └─────────────────┘
    
    职责典型使用场景
    ai通用 AI 运行时Server Action / API Route 中调用
    @ai-sdk/openaiOpenAI 提供商gpt-4o / gpt-4o-mini 模型
    @ai-sdk/reactReact Hooks前端流式 UI 组件
    @ai-sdk/svelteSvelte 支持SvelteKit 项目
    npm install ai @ai-sdk/openai @ai-sdk/react zod
    
  2. ³ 类型安全的结构化输出(generateObject)

    相比 JSON 模式,generateObject 提供编译期类型安全 + 运行时校验:

    // app/api/analyze/route.ts
    import { openai } from "@ai-sdk/openai";
    import { generateObject } from "ai";
    import { z } from "zod";
    
    const AnalysisSchema = z.object({
      sentiment: z.enum(["positive", "neutral", "negative"]),
      confidence: z.number().min(0).max(1),
      keyTopics: z.array(z.string()).max(5),
      actionItems: z.array(z.object({
        priority: z.enum(["high", "medium", "low"]),
        description: z.string(),
      })).max(3),
    });
    
    export type AnalysisResult = z.infer<typeof AnalysisSchema>;
    
    export async function POST(req: Request) {
      const { text } = await req.json();
    
      const { object } = await generateObject({
        model: openai("gpt-4o-mini"),
        schema: AnalysisSchema,
        prompt: `Analyze the following text and return structured insights:\n\n${text}`,
        // 自动重试策略:如果解析失败,最多重试 3 次
        maxRetries: 3,
      });
    
      return Response.json(object);  // 类型为 AnalysisResult
    }
    

    前端 Hook 版本(useObject):

    // app/components/Analyzer.tsx
    "use client";
    import { useObject } from "@ai-sdk/react";
    
    export function Analyzer() {
      const { object, submit, isLoading } = useObject({
        api: "/api/analyze",
        schema: AnalysisSchema,
      });
    
      return (
        <div>
          <button onClick={() => submit("Our Q3 revenue grew 45% QoQ...")}>
            Analyze
          </button>
          {isLoading && <span>Processing...</span>}
          {object && (
            <div>
              <p>Sentiment: {object.sentiment} ({object.confidence})</p>
              <ul>{object.keyTopics?.map(t => <li key={t}>{t}</li>)}</ul>
            </div>
          )}
        </div>
      );
    }
    
  3. ⁴ 流式工具调用(streamText + tools)

    核心优势:工具执行状态实时流回前端,无需等待完整响应:

    // app/api/chat/route.ts
    import { streamText, tool } from "ai";
    import { openai } from "@ai-sdk/openai";
    import { z } from "zod";
    
    const weatherTool = tool({
      description: "Get current weather for a location",
      parameters: z.object({
        city: z.string().describe("City name in English"),
        unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
      }),
      execute: async ({ city, unit }) => {
        // 实际调用天气 API
        const res = await fetch(
          `https://api.weather.example.com/v1/current?city=${city}&unit=${unit}`
        );
        return res.json();
      },
    });
    
    const calculatorTool = tool({
      description: "Perform calculations",
      parameters: z.object({
        expression: z.string().describe("Math expression, e.g. '15 * 23'"),
      }),
      execute: async ({ expression }) => {
        // 安全评估:限制为数学表达式
        const safeExpr = expression.replace(/[^0-9+\-*/().\s]/g, "");
        return { result: Function(""return ${safeExpr}`)() };
      },
    });
    
    export async function POST(req: Request) {
      const { messages } = await req.json();
    
      const result = streamText({
        model: openai("gpt-4o"),
        messages,
        tools: { weather: weatherTool, calculator: calculatorTool },
        maxSteps: 5,  // 允许模型自主执行最多 5 轮工具调用
      });
    
      return result.toDataStreamResponse();
    }
    

    前端消费流式工具状态:

    // app/components/Chat.tsx
    "use client";
    import { useChat } from "@ai-sdk/react";
    
    export function Chat() {
      const { messages, input, handleInputChange, handleSubmit, toolInvocations } = useChat({
        api: "/api/chat",
      });
    
      return (
        <div>
          {messages.map(m => (
            <div key={m.id}>
              <strong>{m.role}:</strong> {m.content}
              {m.toolInvocations?.map(tool => (
                <div key={tool.toolCallId} className="tool-call">
                  <span>🔧 Calling {tool.toolName}...</span>
                  {tool.state === "result" && (
                    <pre>{JSON.stringify(tool.result, null, 2)}</pre>
                  )}
                </div>
              ))}
            </div>
          ))}
          <form onSubmit={handleSubmit}>
            <input value={input} onChange={handleInputChange} placeholder="Ask about weather or math..." />
          </form>
        </div>
      );
    }
    
  4. ⁵ 多模型路由与故障回退

    // lib/ai-router.ts
    import { openai } from "@ai-sdk/openai";
    import { anthropic } from "@ai-sdk/anthropic";
    import { google } from "@ai-sdk/google";
    import { LanguageModel } from "ai";
    
    type ModelTier = "fast" | "balanced" | "quality";
    type TaskType = "chat" | "code" | "analysis" | "creative";
    
    const MODEL_REGISTRY: Record<ModelTier, Record<TaskType, LanguageModel[]>> = {
      fast: {
        chat: [openai("gpt-4o-mini"), google("gemini-1.5-flash")],
        code: [openai("gpt-4o-mini")],
        analysis: [google("gemini-1.5-flash")],
        creative: [openai("gpt-4o-mini")],
      },
      balanced: {
        chat: [openai("gpt-4o"), anthropic("claude-3-5-sonnet-20241022")],
        code: [anthropic("claude-3-5-sonnet-20241022"), openai("gpt-4o")],
        analysis: [openai("gpt-4o")],
        creative: [anthropic("claude-3-5-sonnet-20241022")],
      },
      quality: {
        chat: [anthropic("claude-3-opus-20240229"), openai("gpt-4o")],
        code: [anthropic("claude-3-opus-20240229")],
        analysis: [openai("gpt-4o")],
        creative: [anthropic("claude-3-opus-20240229")],
      },
    };
    
    export class ModelRouter {
      async routeWithFallback(
        tier: ModelTier,
        task: TaskType,
        promptFn: (model: LanguageModel) => Promise<any>
      ): Promise<{ result: any; model: string; attempts: number }> {
        const candidates = MODEL_REGISTRY[tier][task];
        let lastError: Error | null = null;
    
        for (let i = 0; i < candidates.length; i++) {
          try {
            const result = await promptFn(candidates[i]);
            return {
              result,
              model: candidates[i].modelId,
              attempts: i + 1,
            };
          } catch (err) {
            lastError = err as Error;
            console.warn(`Model ${candidates[i].modelId} failed:`, err.message);
            // 指数退避
            await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
          }
        }
    
        throw new Error(
          `All ${candidates.length} models failed. Last error: ${lastError?.message}`
        );
      }
    }
    
    // 使用示例:Route 并自动降级
    const router = new ModelRouter();
    const { result, model, attempts } = await router.routeWithFallback(
      "balanced",
      "code",
      async (model) => {
        const { text } = await generateText({ model, prompt: "Explain async/await in Python" });
        return text;
      }
    );
    console.log(`Used ${model} after ${attempts} attempt(s)`);
    
  5. ⁶ Server Action 集成(Next.js App Router)

    无需 API Route,直接在 Server Action 中调用 AI SDK:

    // app/actions/generate.ts
    "use server";
    import { generateText, streamText } from "ai";
    import { openai } from "@ai-sdk/openai";
    import { createStreamableValue } from "ai/rsc";
    
    // 同步生成
    export async function generateSummary(content: string) {
      const { text } = await generateText({
        model: openai("gpt-4o-mini"),
        prompt: `Summarize in 3 bullet points:\n${content}`,
      });
      return text;
    }
    
    // 流式生成(Server Component 流式传输)
    export async function streamSummary(content: string) {
      const stream = createStreamableValue("");
    
      (async () => {
        const { textStream } = await streamText({
          model: openai("gpt-4o-mini"),
          prompt: `Summarize:\n${content}`,
        });
    
        for await (const delta of textStream) {
          stream.update(delta);
        }
        stream.done();
      })();
    
      return stream.value;
    }
    
    // app/components/Summary.tsx
    import { useStreamableValue } from "ai/rsc";
    import { streamSummary } from "@/app/actions/generate";
    
    export async function SummaryCard({ content }: { content: string }) {
      const stream = await streamSummary(content);
      return <StreamingContent stream={stream} />;
    }
    
    "use client";
    function StreamingContent({ stream }: { stream: any }) {
      const [text] = useStreamableValue(stream);
      return <div className="whitespace-pre-wrap">{text}</div>;
    }
    
  6. ⁷ 性能基准与最佳实践

    模式首字节延迟 (TTFB)总延迟适用场景
    generateText800-1500ms完整后返回短回答、结构化输出
    streamText200-500ms流式持续长文本生成、Chat UI
    generateObject1000-2000ms完整后返回需要类型安全的 API
    streamObject300-600ms流式持续结构化数据的渐进渲染

    关键优化

    // 启用响应式流式传输
    const result = streamText({
      model: openai("gpt-4o-mini"),
      prompt: "...",
      // 将长文本分块发送,减少前端等待
      experimental_streamData: true,
      // 限制最大 Token,控制成本和延迟
      maxTokens: 2048,
      // 温度控制:确定任务用 0,创意任务用 0.7+
      temperature: 0.3,
    });
    

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「工具与平台」更多文章