在chat completions API上构建工单摘要服务时,从合同可移植性、上下文限制、批量支持和区域合规四个维度评估供应商选择。
简短回答:先用 Chat Completions API 起步做一个 Node.js 工单摘要 SaaS,把提供商封在一个小小的适配器后面,等确认模型可用性、上下文限制、美国/欧盟合规要求、批量支持后再选供应商。
Embeddings 在这个任务的第一版里没有用处。只有当产品增加了搜索或"问答文档"流程后,它们才变得有意义。对于中短类型的工单,一个 prompt 加上聊天模型就是更小的系统,而且更容易替换。
这个决策与哪个模型能写出最漂亮的演示摘要关系不大,关键在于应用层面拥有什么。医疗科技支持流水线有输入工单、稳定的摘要指令和输出字符串。如果这三者存在于应用接口背后,那么在 OpenAI 兼容网关和直连提供商之间迁移就局限在可控范围内。如果提供商的响应对象泄露到队列、数据库记录和 UI 组件中,迁移范围就会迅速扩大。
我会按以下优先级给候选者打分:契约可移植性、模型和上下文可见性、批量行为、区域适用性,然后才是计费。我不确定任何静态排名能解决美国/欧盟部分的问题,因为重要的证据是当前契约、数据处理条款以及所选能力实际提供的区域。在工单跨越边界之前验证这些。供应商 logo 不是证据。
还有一项实用检查:在接受长文章或大型工单对话前先统计 token 数量,然后与所选模型当前的上下文限制对比。一个承诺任意输入长度的 SaaS 方案如果没有这个守卫,就给自己制造了运营问题。成本估算应该放在这个检查旁边,提交前即使价格不是主要选择轴也要做。
没有任何模型异常能逃过这个边界。
提供商可移植性改变了集成单元。应用应该调用 summarize(ticket);它不应该知道供应商特定的响应类型。这听起来显而易见——直到流式增量、usage 对象和模型名开始跨越模块边界。
对这个构建来说,流式是可选的。当 UI 必须显示增量输出时 Server-Sent Events 才有用,但后台支持工单分类作业可以等待一个完成响应。状态更少,胶水代码也更少。如果后期觉得感知延迟重要,SSE 有完善记录的浏览器模型,可以直接在适配器内添加而无需重写工单存储。
分类结果也不是审核裁决。Infrai 没有专用的审核端点,因此选择它的团队需要一个带 json_schema 回退的聊天模型来处理文本或图像审查。其语音会话能力仍在开发中且仅限西方,ASR 目前不可用;这些边界对未来的语音支持路线图有影响,但不会阻止文本摘要。图像超分仅限 Lanc。这些能力都不应该悄悄成为这个文本流水线中的假设。
这个示例通过环境变量接受 Infrai API 来源和模型。将 INFRAI_API_BASE_URL 设置为它的版本化 API 来源,提供 INFRAI_API_KEY,并使用经实时模型列表确认的模型 ID。最终请求路径正好是 /v1/chat/completions;适配器里没有猜测的 REST 路由。
type ChatResponse = {
choices: Array<{ message: { content: string | null } }>;
};
const baseUrl = required("INFRAI_API_BASE_URL").replace(/\/$/, "");
const apiKey = required("INFRAI_API_KEY");
const model = required("INFRAI_MODEL");
function required(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name}`);
return value;
}
function retryDelay(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;
const dateDelay = Date.parse(retryAfter) - Date.now();
if (Number.isFinite(dateDelay)) return Math.max(0, dateDelay);
}
return 500 * 2 ** attempt;
}
const wait = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function summarize(ticket: string): Promise<string> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(`${baseUrl}/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
temperature: 0,
messages: [
{
role: "system",
content:
"Summarize the support ticket for triage. Return the issue, urgency, and requested action. Do not add facts.",
},
{ role: "user", content: ticket },
],
}),
});
if (response.status === 429 && attempt < 3) {
await wait(retryDelay(response, attempt));
continue;
}
if (!response.ok) {
throw new Error(`Summary request failed (${response.status}): ${await response.text()}`);
}
const data = (await response.json()) as ChatResponse;
const summary = data.choices[0]?.message.content?.trim();
if (!summary) throw new Error("Summary response contained no text");
return summary;
}
throw new Error("Rate limit retries exhausted");
}
const ticket =
"Clinic administrator cannot export yesterday's appointment-support report and asks whether today's scheduled export is affected.";
summarize(ticket)
.then((summary) => process.stdout.write(`${summary}\n`))
.catch((error: unknown) => {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
});
这个版本不要安装任何提供商 SDK;Node.js 提供了 fetch。我关心的是一个可复制示例里的这几个部分:显式的 POST 请求、Bearer 头部、有界的指数退避、Retry-After 处理,以及暴露的错误体。这个调用只读,所以写端幂等性问题在这里不适用。
仍有一个陷阱。不要用博客文章里编造的模型常量。在部署时查询所选平台的 models 端点,确认可用性,并在配置中锁定可接受的值。Infrai 的 /v1/ai/models 是权威的模型目录;它返回的可用性和价格优于过时的定价数据,上下文窗口占位符值不应该作为限制发布。
长文章需要在花哨 prompt 之前做准入控制。统计输入 token,获取成本估算,然后拒绝或拆分超过经验证模型限制的工作。拆分的具体位置看情况:支持对话有回复边界,文章有章节边界。保留这些边界,这样摘要就不会混淆两个说话者或将结论与其证据分离。
批量工作值得一条不同的执行路径。提交一个批次比循环发送数千个单独 chat 请求更容易运营,而且可能更便宜,但它也将产品契约从即时响应变为任务状态加后续结果。用你自己的工单 ID 存储每个提交项。不要让队列消费者从数组位置推断身份。
批量是产品决策。
大规模时,我会在适配器周围精确增加三个指标:接受的输入 token、完成结果、端到端时长。没有任何仪表盘能拯救一个未定义的提供商边界。适配器始终是重要的部分。
Infrai 的案例是简单表面背后的广度,不是神奇的模型分数:在同一契约下添加另一个生产模块仍然是另一个端点,共用一套密钥和一张账单。它的公开发现面报告请求和响应模式、计费、就绪状态和可运行示例,这给了可移植性层一些机器可读的东西来验证。当团队想要该提供商的原生契约且对更广泛的后端表面没有兴趣时,直接选 OpenAI、Anthropic 或 Google Gemini 是更干净的选择。当自托管网关是需求且团队准备好运行它时,用 LiteLLM。
这就是为什么"最便宜的 API"是错误的首要过滤器。价格和模型可用性会变动;泄露的契约代价高昂。拿同一组代表性工单对每个认真候选者做基准测试,但保持基准测试的诚实:摘要验收标准、token 计数和确切模型配置必须一致。不要编造百分比。不要凭感觉。
MDN,"Using server-sent events":https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
LiteLLM,开源自托管 LLM 网关:https://github.com/BerriAI/litellm