From f1d6f01585acaa5c4aa79f36957ef537767401bb Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Thu, 4 Sep 2025 09:43:22 +0800 Subject: [PATCH] Add reasoning/thinking configuration handling for Claude and OpenAI translators - Implemented `thinkingConfig` handling to allow reasoning effort configuration in request generation. - Added support for reasoning content deltas (`thinking_delta`) in response processing. - Enhanced reasoning-related token budget mappings for various reasoning levels. - Improved response handling logic to ensure proper reasoning content inclusion. --- .../claude/gemini/claude_gemini_request.go | 11 +++++++++++ .../claude/gemini/claude_gemini_response.go | 4 ++-- .../chat-completions/claude_openai_request.go | 15 +++++++++++++++ .../chat-completions/claude_openai_response.go | 17 ++++++++++++++--- .../claude_openai-responses_request.go | 17 +++++++++++++++++ 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/internal/translator/claude/gemini/claude_gemini_request.go b/internal/translator/claude/gemini/claude_gemini_request.go index 43a07000..f1668580 100644 --- a/internal/translator/claude/gemini/claude_gemini_request.go +++ b/internal/translator/claude/gemini/claude_gemini_request.go @@ -89,6 +89,17 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream out, _ = sjson.Set(out, "stop_sequences", stopSequences) } } + // Include thoughts configuration for reasoning process visibility + if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { + if includeThoughts := thinkingConfig.Get("include_thoughts"); includeThoughts.Exists() { + if includeThoughts.Type == gjson.True { + out, _ = sjson.Set(out, "thinking.type", "enabled") + if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { + out, _ = sjson.Set(out, "thinking.budget_tokens", thinkingBudget.Int()) + } + } + } + } } // System instruction conversion to Claude Code format diff --git a/internal/translator/claude/gemini/claude_gemini_response.go b/internal/translator/claude/gemini/claude_gemini_response.go index aa62e03f..aab4b344 100644 --- a/internal/translator/claude/gemini/claude_gemini_response.go +++ b/internal/translator/claude/gemini/claude_gemini_response.go @@ -128,7 +128,7 @@ func ConvertClaudeResponseToGemini(_ context.Context, modelName string, original } case "thinking_delta": // Thinking/reasoning content delta for models with reasoning capabilities - if text := delta.Get("text"); text.Exists() && text.String() != "" { + if text := delta.Get("thinking"); text.Exists() && text.String() != "" { thinkingPart := `{"thought":true,"text":""}` thinkingPart, _ = sjson.Set(thinkingPart, "text", text.String()) template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", thinkingPart) @@ -411,7 +411,7 @@ func ConvertClaudeResponseToGeminiNonStream(_ context.Context, modelName string, } case "thinking_delta": // Process reasoning/thinking content - if text := delta.Get("text"); text.Exists() && text.String() != "" { + if text := delta.Get("thinking"); text.Exists() && text.String() != "" { partJSON := `{"thought":true,"text":""}` partJSON, _ = sjson.Set(partJSON, "text", text.String()) part := gjson.Parse(partJSON).Value().(map[string]interface{}) diff --git a/internal/translator/claude/openai/chat-completions/claude_openai_request.go b/internal/translator/claude/openai/chat-completions/claude_openai_request.go index 78f5ab5b..b978a411 100644 --- a/internal/translator/claude/openai/chat-completions/claude_openai_request.go +++ b/internal/translator/claude/openai/chat-completions/claude_openai_request.go @@ -41,6 +41,21 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream root := gjson.ParseBytes(rawJSON) + if v := root.Get("reasoning_effort"); v.Exists() { + out, _ = sjson.Set(out, "thinking.type", "enabled") + + switch v.String() { + case "none": + out, _ = sjson.Set(out, "thinking.type", "disabled") + case "low": + out, _ = sjson.Set(out, "thinking.budget_tokens", 1024) + case "medium": + out, _ = sjson.Set(out, "thinking.budget_tokens", 8192) + case "high": + out, _ = sjson.Set(out, "thinking.budget_tokens", 24576) + } + } + // Helper for generating tool call IDs in the form: toolu_ // This ensures unique identifiers for tool calls in the Claude Code format genToolCallID := func() string { diff --git a/internal/translator/claude/openai/chat-completions/claude_openai_response.go b/internal/translator/claude/openai/chat-completions/claude_openai_response.go index 9ba58110..7cdbdfd0 100644 --- a/internal/translator/claude/openai/chat-completions/claude_openai_response.go +++ b/internal/translator/claude/openai/chat-completions/claude_openai_response.go @@ -128,10 +128,11 @@ func ConvertClaudeResponseToOpenAI(_ context.Context, modelName string, original return []string{} } } - return []string{template} + return []string{} case "content_block_delta": // Handle content delta (text, tool use arguments, or reasoning content) + hasContent := false if delta := root.Get("delta"); delta.Exists() { deltaType := delta.Get("type").String() @@ -140,8 +141,14 @@ func ConvertClaudeResponseToOpenAI(_ context.Context, modelName string, original // Text content delta - send incremental text updates if text := delta.Get("text"); text.Exists() { template, _ = sjson.Set(template, "choices.0.delta.content", text.String()) + hasContent = true + } + case "thinking_delta": + // Accumulate reasoning/thinking content + if thinking := delta.Get("thinking"); thinking.Exists() { + template, _ = sjson.Set(template, "choices.0.delta.reasoning_content", thinking.String()) + hasContent = true } - case "input_json_delta": // Tool use input delta - accumulate arguments for tool calls if partialJSON := delta.Get("partial_json"); partialJSON.Exists() { @@ -156,7 +163,11 @@ func ConvertClaudeResponseToOpenAI(_ context.Context, modelName string, original return []string{} } } - return []string{template} + if hasContent { + return []string{template} + } else { + return []string{} + } case "content_block_stop": // End of content block - output complete tool call if it's a tool_use block diff --git a/internal/translator/claude/openai/responses/claude_openai-responses_request.go b/internal/translator/claude/openai/responses/claude_openai-responses_request.go index 305b732f..4b6d828c 100644 --- a/internal/translator/claude/openai/responses/claude_openai-responses_request.go +++ b/internal/translator/claude/openai/responses/claude_openai-responses_request.go @@ -28,6 +28,23 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte root := gjson.ParseBytes(rawJSON) + if v := root.Get("reasoning.effort"); v.Exists() { + out, _ = sjson.Set(out, "thinking.type", "enabled") + + switch v.String() { + case "none": + out, _ = sjson.Set(out, "thinking.type", "disabled") + case "minimal": + out, _ = sjson.Set(out, "thinking.budget_tokens", 1024) + case "low": + out, _ = sjson.Set(out, "thinking.budget_tokens", 4096) + case "medium": + out, _ = sjson.Set(out, "thinking.budget_tokens", 8192) + case "high": + out, _ = sjson.Set(out, "thinking.budget_tokens", 24576) + } + } + // Helper for generating tool call IDs when missing genToolCallID := func() string { const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"