> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# AI 服务商

> 六个可插拔的 provider，靠环境变量挑选，支持降级和模型级定价。

AI 厂商变化很快 —— 价格调整、新模型发布、品牌起落。vibestrap 在统一 facade 后面接了
**六个 provider**，让你的代码完全不需要知道、也不在意当前激活的是哪个。靠永远在线的
`mock` 离线开发、按环境切换 provider、永远不会上线时漏配置 —— `AI_PROVIDER` 决定激活
哪一个,只有配了 API key 的 provider 才会在启动时注册。

## 前置条件

* 项目能跑（`pnpm dev` 起得来）。
* 至少一个真实 API key —— 如果只挑一个，OpenRouter 最省事
  （[openrouter.ai](https://openrouter.ai/docs)）。
* 先看一遍 [`src/ai/index.ts`](https://github.com/larryworld/vibestrap/blob/main/src/ai/index.ts)，80 行讲清楚整个故事。

## 六个 provider

| 名称           | 源文件                          | 支持操作                    | 说明                          |
| ------------ | ---------------------------- | ----------------------- | --------------------------- |
| `mock`       | `providers/mock.ts`          | chat, chatStream, image | 默认。返回固定文本 + picsum 图片 URL。  |
| `openrouter` | `providers/openai-compat.ts` | chat, chatStream        | OpenAI 兼容形式，路由上百模型。         |
| `openai`     | `providers/openai-compat.ts` | chat, chatStream        | 直连 OpenAI 端点。               |
| `anthropic`  | `providers/anthropic.ts`     | chat, chatStream        | 自有 Messages API + SSE 事件类型。 |
| `replicate`  | `providers/replicate.ts`     | image                   | 轮询式预测 API。                  |
| `fal`        | `providers/fal.ts`           | image                   | 同步队列，FLUX schnell 大约 1 秒。   |

调用 provider 不支持的方法（比如对 `anthropic` 调 `image()`）会抛 `AIUnsupportedError`，
manager 会捕获并尝试 `opts.fallback`（如果传了的话）。

## 步骤：切换到真实 provider

1. 选一个 provider，把 key 写进 `.env.local`：

   ```bash theme={null}
   AI_PROVIDER=openrouter
   OPENROUTER_API_KEY=sk-or-v1-...
   # OPENROUTER_BASE_URL 默认是 https://openrouter.ai/api/v1
   ```

2. 重启 dev server。条件注册在 import 时执行：

   ```ts theme={null}
   if (env.OPENROUTER_API_KEY) {
     PROVIDERS.openrouter = createOpenAICompatProvider({ ... });
   }
   ```

3. 在 server action 或 route handler 里发起调用：

   ```ts theme={null}
   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）。改完保存即生效：

```ts theme={null}
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`（很便宜），调用照样能跑但成本统计就
偏低了。在 [可观测性](/zh/ai/observability) 里搜「忘记价目表」可以看修复方法。

## 你需要捕获的错误

```ts theme={null}
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` 是降级开关。`AIProviderError` 或 `AIUnsupportedError` 时 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` 走分支。

## 官方文档

* OpenRouter：[openrouter.ai/docs](https://openrouter.ai/docs)
* OpenAI：[platform.openai.com/docs](https://platform.openai.com/docs)
* Anthropic：[docs.anthropic.com](https://docs.anthropic.com/)
* Replicate：[replicate.com/docs](https://replicate.com/docs)
* fal.ai：[fal.ai/docs](https://fal.ai/docs)
* 源码：`src/ai/index.ts`、`src/ai/manager.ts`、`src/ai/types.ts`
