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. AI SDK 3.0+ 的演进与 Breaking Changes

    Vercel AI SDK 经历了从 2.x 到 3.x 的重大架构重构,核心目标是解耦模型提供商与运行时逻辑,使代码具备更强的可移植性。

    3.x 相比 2.x 的关键变化:

    • 包拆分:旧版所有功能集中在 ai 包中,3.x 将 provider 相关逻辑拆分到独立的 @ai-sdk/* 命名空间,安装体积减小约 40%。
    • Provider 协议标准化:引入 @ai-sdk/provider 规范,任何第三方模型都能通过实现 LanguageModelV1 接口接入 AI SDK,无需等待官方适配。
    • 新版 React Hooks:useChat / useCompletion 的返回值和事件流在 3.1+ 中重新设计,支持更细粒度的 toolInvocations 状态追踪,废弃了旧版的 experimental_ 前缀 API。
    • RSC(React Server Components)深度集成:ai/rsc 子路径提供 createStreamableValue / useStreamableValue,使 Server Action 流式传输不再需要 ReadableStream 的手动封装。

    迁移指南(2.x → 3.x):

    // ❌ 2.x 写法
    import { OpenAIStream, StreamingTextResponse } from 'ai';
    const stream = OpenAIStream(response);
    return new StreamingTextResponse(stream);
    
    // ✅ 3.x 写法
    import { streamText } from 'ai';
    import { openai } from '@ai-sdk/openai';
    const result = streamText({ model: openai('gpt-4o'), prompt: '...' });
    return result.toDataStreamResponse();
    

    Core API 全景:ai 包提供六大核心函数,覆盖 90% 的 LLM 交互场景:

    API模式输出适用场景
    generateText同步字符串短文本、确定性回复
    streamText流式字符串流Chat UI、长文本生成
    generateObject同步Zod 对象类型安全的结构化数据
    streamObject流式部分对象流渐进式表单/卡片渲染
    embed同步向量数组单条文本 Embedding
    embedMany批量向量数组文档集批量向量化
  3. 类型安全的结构化输出(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>
      );
    }
    
  4. 复杂 Schema 与嵌套类型处理

    真实业务场景中,AI 生成的数据结构远比基础平面对象复杂。generateObject 与 streamObject 对 zod 高级类型的支持,让深嵌套、联合类型、数组结构的输出同样具备类型安全。

    嵌套对象与数组验证:

    const ProductCatalogSchema = z.object({
      category: z.string(),
      products: z.array(z.object({
        id: z.string().uuid(),
        name: z.string().min(1).max(100),
        price: z.number().positive(),
        tags: z.array(z.string()).min(1).max(5),
        metadata: z.record(z.string(), z.union([z.string(), z.number()])),
      })).min(1).max(20),
    });
    
    const { object } = await generateObject({
      model: openai('gpt-4o'),
      schema: ProductCatalogSchema,
      prompt: 'Generate a catalog of 3 fictional AI-powered developer tools...',
    });
    // object.products[0].metadata 的类型为 Record<string, string | number>
    

    联合类型(z.union)与 discriminated union:当输出可能是多种形态之一时,使用 z.discriminatedUnion 让模型通过 type 字段自动路由:

    const EventSchema = z.discriminatedUnion('type', [
      z.object({ type: z.literal('click'), elementId: z.string(), timestamp: z.number() }),
      z.object({ type: z.literal('scroll'), scrollTop: z.number(), timestamp: z.number() }),
      z.object({ type: z.literal('input'), fieldName: z.string(), value: z.string(), timestamp: z.number() }),
    ]);
    
    const { object: event } = await generateObject({
      model: openai('gpt-4o-mini'),
      schema: EventSchema,
      prompt: 'Generate a user interaction event for an e-commerce checkout page.',
    });
    // TypeScript 自动推断 event.type 为 'click' | 'scroll' | 'input'
    

    生成失败重试与降级策略:复杂 Schema 的解析失败率高于简单对象,可结合 maxRetries 与备用模型实现自动降级:

    async function generateWithFallback<T>(schema: z.ZodSchema<T>, prompt: string): Promise<T> {
      const models = [openai('gpt-4o'), anthropic('claude-3-5-sonnet-20241022')];
      for (const model of models) {
        try {
          const { object } = await generateObject({ model, schema, prompt, maxRetries: 2 });
          return object;
        } catch (err) {
          console.warn(`Schema generation failed with ${model.modelId}:`, err);
        }
      }
      throw new Error('All models failed to generate valid schema.');
    }
    

    流式对象生成(streamObject)与部分解析:streamObject 允许在对象未完全生成时开始渲染,配合 useObject Hook 实现实时填充 React 表单:

    // app/api/stream-form/route.ts
    import { streamObject } from 'ai';
    
    export async function POST(req: Request) {
      const { description } = await req.json();
      const result = streamObject({
        model: openai('gpt-4o-mini'),
        schema: z.object({
          title: z.string(),
          description: z.string(),
          priority: z.enum(['low', 'medium', 'high']),
          tags: z.array(z.string()),
        }),
        prompt: `Generate a task form from: ${description}`,
      });
      return result.toTextStreamResponse();
    }
    
    // app/components/TaskForm.tsx
    'use client';
    import { useObject } from '@ai-sdk/react';
    
    export function TaskForm() {
      const { object, submit, isLoading } = useObject({
        api: '/api/stream-form',
        schema: z.object({ title: z.string(), description: z.string(), priority: z.enum(['low','medium','high']), tags: z.array(z.string()) }),
      });
    
      return (
        <form>
          <input value={object?.title ?? ''} onChange={() => {}} placeholder="Title" />
          <textarea value={object?.description ?? ''} onChange={() => {}} placeholder="Description" />
          <select value={object?.priority ?? 'medium'}>
            <option value="low">Low</option>
            <option value="medium">Medium</option>
            <option value="high">High</option>
          </select>
          <div>{object?.tags?.map(t => <span key={t} className="tag">{t}</span>)}</div>
          <button type="button" onClick={() => submit('Create a high-priority bug fix task for login page')}>AI 填充</button>
        </form>
      );
    }
    

    流式对象的核心优势在于首字节时间(约 200-400ms)即可渲染第一个字段,用户无需等待整段 JSON 完成。

  5. 流式工具调用(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>
      );
    }
    
  6. 多步 Agent 与自主工具调用

    AI SDK 3.0+ 的 maxSteps 参数不仅仅是限制轮数,它实际上驱动了一个隐式的 Agent 循环:模型决定调用工具 → 工具执行返回结果 → 结果追加到消息历史 → 模型再次推理 → 直到得出最终答案或达到最大步数。

    Agent 循环原理:

    const result = streamText({
      model: openai('gpt-4o'),
      messages: history,
      tools: { search: searchTool, calculate: calcTool },
      maxSteps: 10,
      // 每一步之间的回调,可用于状态追踪
      onStepFinish: async ({ text, toolCalls, toolResults, finishReason, usage }) => {
        console.log(`Step finished: ${finishReason}, tokens: ${usage?.totalTokens}`);
      },
    });
    

    在循环中,AI SDK 自动维护 messages 上下文。工具调用的结果通过 toolResults 重新注入到对话中,使模型具备"看到"工具返回并继续思考的能力。

    状态机模式实现多步任务:对于需要严格阶段控制的复杂任务(如:数据查询 → 计算聚合 → 生成报告),可在外层封装状态机:

    type AgentState = 'idle' | 'querying' | 'calculating' | 'summarizing' | 'done';
    
    interface AgentContext {
      state: AgentState;
      data: Record<string, unknown>;
      history: CoreMessage[];
    }
    
    async function runMultiStepAgent(goal: string): Promise<string> {
      const ctx: AgentContext = { state: 'idle', data: {}, history: [{ role: 'user', content: goal }] };
    
      while (ctx.state !== 'done' && ctx.history.length < 20) {
        const result = await generateText({
          model: openai('gpt-4o'),
          messages: ctx.history,
          tools: {
            queryDatabase: tool({
              description: 'Query the sales database',
              parameters: z.object({ sql: z.string() }),
              execute: async ({ sql }) => { /* ... */ },
            }),
            calculate: tool({
              description: 'Perform aggregation',
              parameters: z.object({ expression: z.string() }),
              execute: async ({ expression }) => { /* ... */ },
            }),
            summarize: tool({
              description: 'Generate final report',
              parameters: z.object({ findings: z.string() }),
              execute: async ({ findings }) => { ctx.state = 'done'; return findings; },
            }),
          },
          maxSteps: 5,
        });
    
        ctx.history.push({ role: 'assistant', content: result.text });
    
        // 根据工具调用结果更新状态
        if (result.toolCalls.some(t => t.toolName === 'queryDatabase')) ctx.state = 'querying';
        if (result.toolCalls.some(t => t.toolName === 'calculate')) ctx.state = 'calculating';
        if (result.toolCalls.some(t => t.toolName === 'summarize')) ctx.state = 'done';
      }
    
      return ctx.history[ctx.history.length - 1].content as string;
    }
    

    与 LangChain Agent 模式对比:

    维度Vercel AI SDK AgentLangChain Agent
    运行时Edge / Node.js / Browser主要 Node.js
    流式支持原生 streamText 实时流需额外封装 CallbackHandler
    状态管理轻量,自定义状态机内置 AgentExecutor,较重
    工具定义zod schema + tool() 函数StructuredTool 类
    集成成本低,直接绑定 Next.js中等,需配置 Chain 和 Memory
    社区生态Vercel / Next.js 生态更广泛的第三方集成

    对于以 Next.js 为技术栈、追求流式 UI 体验的项目,AI SDK 的原生 Agent 模式在延迟和开发效率上具有显著优势。

  7. 多模型路由与故障回退

    // 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)`);
    
  8. 成本优化与 Token 预算管理

    在生产环境中,LLM 调用成本是仅次于模型质量的考量因素。不同模型的 Token 定价差异可达 30 倍以上,合理的成本控制策略直接影响项目的可持续性。

    主流模型 Token 定价对比(每百万 Token / 2025):

    模型Input (USD)Output (USD)上下文长度
    gpt-4o-mini$0.15$0.60128K
    gemini-1.5-flash$0.075$0.301M
    gpt-4o$2.50$10.00128K
    claude-3-5-sonnet$3.00$15.00200K
    claude-3-opus$15.00$75.00200K

    以上数据表明,简单任务使用 gpt-4o-mini 比 claude-3-opus 便宜约 125 倍。

    请求前 Token 预估:使用 js-tiktoken 在服务端预估 Prompt Token 数量,据此动态选择模型:

    import { encoding_for_model } from 'tiktoken';
    
    function estimateTokens(text: string, model: string): number {
      const enc = encoding_for_model(model as any);
      const tokens = enc.encode(text);
      enc.free();
      return tokens.length;
    }
    
    function selectModel(prompt: string, complexity: 'low' | 'medium' | 'high'): LanguageModel {
      const tokenCount = estimateTokens(prompt, 'gpt-4o');
      if (tokenCount > 16000 || complexity === 'high') return openai('gpt-4o');
      if (tokenCount > 4000 || complexity === 'medium') return anthropic('claude-3-5-sonnet-20241022');
      return openai('gpt-4o-mini');
    }
    

    月度预算上限实现:结合 Redis 或内存存储实现硬限制与软告警:

    import { kv } from '@vercel/kv';
    
    const MONTHLY_BUDGET_USD = 500;
    const SOFT_ALERT_THRESHOLD = 0.8;
    
    async function checkBudget(costCents: number): Promise<{ allowed: boolean; alert?: string }> {
      const key = `ai:cost:${new Date().toISOString().slice(0, 7)}`; // YYYY-MM
      const currentCents = await kv.incrby(key, costCents);
      await kv.expire(key, 60 * 60 * 24 * 40); // 40 days TTL
    
      const currentUsd = currentCents / 100;
      if (currentUsd > MONTHLY_BUDGET_USD) {
        return { allowed: false, alert: `Budget exceeded: $${currentUsd.toFixed(2)} / $${MONTHLY_BUDGET_USD}` };
      }
      if (currentUsd > MONTHLY_BUDGET_USD * SOFT_ALERT_THRESHOLD) {
        return { allowed: true, alert: `Budget warning: $${currentUsd.toFixed(2)} / $${MONTHLY_BUDGET_USD}` };
      }
      return { allowed: true };
    }
    

    动态模型选择策略:基于任务特征进行模型路由,在质量与成本之间取得平衡。常见规则包括:分类/提取用轻量模型、代码/复杂推理用 sonnet、创意/长文本用 opus 或 gpt-4o。建议配合 A/B 测试持续校准各任务的质量门槛值。

  9. 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>;
    }
    
  10. Vercel KV / Edge Config 与 AI SDK 的缓存策略

    LLM 调用成本高昂且速度受限,对常见查询启用缓存是最立竿见影的优化手段。Vercel KV(基于 Redis)与 Edge Config 是部署在边缘的缓存基础设施,天然适配 AI SDK 的流式响应。

    缓存键设计:缓存键应当精确标识请求的"语义等价性",同时包含影响输出的参数:

    import { createHash } from 'crypto';
    
    function createCacheKey(prompt: string, model: string, temperature: number): string {
      const hash = createHash('sha256').update(prompt).digest('hex').slice(0, 16);
      return `ai:cache:${model}:${temperature}:${hash}`;
    }
    

    实现 LLM 响应缓存层:

    import { kv } from '@vercel/kv';
    import { generateText, streamText } from 'ai';
    
    async function cachedGenerateText(
      model: LanguageModel,
      prompt: string,
      options?: { ttl?: number; temperature?: number }
    ) {
      const key = createCacheKey(prompt, model.modelId, options?.temperature ?? 0.7);
      const cached = await kv.get<string>(key);
    
      if (cached) {
        console.log('Cache hit for', model.modelId);
        return { text: cached, fromCache: true };
      }
    
      const { text } = await generateText({ model, prompt, temperature: options?.temperature });
      await kv.set(key, text, { ex: options?.ttl ?? 3600 * 24 }); // 默认 24h TTL
      return { text, fromCache: false };
    }
    

    差异化 TTL 策略:根据查询类型设置不同的缓存有效期,在命中率与响应新鲜度之间取得平衡:

    const TTL_STRATEGY: Record<string, number> = {
      'faq': 3600 * 24 * 7,      // FAQ 类:7 天
      'code-example': 3600 * 24,  // 代码示例:1 天
      'creative': 3600,          // 创意内容:1 小时
      'news-summary': 1800,      // 新闻摘要:30 分钟
    };
    
    function determineTTL(prompt: string): number {
      if (prompt.includes('write') || prompt.includes('create')) return TTL_STRATEGY.creative;
      if (prompt.includes('news') || prompt.includes('latest')) return TTL_STRATEGY['news-summary'];
      if (prompt.includes('how to') || prompt.includes('example')) return TTL_STRATEGY['code-example'];
      return TTL_STRATEGY.faq;
    }
    

    边缘缓存减少 API 调用成本:将高频查询的缓存预热到 Edge Config(只读、极低延迟),KV 用于动态缓存。配合 Next.js 的 revalidate 机制,可实现近乎零延迟的 AI 响应。

  11. RAG 集成实战

    RAG(Retrieval-Augmented Generation,检索增强生成)是构建知识库问答系统的标准架构。使用 AI SDK 与向量数据库,可高效实现文档 Embedding、语义检索与生成的完整闭环。

    文档分块与 Embedding 生成:长文档需要先切分为语义连贯的段落,每段控制在 512-1024 Token 之间,overlap 约 10% 保证上下文连续性:

    import { embedMany } from 'ai';
    import { openai } from '@ai-sdk/openai';
    
    interface DocumentChunk {
      id: string;
      content: string;
      metadata: { source: string; page?: number; title: string };
    }
    
    async function embedChunks(chunks: DocumentChunk[]) {
      const { embeddings } = await embedMany({
        model: openai.embedding('text-embedding-3-small'),
        values: chunks.map(c => c.content),
      });
      return chunks.map((chunk, i) => ({ ...chunk, embedding: embeddings[i] }));
    }
    

    Supabase Vector 存储与检索:Supabase 内置 pgvector 扩展,是托管型向量存储的轻量选择:

    import { createClient } from '@supabase/supabase-js';
    
    const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_KEY!);
    
    // 存储文档块
    async function storeEmbeddings(chunks: DocumentChunk[]) {
      const embedded = await embedChunks(chunks);
      await supabase.from('documents').insert(embedded.map(c => ({
        content: c.content,
        embedding: c.embedding,
        source: c.metadata.source,
      })));
    }
    
    // 语义检索
    async function retrieveRelevant(query: string, topK: number = 5) {
      const { embedding } = await embed({
        model: openai.embedding('text-embedding-3-small'),
        value: query,
      });
    
      const { data } = await supabase.rpc('match_documents', {
        query_embedding: embedding,
        match_threshold: 0.7,
        match_count: topK,
      });
      return data ?? [];
    }
    

    检索-生成一体化实现:将检索到的上下文注入 Prompt,配合 generateText 生成带引用来源的回复:

    async function ragGenerate(query: string): Promise<{ answer: string; sources: string[] }> {
      const docs = await retrieveRelevant(query, 5);
      const context = docs.map((d, i) => `[${i + 1}] ${d.content}`).join('\n\n');
    
      const { text } = await generateText({
        model: openai('gpt-4o'),
        system: 'You are a helpful assistant. Answer based on the provided context. Cite sources using [1], [2] format.',
        prompt: `Context:\n${context}\n\nQuery: ${query}`,
      });
    
      const sources = [...new Set(docs.map(d => d.source))];
      return { answer: text, sources };
    }
    

    引用来源追踪:前端展示时,将 [1]、[2] 等引用标记渲染为可点击的文献链接,提升答案可信度。对引用的来源进行去重,按相关性排序展示,避免来源列表过于冗长。

  12. 性能基准与最佳实践

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

    不同 Provider 延迟对比(基于相同 Prompt、TTFB):

    Providergpt-4o-minigpt-4oclaude-3-5-sonnetgemini-1.5-flash
    首字节时间120-250ms200-400ms250-450ms150-300ms
    Token 吞吐快中中快
    稳定性高高高中

    Gemini Flash 在首字节时间上略优于 gpt-4o-mini,但在复杂推理任务的稳定性方面稍逊。对于要求低延迟的边缘部署场景,优先选择 Flash Mini 系列。

    流式输出首字节时间(Time to First Token)优化:影响 TTFB 的核心因素包括模型加载时间、网络往返延迟和 Prompt Token 数量。实践上可以采取以下措施降低首字节延迟:启用 Vercel Edge Runtime 就近调用、使用 prompt caching 减少重复处理、压缩 system prompt 避免冗余 Token、合理设置 maxTokens 避免超时等待。

    关键优化:

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

    FAQ

    Q1: AI SDK 是否有免费额度限制?
    AI SDK 本身是开源免费的,没有使用限制。但实际调用的模型 API(OpenAI、Anthropic 等)按 Token 收费。OpenAI 新注册用户有 $5 额度,Anthropic 提供少量免费试用。建议在开发阶段使用 gpt-4o-mini 降低成本。

    Q2: 流式输出中如何处理错误?
    streamText 的流在服务端抛出异常时,前端 useChat 会自动捕获并通过 error 状态暴露。建议在 API Route 中包装 try-catch,将错误转换为 JSON 错误帧;前端通过 onError 回调展示友好提示,并允许用户重试。

    Q3: 工具调用如何设置超时控制?
    在 tool() 的 execute 函数内部使用 Promise.race 或 AbortSignal 实现超时。推荐方式为传入 AbortSignal 到 fetch 调用中,并在服务端设置全局的 serverActionTimeout:

    execute: async ({ city }, { signal }) => {
      const res = await fetch(url, { signal });
      // 超时由 outer 的 AbortController 控制
    }
    

    Q4: v0 与 AI SDK 的关系是什么?
    v0 是 Vercel 的 AI 生成式 UI 构建工具,底层复用了 AI SDK 的 streamObject 和 streamText 能力。你可以将 v0 视为 AI SDK 的一个消费者应用。v0 生成的组件代码同样可以使用 AI SDK 增加交互式 AI 功能,两者属于同一技术栈的不同层级。

    Q5: generateObject 支持非 zod 的 schema 库吗?
    AI SDK 3.x 主要原生支持 zod。对于 valibot 或 JSON Schema,可以通过 jsonSchema 辅助函数转换后再传入。官方路线图显示未来可能扩展更多 schema 库的原生支持。

    Q6: streamObject 能否配合 React Server Components 使用?
    可以。在 Server Component 中使用 streamObject,通过 createStreamableValue 包装结果流,客户端使用 useStreamableValue 消费。这种方式避免了客户端 JavaScript 的水合开销,适合首屏渲染性能敏感的场景。

    延伸阅读:

继续阅读

探索更多技术文章

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

全部文章 返回首页

「工具与平台」更多文章

  1. Vercel Edge Config 完全指南:毫秒级配置下发与 A/B 测试驱动
  2. Vercel Analytics 深度指南:Web Vitals 监控、真实用户性能与转化归因
  3. Cloudflare Workers AI 高级实战:自定义模型部署、批量推理与 AI Gateway 缓存策略