ai-lcr
v0.6.5
Published
Least Cost Routing for LLMs — route every model call to the cheapest available provider, fall back automatically, and track real cost. Built for the Vercel AI SDK.
Maintainers
Readme
AI-LCR — AI 最低成本路由(Least Cost Routing)
同一个模型在不同 provider 上的价格不同——而且没有任何单一 provider 在所有模型上都最便宜。ai-lcr 为每个模型维护一份「最便宜优先」的列表,路由到其中最便宜且健康的 provider(下表中的 ⭐),失败时向下穿透——这正是电话运营商几十年来一直在做的 最低成本路由(Least Cost Routing)。
安装
npm install ai-lcrai(Vercel AI SDK)是 peer dependency。
快速开始
import { createLCR } from "ai-lcr";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { generateText } from "ai";
const kunavo = createOpenAICompatible({
name: "kunavo",
baseURL: "https://api.kunavo.com/v1",
apiKey: process.env.KUNAVO_API_KEY,
});
const openrouter = createOpenAICompatible({
name: "openrouter",
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
});
const lcr = createLCR({
autoSort: true, // 按 `cost` 把每个模型的 provider 排成最便宜优先
models: {
// 一个逻辑模型,跨多个 provider 最便宜优先地提供服务。
"gemini-3-flash": [
{ model: kunavo("gemini-3-flash"), label: "kunavo", cost: { input: 0.40, output: 2.40 } },
{ model: openrouter("google/gemini-3-flash-preview"), label: "openrouter", cost: { input: 0.5, output: 3.0 } },
],
},
// 看清每次调用的实际花费,以及由哪个 provider 提供。
onCost: ({ provider, costUsd }) => console.log(`${provider}: $${costUsd.toFixed(6)}`),
});
const { text } = await generateText({
model: lcr("gemini-3-flash"),
prompt: "Explain Least Cost Routing in one sentence.",
});cost 和 label 都是可选的——如果你不需要成本核算或 autoSort,可以直接传裸模型(kunavo("gemini-3-flash"))。lcr("gemini-3-flash") 返回一个标准的 AI SDK 模型,因此可与 generateText、streamText、generateObject、工具调用和 agent 一起使用。
直连模型厂商官方 API(原生 provider)
「provider」不一定是聚合器。模型厂商自己的官方 API 就是列表里的又一个 entry——往往是最便宜的那个(没有聚合器加价),也最不容易悄悄破坏原生特性(prompt 缓存、工具调用)。任何 AI SDK 的 provider 包都返回标准模型,所以厂商的原生 API 和 OpenAI 兼容的聚合器可以并排放在同一个列表里:
import { createLCR } from "ai-lcr";
import { createDeepSeek } from "@ai-sdk/deepseek"; // DeepSeek 官方 API
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const deepseek = createDeepSeek({ apiKey: process.env.DEEPSEEK_API_KEY });
const openrouter = createOpenAICompatible({
name: "openrouter",
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
});
const lcr = createLCR({
autoSort: true,
models: {
"deepseek-v4": [
// 官方 API 优先——无加价,原生特性齐全(缓存、错峰折扣)。
{ model: deepseek("deepseek-chat"), label: "deepseek", cost: { input: 0.43, output: 0.87 } },
// 聚合器作为兜底,保可用性 + 广覆盖。
{ model: openrouter("deepseek/deepseek-v4"), label: "openrouter", cost: { input: 0.43, output: 0.87 } },
],
},
});同样的模式适用于任何厂商的原生 SDK provider——@ai-sdk/anthropic、@ai-sdk/google、@ai-sdk/openai、@ai-sdk/xai 等等。ProviderEntry 接受 AnyLanguageModel——一个鸭子类型接口(doGenerate + doStream + provider + modelId),任何 AI SDK model 无论 V2 还是 V3 spec 都满足,集成边界无需 as 强转。原生 API 覆盖窄(只有该厂商自己的模型)但特性全;聚合器覆盖广。官方优先 + 聚合器兜底 正是 LCR 最自然的形态。
开源权重模型的最便宜路由(DeepInfra)
对开源权重模型——DeepSeek、Kimi、MiniMax、GLM、Qwen——专门的推理托管商通常是最便宜的路由,明显低于聚合器价格。DeepInfra 兼容 OpenAI,直接当成列表里的又一个 entry 即可。有一个坑:它的 OpenAI endpoint 在 /v1/openai(/v1/ 在 openai 前面),不是常规的 /v1:
import { createLCR } from "ai-lcr";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const deepinfra = createOpenAICompatible({
name: "deepinfra",
baseURL: "https://api.deepinfra.com/v1/openai", // 注意:/v1/openai,不是 /v1
apiKey: process.env.DEEPINFRA_API_KEY,
});
const openrouter = createOpenAICompatible({
name: "openrouter",
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
});
const lcr = createLCR({
autoSort: true,
models: {
// DeepInfra 最便宜;OpenRouter 作广覆盖 / 可用性兜底。
// DeepInfra 用 HuggingFace 风格的 id(org/Name)。
"deepseek-v4-flash": [
{ model: deepinfra("deepseek-ai/DeepSeek-V4-Flash"), label: "deepinfra", cost: { input: 0.10, output: 0.20 } },
{ model: openrouter("deepseek/deepseek-v4-flash"), label: "openrouter", cost: { input: 0.27, output: 1.10 } },
],
"minimax-m2.5": [
{ model: deepinfra("MiniMaxAI/MiniMax-M2.5"), label: "deepinfra", cost: { input: 0.15, output: 1.15 } },
],
"kimi-k2.5": [
{ model: deepinfra("moonshotai/Kimi-K2.5"), label: "deepinfra", cost: { input: 0.45, output: 2.25 } },
],
},
});DeepInfra 只承载开源权重——没有第一方 Claude / GPT / Gemini。那些闭源模型请走 OpenRouter 或折扣中转。
省掉样板代码(DEFAULT_PROVIDERS)
每个路由 OpenRouter、DeepInfra、TokenMart、DeepSeek 等的项目都要重复声明相同的 baseURL + apiKeyEnv。DEFAULT_PROVIDERS 是一份内置字典——import 你需要的那几个就行,不用再复制粘贴 URL:
import { DEFAULT_PROVIDERS } from "ai-lcr";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
// 按需取——类型安全,无硬编码 URL。
const deepinfra = createOpenAICompatible({
name: "deepinfra",
baseURL: DEFAULT_PROVIDERS.deepinfra.baseURL,
apiKey: process.env[DEFAULT_PROVIDERS.deepinfra.apiKeyEnv],
});可用 provider:
| Key | Base URL | Env 变量 |
|---|---|---|
| openrouter | https://openrouter.ai/api/v1 | OPENROUTER_API_KEY |
| deepinfra | https://api.deepinfra.com/v1/openai | DEEPINFRA_API_KEY |
| tokenmart | https://model.service-inference.ai/v1 | INFERENCE_API_KEY |
| deepseek | https://api.deepseek.com | DEEPSEEK_API_KEY |
| kunavo | https://api.kunavo.com/v1 | KUNAVO_API_KEY |
| runware | https://api.runware.ai/v1 | RUNWARE_API_KEY |
| fal | https://queue.fal.run | FAL_KEY |
常见用法是取 DEFAULT_PROVIDERS 的子集,并声明一个项目级类型保证编译安全:
import { DEFAULT_PROVIDERS } from "ai-lcr";
type ProviderId = "deepinfra" | "openrouter";
export const PROVIDERS = {
deepinfra: DEFAULT_PROVIDERS.deepinfra,
openrouter: DEFAULT_PROVIDERS.openrouter,
} satisfies Record<ProviderId, { baseURL: string; apiKeyEnv: string }>;它如何路由
- 最便宜优先。 provider 按顺序依次尝试——把它们排成最便宜优先,或设置
autoSort: true让它按cost自动排序。 - 失败时向下穿透。 遇到任何 provider 失败——限流、5xx、超时、额度耗尽(402 / 欠费 / 余额不足),以及 400 这类 client 错误——都会前进到下一个 provider,且对流式安全。400 会 failover 是有意为之:在 OpenAI 兼容聚合层里,400 往往是"这家 provider 不吃这个请求"(不支持的参数、它没上架这个 model、更严格的 schema),而非请求本身坏了——换一家很可能就能服务。若所有 provider 都拒绝,请求仍会失败,并抛出第一个(原始)错误,让真正的调用方 bug 保持可调试。唯一永远不 failover 的是调用方主动取消(
AbortSignal)。想恢复旧的"client 错误立即失败"行为,给createLCR传shouldRetry: isRetryableError。 - 恢复。 在一段空闲窗口(
resetIntervalMs,默认 60s)之后,自动回到最便宜的 provider。
缓存
LLM 世界里有两种完全不同的"缓存",ai-lcr 两种都做——都默认关闭,都只是一个配置开关,不需要你跑任何服务。
整次调用都省掉(cache)—— 响应缓存
当一个请求和之前回答过的一模一样时,直接重放已存的响应、完全不调用任何 provider:零延迟、costUsd: 0。这正是 Vercel AI Gateway 明显没做的那一层。
const lcr = createLCR({
models: { /* … */ },
cache: true, // 精确匹配响应缓存(默认进程内内存)
});存储可插拔,且 ai-lcr 为此零依赖:
cache: true用进程内内存。在长驻 server 上是真缓存;在单次 serverless 调用内(比如 agent 循环里重复的子调用)也有用——但它不会跨 serverless 请求存活,因为不同函数实例之间不共享内存。- 想在 serverless 上跨请求命中,自带一个由共享层(Upstash Redis、Vercel KV)支撑的 store:
cache: myStore。ai-lcr 自己不跑任何服务——共享层是你的。自定义 store 就是{ get, set }(见CacheStore);想要带上限的内置实现,用导出的createMemoryCacheStore({ maxEntries })。 cache: { store?, ttlMs? }可设置过期时间。
命中会落一条 CallRecord:cacheHit: true、costUsd: 0,并把省下的钱单独记在 cacheHitSavingUsd 一行——这是缓存省的钱,和路由省的钱(baselineUsd − costUsd)分开,绝不混在一起。空回复和无 usage 的结果永不缓存。一个要点:缓存会让相同请求返回相同响应——对幂等 / temperature: 0 的调用正好,对采样型调用则是行为改变。
让这次调用更便宜(promptCache)—— provider 提示缓存
另一套机制:模型照样跑,但提示的静态开头(你的 system prompt)在重复时按 provider 的缓存读价(约 0.1× input)计费。Anthropic 需要显式 cache_control 标记;OpenAI / Gemini / DeepSeek 自动缓存前缀。promptCache: true 帮你在最后一条 system 消息上插入这个标记:
const lcr = createLCR({
models: { /* … */ },
promptCache: true, // 5 分钟窗口;想要更长用 { ttl: "1h" }
});它只写 anthropic 这个 provider-options 命名空间,其他 provider 一律忽略——所以在混合链路上也安全。而且只要你在 prompt 里自己设了 cacheControl,它就完全让位。省下的钱照旧体现在 CallRecord 的 cachedInputTokens 和 cachedSavingUsd 上。
看清每次调用发生了什么(onCall)
onError/onCost 各自独立触发、互不关联,事后很难还原一次 failover 的全貌。onCall 给你每个请求一条记录——完整的尝试链、最终服务者、每跳失败的原因、延迟和成本;formatCallRecord 把它变成一行可扫读的日志:
✓ text tokenmart 412ms $0.0003
⚠ text tokenmart→openrouter 910ms $0.0004 ⤷ tokenmart 502
✗ text deepseek→tokenmart→openrouter 1240ms FAILED ⤷ deepseek 401, tokenmart 502, openrouter 429record 是一个纯 CallRecord 对象,关键字段:
interface CallRecord {
id: string; // 每个请求一个关联 id
model: string; // 逻辑模型名
attempts: { provider; ok; latencyMs; errorClass? }[];
winner?: string; // 最终服务的 provider;全失败则为 undefined
ok: boolean;
failedOver: boolean; // 尝试了不止一家
latencyMs: number;
ttftMs?: number; // 仅流式:首 token 时间
inputTokens: number;
outputTokens: number;
cachedInputTokens?: number; // 命中 prompt 缓存的输入 token
costUsd: number; // 实际成本(已按 cacheRead 折扣)
baselineUsd?: number; // 同样用量在「节约基线」上的价格 → 节约 = baselineUsd − costUsd
baselineKind?: "last-leg" | "official" | "priciest-route"; // 基线的来源(见下)
cachedSavingUsd?: number; // provider 自己的缓存折扣——是真金白银,但不是路由的功劳,别混进节约
usageMissing?: boolean; // 服务成功但 token 报 0/0 → 成本是「未知」而非「免费」
// 媒体调用(createMediaLCR)额外携带:
modality?: "image" | "video";
usage?: { seconds?; outputs?; megapixels? }; // 账单依据的实际用量
officialUsd?: number; // 官方第一方价(按本次实际用量)
estCostUsd?: number; // 价格表的预估——与 costUsd 的差 = 价格表漂移
}节约怎么算才诚实: baselineKind 说明 baselineUsd 是哪种基线——文本是链尾兜底 provider 的列表价("last-leg",故意不取最贵的一条:prompt 缓存可能让标价更便宜的那家在缓存重的调用上反而更贵,取最大值会凭空造出"节约");媒体是模型厂商官方第一方价("official",按实际秒数算),查不到官方价时退化为你配置里最贵的路由("priciest-route",自我参照,仅说明跨 provider 价差)。
送进收集器: createHttpSink 把每条记录 POST 到任意 endpoint(serverless 上传 Next.js 的 after 作 dispatch 防止被掐断)。如果你用标准环境变量(LCR_INGEST_URL、LCR_PROJECT、LCR_INGEST_KEY),createEnvSink 全部替你读好——三行搞定:
import { createEnvSink } from "ai-lcr";
import { after } from "next/server";
export const lcrCallSink = createEnvSink(after);LCR_INGEST_URL 不设 → sink 是 undefined,本地开发自动静默。唯一必传参数是 dispatch——框架相关的 fire-and-forget runner(Next.js: after;Cloudflare: ctx.waitUntil;长驻服务: (fn) => fn())。
配套的自托管 dashboard ai-lcr-dashboard(Next.js + Postgres,Vercel 一键部署)专为这些记录而建:花费 vs 节约趋势、各 provider failover 健康度、媒体 $/秒 与 $/张、以及价格漂移面板——某条 model@provider 路由的实报账单与价格表偏差超过 ±20% 时点名示警(约 100× 基本就是美元当美分的笔误)。只存元数据,不存 prompt 和输出。
支持的 provider
任何 OpenAI 兼容的 endpoint 都可用——任何 AI SDK 的 provider 包也都可用,包括模型厂商自己的官方 API。
- 模型厂商官方 API(原生): 通过各自的 AI SDK provider 包直连 DeepSeek、OpenAI、Anthropic、Google、xAI 等——无加价,原生特性齐全。见上方「直连模型厂商官方 API(原生 provider)」一节。
- 文本聚合器: OpenRouter(覆盖最广,列表定价)· Kunavo(全模型 8 折)· TokenMart(按模型 85 折–35 折不等)
- 图像 / 视频: Kunavo(8 折)· TokenMart · fal.ai · Runware —— 通过
createMediaLCR路由。图像:Kunavo(生成 +*-edit参考图端点)+ Runware + fal。视频:fal(异步队列)、Kunavo(异步POST /v1/videos+ 轮询,另有同步兜底)、Runware(异步videoInference+getResponse轮询)——三家都在异步submit/poll路径上
文本模型价格
单位为每 100 万 token 的美元价格,input / output。官方价格截至 2026-05——请向各 provider 核对当前价格。OpenRouter 直接透传列表价;Kunavo 在官方价基础上统一 8 折。TokenMart 折扣按模型不同(85 折–35 折),请在 thetokenmart.ai 核对当前价格。
| 模型 | 官方价(in / out) | OpenRouter | Kunavo | TokenMart | 最便宜 | |---|---|---|---|---|---| | Gemini 3 Flash | $0.50 / $3.00 | 无折扣 | −20% | — | ⭐ Kunavo | | Gemini 3 Pro / 3.1 Pro | $2.00 / $12.00 | 无折扣 | −20% | −20% → $2.40 / $9.60 | ⭐ Kunavo | | Gemini 2.5 Pro | $1.25 / $10.00 | 无折扣 | −20% | — | ⭐ Kunavo | | Gemini 2.5 Flash | $0.30 / $2.50 | 无折扣 | −20% | — | ⭐ Kunavo | | Claude Opus 4.7 | $15.00 / $75.00 | 无折扣 | −20% | $4.25 / $21.25 | ⭐ TokenMart | | Claude Sonnet 4.6 | $3.00 / $15.00 | 无折扣 | −20% | −15% → $2.55 / $12.75 | ⭐ Kunavo | | Claude Haiku 4.5 | $1.00 / $5.00 | 无折扣 | −20% | — | ⭐ Kunavo | | DeepSeek V4 | $0.43 / $0.87 | 无折扣 | 未提供 | — | ⭐ DeepSeek(官方) |
Kunavo 提供 Anthropic + Google。DeepSeek / OpenAI / Grok / Mistral 路由到各自的官方 API(最便宜,原生特性齐全),以 OpenRouter 作为广覆盖兜底——一份配置即可混用原生厂商与聚合器。
注: list 价 ≠ 有效价——请始终用 probe 验证。截至 2026-05-28,Kunavo 在 Gemini(~1.1–1.4×)和 Claude(~1.0×)两条路上的 token 计数均已干净。现存问题:两个模型均忽略
max_tokens,Claude 隐藏 prompt 注入仍为间歇性——生产路由前请重新 probe。
注: TokenMart token 计数同样经 probe 验证干净(后端与 Inference.ai 相同,2026-05-27 全项通过:工具调用、
max_tokens、无注入、token ~1.0×、prompt 缓存)——如需 Claude 的第二 provider,TokenMart 是可靠备选。生产路由前请重新 probe 确认。
图像模型价格
单位为每张图的美元价格,截至 2026-05(provider 列表价 / 零售价;请核对当前价格)。Kunavo 为官方价 8 折。fal 与 Runware 是算力 provider——ai-lcr 为每个模型挑选最便宜的那个(⭐)。
| 模型 | fal.ai | Runware | Kunavo | TokenMart | 最便宜 | |---|---|---|---|---|---| | Nano Banana 2 | $0.080 | $0.069 | $0.054 | $0.050 | ⭐ TokenMart | | Nano Banana Pro | $0.080 | — | $0.107 | — | ⭐ fal | | GPT-Image-2 | $0.210 | $0.094 | $0.102 | — | ⭐ Runware | | Imagen 4 Ultra | $0.060 | $0.060 | — | — | ⭐ fal / Runware | | Ideogram V3 | $0.060 | $0.060 | — | — | ⭐ fal / Runware | | Seedream 4 | $0.030 | — | — | — | ⭐ fal | | Flux 1.1 Pro | $0.040 | $0.040 | — | — | ⭐ fal / Runware | | Flux Dev | $0.025 | $0.025 | — | — | ⭐ fal / Runware | | Flux Schnell | $0.0030 | $0.0013 | — | — | ⭐ Runware | | Qwen-Image | — | $0.0038 | — | — | ⭐ Runware | | FLUX.2 Klein 4B | — | $0.0006 | — | — | ⭐ Runware |
视频模型价格
单位为每秒的美元价格,截至 2026-05——请核对当前价格。视频计费方式因 provider 而异,因此无法做严格对等的跨 provider 表格:fal.ai 和 Runware 按秒计费,而 Kunavo 的 Veo 按段计费(Fast ~$0.28 / Lite ~$0.168 / Quality ~$1.34)。下表为 fal.ai 的每秒价格(测试中的视频主力);fal / Runware / Kunavo 的归一化对比是一个 TODO。
| 模型 | fal.ai($/s) | |---|---| | Seedance Lite | $0.036 | | Hailuo 02 Standard | $0.045 | | LTX-2 | $0.060 | | Kling 2.6 Pro | $0.070 | | WAN 2.2 | $0.080 | | Veo 3.1 Lite | $0.080 | | Kling V3 Pro | $0.112 | | Seedance Pro | $0.124 | | Veo 3.1(audio-on) | $0.400 |
图像与视频路由(createMediaLCR)
图像和视频是 ai-lcr 独立的一侧(输出是文件、计价单位混杂、视频是异步任务)—— 见 src/media.ts。你提供一个 registry(每个模型的 provider 路由 + 单位价)和一组 adapter,它就按最便宜优先路由、自动 failover,并通过与文本侧相同的 onCall sink 报告真实成本。
两个价格、两份职责(0.6+):排序用归一化到参考输出(1080p 一张图 / 5 秒一段片)的价格,让混杂的计价单位可以公平比较;但每次调用的计费按实际用量——按秒计价的 SKU,一条 8 秒的片就按 8 秒收,节约基线也按同样的 8 秒官方价算。adapter 上报带类型的实际用量(usage: { seconds, outputs, megapixels });provider 自己报了账单时以账单为准,而账单与价格表预估差距悬殊时(经典的"美元当美分"笔误正好是 100×)会触发 onError,提醒你修价格表。
import { createMediaLCR, createKunavoMediaAdapter, createFalMediaAdapter } from 'ai-lcr'
const lcr = createMediaLCR({
registry: {
'google/veo-3-lite': {
id: 'google/veo-3-lite', modality: 'video',
routes: [
{ provider: 'kunavo', externalId: 'veo-3-lite', pricing: { unit: 'call', cents: 16 } },
{ provider: 'fal', externalId: 'fal-ai/veo3.1/lite', pricing: { unit: 'second', cents: 8 } },
],
},
},
adapters: {
kunavo: createKunavoMediaAdapter({ apiKey: process.env.KUNAVO_API_KEY! }),
fal: createFalMediaAdapter({ apiKey: process.env.FAL_KEY! }),
},
onCall: rec => console.log(rec.winner, rec.costUsd, rec.failedOver),
})
// 同步:出片才 resolve(图像够用)。
const { outputs, provider, costCents } = await lcr('google/veo-3-lite', { prompt: 'a wave' })异步(submit / poll)—— 给长耗时的视频
几分钟的视频任务没法把一个 serverless 请求一直挂住。submit 负责路由 + 入队,返回一个纯 JSON 句柄;poll 负责查它。两者跑在不同进程——句柄能扛过一次数据库/队列的来回。
// 进程 A —— 请求处理器:路由 + 入队,立即返回
const handle = await lcr.submit('google/veo-3-lite', { prompt: 'a wave', aspect_ratio: '16:9' })
await db.jobs.put(jobId, JSON.stringify(handle))
// 进程 B —— cron / 队列 worker:轮询到终态
let handle = JSON.parse(await db.jobs.get(jobId))
const r = await lcr.poll(handle)
if (r.done) {
save(r.outputs, r.costCents) // 已落地——telemetry 此刻已落一条
} else {
await db.jobs.put(jobId, JSON.stringify(r.handle)) // 继续轮询 r.handle
}几个值得知道的设计取舍:
- 路由发生在
submit(选中最便宜的、支持异步的 provider);句柄携带尚未尝试的 fallback 列表,所以—— - failover 发生在
poll——某个 provider 的任务在轮询途中失败时,会自动 re-submit 到下一个 provider(返回一个新的r.handle继续轮询),而不是让请求直接死掉。 - telemetry 只在终态轮询落一条——一条
onCallCallRecord,带完整 failover 链,跨两个进程串起来(不是在submit时落)。 - adapter 通过实现
submit+checkStatus来声明支持异步;只做图像的 adapter 省略它们,异步路由会跳过这种路由。内置的 Kunavo、fal、Runware adapter 都实现了异步路径(Kunavo/Runware 异步仅视频;fal 图像视频皆可)。
自己写 adapter
MediaAdapter 很小——同步用 run,异步可选 submit/checkStatus——唯一要紧的合同是如何上报产出:
// 落定的结果上报:
{
outputs: [{ url, type: "image" | "video" }],
costCents?: number, // provider 自己的账单,单位是美分——API 返回美元的要 ×100 转换!
usage?: { // 带类型的实际用量——账单(或估算)以它为准
seconds?: number, // 实际产出的视频秒数(按秒计价的 SKU 按它计费)
outputs?: number, // 产出个数——图或片(按张 / 按次计价按它计费)
megapixels?: number // 产出总百万像素(按 MP 计价按它计费)
}
}保证计费正确的几条规则:
- 维度在
usage里显式命名,绝不报裸数字。 秒数和产出数是两个不同的字段,按次的平价永远不可能被片长乘爆(经典的 8× 过计)。 costCents是美分。 API 返回美元的,必须在 adapter 里转换(参考 Runware adapter)。万一失手,路由器的异常账单守卫会在偏差 ≥25× 时触发onError——但上报的数字仍然作数。- 什么都不报时,路由器会估算:按秒 SKU 依次读
usage.seconds→ 输入的duration(数字或"8s"这类字符串)→ 最后才退到 5 秒参考;按张/按次 SKU 按产出数计。 - 抛错时带上 HTTP
status属性(见FalMediaError/KunavoMediaError),路由器才能正确分类并 failover。
给 provider 做体检(能力 + 成本探测)
折扣再大,如果 provider 偷偷破坏了协议就一文不值。ai-lcr 自带一个零依赖的检查脚本(scripts/check-provider.sh,只需 bash + curl + python3),逐模型核查那些真正会让你多花钱或污染输出的点:
媒体 provider 有独立探针:
scripts/check-kunavo-media.sh(bash+curl+jq)实测 Kunavo 的图像生成、*-edit参考图端点、以及异步 + 同步视频;scripts/check-media-async.mjs则逐 provider(kunavo · fal · runware,有 key 的才跑)跑ai-lcr自己的submit/pollAPI——submit → 把句柄做 JSON 来回 → 轮询到 done → 断言 URL 真能 GET 到、成本有上报(PROBE_FAILOVER=1再加一条实时 submit 期 failover)。上生产前先跑一遍。
- 工具调用 —— 单次调用 + 带
content: null的多步 round-trip(每个 agent 循环都会发的形态) max_tokens是否生效 —— cap 必须能限制输出长度- 隐藏 prompt 注入 —— 发一条中性消息,如果模型开始回应一段它从没收到过的 system prompt,就说明 provider 注入了东西
- token 超计 —— 把上报的
prompt_tokens和一个可信基线 provider 对照,>1.5× 说明账单被灌水、"折扣"可能是亏本 - prompt 缓存 ——
cache_control在重复请求时是否真的产生cache_read
# 指向你要体检的 provider;模型用通用编号槽位(Gemini / Claude / GPT / Llama 都行)。
# 给某个模型配上 REF_n(可信基线上的对应模型 id)即可启用 token 超计检查。
# CACHE_MODEL(可选)跑 Anthropic 原生 /v1/messages 的 prompt 缓存测试。
API_KEY=$KUNAVO_API_KEY BASE=https://api.kunavo.com \
MODEL_1=gemini-3-flash REF_1=google/gemini-3-flash-preview \
MODEL_2=claude-sonnet-4-6 REF_2=anthropic/claude-sonnet-4.6 \
CACHE_MODEL=claude-sonnet-4-6 \
REF_API_KEY=$OPENROUTER_API_KEY REF_BASE=https://openrouter.ai/api \
bash scripts/check-provider.sh
# TokenMart(Inference AI)使用不带 vendor 前缀的裸模型 ID
API_KEY=$INFERENCE_API_KEY BASE=https://model.service-inference.ai \
MODEL_1=gemini-3-flash-preview REF_1=google/gemini-3-flash-preview \
MODEL_2=claude-sonnet-4-6 REF_2=anthropic/claude-sonnet-4.6 \
CACHE_MODEL=claude-sonnet-4-6 \
REF_API_KEY=$OPENROUTER_API_KEY REF_BASE=https://openrouter.ai/api \
bash scripts/check-provider.sh注入或 token 超计这两项 FAIL,意味着该 provider 对那个模型来说不是安全的最低成本目标——在它修好之前,别把它放进那个模型的「最便宜优先」列表,修好后重新探测。
信任矩阵(探测于 2026-05-27)
两个 OpenAI 兼容 provider,同一脚本,同一天。单元格覆盖两个家族(G = Gemini,C = Claude)。
| 检查项 | Kunavo | TokenMart |
|---|---|---|
| 工具调用(单次 + 多步 content: null) | G ⚠️ 间歇性¹ · C ✅ | ✅ 两者 |
| token 计数 vs OpenRouter 基线 | G ✅ ~1.1–1.4× · C ✅ ~1.0× | ✅ 两者 ~1.0× |
| 隐藏 prompt 注入 | G ✅ 无 · C ❌ 间歇性² | ✅ 无 |
| max_tokens 是否生效 | ❌ 被忽略(两者) | ✅ 两者 |
| prompt 缓存(cache_control) | C ❌ 未生效(探测中途 endpoint 还卡死) | C ✅ cache_read > 0 |
¹ Kunavo Gemini 一次返回干净的工具调用,下一次相同请求却完全丢掉了 tools——不是稳定通过。 ² Kunavo Claude 一次对着幻觉中的"fake system prompt"作出反应,另一次又干净——注入是间歇性的,不是被移除了。
结论: TokenMart 在 Gemini 和 Claude 两条路上每一项都通过,且结果稳定可复现——可以放心路由。Kunavo:Claude token 计数已干净(2026-05-28 重新 probe),按 8 折 list 价,Kunavo 现在是 Claude 模型的最便宜选择。现存问题:两个模型均忽略 max_tokens、Claude 隐藏 prompt 注入仍为间歇性、Gemini 也会间歇性丢工具调用——用新模型前先重新探测。
路线图
- [x] 自有 failover 引擎——最便宜优先路由 + 流式安全的 fallback,不依赖外部路由库
- [x] 真实的逐次调用成本核算(
onCost) - [x] 基于各 provider
cost的自动最便宜优先排序(autoSort) - [x] 离线能力 + 成本检查(
scripts/check-provider.sh)→ 逐模型信任矩阵 - [ ] 内置价格表,实现零配置定价(省去手填
cost数字) - [ ] provider 怪癖中间件(透明地修补已知怪癖,如 Kunavo 被忽略的
max_tokens) - [ ] 把 probe 结果自动接入路由(探测失败的 provider×model 自动从列表剔除)
- [x] 图像与视频模型路由(
createMediaLCR)—— 图像走 Kunavo(含*-edit)+ Runware + fal;视频异步(submit/poll)走 fal、Kunavo、Runware 三家 - [x] 按实际用量的结算计费(0.6)—— typed
usage、时长感知的节约基线、estCostUsd价格漂移信号、异常账单守卫 - [x] 自托管 dashboard(
ai-lcr-dashboard)—— 节约、failover 健康度、媒体单位成本、价格漂移面板 - [ ] 内置价格表中的归一化跨 provider 视频价格对比
联盟(Affiliate)披露
ai-lcr 是 provider 中立的,可与任何 OpenAI 兼容的 endpoint 配合使用。作者与 Kunavo 之间存在联盟(affiliate)关系——在官方价 8 折的情况下,它往往(但并非总是)是最便宜的选项,正如上面的表格所示。通过该链接注册可能会让作者获得一份分成。你完全不必使用它;自带 provider,路由功能照常工作。
开发
npm install
npm run typecheck
npm test # mock 的路由 / failover 测试 + 真实 Kunavo 测试测试套件覆盖了:最便宜优先路由、可重试错误以及 provider 400 时的 failover(但调用方主动取消时不做 failover)、穷尽整条链路时抛出原始错误,以及一次真实的「provider 故障 → Kunavo 恢复」。真实测试仅在环境变量 KUNAVO_API_KEY 设置时运行,否则跳过。
致谢
流式安全的 failover 方案改编自 ai-fallback(MIT)——在内部重新实现,使 ai-lcr 拥有自己的引擎,并把成本核算 + 路由直接融入其中。基于 Vercel AI SDK 构建。
许可证
MIT © Victor
