很多模型服务都提供与 OpenAI Chat Completions 相近的请求格式:发送 model 和 messages,再从 choices[0].message.content 读取结果。接口外形相似,不代表所有配置可以混在一起。
最常见的接入错误不是提示词写得不好,而是把供应商名称、接口地址、模型名和密钥的职责弄混:有人把完整 /chat/completions 地址当成 SDK 的 base URL,有人更换了 provider 却继续使用旧密钥,还有人只测试 HTTP 200,没有检查响应中是否真的有正文。
本文只解决一件事:建立一套可管理、可验证、不会把密钥暴露给前端的多模型配置。模型轮询、自动切换与熔断属于运行策略,可另见Node.js 多模型重试、轮询与熔断。
一、四个字段分别负责什么
一条模型配置至少包含以下字段:
{
"id": "glm-flash-production",
"provider": "zhipu",
"name": "智谱 GLM Flash",
"baseUrl": "https://open.bigmodel.cn/api/paas/v4",
"model": "请填写控制台当前提供的准确模型标识",
"enabled": true,
"apiKeyRef": "MODEL_KEY_GLM_FLASH"
}
provider是供应商标识,用于后台筛选、显示和应用供应商差异,不应直接代替模型名。baseUrl是接口根地址,或者在自建实现中明确约定为完整端点。整个系统必须统一一种约定。model是请求体传给上游的精确模型标识,不能使用后台展示名称代替。apiKeyRef指向服务端密钥,不保存密钥明文。
name 只是给管理员看的名称。即使两条配置使用相同模型,也可以分别命名为“生产总结”和“内容分类”,但它们应拥有不同的稳定 id。
二、先统一 baseURL 约定
使用原生 fetch 时,可以把配置约定为 API 根地址,然后由代码拼接固定路径:
function normalizeBaseUrl(value) {
const url = new URL(String(value).trim());
if (url.protocol !== 'https:' && url.hostname !== '127.0.0.1' && url.hostname !== 'localhost') {
throw new Error('公网模型接口必须使用 HTTPS');
}
return url.toString().replace(/\/$/, '');
}
function chatCompletionsUrl(baseUrl) {
const normalized = normalizeBaseUrl(baseUrl);
return normalized.endsWith('/chat/completions')
? normalized
: `${normalized}/chat/completions`;
}
按照这一约定,智谱开放平台常见的 Chat Completions 完整地址是:
https://open.bigmodel.cn/api/paas/v4/chat/completions
因此配置根地址时填写:
https://open.bigmodel.cn/api/paas/v4
如果现有系统的 baseUrl 字段明确要求完整端点,就应填写包含 /chat/completions 的地址,并确保调用层不再重复拼接。关键不在字段名字,而在全站只有一种明确约定。
还要注意,部分 OpenAI SDK 的 baseURL 可能要求 API 根路径,第三方 SDK 或代理又可能有不同规则。复制配置前必须查看当前所用客户端的文档,不能只看浏览器中能否打开 URL。
三、配置与密钥必须分开保存
非敏感信息可以进入普通配置文件:
{
"models": [
{
"id": "glm-flash-production",
"provider": "zhipu",
"baseUrl": "https://open.bigmodel.cn/api/paas/v4",
"model": "控制台中的准确模型标识",
"apiKeyRef": "MODEL_KEY_GLM_FLASH",
"enabled": true
}
]
}
密钥则只存在服务端环境变量、加密配置或专用密钥管理系统中:
function resolveApiKey(modelConfig) {
const key = process.env[modelConfig.apiKeyRef];
if (!key) throw new Error(`缺少模型密钥:${modelConfig.apiKeyRef}`);
return key;
}
不要把 API Key 写进 admin.js、HTML、公开 JSON、日志、错误消息或 Git 仓库。管理后台读取配置时,只返回 hasKey: true 和脱敏提示,不返回可逆的密钥内容:
function toPublicModel(model, secrets) {
return {
id: model.id,
name: model.name,
provider: model.provider,
baseUrl: model.baseUrl,
model: model.model,
enabled: model.enabled,
hasKey: Boolean(secrets[model.id])
};
}
编辑配置时,空白密钥应表示“保留原密钥”,而不是覆盖为空。只有管理员明确点击“替换密钥”或“删除密钥”时才修改秘密存储。日志应记录配置 ID 和操作人,不应记录密钥值。
四、建立一层统一配置解析器
调用业务不应到处判断 provider === 'zhipu'。先把保存格式转换为统一的运行时配置:
function buildRuntimeConfig(saved, secretStore) {
if (!saved.id || !saved.provider || !saved.model) {
throw new Error('模型配置缺少 id、provider 或 model');
}
return {
id: saved.id,
provider: saved.provider,
endpoint: chatCompletionsUrl(saved.baseUrl),
model: saved.model,
apiKey: secretStore.get(saved.id),
headers: { 'Content-Type': 'application/json' }
};
}
供应商确实需要额外请求头或参数时,可以在适配器层声明白名单,而不是允许管理员保存任意 JavaScript:
const PROVIDER_ADAPTERS = {
openai: config => config,
zhipu: config => config,
internal: config => ({
...config,
headers: { ...config.headers, 'X-Client': 'ai-daily' }
})
};
配置抽象的目标不是假设所有供应商完全相同,而是把共同字段统一,把少量差异限制在可审计的位置。
如果你是在 Agent Harness 中配置公司网关,可参考DeepSeek Harness 的自定义 Provider 与兼容参数实测。需要特别核对 developer 角色、max_tokens 与 max_completion_tokens 等请求形状差异,不能只确认地址和密钥有效。
五、“测试模型”要测试什么
保存成功只说明 JSON 能写入,不能说明模型可用。后台应提供独立的“测试连接”操作,发送一个最小请求:
async function testModel(config) {
const startedAt = Date.now();
const response = await fetch(config.endpoint, {
method: 'POST',
headers: {
...config.headers,
Authorization: `Bearer ${config.apiKey}`
},
body: JSON.stringify({
model: config.model,
messages: [{ role: 'user', content: '只回复:连接成功' }],
temperature: 0
}),
signal: AbortSignal.timeout(30_000)
});
const raw = await response.text();
let payload;
try {
payload = JSON.parse(raw);
} catch {
throw new Error(`上游没有返回 JSON,HTTP ${response.status}`);
}
if (!response.ok) {
const message = payload?.error?.message || `HTTP ${response.status}`;
throw new Error(`模型测试失败:${message}`);
}
const content = payload?.choices?.[0]?.message?.content;
if (typeof content !== 'string' || !content.trim()) {
throw new Error('请求成功,但响应中没有 choices[0].message.content');
}
return {
ok: true,
model: config.model,
elapsedMs: Date.now() - startedAt,
preview: content.slice(0, 80)
};
}
测试结果至少应显示:当前配置名称、provider、模型名、测试时间、HTTP 状态、是否解析成功和安全截断后的回复预览。不要显示请求头或完整响应,因为上游错误或代理信息可能包含不宜公开的数据。
这次测试只能证明“该配置在这个时刻完成了一次最小调用”,不能证明长期稳定,也不能替代真实总结任务的结构化输出测试。GLM 的基础调用可继续参考GLM-4.7-Flash Node.js 接入指南,需要 JSON 输出时参考GLM 结构化 JSON 输出与解析。
六、按错误阶段给管理员明确提示
不同错误应分别展示,避免所有失败都变成“模型不可用”:
| 阶段 | 常见现象 | 优先检查 |
|---|---|---|
| 配置校验 | URL 无法解析、字段为空 | baseURL 格式、model、provider |
| DNS/TLS | 无法建立连接、证书错误 | 域名、网络出口、HTTPS 证书 |
| 鉴权 | HTTP 401 或 403 | API Key 是否属于该供应商、权限与余额状态 |
| 请求参数 | HTTP 400、模型不存在 | 精确模型标识、请求体字段 |
| 响应解析 | HTTP 成功但没有正文 | 供应商响应结构、代理改写、流式与非流式模式 |
不要把密钥末尾几位、完整上游响应或堆栈直接返回浏览器。服务端可以保存经过清洗的诊断信息,并为每次测试分配请求 ID,方便在日志中定位。
七、配置更新需要版本和审计记录
模型名或 baseURL 改动后,同一段内容可能得到不同结果。建议为每次配置保存版本:
{
"configId": "glm-flash-production",
"configVersion": 3,
"provider": "zhipu",
"model": "控制台中的准确模型标识",
"updatedAt": "2026-08-13T04:00:00.000Z",
"updatedBy": "admin"
}
文章总结记录只保存 configId、configVersion、实际模型名和执行时间,不保存 API Key。这样管理员能回答“这篇文章由哪个配置生成”,又不会把秘密复制到业务数据中。
删除配置前,应检查它是否仍被定时任务或内容流程引用。历史记录可以保留配置 ID 与非敏感快照;秘密则按权限和保留政策撤销或删除。
八、上线前检查清单
provider、baseUrl、model和展示名称职责分离;- 系统明确约定 baseURL 是根地址还是完整端点;
- 公网接口只允许 HTTPS,并限制可配置协议;
- API Key 只在服务端解析,不进入前端和日志;
- 管理后台只返回
hasKey,空白输入不会清除旧密钥; - 保存后可以独立执行最小连接测试;
- 测试会校验 HTTP 状态、JSON 格式和正文路径;
- 每次调用记录配置 ID、版本、模型名和时间;
- 更换供应商时同时核对 URL、模型名和对应密钥;
- 正式用于内容流程前,再用真实输入验证输出格式。
FAQ
https://open.bigmodel.cn/api/paas/v4/chat/completions 配置正确吗?
它是常见的 Chat Completions 完整端点。如果你的字段保存完整请求地址,可以直接配置;如果调用代码会自动追加 /chat/completions,应把 baseURL 配置为 https://open.bigmodel.cn/api/paas/v4。最终以智谱当前官方文档和实际客户端约定为准。
provider 应该填写 zhipu 还是 glm-4.7-flash?
provider 应表示供应商,例如 zhipu;精确模型标识放在 model 字段。展示给管理员的中文名称则放在 name。
可以在浏览器里直接测试 API Key 吗?
不建议。浏览器请求会让密钥进入前端运行环境和开发者工具。测试操作应由已鉴权的管理后台请求自己的服务端,再由服务端调用模型。
为什么 HTTP 200 仍然判定测试失败?
代理可能返回非 JSON 页面,供应商也可能返回与预期不同的结构。测试需要同时验证状态码、JSON 解析和 choices[0].message.content。
多个模型可以共用一个 API Key 吗?
只有供应商明确允许且权限范围合适时才可以。配置层不应默认所有模型都能共用密钥;更不能把一个供应商的密钥用于另一个供应商。
加密保存密钥后就绝对安全吗?
不是。还需要保护主密钥、限制后台权限、避免日志泄漏、建立轮换与撤销流程,并记录谁修改过配置。可参考 OWASP Secrets Management 指南。
延伸阅读
配置完成后,可以在AI 内容工程专题查看抓取、总结、分类和发布的完整流程。若需要把抓取与总结分开执行,阅读Node.js 抓取与 AI 总结拆分实践。
配置之后还要保护密钥
统一模型配置只解决字段和调用方式,密钥仍需独立管理。服务端加密、轮换和泄露处置见 大模型 API Key 安全,完整生产链路可从 大模型 API 生产运维专题 开始。
如果你的目标是在 AI 编程代理中使用同一套 Provider、baseURL 和模型名,可继续阅读 OpenCode 中文教程:安装、GLM / OpenAI 兼容模型配置与真实任务实测。该文展示 OpenCode 1.18.18 的实际配置解析、权限边界和成功/失败任务记录。