From 25674d206ce338ff5cf3fce4ba05c5286b0f702d Mon Sep 17 00:00:00 2001 From: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:34:24 +0800 Subject: [PATCH 1/2] fix(core): omit deprecated temperature param for Claude 4.8+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Opus 4.8 deprecated the `temperature` sampling parameter — the server returns 400 with "temperature is deprecated for this model" when it is sent. Add `modelRejectsTemperature()` version gate (mirroring the existing `modelRejectsManualThinking()` pattern) and conditionally omit `temperature` from the request body for models with major > 4 or 4.minor >= 8. Older models and unknown/unversioned ids retain the previous behavior (default temperature of 1). Fixes #6519 --- .../anthropicContentGenerator.test.ts | 67 +++++++++++++++++++ .../anthropicContentGenerator.ts | 25 ++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts index cca35458f76..10a4e9456b1 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts @@ -1557,6 +1557,73 @@ describe('AnthropicContentGenerator', () => { ); }); + // Claude 4.8+ deprecated the `temperature` sampling parameter — the + // server responds with a 400 when it is sent. Verify the generator + // omits it for 4.8+ and keeps it for older models. + it('omits temperature on Opus 4.8 (deprecated)', async () => { + const { AnthropicContentGenerator } = await importGenerator(); + anthropicState.createImpl.mockResolvedValue({ + id: 'anthropic-1', + model: 'claude-opus-4-8', + content: [{ type: 'text', text: 'hi' }], + }); + + const generator = new AnthropicContentGenerator( + { + model: 'claude-opus-4-8', + apiKey: 'test-key', + baseUrl: 'https://api.anthropic.com', + timeout: 10_000, + maxRetries: 2, + samplingParams: { max_tokens: 500, temperature: 0.7 }, + schemaCompliance: 'auto', + }, + mockConfig, + ); + + await generator.generateContent({ + model: 'models/ignored', + contents: 'Hello', + } as unknown as GenerateContentParameters); + + const [anthropicRequest] = + anthropicState.lastCreateArgs as AnthropicCreateArgs; + expect(anthropicRequest).not.toHaveProperty('temperature'); + }); + + it('keeps temperature on Opus 4.7 (still accepted)', async () => { + const { AnthropicContentGenerator } = await importGenerator(); + anthropicState.createImpl.mockResolvedValue({ + id: 'anthropic-1', + model: 'claude-opus-4-7', + content: [{ type: 'text', text: 'hi' }], + }); + + const generator = new AnthropicContentGenerator( + { + model: 'claude-opus-4-7', + apiKey: 'test-key', + baseUrl: 'https://api.anthropic.com', + timeout: 10_000, + maxRetries: 2, + samplingParams: { max_tokens: 500, temperature: 0.7 }, + schemaCompliance: 'auto', + }, + mockConfig, + ); + + await generator.generateContent({ + model: 'models/ignored', + contents: 'Hello', + } as unknown as GenerateContentParameters); + + const [anthropicRequest] = + anthropicState.lastCreateArgs as AnthropicCreateArgs; + expect(anthropicRequest).toEqual( + expect.objectContaining({ temperature: 0.7 }), + ); + }); + it("clamps effort: 'xhigh' to 'max' on Opus 4.6 (has max, lacks xhigh)", async () => { const { AnthropicContentGenerator } = await importGenerator(); anthropicState.createImpl.mockResolvedValue({ diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts index e903fa69dab..350f294c9e6 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts @@ -744,9 +744,15 @@ export class AnthropicContentGenerator implements ContentGenerator { } } + // Claude 4.8+ deprecated temperature — the server rejects it with a 400. + // Omit the parameter entirely for those models; older models keep the + // default of 1 (Anthropic's documented neutral value). + const temperatureValue = + getParam('temperature', 'temperature') ?? 1; + return { max_tokens: maxTokens, - temperature: getParam('temperature', 'temperature') ?? 1, + ...(!this.modelRejectsTemperature() && { temperature: temperatureValue }), top_p: getParam('top_p', 'topP'), top_k: getParam('top_k', 'topK'), }; @@ -858,6 +864,23 @@ export class AnthropicContentGenerator implements ContentGenerator { return major > 4 || (major === 4 && minor >= 7); } + /** + * Whether the model rejects the `temperature` sampling parameter with a 400. + * Claude Opus 4.8+ deprecated temperature — the server controls sampling + * determinism internally and responds with + * `"temperature is deprecated for this model."` when the parameter is sent. + * Older models (4.7 and below) and unknown/unversioned ids still accept it, + * so both return false. + */ + private modelRejectsTemperature(): boolean { + const parsed = parseClaudeModelVersion( + this.contentGeneratorConfig.model || '', + ); + if (!parsed) return false; + const { major, minor } = parsed; + return major > 4 || (major === 4 && minor >= 8); + } + private buildThinkingConfig( request: GenerateContentParameters, effectiveEffort: ReasoningEffort | undefined, From 1872203893c744265d08e7dba2a20a85db5a2ea0 Mon Sep 17 00:00:00 2001 From: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:09:36 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(core):=20address=20review=20=E2=80=94?= =?UTF-8?q?=20ternary=20form,=20debug=20warn,=205.x=20+=20unknown=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch from boolean-spread to ternary form for idiom consistency with the thinking/output_config spreads at L697-698. - Add a once-per-generator debugLogger.warn when a user-configured temperature is silently dropped on 4.8+ (latched like effortClampWarned). - Add test coverage for claude-sonnet-5 (5.x family omits temperature) and unknown/unversioned model id (keeps temperature) to pin the major > 4 branch and prevent future narrowing. --- .../anthropicContentGenerator.test.ts | 64 +++++++++++++++++++ .../anthropicContentGenerator.ts | 19 +++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts index 10a4e9456b1..9cf6cdd3624 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts @@ -1624,6 +1624,70 @@ describe('AnthropicContentGenerator', () => { ); }); + it('omits temperature on Sonnet 5 (5.x family, deprecated)', async () => { + const { AnthropicContentGenerator } = await importGenerator(); + anthropicState.createImpl.mockResolvedValue({ + id: 'anthropic-1', + model: 'claude-sonnet-5', + content: [{ type: 'text', text: 'hi' }], + }); + + const generator = new AnthropicContentGenerator( + { + model: 'claude-sonnet-5', + apiKey: 'test-key', + baseUrl: 'https://api.anthropic.com', + timeout: 10_000, + maxRetries: 2, + samplingParams: { max_tokens: 500, temperature: 0.5 }, + schemaCompliance: 'auto', + }, + mockConfig, + ); + + await generator.generateContent({ + model: 'models/ignored', + contents: 'Hello', + } as unknown as GenerateContentParameters); + + const [anthropicRequest] = + anthropicState.lastCreateArgs as AnthropicCreateArgs; + expect(anthropicRequest).not.toHaveProperty('temperature'); + }); + + it('keeps temperature on unknown/unversioned model id', async () => { + const { AnthropicContentGenerator } = await importGenerator(); + anthropicState.createImpl.mockResolvedValue({ + id: 'anthropic-1', + model: 'some-custom-model', + content: [{ type: 'text', text: 'hi' }], + }); + + const generator = new AnthropicContentGenerator( + { + model: 'some-custom-model', + apiKey: 'test-key', + baseUrl: 'https://api.anthropic.com', + timeout: 10_000, + maxRetries: 2, + samplingParams: { max_tokens: 500, temperature: 0.3 }, + schemaCompliance: 'auto', + }, + mockConfig, + ); + + await generator.generateContent({ + model: 'models/ignored', + contents: 'Hello', + } as unknown as GenerateContentParameters); + + const [anthropicRequest] = + anthropicState.lastCreateArgs as AnthropicCreateArgs; + expect(anthropicRequest).toEqual( + expect.objectContaining({ temperature: 0.3 }), + ); + }); + it("clamps effort: 'xhigh' to 'max' on Opus 4.6 (has max, lacks xhigh)", async () => { const { AnthropicContentGenerator } = await importGenerator(); anthropicState.createImpl.mockResolvedValue({ diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts index 350f294c9e6..537c1442dc3 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts @@ -271,6 +271,7 @@ export class AnthropicContentGenerator implements ContentGenerator { // instead of on every request that needs the downgrade. private effortClampWarned = false; private budgetDropWarned = false; + private temperatureDropWarned = false; constructor( private contentGeneratorConfig: ContentGeneratorConfig, @@ -747,12 +748,24 @@ export class AnthropicContentGenerator implements ContentGenerator { // Claude 4.8+ deprecated temperature — the server rejects it with a 400. // Omit the parameter entirely for those models; older models keep the // default of 1 (Anthropic's documented neutral value). - const temperatureValue = - getParam('temperature', 'temperature') ?? 1; + const temperatureDropped = this.modelRejectsTemperature(); + if (temperatureDropped && !this.temperatureDropWarned) { + const userTemp = getParam('temperature', 'temperature'); + if (userTemp !== undefined) { + debugLogger.warn( + `temperature=${userTemp} is not supported by '${ + this.contentGeneratorConfig.model ?? 'unknown' + }' (deprecated on 4.8+); ignoring.`, + ); + } + this.temperatureDropWarned = true; + } return { max_tokens: maxTokens, - ...(!this.modelRejectsTemperature() && { temperature: temperatureValue }), + ...(temperatureDropped + ? {} + : { temperature: getParam('temperature', 'temperature') ?? 1 }), top_p: getParam('top_p', 'topP'), top_k: getParam('top_k', 'topK'), };