diff --git a/__tests__/validate.test.mjs b/__tests__/validate.test.mjs index 13ff368..f7e1264 100644 --- a/__tests__/validate.test.mjs +++ b/__tests__/validate.test.mjs @@ -777,6 +777,28 @@ describe('provider schema 反例(防 PR #1 重演)', () => { } }) + it('校验 Provider model 的工具轮 thinking 校验口径', () => { + for (const value of ['requires-thinking', 'accepts-thinkless']) { + const valid = makeValidProvider() + valid.models[0].extra = { + thinkingRoundTrip: { + enabled: true, + protocol: 'anthropic-messages', + allowEmptySignature: false, + scope: 'active-tool-turn', + }, + thinkingToolTurnValidation: value, + } + assert.equal(validate(valid), true, JSON.stringify(validate.errors)) + } + + for (const broken of ['accept', true, null]) { + const invalid = makeValidProvider() + invalid.models[0].extra = { thinkingToolTurnValidation: broken } + assert.equal(validate(invalid), false) + } + }) + it('拒绝 Ultra 与重复 reasoning effort', () => { const ultra = makeValidProvider() ultra.models[0].extra = { @@ -868,6 +890,22 @@ describe('model-spec schema 接入面边界', () => { assert.equal(validate(data), false) }) + it('拒绝在模型内在规格中声明接入面的 thinking 回放能力与工具轮校验口径', () => { + for (const extra of [ + { + thinkingRoundTrip: { + enabled: true, + protocol: 'anthropic-messages', + allowEmptySignature: false, + scope: 'active-tool-turn', + }, + }, + { thinkingToolTurnValidation: 'accepts-thinkless' }, + ]) { + assert.equal(validate({ specs: [{ id: 'claude-test', spec: { extra } }] }), false) + } + }) + it('仍允许模型内在扩展参数', () => { const data = { specs: [{ id: 'gpt-test', spec: { extra: { intrinsicBudgetHint: 8192 } } }], diff --git a/compute/providers/anthropic-claude.json b/compute/providers/anthropic-claude.json index 8ff03dd..3860592 100644 --- a/compute/providers/anthropic-claude.json +++ b/compute/providers/anthropic-claude.json @@ -62,7 +62,8 @@ "protocol": "anthropic-messages", "allowEmptySignature": false, "scope": "active-tool-turn" - } + }, + "thinkingToolTurnValidation": "accepts-thinkless" }, "source": "preset" }, @@ -102,7 +103,8 @@ "protocol": "anthropic-messages", "allowEmptySignature": false, "scope": "active-tool-turn" - } + }, + "thinkingToolTurnValidation": "accepts-thinkless" }, "source": "preset" }, @@ -141,7 +143,8 @@ "protocol": "anthropic-messages", "allowEmptySignature": false, "scope": "active-tool-turn" - } + }, + "thinkingToolTurnValidation": "accepts-thinkless" }, "source": "preset" } diff --git a/compute/providers/anthropic.json b/compute/providers/anthropic.json index 5270432..26c51e0 100644 --- a/compute/providers/anthropic.json +++ b/compute/providers/anthropic.json @@ -41,6 +41,7 @@ "allowEmptySignature": false, "scope": "active-tool-turn" }, + "thinkingToolTurnValidation": "accepts-thinkless", "serverSideWebSearch": { "enabled": true, "dialect": "anthropic-messages", @@ -97,6 +98,7 @@ "allowEmptySignature": false, "scope": "active-tool-turn" }, + "thinkingToolTurnValidation": "accepts-thinkless", "serverSideWebSearch": { "enabled": true, "dialect": "anthropic-messages", @@ -156,6 +158,7 @@ "allowEmptySignature": false, "scope": "active-tool-turn" }, + "thinkingToolTurnValidation": "accepts-thinkless", "serverSideWebSearch": { "enabled": true, "dialect": "anthropic-messages", diff --git a/manifest.json b/manifest.json index 3ce6ecc..c14b51f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "presetDataVersion": 113, + "presetDataVersion": 114, "updatedAt": "2026-09-13", "description": "DesireCore 官方配置中心" } diff --git a/schemas/model-spec.schema.json b/schemas/model-spec.schema.json index ba6e135..c08a3e9 100644 --- a/schemas/model-spec.schema.json +++ b/schemas/model-spec.schema.json @@ -259,8 +259,12 @@ }, "extra": { "type": "object", - "description": "模型内在开放扩展:modelOrigin 描述真实原厂身份,speech 描述可跨小版本复用的语音协议;不含价格,也不得声明 Provider/接入面的 reasoning effort 能力。", - "not": { "required": ["reasoning"] }, + "description": "模型内在开放扩展:modelOrigin 描述真实原厂身份,speech 描述可跨小版本复用的语音协议;不含价格,也不得声明 Provider/接入面的 reasoning effort、thinking 回放能力(thinkingRoundTrip)或工具轮 thinking 校验口径(thinkingToolTurnValidation)——这些只能写在 provider model 的 extra 中,客户端 Schema 会拒收整份规格。", + "allOf": [ + { "not": { "required": ["reasoning"] } }, + { "not": { "required": ["thinkingRoundTrip"] } }, + { "not": { "required": ["thinkingToolTurnValidation"] } } + ], "properties": { "modelOrigin": { "$ref": "#/definitions/modelOrigin" }, "speech": { "$ref": "#/definitions/speechModelProfile" }, diff --git a/schemas/provider.schema.json b/schemas/provider.schema.json index 471953b..3bc78e4 100644 --- a/schemas/provider.schema.json +++ b/schemas/provider.schema.json @@ -252,7 +252,7 @@ }, "extra": { "type": "object", - "description": "接入面特定配置:如 TTS 模型音色列表、ASR 支持格式、reasoning effort 与 native thinking 回放能力。这里是兼容扩展面:旧客户端会安全保留并忽略未知子字段。", + "description": "接入面特定配置:如 TTS 模型音色列表、ASR 支持格式、reasoning effort、native thinking 回放能力与工具轮 thinking 校验口径。这里是兼容扩展面:旧客户端会安全保留并忽略未知子字段。", "properties": { "thinkingRoundTrip": { "type": "object", @@ -280,6 +280,11 @@ }, "additionalProperties": false }, + "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 能否使用、不声明任何深度档位。",