diff --git a/llms/anthropic/adaptive_thinking_test.go b/llms/anthropic/adaptive_thinking_test.go index 3103f1be..f808847f 100644 --- a/llms/anthropic/adaptive_thinking_test.go +++ b/llms/anthropic/adaptive_thinking_test.go @@ -16,7 +16,7 @@ import ( // captureMessagesRequest drives a real GenerateContent call against a recording // server and returns the decoded outbound /v1/messages body and headers, so // assertions cover generateMessagesContent (thinking construction, sampling-param -// handling, beta headers), not a hand-built payload. +// handling, beta headers). func captureMessagesRequest(t *testing.T, callOpts ...llms.CallOption) (map[string]any, http.Header) { t.Helper() @@ -124,9 +124,11 @@ func TestAnthropic_BudgetThinkingRequest(t *testing.T) { _, hasOutputConfig := payload["output_config"] assert.False(t, hasOutputConfig, "budget thinking has no output_config") - // Budget thinking pins temperature to 1.0 and keeps top_p as provided. + // Budget thinking pins temperature to 1.0 and drops top_p — the API rejects + // temperature and top_p together. assert.EqualValues(t, 1.0, payload["temperature"]) - assert.EqualValues(t, 0.9, payload["top_p"]) + _, hasTopP := payload["top_p"] + assert.False(t, hasTopP, "budget thinking must drop top_p") assert.EqualValues(t, 4096, payload["max_tokens"]) // max(budget*2, maxTokens) } diff --git a/llms/anthropic/anthropicllm.go b/llms/anthropic/anthropicllm.go index bcb0e896..72da22b6 100644 --- a/llms/anthropic/anthropicllm.go +++ b/llms/anthropic/anthropicllm.go @@ -210,8 +210,10 @@ func generateMessagesContent(ctx context.Context, o *LLM, messages []llms.Messag } } - // Budget thinking pins temperature to 1.0; adaptive models reject sampling - // params, so omit temperature/top_p entirely for them. + // Thinking constrains sampling params: the API rejects temperature and top_p + // together, and requires temperature=1.0 with budget thinking. So pin + // temperature and drop top_p for budget; drop both for adaptive (which + // rejects sampling params outright). temperature, topP, maxTokens := opts.Temperature, opts.TopP, opts.GetMaxTokens() switch { case thinking != nil && thinking.Type == "adaptive": @@ -219,6 +221,7 @@ func generateMessagesContent(ctx context.Context, o *LLM, messages []llms.Messag topP = nil case thinking != nil && thinking.Type == "enabled" && thinking.Budget > 0: temperature = getFloatPointer(1.0) + topP = nil maxTokens = max(thinking.Budget*2, maxTokens) // 2x the budget for thinking }