refactor(translator): remove registry model lookups from thinking config conversions

This commit is contained in:
hkfires
2026-01-17 21:25:56 +08:00
parent 97b67e0e49
commit d5ef4a6d15
10 changed files with 107 additions and 98 deletions

View File

@@ -12,7 +12,6 @@ import (
"strings" "strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/cache" "github.com/router-for-me/CLIProxyAPI/v6/internal/cache"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
@@ -388,14 +387,11 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled // Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() { if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
modelInfo := registry.LookupModelInfo(modelName) if t.Get("type").String() == "enabled" {
if modelInfo != nil && modelInfo.Thinking != nil { if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number {
if t.Get("type").String() == "enabled" { budget := int(b.Int())
if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number { out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
budget := int(b.Int()) out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.includeThoughts", true)
out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.include_thoughts", true)
}
} }
} }
} }

View File

@@ -343,8 +343,8 @@ func TestConvertClaudeRequestToAntigravity_ThinkingConfig(t *testing.T) {
if thinkingConfig.Get("thinkingBudget").Int() != 8000 { if thinkingConfig.Get("thinkingBudget").Int() != 8000 {
t.Errorf("Expected thinkingBudget 8000, got %d", thinkingConfig.Get("thinkingBudget").Int()) t.Errorf("Expected thinkingBudget 8000, got %d", thinkingConfig.Get("thinkingBudget").Int())
} }
if !thinkingConfig.Get("include_thoughts").Bool() { if !thinkingConfig.Get("includeThoughts").Bool() {
t.Error("include_thoughts should be true") t.Error("includeThoughts should be true")
} }
} else { } else {
t.Log("thinkingConfig not present - model may not be registered in test registry") t.Log("thinkingConfig not present - model may not be registered in test registry")

View File

@@ -15,7 +15,7 @@ import (
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry" "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
@@ -115,18 +115,41 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
} }
} }
// Include thoughts configuration for reasoning process visibility // Include thoughts configuration for reasoning process visibility
// Only apply for models that support thinking and use numeric budgets, not discrete levels. // Translator only does format conversion, ApplyThinking handles model capability validation.
if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() {
modelInfo := registry.LookupModelInfo(modelName) if thinkingLevel := thinkingConfig.Get("thinkingLevel"); thinkingLevel.Exists() {
if modelInfo != nil && modelInfo.Thinking != nil && len(modelInfo.Thinking.Levels) == 0 { level := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
// Check for thinkingBudget first - if present, enable thinking with budget switch level {
if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() && thinkingBudget.Int() > 0 { case "":
out, _ = sjson.Set(out, "thinking.type", "enabled") case "none":
out, _ = sjson.Set(out, "thinking.budget_tokens", thinkingBudget.Int()) out, _ = sjson.Set(out, "thinking.type", "disabled")
} else if includeThoughts := thinkingConfig.Get("include_thoughts"); includeThoughts.Exists() && includeThoughts.Type == gjson.True { out, _ = sjson.Delete(out, "thinking.budget_tokens")
// Fallback to include_thoughts if no budget specified case "auto":
out, _ = sjson.Set(out, "thinking.type", "enabled") out, _ = sjson.Set(out, "thinking.type", "enabled")
out, _ = sjson.Delete(out, "thinking.budget_tokens")
default:
if budget, ok := thinking.ConvertLevelToBudget(level); ok {
out, _ = sjson.Set(out, "thinking.type", "enabled")
out, _ = sjson.Set(out, "thinking.budget_tokens", budget)
}
} }
} else if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() {
budget := int(thinkingBudget.Int())
switch budget {
case 0:
out, _ = sjson.Set(out, "thinking.type", "disabled")
out, _ = sjson.Delete(out, "thinking.budget_tokens")
case -1:
out, _ = sjson.Set(out, "thinking.type", "enabled")
out, _ = sjson.Delete(out, "thinking.budget_tokens")
default:
out, _ = sjson.Set(out, "thinking.type", "enabled")
out, _ = sjson.Set(out, "thinking.budget_tokens", budget)
}
} else if includeThoughts := thinkingConfig.Get("includeThoughts"); includeThoughts.Exists() && includeThoughts.Type == gjson.True {
out, _ = sjson.Set(out, "thinking.type", "enabled")
} else if includeThoughts := thinkingConfig.Get("include_thoughts"); includeThoughts.Exists() && includeThoughts.Type == gjson.True {
out, _ = sjson.Set(out, "thinking.type", "enabled")
} }
} }
} }

View File

@@ -15,7 +15,6 @@ import (
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
@@ -66,23 +65,21 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream
root := gjson.ParseBytes(rawJSON) root := gjson.ParseBytes(rawJSON)
// Convert OpenAI reasoning_effort to Claude thinking config.
if v := root.Get("reasoning_effort"); v.Exists() { if v := root.Get("reasoning_effort"); v.Exists() {
modelInfo := registry.LookupModelInfo(modelName) effort := strings.ToLower(strings.TrimSpace(v.String()))
if modelInfo != nil && modelInfo.Thinking != nil && len(modelInfo.Thinking.Levels) == 0 { if effort != "" {
effort := strings.ToLower(strings.TrimSpace(v.String())) budget, ok := thinking.ConvertLevelToBudget(effort)
if effort != "" { if ok {
budget, ok := thinking.ConvertLevelToBudget(effort) switch budget {
if ok { case 0:
switch budget { out, _ = sjson.Set(out, "thinking.type", "disabled")
case 0: case -1:
out, _ = sjson.Set(out, "thinking.type", "disabled") out, _ = sjson.Set(out, "thinking.type", "enabled")
case -1: default:
if budget > 0 {
out, _ = sjson.Set(out, "thinking.type", "enabled") out, _ = sjson.Set(out, "thinking.type", "enabled")
default: out, _ = sjson.Set(out, "thinking.budget_tokens", budget)
if budget > 0 {
out, _ = sjson.Set(out, "thinking.type", "enabled")
out, _ = sjson.Set(out, "thinking.budget_tokens", budget)
}
} }
} }
} }

View File

@@ -10,7 +10,6 @@ import (
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
@@ -54,23 +53,21 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
root := gjson.ParseBytes(rawJSON) root := gjson.ParseBytes(rawJSON)
// Convert OpenAI Responses reasoning.effort to Claude thinking config.
if v := root.Get("reasoning.effort"); v.Exists() { if v := root.Get("reasoning.effort"); v.Exists() {
modelInfo := registry.LookupModelInfo(modelName) effort := strings.ToLower(strings.TrimSpace(v.String()))
if modelInfo != nil && modelInfo.Thinking != nil && len(modelInfo.Thinking.Levels) == 0 { if effort != "" {
effort := strings.ToLower(strings.TrimSpace(v.String())) budget, ok := thinking.ConvertLevelToBudget(effort)
if effort != "" { if ok {
budget, ok := thinking.ConvertLevelToBudget(effort) switch budget {
if ok { case 0:
switch budget { out, _ = sjson.Set(out, "thinking.type", "disabled")
case 0: case -1:
out, _ = sjson.Set(out, "thinking.type", "disabled") out, _ = sjson.Set(out, "thinking.type", "enabled")
case -1: default:
if budget > 0 {
out, _ = sjson.Set(out, "thinking.type", "enabled") out, _ = sjson.Set(out, "thinking.type", "enabled")
default: out, _ = sjson.Set(out, "thinking.budget_tokens", budget)
if budget > 0 {
out, _ = sjson.Set(out, "thinking.type", "enabled")
out, _ = sjson.Set(out, "thinking.budget_tokens", budget)
}
} }
} }
} }

View File

@@ -12,7 +12,6 @@ import (
"strings" "strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
@@ -218,18 +217,15 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
// Add additional configuration parameters for the Codex API. // Add additional configuration parameters for the Codex API.
template, _ = sjson.Set(template, "parallel_tool_calls", true) template, _ = sjson.Set(template, "parallel_tool_calls", true)
// Convert thinking.budget_tokens to reasoning.effort for level-based models // Convert thinking.budget_tokens to reasoning.effort.
reasoningEffort := "medium" // default reasoningEffort := "medium"
if thinkingConfig := rootResult.Get("thinking"); thinkingConfig.Exists() && thinkingConfig.IsObject() { if thinkingConfig := rootResult.Get("thinking"); thinkingConfig.Exists() && thinkingConfig.IsObject() {
modelInfo := registry.LookupModelInfo(modelName)
switch thinkingConfig.Get("type").String() { switch thinkingConfig.Get("type").String() {
case "enabled": case "enabled":
if modelInfo != nil && modelInfo.Thinking != nil && len(modelInfo.Thinking.Levels) > 0 { if budgetTokens := thinkingConfig.Get("budget_tokens"); budgetTokens.Exists() {
if budgetTokens := thinkingConfig.Get("budget_tokens"); budgetTokens.Exists() { budget := int(budgetTokens.Int())
budget := int(budgetTokens.Int()) if effort, ok := thinking.ConvertBudgetToLevel(budget); ok && effort != "" {
if effort, ok := thinking.ConvertBudgetToLevel(budget); ok && effort != "" { reasoningEffort = effort
reasoningEffort = effort
}
} }
} }
case "disabled": case "disabled":

View File

@@ -14,7 +14,6 @@ import (
"strings" "strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc" "github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking" "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@@ -249,22 +248,28 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
// Fixed flags aligning with Codex expectations // Fixed flags aligning with Codex expectations
out, _ = sjson.Set(out, "parallel_tool_calls", true) out, _ = sjson.Set(out, "parallel_tool_calls", true)
// Convert thinkingBudget to reasoning.effort for level-based models // Convert Gemini thinkingConfig to Codex reasoning.effort.
reasoningEffort := "medium" // default effortSet := false
if genConfig := root.Get("generationConfig"); genConfig.Exists() { if genConfig := root.Get("generationConfig"); genConfig.Exists() {
if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() {
modelInfo := registry.LookupModelInfo(modelName) if thinkingLevel := thinkingConfig.Get("thinkingLevel"); thinkingLevel.Exists() {
if modelInfo != nil && modelInfo.Thinking != nil && len(modelInfo.Thinking.Levels) > 0 { effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { if effort != "" {
budget := int(thinkingBudget.Int()) out, _ = sjson.Set(out, "reasoning.effort", effort)
if effort, ok := thinking.ConvertBudgetToLevel(budget); ok && effort != "" { effortSet = true
reasoningEffort = effort }
} } else if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() {
if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok {
out, _ = sjson.Set(out, "reasoning.effort", effort)
effortSet = true
} }
} }
} }
} }
out, _ = sjson.Set(out, "reasoning.effort", reasoningEffort) if !effortSet {
// No thinking config, set default effort
out, _ = sjson.Set(out, "reasoning.effort", "medium")
}
out, _ = sjson.Set(out, "reasoning.summary", "auto") out, _ = sjson.Set(out, "reasoning.summary", "auto")
out, _ = sjson.Set(out, "stream", true) out, _ = sjson.Set(out, "stream", true)
out, _ = sjson.Set(out, "store", false) out, _ = sjson.Set(out, "store", false)

View File

@@ -9,7 +9,6 @@ import (
"bytes" "bytes"
"strings" "strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
@@ -161,14 +160,11 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled // Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when type==enabled
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() { if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
modelInfo := registry.LookupModelInfo(modelName) if t.Get("type").String() == "enabled" {
if modelInfo != nil && modelInfo.Thinking != nil { if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number {
if t.Get("type").String() == "enabled" { budget := int(b.Int())
if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number { out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
budget := int(b.Int()) out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.includeThoughts", true)
out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
out, _ = sjson.Set(out, "request.generationConfig.thinkingConfig.include_thoughts", true)
}
} }
} }
} }

View File

@@ -9,7 +9,6 @@ import (
"bytes" "bytes"
"strings" "strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common" "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/gemini/common"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
@@ -153,16 +152,13 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
} }
// Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when enabled // Map Anthropic thinking -> Gemini thinkingBudget/include_thoughts when enabled
// Only apply for models that use numeric budgets, not discrete levels. // Translator only does format conversion, ApplyThinking handles model capability validation.
if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() { if t := gjson.GetBytes(rawJSON, "thinking"); t.Exists() && t.IsObject() {
modelInfo := registry.LookupModelInfo(modelName) if t.Get("type").String() == "enabled" {
if modelInfo != nil && modelInfo.Thinking != nil && len(modelInfo.Thinking.Levels) == 0 { if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number {
if t.Get("type").String() == "enabled" { budget := int(b.Int())
if b := t.Get("budget_tokens"); b.Exists() && b.Type == gjson.Number { out, _ = sjson.Set(out, "generationConfig.thinkingConfig.thinkingBudget", budget)
budget := int(b.Int()) out, _ = sjson.Set(out, "generationConfig.thinkingConfig.includeThoughts", true)
out, _ = sjson.Set(out, "generationConfig.thinkingConfig.thinkingBudget", budget)
out, _ = sjson.Set(out, "generationConfig.thinkingConfig.include_thoughts", true)
}
} }
} }
} }

View File

@@ -77,12 +77,15 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
} }
} }
// Convert thinkingBudget to reasoning_effort // Map Gemini thinkingConfig to OpenAI reasoning_effort.
// Always perform conversion to support allowCompat models that may not be in registry
if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() {
if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { if thinkingLevel := thinkingConfig.Get("thinkingLevel"); thinkingLevel.Exists() {
budget := int(thinkingBudget.Int()) effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
if effort, ok := thinking.ConvertBudgetToLevel(budget); ok && effort != "" { if effort != "" {
out, _ = sjson.Set(out, "reasoning_effort", effort)
}
} else if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() {
if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok {
out, _ = sjson.Set(out, "reasoning_effort", effort) out, _ = sjson.Set(out, "reasoning_effort", effort)
} }
} }