🚀 Vercel AI SDK 使用指南:测试 (Testing)

0 阅读3分钟

在开发 AI 应用时,我们经常面临一个巨大的挑战:LLM(大语言模型)是非确定性的

同一个 Prompt,今天输出 "Hello",明天可能输出 "Hi"。而且,每次运行测试都要消耗 Token,不仅速度慢,还要花钱(尤其是 GPT-4 这种昂贵的模型)。

Vercel AI SDK Core 提供了一套强大的测试工具 (Test Helpers) ,允许我们构建 Mock Provider,模拟 AI 的各种行为(包括流式输出、延迟、结构化数据)。这样,我们就可以编写稳定、快速且免费的单元测试。

本文将带你掌握 MockLanguageModelV3simulateReadableStream 的核心用法。

🛠️ 核心工具准备

所有测试工具都可以从 ai/testai 中直接导入:

TypeScript

import { generateText, streamText } from 'ai';
// 核心 Mock 模型
import { MockLanguageModelV3 } from 'ai/test';
// 用于模拟流式传输的工具
import { simulateReadableStream } from 'ai';

场景一:测试文本生成 (generateText)

这是最基础的测试场景。我们不需要真正去调 OpenAI 或通义千问的 API,而是告诉 SDK:“当你可以生成文本时,直接返回这个结果。”

示例代码

TypeScript

import { generateText } from 'ai';
import { MockLanguageModelV3 } from 'ai/test';

// 模拟一个总是返回 "Hello, Juejin!" 的模型
const model = new MockLanguageModelV3({
  doGenerate: async () => ({
    // 模拟输出内容
    content: [{ type: 'text', text: 'Hello, Juejin!' }],
    // 模拟结束原因
    finishReason: { unified: 'stop', raw: undefined },
    // 模拟 Token 消耗(可选,用于测试计费逻辑)
    usage: {
      inputTokens: 10,
      outputTokens: 20,
    },
  }),
});

async function main() {
  const result = await generateText({
    model,
    prompt: 'Write a greeting',
  });

  console.log(result.text); // 输出: "Hello, Juejin!"
}

main();

🔍 关键点: doGenerate 是一个异步函数,必须返回符合 AI SDK 规范的对象结构。


场景二:测试流式输出 (streamText)

流式传输 (Streaming) 是 AI 应用体验的核心。在测试中,我们需要模拟数据块 (Chunks) 一个接一个到达的过程,甚至可以模拟网络延迟。

simulateReadableStream 是实现这一点的神器。

示例代码

TypeScript

import { streamText, simulateReadableStream } from 'ai';
import { MockLanguageModelV3 } from 'ai/test';

const model = new MockLanguageModelV3({
  doStream: async () => ({
    stream: simulateReadableStream({
      // 定义流式输出的每一个块
      chunks: [
        { type: 'text-start', id: 'chunk-1' },
        { type: 'text-delta', id: 'chunk-1', delta: '正在' },
        { type: 'text-delta', id: 'chunk-1', delta: '生成' },
        { type: 'text-delta', id: 'chunk-1', delta: '内容...' },
        { type: 'text-end', id: 'chunk-1' },
        {
          type: 'finish',
          finishReason: { unified: 'stop', raw: undefined },
          usage: { inputTokens: 5, outputTokens: 10 },
        },
      ],
      // 可选:模拟每个块之间的延迟(毫秒),测试前端打字机效果
      chunkDelayInMs: 100, 
    }),
  }),
});

async function main() {
  const result = streamText({
    model,
    prompt: 'Show me streaming',
  });

  for await (const textPart of result.textStream) {
    process.stdout.write(textPart); // 依次输出:正在 -> 生成 -> 内容...
  }
}

main();

场景三:测试结构化数据 (generateObject)

当你使用 generateObjectstreamObject 生成 JSON 数据时(例如生成前端组件 Props 或提取信息),测试需要确保返回的是合法的 JSON 结构。

示例代码

TypeScript

import { generateObject } from 'ai';
import { MockLanguageModelV3 } from 'ai/test';
import { z } from 'zod';

const model = new MockLanguageModelV3({
  doGenerate: async () => ({
    // 注意:这里 text 必须是 JSON 字符串
    content: [{ type: 'text', text: '{"recipe": "番茄炒蛋", "calories": 200}' }],
    finishReason: { unified: 'stop', raw: undefined },
    usage: { inputTokens: 10, outputTokens: 20 },
  }),
});

async function main() {
  const result = await generateObject({
    model,
    schema: z.object({
      recipe: z.string(),
      calories: z.number(),
    }),
    prompt: 'Recommend a dish',
  });

  console.log(result.object); 
  // 输出: { recipe: '番茄炒蛋', calories: 200 }
}

main();

💡 进阶技巧:在 CI/CD 中集成

使用 Mock 模型的最大优势在于解耦

  • 本地开发:你可以使用真实的 API Key(OpenAI / 通义千问)来验证效果。
  • CI/CD 流水线:在 GitHub Actions 或 GitLab CI 中,你完全不需要配置 OPENAI_API_KEY。直接在测试代码中注入 MockLanguageModelV3,既安全又无需担心触发 API 速率限制。

配合 Vitest 单元测试示例

TypeScript

import { describe, it, expect } from 'vitest';
import { generateText } from 'ai';
import { MockLanguageModelV3 } from 'ai/test';

describe('AI Service', () => {
  it('should return fixed response', async () => {
    const mockModel = new MockLanguageModelV3({
      doGenerate: async () => ({
        content: [{ type: 'text', text: 'MOCKED_RESPONSE' }],
        finishReason: { unified: 'stop', raw: undefined },
        usage: { inputTokens: 0, outputTokens: 0 },
      }),
    });

    const { text } = await generateText({
      model: mockModel,
      prompt: 'test',
    });

    expect(text).toBe('MOCKED_RESPONSE');
  });
});

总结

MockLanguageModelV3 是 Vercel AI SDK 中被低估的神器。通过它,我们可以:

  1. 省钱:测试不消耗 API 额度。
  2. 提速:Mock 是毫秒级返回,而真实 LLM 可能需要几秒。
  3. 稳定:消除了 AI 的随机性,让测试结果 100% 可复现。

Happy Coding! 🚀