mirror of
https://gh-proxy.org/https://github.com/klinsmaya/CLIProxyAPI.git
synced 2026-11-09 09:56:49 +08:00
fix(translator): remove temperature parameter handling in Claude request transformations
Closes: #4071
This commit is contained in:
@@ -244,7 +244,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
||||
|
||||
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
|
||||
body = disableThinkingIfToolChoiceForced(body)
|
||||
body = normalizeClaudeSamplingForThinking(body)
|
||||
body = normalizeClaudeSamplingForUpstream(body)
|
||||
|
||||
// Auto-inject cache_control if missing (optimization for ClawdBot/clients without caching support)
|
||||
if countCacheControls(body) == 0 {
|
||||
@@ -434,7 +434,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
||||
|
||||
// Disable thinking if tool_choice forces tool use (Anthropic API constraint)
|
||||
body = disableThinkingIfToolChoiceForced(body)
|
||||
body = normalizeClaudeSamplingForThinking(body)
|
||||
body = normalizeClaudeSamplingForUpstream(body)
|
||||
|
||||
// Auto-inject cache_control if missing (optimization for ClawdBot/clients without caching support)
|
||||
if countCacheControls(body) == 0 {
|
||||
@@ -865,16 +865,13 @@ func disableThinkingIfToolChoiceForced(body []byte) []byte {
|
||||
return body
|
||||
}
|
||||
|
||||
// normalizeClaudeSamplingForThinking keeps Anthropic message requests valid when
|
||||
// thinking is active. Anthropic rejects non-default sampling while thinking is
|
||||
// enabled/adaptive/auto.
|
||||
func normalizeClaudeSamplingForThinking(body []byte) []byte {
|
||||
// normalizeClaudeSamplingForUpstream keeps Anthropic message requests valid.
|
||||
func normalizeClaudeSamplingForUpstream(body []byte) []byte {
|
||||
body, _ = sjson.DeleteBytes(body, "temperature")
|
||||
|
||||
thinkingType := strings.ToLower(strings.TrimSpace(gjson.GetBytes(body, "thinking.type").String()))
|
||||
switch thinkingType {
|
||||
case "enabled", "adaptive", "auto":
|
||||
if temp := gjson.GetBytes(body, "temperature"); temp.Exists() && (temp.Type != gjson.Number || temp.Float() != 1) {
|
||||
body, _ = sjson.SetBytes(body, "temperature", 1)
|
||||
}
|
||||
body, _ = sjson.DeleteBytes(body, "top_p")
|
||||
body, _ = sjson.DeleteBytes(body, "top_k")
|
||||
}
|
||||
|
||||
@@ -2659,30 +2659,30 @@ func TestApplyCloaking_PreservesConfiguredStrictModeAndSensitiveWordsWhenModeOmi
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeClaudeSamplingForThinking_AdaptiveCoercesTemperatureToOne(t *testing.T) {
|
||||
func TestNormalizeClaudeSamplingForUpstream_RemovesTemperature(t *testing.T) {
|
||||
payload := []byte(`{"temperature":0,"thinking":{"type":"adaptive"},"output_config":{"effort":"max"}}`)
|
||||
out := normalizeClaudeSamplingForThinking(payload)
|
||||
out := normalizeClaudeSamplingForUpstream(payload)
|
||||
|
||||
if got := gjson.GetBytes(out, "temperature").Float(); got != 1 {
|
||||
t.Fatalf("temperature = %v, want 1", got)
|
||||
if gjson.GetBytes(out, "temperature").Exists() {
|
||||
t.Fatalf("temperature should be removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeClaudeSamplingForThinking_EnabledCoercesTemperatureToOne(t *testing.T) {
|
||||
func TestNormalizeClaudeSamplingForUpstream_RemovesTemperatureWithThinkingEnabled(t *testing.T) {
|
||||
payload := []byte(`{"temperature":0.2,"thinking":{"type":"enabled","budget_tokens":2048}}`)
|
||||
out := normalizeClaudeSamplingForThinking(payload)
|
||||
out := normalizeClaudeSamplingForUpstream(payload)
|
||||
|
||||
if got := gjson.GetBytes(out, "temperature").Float(); got != 1 {
|
||||
t.Fatalf("temperature = %v, want 1", got)
|
||||
if gjson.GetBytes(out, "temperature").Exists() {
|
||||
t.Fatalf("temperature should be removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeClaudeSamplingForThinking_RemovesTopPAndTopK(t *testing.T) {
|
||||
func TestNormalizeClaudeSamplingForUpstream_RemovesTopPAndTopKForThinking(t *testing.T) {
|
||||
payload := []byte(`{"temperature":0.2,"top_p":0.9,"top_k":40,"thinking":{"type":"adaptive"}}`)
|
||||
out := normalizeClaudeSamplingForThinking(payload)
|
||||
out := normalizeClaudeSamplingForUpstream(payload)
|
||||
|
||||
if got := gjson.GetBytes(out, "temperature").Float(); got != 1 {
|
||||
t.Fatalf("temperature = %v, want 1", got)
|
||||
if gjson.GetBytes(out, "temperature").Exists() {
|
||||
t.Fatalf("temperature should be removed")
|
||||
}
|
||||
if gjson.GetBytes(out, "top_p").Exists() {
|
||||
t.Fatalf("top_p should be removed when thinking is active")
|
||||
@@ -2692,12 +2692,12 @@ func TestNormalizeClaudeSamplingForThinking_RemovesTopPAndTopK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeClaudeSamplingForThinking_NoThinkingLeavesTemperatureAlone(t *testing.T) {
|
||||
func TestNormalizeClaudeSamplingForUpstream_NoThinkingRemovesOnlyTemperature(t *testing.T) {
|
||||
payload := []byte(`{"temperature":0,"top_p":0.9,"top_k":40,"messages":[{"role":"user","content":"hi"}]}`)
|
||||
out := normalizeClaudeSamplingForThinking(payload)
|
||||
out := normalizeClaudeSamplingForUpstream(payload)
|
||||
|
||||
if got := gjson.GetBytes(out, "temperature").Float(); got != 0 {
|
||||
t.Fatalf("temperature = %v, want 0", got)
|
||||
if gjson.GetBytes(out, "temperature").Exists() {
|
||||
t.Fatalf("temperature should be removed")
|
||||
}
|
||||
if got := gjson.GetBytes(out, "top_p").Float(); got != 0.9 {
|
||||
t.Fatalf("top_p = %v, want 0.9", got)
|
||||
@@ -2707,16 +2707,16 @@ func TestNormalizeClaudeSamplingForThinking_NoThinkingLeavesTemperatureAlone(t *
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeClaudeSamplingForThinking_AfterForcedToolChoiceKeepsOriginalTemperature(t *testing.T) {
|
||||
func TestNormalizeClaudeSamplingForUpstream_AfterForcedToolChoiceRemovesTemperature(t *testing.T) {
|
||||
payload := []byte(`{"temperature":0,"thinking":{"type":"adaptive"},"output_config":{"effort":"max"},"tool_choice":{"type":"any"}}`)
|
||||
out := disableThinkingIfToolChoiceForced(payload)
|
||||
out = normalizeClaudeSamplingForThinking(out)
|
||||
out = normalizeClaudeSamplingForUpstream(out)
|
||||
|
||||
if gjson.GetBytes(out, "thinking").Exists() {
|
||||
t.Fatalf("thinking should be removed when tool_choice forces tool use")
|
||||
}
|
||||
if got := gjson.GetBytes(out, "temperature").Float(); got != 0 {
|
||||
t.Fatalf("temperature = %v, want 0", got)
|
||||
if gjson.GetBytes(out, "temperature").Exists() {
|
||||
t.Fatalf("temperature should be removed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,11 +114,8 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
|
||||
if maxTokens := genConfig.Get("maxOutputTokens"); maxTokens.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "max_tokens", maxTokens.Int())
|
||||
}
|
||||
// Temperature setting for controlling response randomness
|
||||
if temp := genConfig.Get("temperature"); temp.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "temperature", temp.Float())
|
||||
} else if topP := genConfig.Get("topP"); topP.Exists() {
|
||||
// Top P setting for nucleus sampling (filtered out if temperature is set)
|
||||
// Top P setting for nucleus sampling.
|
||||
if topP := genConfig.Get("topP"); topP.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "top_p", topP.Float())
|
||||
}
|
||||
// Stop sequences configuration for custom termination conditions
|
||||
|
||||
@@ -61,3 +61,27 @@ func TestConvertGeminiRequestToClaude_PreservesCustomToolIDs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertGeminiRequestToClaude_DropsTemperature(t *testing.T) {
|
||||
raw := []byte(`{
|
||||
"generationConfig": {
|
||||
"temperature": 0.2,
|
||||
"topP": 0.8
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"role": "user",
|
||||
"parts": [{"text": "hi"}]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
|
||||
out := ConvertGeminiRequestToClaude("claude-sonnet-5", raw, false)
|
||||
|
||||
if gjson.GetBytes(out, "temperature").Exists() {
|
||||
t.Fatalf("temperature should be removed")
|
||||
}
|
||||
if got := gjson.GetBytes(out, "top_p").Float(); got != 0.8 {
|
||||
t.Fatalf("top_p = %v, want 0.8", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
// It extracts the model name, system instruction, message contents, and tool declarations
|
||||
// from the raw JSON request and returns them in the format expected by the Claude Code API.
|
||||
// The function performs comprehensive transformation including:
|
||||
// 1. Model name mapping and parameter extraction (max_tokens, temperature, top_p, etc.)
|
||||
// 1. Model name mapping and parameter extraction (max_tokens, top_p, etc.)
|
||||
// 2. Message content conversion from OpenAI to Claude Code format
|
||||
// 3. Tool call and tool result handling with proper ID mapping
|
||||
// 4. Image data conversion from OpenAI data URLs to Claude Code base64 format
|
||||
@@ -136,11 +136,8 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream
|
||||
out, _ = sjson.SetBytes(out, "max_tokens", maxTokens.Int())
|
||||
}
|
||||
|
||||
// Temperature setting for controlling response randomness
|
||||
if temp := root.Get("temperature"); temp.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "temperature", temp.Float())
|
||||
} else if topP := root.Get("top_p"); topP.Exists() {
|
||||
// Top P setting for nucleus sampling (filtered out if temperature is set)
|
||||
// Top P setting for nucleus sampling.
|
||||
if topP := root.Get("top_p"); topP.Exists() {
|
||||
out, _ = sjson.SetBytes(out, "top_p", topP.Float())
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,27 @@ func TestConvertOpenAIRequestToClaude_SanitizesToolCallIDsForClaude(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIRequestToClaude_DropsTemperature(t *testing.T) {
|
||||
inputJSON := `{
|
||||
"model": "gpt-4.1",
|
||||
"temperature": 0.2,
|
||||
"top_p": 0.8,
|
||||
"messages": [
|
||||
{"role": "user", "content": "hi"}
|
||||
]
|
||||
}`
|
||||
|
||||
result := ConvertOpenAIRequestToClaude("claude-sonnet-5", []byte(inputJSON), false)
|
||||
resultJSON := gjson.ParseBytes(result)
|
||||
|
||||
if resultJSON.Get("temperature").Exists() {
|
||||
t.Fatalf("temperature should be removed")
|
||||
}
|
||||
if got := resultJSON.Get("top_p").Float(); got != 0.8 {
|
||||
t.Fatalf("top_p = %v, want 0.8", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertOpenAIRequestToClaude_ToolResultTextAndBase64Image(t *testing.T) {
|
||||
inputJSON := `{
|
||||
"model": "gpt-4.1",
|
||||
|
||||
Reference in New Issue
Block a user