Files
config-center/schemas/provider.schema.json
Yige 5171219a13 feat(compute): 声明 Anthropic 模型拒绝的采样参数与强制工具选择 (#103)
Claude Opus 4.7+ / Opus 5 / Fable 携带 temperature / top_p / top_k 会被官方 400
(Sonnet 5 非默认值 400),Claude Fable 5.1 对 tool_choice any / tool 回 400。客户端
改为纯按声明在出站删除采样参数、把强制工具选择降级为 auto(desirecore 主仓配套 PR)。

- providers/anthropic.json:claude-fable-5-1 增加 forcedToolChoiceUnsupported
- model-specs/anthropic.json:fable-5-1 / opus-5 / opus-4-8 / opus-4-7 / sonnet-5 声明
  samplingParametersDeprecated,fable-5-1 声明 forcedToolChoiceUnsupported,供精确命中
  补全的动态 Provider 使用;opus-4-7 的 defaultTemperature 由 1 改为 null(不支持温度调节)
- schemas:provider model extra 与 ModelSpec spec.extra 声明两个布尔字段(含 description)
- 测试:已知拒绝代际必须声明(单向)、布尔类型校验
- presetDataVersion 116 → 117
2026-09-13 22:56:51 -04:00

348 lines
20 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://desirecore.net/schemas/config-center/provider.schema.json",
"title": "Provider",
"description": "frozen baseline schema 镜像 desirecore d185299(fix #471 之前)的 computeProviderSchema/providerModelSchema,作为已发布客户端兼容契约。任何写入 compute/providers/*.json 或 compute/coding-plans/*.json 的数据都必须通过此校验,否则会破坏老客户端(已发布版本的 schema 无法理解超出此契约的字段或类型)。\n\n关键约束:\n- defaultTemperature / defaultTopP 必须是 number(不接受 null)—— 历史教训:曾因 null 写入导致老客户端校验失败死锁\n- 顶层与 model 内 additionalProperties 均为 false —— 新增字段必须先升级老客户端 schema 再推送数据",
"type": "object",
"required": [
"id",
"provider",
"label",
"baseUrl",
"apiKeyRef",
"apiKeyVerified",
"enabled",
"status",
"services",
"models"
],
"properties": {
"id": {
"type": "string",
"description": "提供商唯一标识符(如 provider-openai-001)",
"minLength": 1
},
"provider": {
"type": "string",
"description": "提供商标识:openai、anthropic、deepseek、dashscope(阿里云)等",
"minLength": 1
},
"label": {
"type": "string",
"description": "提供商显示名称(如 OpenAI、Anthropic、阿里云 DashScope)",
"minLength": 1
},
"baseUrl": {
"type": "string",
"description": "API 基础 URL(如 https://api.openai.com/v1)",
"minLength": 1
},
"apiFormat": {
"type": "string",
"description": "API 协议格式:openai-completions、anthropic-messages、openai-responses、google-generative-ai 等"
},
"mediaBaseUrl": {
"type": "string",
"description": "媒体 API 基础 URL。当 provider 使用 Anthropic 格式(baseUrl 指向 /anthropic 端点)但其媒体 API(图片/语音/视频/音乐)仍走 OpenAI /v1 端点时使用"
},
"apiKeyRef": {
"type": "string",
"description": "密钥引用名,对应 secrets.json 中的 key;空字符串表示未配置密钥"
},
"apiKeyVerified": {
"type": "boolean",
"description": "API Key 是否已通过验证"
},
"enabled": {
"type": "boolean",
"description": "是否启用此提供商"
},
"status": {
"type": "string",
"enum": ["configured", "unconfigured", "error"],
"description": "提供商状态"
},
"services": {
"type": "array",
"description": "支持的服务类型列表(如 chat、reasoning、vision、embedding)",
"items": { "type": "string" }
},
"priceCurrency": {
"type": "string",
"enum": ["USD", "CNY"],
"description": "价格货币单位。models 中的 inputPrice/outputPrice 均以此货币计价"
},
"accessMode": {
"type": "string",
"enum": ["api", "coding-plan", "token-plan"],
"description": "接入模式:api(按量付费)、coding-plan(编程订阅套餐)或 token-plan(Token 团队版订阅)"
},
"brandGroup": {
"type": "string",
"description": "品牌分组标识,UI 按此字段排序"
},
"credentialSource": {
"type": "string",
"enum": ["codex-cli", "claude-oauth", "github-copilot"],
"description": "凭证托管来源。codex-cli:密钥由客户端本地 Codex CLI 凭证检测器托管(读取 ~/.codex/auth.json 自动刷新写回,fresh token 同步 secrets.json)。claude-oauth:Claude 订阅接入,由客户端 claude-auth 检测器托管——首选复用本机 Claude Code 登录(Agent SDK 自读,不写不刷新),或应用内 OAuth / setup-token 兜底;模型调用经 compat-proxy 内 claude-agent-sdk 后端履约,不直连。github-copilot:GitHub Copilot 订阅接入,由客户端 copilot-auth 检测器托管——GitHub Device Flow 登录,长期 GitHub OAuth token 存 config/copilot-oauth.json,定期交换约 30 分钟有效的 Copilot session token 写入 secrets.json;模型调用携带 Copilot 协议 headers 直连交换响应 endpoints.api 指定域名,模型列表由客户端在登录后经 /models 动态发现(可用集合随订阅档位与组织策略变化,故预设 models 为空)。需 requiredClientVersion ≥ 引入此字段的客户端版本——老客户端不识别本字段,必须先发版铺开 compute.json 韧性(容忍未知字段)再推送本数据"
},
"codingPlan": {
"type": "object",
"description": "Coding Plan / Token Plan 专属配置(仅当 accessMode = coding-plan 或 token-plan 时有效)",
"properties": {
"planTier": { "type": "string" },
"planLabel": { "type": "string" },
"quotas": {
"type": "object",
"properties": {
"per5h": { "type": "number", "minimum": 0 },
"perWeek": { "type": "number", "minimum": 0 },
"perMonth": { "type": "number", "minimum": 0 },
"per7d": { "type": "number", "minimum": 0 }
},
"additionalProperties": false
},
"usageTracking": {
"type": "object",
"properties": {
"method": {
"type": "string",
"enum": ["rest-api", "response-header", "manual", "credits", "none"]
},
"endpoint": { "type": "string" },
"headerKeys": {
"type": "object",
"properties": {
"remaining": { "type": "string" },
"limit": { "type": "string" },
"reset": { "type": "string" }
},
"additionalProperties": false
},
"consoleUrl": { "type": "string" }
},
"additionalProperties": false
},
"modelIdOverride": { "type": "string" },
"maxConcurrent": { "type": "number", "minimum": 1 },
"expiresAt": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
},
"tokenPlan": {
"type": "object",
"description": "Token Plan 团队版专属配置(仅当 accessMode = token-plan 时有效)",
"properties": {
"planTier": { "type": "string" },
"planLabel": { "type": "string" },
"quotas": {
"type": "object",
"properties": {
"per5h": { "type": "number", "minimum": 0 },
"perWeek": { "type": "number", "minimum": 0 },
"perMonth": { "type": "number", "minimum": 0 },
"per7d": { "type": "number", "minimum": 0 }
},
"additionalProperties": false
},
"usageTracking": {
"type": "object",
"properties": {
"method": {
"type": "string",
"enum": ["rest-api", "response-header", "manual", "credits", "none"]
},
"endpoint": { "type": "string" },
"headerKeys": {
"type": "object",
"properties": {
"remaining": { "type": "string" },
"limit": { "type": "string" },
"reset": { "type": "string" }
},
"additionalProperties": false
},
"consoleUrl": { "type": "string" }
},
"additionalProperties": false
},
"modelIdOverride": { "type": "string" },
"maxConcurrent": { "type": "number", "minimum": 1 },
"expiresAt": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
},
"models": {
"type": "array",
"description": "此接入面当前可调用的模型列表。可用性、凭据、价格与 API 映射属于 Provider;模型能力、上下文、Smart tier 与产品 reasoning 合同以 compute/model-specs 为准。",
"items": { "$ref": "#/definitions/model" }
},
"tombstones": {
"type": "array",
"description": "预置显式删除的模型 modelName 白名单",
"items": { "type": "string" }
},
"requiredClientVersion": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$",
"description": "使用此 provider 所需的最低客户端版本(semver x.y.z)。对已铺开韧性(#848/#1021)的客户端是可安全推送的未知字段(非毒丸——毒丸只源自已知字段的 enum 值扩展);≥ 支持版本(含 desirecore #1038)的客户端据此把 provider 优雅门控为「需更新客户端」并从模型选择器排除。规约:新凭据源/新能力 provider 必须声明本字段;降低/解除要求请改为更小版本号而非删除字段(预设合并不回传字段删除)"
}
},
"additionalProperties": false,
"definitions": {
"model": {
"type": "object",
"required": ["modelName", "displayName", "serviceType", "capabilities"],
"properties": {
"modelName": {
"type": "string",
"description": "此 Provider 接入面实际接受的模型 ID,用于 API 调用并与 ModelSpec 的精确/受控别名匹配(如 gpt-5-mini、claude-sonnet-4)。",
"minLength": 1
},
"displayName": {
"type": "string",
"description": "旧客户端展示兼容副本。新 Smart 路径以 ModelSpec.displayName 为模型事实,此字段仅随接入面保留。",
"minLength": 1
},
"serviceType": {
"type": ["string", "array"],
"items": { "type": "string" },
"description": "旧客户端兼容的接入面服务声明;新 Smart 路径以 ModelSpec.spec 为能力事实。支持单个字符串或数组:chat、reasoning、fast、responses、translation、tts、asr、voice_clone、realtime_voice、simultaneous_interpret、vision、ocr、image_gen、video_gen、embedding、rerank、omni、computer_use 等"
},
"description": {
"type": "string",
"description": "模型简要描述"
},
"contextWindow": {
"type": "number",
"description": "旧客户端兼容副本;Smart 以 ModelSpec.spec.contextWindow 为模型事实,本地接入面覆盖只能收紧。",
"minimum": 0
},
"maxOutputTokens": {
"type": "number",
"description": "旧客户端兼容副本;新模型规格以 ModelSpec.spec.maxOutputTokens 为准。",
"minimum": 0
},
"capabilities": {
"type": "array",
"description": "旧客户端兼容副本;Smart 的能力标签以 ModelSpec.spec.capabilities 为准,接入面或用户覆盖只能收紧,不能扩张。",
"items": { "type": "string" }
},
"inputPrice": {
"type": "number",
"description": "输入价格(每百万 token),货币由 Provider.priceCurrency 决定",
"minimum": 0
},
"outputPrice": {
"type": "number",
"description": "输出价格(每百万 token),货币由 Provider.priceCurrency 决定",
"minimum": 0
},
"defaultTemperature": {
"type": "number",
"description": "默认温度参数(0-2)。【重要】必须是 number,禁止写为 null 或字符串。reasoning 等不支持温度的模型应完全省略此字段。null 会破坏 fix #471 之前发布的客户端(schema 严格 number),导致 readComputeConfig 死锁。",
"minimum": 0,
"maximum": 2
},
"defaultTopP": {
"type": "number",
"description": "默认 Top-P 参数(0-1)。【重要】必须是 number,禁止写为 null 或字符串。reasoning 等不支持 Top-P 的模型应完全省略此字段。null 会破坏 fix #471 之前发布的客户端,导致死锁。",
"minimum": 0,
"maximum": 1
},
"extra": {
"type": "object",
"description": "接入面特定配置:如 TTS 模型音色列表、ASR 支持格式、reasoning effort、native thinking 回放能力与工具轮 thinking 校验口径。这里是兼容扩展面:旧客户端会安全保留并忽略未知子字段。",
"properties": {
"adaptiveThinking": {
"type": "boolean",
"description": "Anthropic Messages 模型是否属于 adaptive thinking 代际(Claude Opus 4.7+ / Opus 5 / Sonnet 5 / Fable / Mythos)。true 时客户端把思考参数发成 thinking.type=adaptive + output_config.effort,而不是会被上游 400 拒绝的 thinking.type=enabled + budget_tokens;false 表示明确不是,禁止客户端按模型 ID 推断;缺省时客户端仅在官方 api.anthropic.com 端点上按模型代际表兜底。只作用于 anthropic-messages 接入面,Claude 订阅(claude-oauth)由 Claude CLI 自行改写、不受本字段影响。这是模型固有属性,权威声明写在 model-spec 的 spec.extra.adaptiveThinking;预置 Provider 条目不经规格库补全,必须在这里同样声明。"
},
"thinkingRoundTrip": {
"type": "object",
"description": "当前 Provider/model 接入面是否允许回放 Provider 原样返回的 Anthropic thinking。只授权当前未闭合工具轨迹;UI 展示 thinking、已完成旧轮次、跨模型/凭据数据永远没有回放资格。",
"required": ["enabled", "protocol", "allowEmptySignature", "scope"],
"properties": {
"enabled": {
"type": "boolean",
"description": "显式能力开关;客户端只接受 true,false 或缺失均 fail closed。"
},
"protocol": {
"type": "string",
"enum": ["anthropic-messages"],
"description": "能力对应的线上协议;不得由 Provider 名称或 URL 猜测。"
},
"allowEmptySignature": {
"type": "boolean",
"description": "是否允许回放该接入面原样返回的空 signature;只有真机证明网关确实要求时才能设为 true,不得制造空签名。"
},
"scope": {
"type": "string",
"enum": ["active-tool-turn"],
"description": "固定只作用于当前未闭合工具轨迹,禁止扩张到完成历史。"
}
},
"additionalProperties": false
},
"samplingParametersDeprecated": {
"type": "boolean",
"description": "模型是否拒绝采样参数(如 Claude Opus 4.7+ / Opus 5 / Fable / Mythos 携带 temperature、top_p 或 top_k 即 400,Sonnet 5 对非默认值 400)。true 时客户端出站删除请求体顶层的 temperature / top_p / top_k——无论取值来自模型 defaultTemperature、用户覆写、工作流默认值还是父会话继承;false 或缺省时原样发送,客户端不按模型 ID 推断。只描述模型固有属性:精确命中的 ModelSpec 会补全到各接入面的同名模型,family 兜底不继承;作用于 Agent 引擎发出的模型请求,Claude 订阅(claude-oauth)请求由 Claude CLI 自建、不受本字段影响。"
},
"forcedToolChoiceUnsupported": {
"type": "boolean",
"description": "模型是否拒绝强制工具选择(如 Claude Fable 5.1 / Mythos 5.1:tool_choice.type 为 any 或 tool 即 400)。true 时客户端把强制工具选择降级为 auto(Anthropic 的 any / tool、OpenAI 的 required / 指定函数),Anthropic 请求保留 disable_parallel_tool_use,none 与 auto 不受影响;false 或缺省时按调用方要求发送,客户端不按模型 ID 推断。只描述模型固有属性:精确命中的 ModelSpec 会补全到各接入面的同名模型,family 兜底不继承;作用于 Agent 引擎发出的模型请求,Claude 订阅(claude-oauth)请求由 Claude CLI 自建、不受本字段影响。"
},
"thinkingToolTurnValidation": {
"type": "string",
"enum": ["requires-thinking", "accepts-thinkless"],
"description": "接入面对 thinking 工具轨迹中不含 thinking 的工具轮的校验口径,仅在同一模型声明了 thinkingRoundTrip 时生效;缺省视为 requires-thinking。requires-thinking:接入面要求每个工具轮都带可回放 thinking(例如只在首轮返回 thinking、之后会拒绝整条会话的兼容网关),客户端在执行工具前把这类响应判为上游失败并重试,同一会话再次违规时关闭思考。accepts-thinkless:接入面按 Anthropic adaptive thinking 规则校验,模型自行跳过思考的工具轮本身就是合法历史,客户端原样接受,不重试也不降级。只有官方文档或真机回放证明接受此类历史的接入面才能声明 accepts-thinkless;不得由 Provider 名称或 URL 推断。旧客户端忽略该字段并保持 requires-thinking 行为。"
},
"reasoning": {
"type": "object",
"description": "当前 Provider 接入面接受哪些 reasoning effort 值——精确档位矩阵。只能写在 provider model 的 extra 里:同一模型经不同网关支持的集合可能不同,因此 model-spec 的 spec.extra 里写本键会被客户端 applySpec 主动丢弃。想表达「这个模型根本关不掉思考」的用 model-spec 的 spec.extra.thinkingOnly,那只回答 off 能否使用、不声明任何深度档位。",
"required": ["supportedEfforts"],
"properties": {
"supportedEfforts": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"enum": ["none", "minimal", "low", "medium", "high", "xhigh", "max"]
},
"description": "当前接入面实际接受的 OpenAI reasoning effort;它只能收紧精确 ModelSpec.routing.reasoning 合同,不能扩张。none 在产品层映射为 off;Ultra 是多 Agent 编排模式,禁止写入。"
},
"defaultEffort": {
"type": "string",
"enum": ["minimal", "low", "medium", "high", "xhigh", "max"],
"description": "当前接入面推荐的显式默认档位;仅当它仍属于 ModelSpec 合同时生效,且必须同时存在于 supportedEfforts。"
}
},
"allOf": [
{ "if": { "required": ["defaultEffort"], "properties": { "defaultEffort": { "const": "minimal" } } }, "then": { "properties": { "supportedEfforts": { "contains": { "const": "minimal" } } } } },
{ "if": { "required": ["defaultEffort"], "properties": { "defaultEffort": { "const": "low" } } }, "then": { "properties": { "supportedEfforts": { "contains": { "const": "low" } } } } },
{ "if": { "required": ["defaultEffort"], "properties": { "defaultEffort": { "const": "medium" } } }, "then": { "properties": { "supportedEfforts": { "contains": { "const": "medium" } } } } },
{ "if": { "required": ["defaultEffort"], "properties": { "defaultEffort": { "const": "high" } } }, "then": { "properties": { "supportedEfforts": { "contains": { "const": "high" } } } } },
{ "if": { "required": ["defaultEffort"], "properties": { "defaultEffort": { "const": "xhigh" } } }, "then": { "properties": { "supportedEfforts": { "contains": { "const": "xhigh" } } } } },
{ "if": { "required": ["defaultEffort"], "properties": { "defaultEffort": { "const": "max" } } }, "then": { "properties": { "supportedEfforts": { "contains": { "const": "max" } } } } }
],
"additionalProperties": false
}
},
"additionalProperties": true
},
"apiModelId": {
"type": "string",
"description": "实际传给上游 API 的 model 参数。当 modelName 与 API 期望名称不同时使用"
},
"source": {
"type": "string",
"enum": ["preset", "synced", "user-added", "ollama-discovery"],
"description": "模型来源。config-center 数据应统一为 preset"
}
},
"additionalProperties": false
}
}
}