validate.js 2.44 KB
/**
 * 模型路由配置断言。调用方可用于写入门禁或非阻断启动诊断。
 *
 * 与运行期错误的分工:运行期只应出现**环境性**失败(上游 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。',
  )
}