mirror of
https://git.openapi.site/https://github.com/desirecore/config-center.git
synced 2026-09-05 20:13:40 +08:00
feat: 下发智能路由模型目录 (#74)
This commit is contained in:
15
README.md
15
README.md
@@ -26,6 +26,7 @@ PR #1 曾把 reasoning 模型的 `defaultTemperature` / `defaultTopP` 写为 `nu
|
||||
| `provider.schema.json` | `compute/providers/*.json`、`compute/coding-plans/*.json` | `defaultTemperature`/`defaultTopP` 必须是 number,禁止 null/string;`additionalProperties: false` |
|
||||
| `manifest.schema.json` | `manifest.json` | `presetDataVersion` 必须是递增整数 |
|
||||
| `service-map.schema.json` | `compute/service-map.json` | 每条映射须含 `modelName` + `providerId` |
|
||||
| `smart-model-catalog.schema.json` | `compute/smart-routing/model-catalog.json` | 智能路由三档量级、接入面、exact model 能力和稳定优先级 |
|
||||
| `providers-index.schema.json` | 两个 `_index.json` | `order` 数组无重复 |
|
||||
| `pricing.schema.json` | `compute/pricing.json` | `markupRatio` / `usdToCny` 为正数 |
|
||||
|
||||
@@ -62,6 +63,17 @@ PR #1 曾把 reasoning 模型的 `defaultTemperature` / `defaultTopP` 写为 `nu
|
||||
|
||||
否则老客户端会因未知字段校验失败死锁。
|
||||
|
||||
### 智能路由目录的边界
|
||||
|
||||
`compute/smart-routing/model-catalog.json` 是路由器直接消费的、按接入面核对过的策略快照:
|
||||
|
||||
- `tier`、`routingPriority`、`eligibleForAgent` 和 `defaultReference` 属于路由策略;
|
||||
- `capabilities`、上下文和 reasoning 是该 `providerId + model` 接入面的可用能力快照;
|
||||
- 它不声明 API key、baseUrl、登录状态、用户额度或实时计价;`desirecore-cloud` 的连接与计费状态仍由登录后的 Provider 接口动态下发;
|
||||
- Codex、Claude 条目必须能在对应 `compute/providers/*.json` 中按 exact model 找到,测试会阻止已下线模型继续参与路由。
|
||||
|
||||
客户端把本文件作为可热更新主数据源,并保留同 Schema 的内置离线兜底。调整 Provider 或 model-spec 的能力事实时,应同步审阅本目录,避免路由快照漂移。
|
||||
|
||||
---
|
||||
|
||||
## 本地校验
|
||||
@@ -78,7 +90,7 @@ CI(GitHub Actions)会在每个 PR 自动运行 `validate` 和 `test`,不
|
||||
|
||||
## 数据修改流程
|
||||
|
||||
1. 编辑 `compute/providers/<name>.json`、`compute/coding-plans/<name>.json` 或 `compute/service-map.json`
|
||||
1. 编辑 `compute/providers/<name>.json`、`compute/coding-plans/<name>.json`、`compute/service-map.json` 或 `compute/smart-routing/model-catalog.json`
|
||||
2. 编辑 `compute/providers/_index.json` 或 `coding-plans/_index.json`(新增/删除 provider 时)
|
||||
3. **必须**递增 `manifest.json#presetDataVersion`,并更新 `updatedAt`
|
||||
4. `npm run validate` 本地确认通过
|
||||
@@ -95,3 +107,4 @@ CI(GitHub Actions)会在每个 PR 自动运行 `validate` 和 `test`,不
|
||||
- **构建期同步**:`npm run sync-config-center` 把数据复制到 desirecore 主仓 `lib/agent-service/defaults/`
|
||||
- **运行时同步**:客户端启动后后台 git fetch 本仓库,每 30 分钟检查一次远程更新
|
||||
- **版本比对**:`presetDataVersion`(递增整数)+ digest(SHA-256)双重校验
|
||||
- **智能路由目录**:新客户端按文件 mtime 热加载 `compute/smart-routing/model-catalog.json`;缺失或校验失败时使用随客户端发布的内置 JSON
|
||||
|
||||
@@ -85,6 +85,49 @@ describe('真实数据全量校验', () => {
|
||||
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
|
||||
})
|
||||
|
||||
it('智能路由模型目录应通过 smart-model-catalog schema', () => {
|
||||
const result = validateFile(
|
||||
join(ROOT, 'compute', 'smart-routing', 'model-catalog.json'),
|
||||
validators,
|
||||
)
|
||||
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
|
||||
|
||||
const catalog = JSON.parse(readFileSync(
|
||||
join(ROOT, 'compute', 'smart-routing', 'model-catalog.json'),
|
||||
'utf8',
|
||||
))
|
||||
assert.deepEqual(catalog.tiers.map((tier) => tier.id), [
|
||||
'flagship',
|
||||
'balanced',
|
||||
'lightweight',
|
||||
])
|
||||
assert.deepEqual(catalog.providers.map((provider) => provider.provider), [
|
||||
'openai-codex',
|
||||
'anthropic-claude',
|
||||
'desirecore-cloud',
|
||||
])
|
||||
assert.equal(
|
||||
catalog.providers.flatMap((provider) => provider.models).length,
|
||||
37,
|
||||
)
|
||||
|
||||
for (const provider of catalog.providers.filter((item) => item.provider !== 'desirecore-cloud')) {
|
||||
const providerFile = JSON.parse(readFileSync(
|
||||
join(ROOT, 'compute', 'providers', `${provider.provider}.json`),
|
||||
'utf8',
|
||||
))
|
||||
assert.equal(providerFile.id, provider.providerId)
|
||||
const publishedModels = new Set(providerFile.models.map((model) => model.modelName))
|
||||
for (const model of provider.models) {
|
||||
assert.equal(
|
||||
publishedModels.has(model.model),
|
||||
true,
|
||||
`${provider.providerId}/${model.model} 必须存在于对应接入面 Provider 清单`,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('两个 _index.json 应通过 providers-index schema', () => {
|
||||
const r1 = validateFile(join(ROOT, 'compute', 'providers', '_index.json'), validators)
|
||||
const r2 = validateFile(join(ROOT, 'compute', 'coding-plans', '_index.json'), validators)
|
||||
@@ -304,6 +347,28 @@ describe('真实数据全量校验', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('智能路由模型目录 schema 反例', () => {
|
||||
const validate = compile('smart-model-catalog')
|
||||
|
||||
it('拒绝未声明的策略字段,避免新旧客户端静默分叉', () => {
|
||||
const data = JSON.parse(readFileSync(
|
||||
join(ROOT, 'compute', 'smart-routing', 'model-catalog.json'),
|
||||
'utf8',
|
||||
))
|
||||
data.providers[0].models[0].unknownRoutingPolicy = true
|
||||
assert.equal(validate(data), false)
|
||||
})
|
||||
|
||||
it('拒绝当前客户端未声明支持的目录版本', () => {
|
||||
const data = JSON.parse(readFileSync(
|
||||
join(ROOT, 'compute', 'smart-routing', 'model-catalog.json'),
|
||||
'utf8',
|
||||
))
|
||||
data.version = 2
|
||||
assert.equal(validate(data), false)
|
||||
})
|
||||
})
|
||||
|
||||
// ==================== Runtime 清单反例 ====================
|
||||
|
||||
describe('runtime-recommended schema 反例', () => {
|
||||
|
||||
1099
compute/smart-routing/model-catalog.json
Normal file
1099
compute/smart-routing/model-catalog.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"presetDataVersion": 87,
|
||||
"updatedAt": "2026-08-08",
|
||||
"presetDataVersion": 88,
|
||||
"updatedAt": "2026-08-10",
|
||||
"description": "DesireCore 官方配置中心"
|
||||
}
|
||||
|
||||
273
schemas/smart-model-catalog.schema.json
Normal file
273
schemas/smart-model-catalog.schema.json
Normal file
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://desirecore.net/schemas/smart-model-catalog.json",
|
||||
"title": "SmartModelCatalog",
|
||||
"description": "智能路由量级、Provider 和模型能力目录;Config Center、内置兜底与未来接口共用此契约。",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"version",
|
||||
"updatedAt",
|
||||
"source",
|
||||
"tiers",
|
||||
"providers"
|
||||
],
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "本数据对应的 JSON Schema 标识。"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 1,
|
||||
"description": "目录结构版本;当前客户端只接受 v1,不兼容的新语义必须递增版本并由新客户端接入。"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "本目录最近一次人工/自动核对时间。"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"builtin",
|
||||
"config-center",
|
||||
"provider-api"
|
||||
],
|
||||
"description": "目录来源:config-center=配置中心下发,builtin=客户端离线兜底,provider-api=未来接口实时数据。"
|
||||
},
|
||||
"tiers": {
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"label",
|
||||
"labelEn",
|
||||
"description",
|
||||
"defaultReasoning"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"flagship",
|
||||
"balanced",
|
||||
"lightweight"
|
||||
],
|
||||
"description": "智能路由量级:flagship=最高能力,balanced=质量与成本平衡,lightweight=最快最省。"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "中文量级标签。"
|
||||
},
|
||||
"labelEn": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "英文量级标签。"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "该量级的任务选择目标。"
|
||||
},
|
||||
"defaultReasoning": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auto",
|
||||
"off",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"max"
|
||||
],
|
||||
"description": "该量级在模型支持时优先采用的默认思考深度。"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"description": "三档量级的自描述定义。"
|
||||
},
|
||||
"providers": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"providerId",
|
||||
"provider",
|
||||
"displayName",
|
||||
"models"
|
||||
],
|
||||
"properties": {
|
||||
"providerId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200,
|
||||
"description": "compute.json 中的 Provider 唯一 ID。"
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 120,
|
||||
"description": "Provider 协议标识。"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200,
|
||||
"description": "Provider 展示名称。"
|
||||
},
|
||||
"models": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model",
|
||||
"displayName",
|
||||
"tier",
|
||||
"routingPriority",
|
||||
"serviceTypes",
|
||||
"capabilities",
|
||||
"contextWindow",
|
||||
"maxOutputTokens",
|
||||
"supportedReasoning",
|
||||
"defaultReasoning",
|
||||
"eligibleForAgent",
|
||||
"defaultReference"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 300,
|
||||
"description": "传给对应 Provider 的真实 modelName;禁止使用 smart 等虚拟名称。"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 200,
|
||||
"description": "客户端展示名称。"
|
||||
},
|
||||
"tier": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"flagship",
|
||||
"balanced",
|
||||
"lightweight"
|
||||
],
|
||||
"description": "此模型在智能路由中的能力量级。"
|
||||
},
|
||||
"routingPriority": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 10000,
|
||||
"description": "同一量级内的稳定选择顺序,数值越小越优先;凭据与硬能力校验仍先于此排序。"
|
||||
},
|
||||
"serviceTypes": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 16,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 64,
|
||||
"pattern": "^[a-z0-9_]+$",
|
||||
"description": "可用于任务硬约束匹配的规范能力标签,例如 vision、code、tool_use、long_context。"
|
||||
},
|
||||
"description": "模型服务类型快照,例如 chat、reasoning、image_gen、video_gen。"
|
||||
},
|
||||
"capabilities": {
|
||||
"type": "array",
|
||||
"maxItems": 64,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 64,
|
||||
"pattern": "^[a-z0-9_]+$",
|
||||
"description": "可用于任务硬约束匹配的规范能力标签,例如 vision、code、tool_use、long_context。"
|
||||
},
|
||||
"description": "模型能力快照;任务 requiredCapabilities 必须全部命中。"
|
||||
},
|
||||
"contextWindow": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 1,
|
||||
"maximum": 10000000,
|
||||
"description": "上下文窗口 token 数;非文本模型或未知时为 null。"
|
||||
},
|
||||
"maxOutputTokens": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 1,
|
||||
"maximum": 1000000,
|
||||
"description": "最大输出 token 数;未知或不适用时为 null。"
|
||||
},
|
||||
"supportedReasoning": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 8,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auto",
|
||||
"off",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"max"
|
||||
],
|
||||
"description": "本次 Run 或当前 assignment 的思考深度;最终值仍受目标 Provider × Model 能力矩阵约束。"
|
||||
},
|
||||
"description": "该 Provider × Model 接入面声明的可用 reasoning 档位。"
|
||||
},
|
||||
"defaultReasoning": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auto",
|
||||
"off",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"max"
|
||||
],
|
||||
"description": "未显式指定时的模型参考 reasoning;路由还会结合量级默认值并做能力协商。"
|
||||
},
|
||||
"eligibleForAgent": {
|
||||
"type": "boolean",
|
||||
"description": "是否可作为 Agent 主模型。图像/视频生成、ASR/TTS 等专用模型为 false。"
|
||||
},
|
||||
"defaultReference": {
|
||||
"type": "boolean",
|
||||
"description": "是否为 Provider/defaultServiceMap 给出的默认参考。它只影响同级排序,不是强制答案。"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"description": "该接入面当前已核对的模型目录。"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"description": "参与智能路由和能力匹配的 Provider 目录。"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -46,6 +46,7 @@ function pickSchemaKey(absPath) {
|
||||
if (rel === 'manifest.json') return 'manifest'
|
||||
if (rel === 'compute/pricing.json') return 'pricing'
|
||||
if (rel === 'compute/service-map.json') return 'service-map'
|
||||
if (rel === 'compute/smart-routing/model-catalog.json') return 'smart-model-catalog'
|
||||
if (rel === 'compute/providers/_index.json') return 'providers-index'
|
||||
if (rel === 'runtimes/recommended.json') return 'runtime-recommended'
|
||||
if (rel === 'runtimes/versions-fallback.json') return 'runtime-versions-fallback'
|
||||
|
||||
Reference in New Issue
Block a user