From 5a997a86f00b49510e5f619a71888785587c7802 Mon Sep 17 00:00:00 2001 From: Andy Lo-A-Foe Date: Mon, 1 Jun 2026 08:59:15 +0200 Subject: [PATCH] fix(bedrock): drop temperature for models that deprecate it Claude Opus 4.8 on Bedrock rejects the temperature inference parameter with a ValidationException ("temperature is deprecated for this model"). buildConverseParams now takes the model id and omits temperature for claude-opus-4-8* (matching both bare model ids and region-prefixed inference profiles), logging when it does so. max_tokens and all other models are unaffected. Co-Authored-By: Claude Opus 4.8 --- pkg/providers/bedrock/provider_bedrock.go | 31 ++++++++++--- .../bedrock/provider_bedrock_test.go | 46 +++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/pkg/providers/bedrock/provider_bedrock.go b/pkg/providers/bedrock/provider_bedrock.go index ee0ac75a0..7d730e2e8 100644 --- a/pkg/providers/bedrock/provider_bedrock.go +++ b/pkg/providers/bedrock/provider_bedrock.go @@ -143,7 +143,22 @@ type converseParams struct { toolConfig *types.ToolConfiguration } -func buildConverseParams(messages []Message, tools []ToolDefinition, options map[string]any) converseParams { +// modelDeprecatesTemperature reports whether the given Bedrock model rejects the +// temperature inference parameter. Newer Claude models (Opus 4.8 and later) treat +// temperature as deprecated and return a ValidationException if it is supplied: +// +// ValidationException: The model returned the following errors: +// temperature is deprecated for this model. +// +// The match is intentionally loose: Bedrock model IDs and inference-profile ARNs +// embed the model name (e.g. "us.anthropic.claude-opus-4-8-20260514-v1:0"), so a +// substring check covers both bare IDs and region-prefixed inference profiles. +func modelDeprecatesTemperature(model string) bool { + m := strings.ToLower(model) + return strings.Contains(m, "claude-opus-4-8") +} + +func buildConverseParams(messages []Message, tools []ToolDefinition, model string, options map[string]any) converseParams { bedrockMessages, systemPrompts := convertMessages(messages) var inferenceConfig *types.InferenceConfiguration @@ -159,10 +174,14 @@ func buildConverseParams(messages []Message, tools []ToolDefinition, options map } if temp, ok := common.AsFloat(options["temperature"]); ok { - if inferenceConfig == nil { - inferenceConfig = &types.InferenceConfiguration{} + if modelDeprecatesTemperature(model) { + log.Printf("bedrock: temperature dropped because model %q no longer supports it", model) + } else { + if inferenceConfig == nil { + inferenceConfig = &types.InferenceConfiguration{} + } + inferenceConfig.Temperature = aws.Float32(float32(temp)) } - inferenceConfig.Temperature = aws.Float32(float32(temp)) } var toolConfig *types.ToolConfiguration @@ -199,7 +218,7 @@ func (p *Provider) Chat( defer cancel() } - params := buildConverseParams(messages, tools, options) + params := buildConverseParams(messages, tools, model, options) input := &bedrockruntime.ConverseInput{ ModelId: aws.String(model), Messages: params.messages, @@ -242,7 +261,7 @@ func (p *Provider) ChatStream( } } - params := buildConverseParams(messages, tools, options) + params := buildConverseParams(messages, tools, model, options) input := &bedrockruntime.ConverseStreamInput{ ModelId: aws.String(model), Messages: params.messages, diff --git a/pkg/providers/bedrock/provider_bedrock_test.go b/pkg/providers/bedrock/provider_bedrock_test.go index 9d6c747f1..a06a93c93 100644 --- a/pkg/providers/bedrock/provider_bedrock_test.go +++ b/pkg/providers/bedrock/provider_bedrock_test.go @@ -875,3 +875,49 @@ func TestParseStreamResponse_StopReasons(t *testing.T) { }) } } + +func TestModelDeprecatesTemperature(t *testing.T) { + tests := []struct { + name string + model string + want bool + }{ + {"opus 4.8 bare id", "claude-opus-4-8-20260514-v1:0", true}, + {"opus 4.8 inference profile", "us.anthropic.claude-opus-4-8-20260514-v1:0", true}, + {"opus 4.8 mixed case", "US.Anthropic.Claude-Opus-4-8", true}, + {"opus 4.7 unaffected", "us.anthropic.claude-opus-4-7-20250101-v1:0", false}, + {"sonnet unaffected", "us.anthropic.claude-sonnet-4-6", false}, + {"empty", "", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, modelDeprecatesTemperature(tt.model)) + }) + } +} + +func TestBuildConverseParams_DropsTemperatureForDeprecatedModel(t *testing.T) { + options := map[string]any{ + "temperature": 0.7, + "max_tokens": 1024, + } + + params := buildConverseParams(nil, nil, "us.anthropic.claude-opus-4-8-20260514-v1:0", options) + + require.NotNil(t, params.inferenceConfig, "max_tokens should still populate inference config") + assert.Nil(t, params.inferenceConfig.Temperature, "temperature must be omitted for opus 4.8") + require.NotNil(t, params.inferenceConfig.MaxTokens) + assert.Equal(t, int32(1024), *params.inferenceConfig.MaxTokens) +} + +func TestBuildConverseParams_KeepsTemperatureForSupportedModel(t *testing.T) { + options := map[string]any{ + "temperature": 0.7, + } + + params := buildConverseParams(nil, nil, "us.anthropic.claude-opus-4-7-20250101-v1:0", options) + + require.NotNil(t, params.inferenceConfig) + require.NotNil(t, params.inferenceConfig.Temperature) + assert.InDelta(t, 0.7, float64(*params.inferenceConfig.Temperature), 0.0001) +}