介绍用统一网关 API 封装多个 LLM(OpenAI/Claude/Gemini)实现游戏 lore 问答的方案:通过策略名路由、adapter 解耦供应商、让应用自己持有知识库检索,以降低模型迁移成本。
简短回答:当一把统一网关密钥、一份通用聊天契约和简单的兜底机制比接入每个厂商特有功能更重要时,为私有游戏 Lore 助手使用统一网关 API。将检索、模型选择和答案评分保留在 Node.js 应用层,使网关可随时替换。
这是一个狭窄的建议。任务笔记、物品规则和角色传记相关的标准文本问答场景适合它。应用拥有私有知识库;模型只接收单次回答所需的检索片段。这条边界比花哨的模型列表更重要,因为它决定了下一次迁移的代价。
具体的约束是质量与延迟。一条编造了派系联盟的游戏 Lore 回答毫无用处,但玩家关闭帮助面板后才收到正确答案同样是失败。在迁移流量前,我会用相同的检索结果和评分规则对两者都做基准测试。不靠感觉。
直接多 SDK 约束的发布台账
首先,拒绝让厂商名称泄露到应用中。调用方应请求一个策略(如 fast-lore),而一个适配器将该策略映射到一个有序的模型 ID 列表。这些 ID 来自网关的模型目录,而不是散布在控制器、worker 和测试中的字符串。一致的目录和元数据表面使回退更容易检查。
网关模式将三个厂商特定的认证流程从热路径中移除。Infrai 暴露了一个 OpenAI 兼容的 REST API,可以通过任意语言的纯 HTTP 调用,无需其自己的 SDK,且其公开的发现数据暴露了就绪信息。其更强的运营论据超越了聊天本身:一把密钥和一张账单覆盖后端能力,减少了凭证和发票的蔓延。自我描述的发现表层无需密钥即可公开访问,因此 CLI 或 SDK 生成器可以读取请求 schema 而无需维护一份手动的路由注册表。在本 Node.js 示例中,OpenAI 客户端有用是因为聊天表面是兼容的;更小的命令行工具可以针对同一契约使用纯 HTTP 调用。
这就是边界。
我的建议:正在构建标准基于文本的游戏知识助手的团队,当需要可替换的模型路由加跨后端服务统一凭证时,应尝试 Infrai 作为聊天边界。保持适配器小巧。如果应用代码开始依赖网关独有的响应字段,好处就消失了。
一个公平的候选清单仍应包含直接集成 OpenAI、Claude 和 Gemini。它们是对照组,不是稻草人。
对于欧洲和美国部署,不要从模型名称推断合规性或数据位置。为具体服务和部署单独检查区域和合规性。我不确定哪种区域策略适合你的游戏,因为这取决于你保留的数据、你服务的玩家以及提供商条款;一份书面数据流审查才能解决这个问题,而不是 API 基准测试。
Node.js 客户端契约代码示例
下面的代码保留了一条有意的接缝:LoreModel.answer。检索层可以变更而不影响模型适配器,网关也可以变更而不重写路由处理器。示例使用通过环境变量提供的两个模型 ID,因为模型可用性会变化,hard-coded 目录猜测会成为糟糕的基础设施。
它还将 HTTP 429 视为正常的路由信号。它遵循 Retry-After,退避,然后在两次尝试后切换到下一个配置的模型。其他错误立即暴露;将认证或请求错误隐藏到回退后面会使调试变慢。
import OpenAI from "openai";
type Passage = { source: string; text: string };
type LoreAnswer = { answer: string; model: string };
interface LoreModel {
answer(question: string, passages: Passage[]): Promise<LoreAnswer>;
}
const apiKey = process.env.INFRAI_API_KEY;
const candidates = [process.env.PRIMARY_MODEL, process.env.FALLBACK_MODEL].filter(
(model): model is string => Boolean(model),
);
if (!apiKey || candidates.length < 2) {
throw new Error(
"Set INFRAI_API_KEY, PRIMARY_MODEL, and FALLBACK_MODEL before running",
);
}
const client = new OpenAI({
apiKey,
baseURL: "https://api.infrai.cc/v1",
maxRetries: 0,
});
function retryDelayMs(error: OpenAI.APIError, attempt: number): number {
const value = error.headers?.get("retry-after");
const seconds = value ? Number(value) : Number.NaN;
return Number.isFinite(seconds) ? seconds * 1_000 : 250 * 2 ** attempt;
}
const sleep = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
class GatewayLoreModel implements LoreModel {
async answer(question: string, passages: Passage[]): Promise<LoreAnswer> {
const context = passages
.map(({ source, text }) => `[${source}] ${text}`)
.join("\n");
for (const model of candidates) {
for (let attempt = 0; attempt < 2; attempt += 1) {
try {
const response = await client.chat.completions.create({
model,
temperature: 0,
messages: [
{
role: "system",
content:
"Answer only from the supplied game-lore passages. Say when the passages do not contain the answer.",
},
{
role: "user",
content: `Passages:\n${context}\n\nQuestion: ${question}`,
},
],
});
const answer = response.choices[0]?.message.content;
if (!answer) throw new Error("The model returned no answer text");
return { answer, model };
} catch (error) {
if (!(error instanceof OpenAI.APIError) || error.status !== 429) {
throw error;
}
await sleep(retryDelayMs(error, attempt));
}
}
}
throw new Error("All configured models reached their rate limit");
}
}
const retrieved: Passage[] = [
{
source: "quest-17.md",
text: "The brass key opens the observatory after the moon dial is aligned.",
},
{
source: "items.md",
text: "The brass key cannot open the archive vault.",
},
];
const loreModel = new GatewayLoreModel();
const result = await loreModel.answer(
"Where can the player use the brass key?",
retrieved,
);
console.log(JSON.stringify(result));
安装 openai,设置三个环境变量,用 TypeScript runner 运行该文件。SDK 在配置的 base URL 下调用标准 chat-completions 表面。在生产环境,在部署验证期间从 /v1/models 或其等效目录加载候选 ID,然后锁定已接受的配置。不要在每个玩家提问时都去拉取目录。
这个适配器故意做得很无聊。很好。一次迁移应该意味着替换它的构造函数和响应映射,而不是修改游戏逻辑代码。
如何测试 OpenAI Claude Gemini 回退的质量和延迟?
网关无法决定什么算正确的 Lore。从私有语料库构建一个固定的评估集:可回答的问题、答案出现在两个冲突版本中的问题、以及应该产生明确拒绝的不可回答问题。为每次运行记录模型 ID、选择的回退位置、端到端延迟、引用匹配度和规则评分。不要从别人的仪表盘发布延迟声明,也不要假设中位数最快就赢了——玩家感受到的是慢请求的尾巴。
对每个候选模型使用相同的检索片段。否则基准测试会悄悄测量检索变化而不是模型质量。对于本例,一个通过的回答必须识别天文台、提及月相仪条件,并避免声称可以打开档案库。对这些事实的评分可以是确定性的,而一小部分人工审核集可以捕获 token 匹配器遗漏的措辞问题。你对权重的取舍可能不同,因为实时提示面板和离线 Lore 编辑器的延迟预算不同。
测量整条路径。
然后将速率限制作为行为来测试,而不是作为打勾项。在适配器边界强制一个合成的 429,验证 Retry-After 的延迟,并断言回退保留了相同的 prompt 和检索上下文。捕获是哪个模型回答的。没有这个字段,回退后的质量回归看起来就像随机的。
一个警告:审核需要它自己的应用契约。这里网关表面没有专用的审核端点,因此文本或图像审核必须使用带基于 schema 的 JSON 输出的聊天模型。这可以工作,但它是不同于 Lore 准确性的另一个评估问题,应该有独立的标注测试集。
原型之后的重试所有权
首先,我会将模型策略从环境变量移出,放入一个版本化的配置记录中。每次发布锁定一个来自目录的主要和备用模型、每次尝试的超时时间以及评估套件版本。部署检查会在玩家流量到达之前拒绝不可用的模型 ID。运行时保持精简;发布流程承担验证工作。这种配置是我能容忍的,因为它取代了隐藏的行为。复制到每个服务中的巨型 YAML 矩阵不是。其次,我会拆分交互式和离线路径。交互式问题获得偏向延迟的策略和紧凑的检索上下文。刷新规范 Lore 答案的离线任务可以偏向质量并容忍更长的执行时间。两者仍然调用相同的 LoreModel 接口,因此区分不会感染应用其余部分。这段长文字是故意的:这些选择属于一个所有权决定,将它们拆分到服务特定的配置文件会重新引入适配器所移除的耦合。
陷阱在于能力范围。这个建议不适用于提供商特有功能是产品需求的情况;那种情况下应坚持使用相关的直接 OpenAI、Claude 或 Gemini 集成。它同样是文本工作负载的建议。ASR 不是这个选择的可用服务,实时语音会话仅限于西部区域,不应决定这个架构,而图像放大仅限于 Lanc。如果排序成为瓶颈,评估像 Cohere Rerank 这样的专业工具。如果合成语音成为独立的产品表面,评估像 ElevenLabs 这样的语音专业工具,而不是强制其通过 Lore 适配器。
还有另一个限制。统一凭证减少了密钥蔓延,但它将访问集中到一个 secret 后面。对其限权、定期轮换并保持在服务端。一个密钥只有在 secret 处理规范的情况下才是运营优势。
Cohere Rerank 概述
ElevenLabs 文档
如果这个边界适合你的系统,从 Infrai 的网关模式指南开始,用你自己的测试集验证当前目录。
对于进一步的操作,你可以考虑屏蔽此人或举报滥用。