跳转到主要内容
vibestrap 在统一 facade 后面接了 六个 AI providermock 永远在线;其余每个真实 provider 只有在对应 API key 出现时才会注册,最后由 AI_PROVIDER 决定激活哪一个。这 样你可以离线开发、按环境切换,也不会上线时漏了配置。

前置条件

  • 项目能跑(pnpm dev 起得来)。
  • 至少一个真实 API key —— 如果只挑一个,OpenRouter 最省事 (openrouter.ai)。
  • 先看一遍 src/ai/index.ts,80 行讲清楚整个故事。

六个 provider

名称源文件支持操作说明
mockproviders/mock.tschat, chatStream, image默认。返回固定文本 + picsum 图片 URL。
openrouterproviders/openai-compat.tschat, chatStreamOpenAI 兼容形式,路由上百模型。
openaiproviders/openai-compat.tschat, chatStream直连 OpenAI 端点。
anthropicproviders/anthropic.tschat, chatStream自有 Messages API + SSE 事件类型。
replicateproviders/replicate.tsimage轮询式预测 API。
falproviders/fal.tsimage同步队列,FLUX schnell 大约 1 秒。
调用 provider 不支持的方法(比如对 anthropicimage())会抛 AIUnsupportedError, manager 会捕获并尝试 opts.fallback(如果传了的话)。

步骤:切换到真实 provider

  1. 选一个 provider,把 key 写进 .env.local
    AI_PROVIDER=openrouter
    OPENROUTER_API_KEY=sk-or-v1-...
    # OPENROUTER_BASE_URL 默认是 https://openrouter.ai/api/v1
    
  2. 重启 dev server。条件注册在 import 时执行:
    if (env.OPENROUTER_API_KEY) {
      PROVIDERS.openrouter = createOpenAICompatProvider({ ... });
    }
    
  3. 在 server action 或 route handler 里发起调用:
    import { chat } from '@/ai/manager';
    
    const result = await chat(
      { model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: 'hi' }] },
      { userId: ctx.user.id }
    );
    
    result 类型是 ChatResult | InsufficientCredits。读 .text 之前一定要先用 isInsufficientCredits(result) 收窄。

给价目表加新模型

src/ai/pricing.ts 是成本的唯一真相源。key 是 provider:model,数值是每 1k tokens 的 micro-cents(百分之一美分 —— $0.0001 = 100 micro-cents)。改完保存即生效:
const TOKEN_PRICES: Record<string, ModelPrice> = {
  'openai:gpt-4o-mini': { inputPer1kMicroCents: 1500, outputPer1kMicroCents: 6000 },
  // 加一行 ↓
  'openai:gpt-4o-2026-q1': { inputPer1kMicroCents: 1200, outputPer1kMicroCents: 4800 },
};
如果忘了加,getTokenPrice 会回退到 mock:any(很便宜),调用照样能跑但成本统计就 偏低了。在 可观测性 里搜「忘记价目表」可以看修复方法。

你需要捕获的错误

import { AIProviderError, AIUnsupportedError } from '@/ai/types';
import { isInsufficientCredits } from '@/ai/manager';

try {
  const r = await chat(req, { userId, fallback: 'mock' });
  if (isInsufficientCredits(r)) return { error: 'topup', needed: r.needed };
  return { text: r.text };
} catch (err) {
  if (err instanceof AIProviderError) console.error(err.provider, err.status);
  if (err instanceof AIUnsupportedError) console.error('换个 provider 试试');
  throw err;
}
opts.fallback 是降级开关。AIProviderErrorAIUnsupportedError 时 manager 会 用它再试一次 —— InsufficientCredits 不会触发降级(积分已经退过了)。开发环境用 mock 当 fallback,demo 永远不会挂。

验证生效

  1. AI_PROVIDER=mock(默认)。打开 /playground/chat,应该看到「(mock provider …)」 那段固定回复。
  2. 设上真实 key 和 AI_PROVIDER=<name>,重启。同样的 demo 现在应该流式回真实内容。
  3. 看 dev server 日志。ai_call 插入行里 provider=<name> 就是当前生效的选择。
  4. psql 进开发库跑 SELECT provider, model, status FROM ai_call ORDER BY created_at DESC LIMIT 5;

常见坑

  • AI_PROVIDER 配了但 key 没配。 Manager 会静默降级到 mock。在 getProvider 里打印 Object.keys(PROVIDERS),发现你的 provider 不在里面。
  • OpenRouter 的 model id 格式。 必须是 vendor/model(比如 openai/gpt-4o-mini), 不是只写 gpt-4o-mini。和直连 OpenAI 那条不一样。
  • Anthropic 的 system 消息。 它在顶层,不在 messages 里。Provider 会自动拆, 但你手搓请求时记住这一点。
  • Replicate 要锁版本。 model 字段是 version hash,不是友好名。用 black-forest-labs/flux-schnell 之前去 Replicate 查当前版本号。
  • Anthropic 上的 AIUnsupportedError: image Anthropic 没有图像能力。设 fallback: 'replicate''fal',或者在上层根据 operation 走分支。

官方文档