/** * 模型路由配置断言。调用方可用于写入门禁或非阻断启动诊断。 * * 与运行期错误的分工:运行期只应出现**环境性**失败(上游 429/超时、key 全冷却), * 配置性失败必须在启动期一次性列清。判据是「这条不变量在启动时是否已经可判定」: * - adapter 是否在网关注册表里 —— 启动时可判定。 * - runtime 会调的 mode 是否配了链 —— 启动时可判定(mode 列表由装配层声明,是入口不变量)。 * 校验「表里已有的行是否自洽」不够:**空表天然自洽**。零行配置一路绿灯启动、第一个请求才抛 * `no chain configured for mode 'kp'`,正是这类校验漏掉入口不变量的后果。 */ /** * 文本能力接入的 adapter 必须是网关注册表里的键,否则该接入在运行期必然 unknown provider。 * @param {Array<{ name:string, adapter:string }>} providerRows enabled 的 model_provider_capability 文本能力行 * @param {string[]} adapters `registeredProviders()` 结果 * @returns {void} */ export function validateAdapters(providerRows, adapters) { const known = new Set(adapters) const bad = providerRows.filter((row) => !known.has(row.adapter)) if (bad.length === 0) return const detail = bad.map((row) => `${row.name}(adapter=${row.adapter})`).join(', ') throw new Error(`llm-router: 文本能力接入的 adapter 未注册: ${detail};可用适配器: ${adapters.join(', ')}`) } /** * runtime 实际会 `forMode(mode)` 的每个场景,必须在 `mode_model_config` 有 enabled 且链非空的行。 * requiredModes 由装配层声明(它是唯一知道 runtime 调哪些 mode 的地方),不从表里反推。 * @param {{ [mode: string]: Array<{ provider:string, model:string }> }} config normalizeConfig 产物 * @param {string[]} requiredModes runtime 会调用的场景名 * @returns {void} */ export function validateModes(config, requiredModes) { const missing = requiredModes.filter((mode) => !config[mode] || config[mode].length === 0) if (missing.length === 0) return const configured = Object.keys(config) throw new Error( `llm-router: runtime 需要的场景在 mode_model_config 无可用链: ${missing.join(', ')};` + `现有 enabled 场景: ${configured.length > 0 ? configured.join(', ') : '(空表)'}。` + '配置说明:apps/visual-game-server/tools/model-config/README.md。', ) }