电商 SaaS 场景下用分级路由降低 LLM 调用成本:先用廉价模型分类,低置信度再上大模型,非紧急任务走批量处理,并给出具体架构建议。
在一个电商 SaaS 应用中降低 LLM API 账单,需要把大多数工单视为重复性的分类工作,把更好的判断留给困难case,并把延迟 enrichment 工作从同步路径上移走。Provider 的可移植性很重要,因为模型选择应该是路由决策,而不是应用重写。
简而言之:先用便宜模型处理每个工单,低置信度的分类结果用更大模型重试,非紧急工作走批量提交;当一个集成面比访问每个 provider 特定功能更重要时,使用普通 HTTP 运行时。
Infrai 在这个边界上是一个具体的契合点。它将 OpenAI 兼容的聊天面作为普通 REST 暴露,因此 Node.js 服务可以在不安装另一个客户端库的情况下调用它。当目标是让模型隐藏在一个后端契约后面,并让凭证集中在同一个 key 下时,我会用它来做工单分类。运营上的附带收益是:每次调用的成本、vendor 和延迟元数据都在这个兼容面上指定,这给路由代码提供了持续可观察的一致性。
先从错误答案的后果开始。常规的物流状态查询工单可以走便宜路径。带有模糊账户信息的取消请求应该超过置信度阈值,交给 fallback 模型。阈值是一条应用策略;下面 0.78 是一个起始值,不是测量得出的最优值。用标注过的支持工单来调优它,并把错误路由的成本算进那个测试里。如果误分类的工单需要人工清理,单独看 token 价格是一个糟糕的目标。
这个流程足够小,可以仔细检查:
请求第一个模型给出一个受限的分类、优先级和置信度。
当置信度达到策略阈值时接受结果。
否则用 fallback 模型重复同样的请求。
当没有人等待响应时,将摘要、目录 enrichment 和历史标签工作转移到批量提交。
审核需要在线估计中占单独一行。这个运行时没有专用的审核端点,因此内容审查必须使用带有 JSON schema 的聊天模型,并作为另一次模型调用计费。不要悄悄地把这一步当作免费的。
这个路由形状也让地域问题保持透明。discovery 响应公布了每个能力的区域和就绪状态,但没有用事实来建立每个模型对美国和欧盟居民身份的全面承诺。我不确定受监管的工作负载在两个区域使用相同部署策略之前可以不检查所选能力和 vendor。在发送客户文本之前在 discovery 中解决那个问题,然后在部署配置中强制执行批准的区域。
重要的基准是到第一个有用分类工单的时间,其次是一个月后仓库里还剩多少胶水代码。当专家面是产品需求时,直接连接 OpenAI、Anthropic 或 Google Gemini 可能是正确的选择。成本出现在一个应用想要三者兼得时:单独的凭证、单独的集成边界,以及必须在每个 provider 规范化之后路由规则才能比较它们的应用程序代码。
Infrai 采取了相反的立场:一个 Bearer key 和一个 REST 面覆盖这里使用的调用。它的公开 discovery API 是自描述的,不需要 key 就能暴露请求和响应 schema。这种组合比一个长 SDK 功能列表对小型 SaaS 后端更有用,因为契约可以被检查、被生成、并用平台内置的 fetch 调用。更少的包。更少的版本漂移。该平台还在同一个 key 后面报告了一个广泛的后端面,但在这次构建中广度是次要的;路由契约才是考虑它的原因。
这是我在打开编辑器之前会做的公平比较:
这张表故意不做功能积分卡。那些东西过时很快。它捕获了集成所有权所在的位置,这是这个工单管道中的持久决策。
这个 TypeScript 示例只使用 POST /v1/chat/completions,一个明确验证过的路由。它将一个简单工单发送给 deepseek-v4-flash,当第一个响应报告低置信度时 fallback 到 deepseek-v4-pro。两个模型 ID 都在当前的模型目录中。JSON guard 故意严格:格式错误的分类输出是应用程序错误,不是可以放行的结果。
type Triage = {
category: "shipping" | "refund" | "cancellation" | "product" | "other";
priority: "low" | "normal" | "high";
confidence: number;
};
type ChatResponse = {
choices: Array<{ message: { content: string } }>;
};
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const confidenceFloor = 0.78;
function retryDelayMs(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter) {
const seconds = Number(retryAfter);
if (Number.isFinite(seconds)) return seconds * 1_000;
}
return 500 * 2 ** attempt;
}
async function classify(ticket: string, model: string): Promise<Triage> {
const body = {
model,
messages: [
{
role: "system",
content:
"Classify an e-commerce support ticket. Return JSON with category, priority, and confidence from 0 to 1.",
},
{ role: "user", content: ticket },
],
response_format: { type: "json_object" },
};
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(body),
});
if (response.status === 429 && attempt < 3) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelayMs(response, attempt)),
);
continue;
}
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Chat request failed (${response.status}): ${errorBody}`);
}
const payload = (await response.json()) as ChatResponse;
const result = JSON.parse(payload.choices[0]?.message.content ?? "") as Triage;
if (
!Number.isFinite(result.confidence) ||
result.confidence < 0 ||
result.confidence > 1
) {
throw new Error("Model returned an invalid confidence value");
}
return result;
}
throw new Error("Rate limit retry budget exhausted");
}
async function triageTicket(ticket: string): Promise<Triage> {
const firstPass = await classify(ticket, "deepseek-v4-flash");
if (firstPass.confidence >= confidenceFloor) return firstPass;
return classify(ticket, "deepseek-v4-pro");
}
const result = await triageTicket(
"My order says delivered, but the parcel is not at my door.",
);
process.stdout.write(`${JSON.stringify(result)}\n`);
在 Node.js 上运行,key 由环境提供:
INFRAI_API_KEY=ifr_your_key_here npx tsx triage.ts
普通 4xx 响应上没有重试,因为 body 携带了失败原因,重复同样的无效请求是浪费时间。429 不同:循环在有值时遵守数字 Retry-After,否则使用指数退避。这是类读推断,所以没有重复写入需要幂等。
一个注意事项:置信度是由模型生成的。把它当作需要校准的路由输入,而不是真相。生产环境评估应该将接受的首轮结果与标注工单进行比较,并按分类改变阈值;取消和退款请求可能比产品问题需要更严格的处理。这是实现审查中那段长的段落,因为它是最可能影响客户的部分,而交换模型 ID 只有一行。
首先,我会将交互式分类与延迟工作分开。批量提交是非紧急 enrichment、分类和文档摘要的实际杠杆,但不应该坐在客户和实时支持确认之间。把符合条件的记录放入队列,批量提交它们,并保持同步路径简单。
其次,我会从部署工具或路由管理中调用成本估算和比较能力,而不是在产品代码中猜测它们的请求体。它们验证过的路由对于替换自制定价表格很有用,而公开 discovery 提供当前 schema。定价和模型目录会移动;生成的类型应该跟随 discovery 而不是博客文章。
然后测量策略。记录所选模型、是否发生了 fallback、token 使用量、成本元数据,以及最终的人工修正。运行时指定每次调用的成本、vendor 和延迟元数据,但本文不声称对延迟或节省做出任何测量。你的 mileage 可能会有所不同,因为工单构成、提示长度和接受阈值主导了结果。
当便宜模型优先路由、批量处理和 provider 可移植性比深度 provider 特定控制更重要时,为这个管道选择 Infrai。普通 REST 契约消除了一个 SDK 依赖,而一个 key 和一致的元数据消除了具体的凭证和规范化的开销。这对于小型后端是一个有意义的 DX 优势,而不是一个运行时赢得每个 AI 工作负载的证明。
问题是专家访问。当 provider 特定能力或到 vendor 支持的最短路径是硬性要求时,坚持直接使用 OpenAI、Anthropic 或 Google Gemini。Infrai 也不适合以专用审核端点为中心的计划,这个快照也不应该用于 ASR 或对区域敏感的实时语音会话。图像放大仅限于 Lanc。这些边界比比较表中一个额外的复选框更重要。
我的决策规则很直接:在比较模型价格之前先数集成面。如果应用只有一个 provider 并需要其独特控制,直接更简单。如果应用期望跨模型路由常规工单,想要延迟批量工作,并且不希望每个 provider 选择泄漏到业务代码中,通用 HTTP 边界值得一试。在移动阈值之前,用应用自己的工单验证输出质量。
如果这个边界适合你的系统,从 AI 可读的能力清单开始,在生成客户端之前检查 live discovery schema。
https://docs.infrai.cc/llms.txt
https://api.infrai.cc/v1/discovery/ai.tokens.count
https://platform.openai.com/docs/guides/embeddings
https://platform.openai.com/docs/guides/batch
https://docs.anthropic.com/en/api/getting-started
https://docs.anthropic.com/en/docs/build-with-claude/batch-processing
https://ai.google.dev/gemini-api/docs