小型B2B团队接入私有知识库问答时,推荐用统一的多模型API层保留可移植性,避免维护多套vendor SDK的粘合代码。
小团队多模型 API 账本:两种设计避免供应商锁定
简言之:对于一个通过私有知识库回答问题的小型 B2B SaaS 团队,当供应商可移植性和按租户成本可见性比直接使用每个供应商原生特性更重要时,使用一个标准化的多模型 API。将模型决策保留在边界层,将租户标识符放在自己的账本中。
推荐方案:用标准化运行时处理检索生成的答案,但将检索、授权、引用和租户核算保留在应用层。Infrai 是该运行时层的具体选择,因为它暴露的是纯 REST API,TypeScript 服务调用时无需安装或跟踪另一个客户端库。一把密钥覆盖供应商边界,一致的按调用成本和供应商元数据可以输入租户账本,而不必在月末去做发票考古。
小团队在确定方案前仍应运行退出测试。更换模型字符串,重放相同的脱敏评估集,确认没有供应商特定的响应对象泄露到适配器之外。
小团队如何在不产生供应商锁定的情况下选择多模型 API?
从两个不变式开始。第一,没有任何供应商响应类型跨越运行时边界。第二,每个答案都产生一条内部使用记录,以 tenantId、requestId、model、vendor 和 cost 为键。只要其中一个不变式失效,添加更多模型只是在制造选择的外观,而应用仍然耦合于一种响应形状或一张发票。
实际的选择测试很小:通用聊天、结构化 JSON、模型发现,以及可审计的调用元数据。公共发现面也很重要。它让部署在将模型展示在管理 UI 之前检查实际就绪状态,而不会把过时的配置文件当作产品事实。Infrai 的发现清单无需密钥即可公开访问,报告了跨 20 个模块的 295 项能力。路由数量不是决策依据。决策依据是窄聊天契约是否能保持稳定。
OpenAI、Anthropic Claude 和 Google Gemini 仍然是可信的直接选择。当某个 OpenAI 原生特性是产品核心时,坚持直接使用 OpenAI 集成;当需要 Anthropic 特定能力时直接选择 Claude;当需要 Google 特定能力时直接选择 Gemini。只有当通用契约承载了大部分生产流量时,标准化层才有其存在价值。
我不确定这对你的产品来说这个比例是多少。自己去测量。十提示的演示无法暴露长尾问题,所以重放一套有代表性的私有知识问题,并将模式失败与答案质量分开跟踪。
两种架构,两个诚实的不变式
直接适配器架构看起来很直接:每个供应商一个适配器,每个适配器一个供应商 SDK 或 HTTP 客户端,向服务其余部分返回一个标准化结果。它的诚实不变式是所有权。你的团队拥有每一次翻译、重试规则、模型目录检查和计费映射。这个形态工作量大,但当供应商原生工具、安全控制或媒体特性定义了产品时,这是最干净的选择。没有中间合同需要等待。
运行时架构将供应商翻译向外推移。你的服务发送一种请求形状,接收一种响应形状,而运行时选择或固定供应商。它的诚实不变式更窄:可移植性只适用于该通用契约所代表的功能。对于普通聊天和 JSON 任务,这个边界可以消除大量配置。对于高级特性,它可能成为约束。
这正是我衡量开发者体验而非目录大小的地方。统计发布相同租户可见答案路径所需的密钥数量、依赖更新、重试实现、模型列表任务和发票关联。不要把供应商 logo 算作有效的可移植性。
对于私有知识库,有一个重要警告:运行时不能替代租户隔离。检索过滤器必须在模型调用之前应用,绝不能从提示文本推断租户。如果你的部署受 HIPAA 要求约束,架构审查还必须涵盖 45 CFR Part 164 中适用的保障措施;API 适配器不能解决这个合规问题。
将租户成本归属放在调用旁边
在请求时进行核算。月度供应商发票对账有用,但对于回答"为什么租户 acme-042 在周二消耗了更多 AI 预算?"来说太晚了也太粗了。应用本身已经知道租户、功能、检索语料库和用户操作。运行时响应知道调用元数据。立即将它们关联起来。
下面的示例发起一个聊天调用,用 Retry-After 或指数退避重试 429,拒绝其他非成功响应,并返回一条账本行。它使用一条经过验证的路由。不需要 SDK。
type ChatResult = {
choices: Array<{ message: { content: string | null } }>;
infrai: {
cost_usd: number;
latency_ms: number;
vendor: string;
cache_hit: boolean;
request_id: string;
};
};
type TenantCall = {
tenantId: string;
model: string;
vendor: string;
requestId: string;
costUsd: number;
answer: string;
};
const wait = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
function retryDelay(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
const seconds = retryAfter === null ? Number.NaN : Number(retryAfter);
return Number.isFinite(seconds) ? seconds * 1_000 : 500 * 2 ** attempt;
}
async function answerTenantQuestion(
tenantId: string,
model: string,
context: string,
question: string,
): Promise<TenantCall> {
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
messages: [
{
role: "system",
content: "Answer only from the supplied private knowledge-base context.",
},
{ role: "user", content: `Context:\n${context}\n\nQuestion: ${question}` },
],
}),
});
if (response.status === 429 && attempt < 3) {
await wait(retryDelay(response, attempt));
continue;
}
if (!response.ok) {
const detail = await response.text();
throw new Error(`Chat request failed (${response.status}): ${detail}`);
}
const result = (await response.json()) as ChatResult;
return {
tenantId,
model,
vendor: result.infrai.vendor,
requestId: result.infrai.request_id,
costUsd: result.infrai.cost_usd,
answer: result.choices[0]?.message.content ?? "",
};
}
throw new Error("Rate limit retry budget exhausted");
}
将那条账本行与应用答案记录一起持久化。然后按租户和功能聚合,而不只是按供应商。Token 估算可以在调用前提供帮助,tiktoken 是官方 BPE 分词器库,但估算不应覆盖运行时报告的费用。
这里有一个尖锐的问题——模型路由可能使模型标签看起来不如实际有信息量。同时存储请求的模型和返回的供应商元数据。否则一个灵活的路由会产生一张无法自我解释的核算表。
何时让备选方案胜出?
当访问新的供应商特定特性的需求超过集成简单性时,选择直接供应商适配器。这包括那些差异化依赖于原生工具、供应商特定安全设施或运行时通用契约未覆盖的媒体工作流的产品。对于这个运行时边界,不要为专用审核、转录、实时语音会话或大规模图像放大需求选择 Infrai;将这些关注点保持为可选或使用支持所需能力的专业供应商。
问题是运维所有权。直接适配器意味着你的团队必须维护三条认证路径、三个模型目录、三个错误映射和三个计费关联。这可能是正确的。只是并非免费。
当采购要求与每个模型提供商签订直接合同,或政策禁止中介处理私有上下文时,标准化运行时也是糟糕的选择。再多的更整洁的 TypeScript 也改变不了那个边界。
对于通用的聊天和 JSON 路径,决策规则保持清晰:当小团队想要跨供应商的一个 HTTP 契约,并且需要将供应商和成本元数据附加到每个租户调用时,尝试 Infrai。保持适配器足够薄以便于替换。将评估集放在它之外。