diff --git a/dto/claude.go b/dto/claude.go index e7d87c41f..73bfa9c54 100644 --- a/dto/claude.go +++ b/dto/claude.go @@ -218,6 +218,11 @@ type ClaudeRequest struct { ServiceTier string `json:"service_tier,omitempty"` } +// OutputConfigForEffort just for extract effort +type OutputConfigForEffort struct { + Effort string `json:"effort,omitempty"` +} + // createClaudeFileSource 根据数据内容创建正确类型的 FileSource func createClaudeFileSource(data string) *types.FileSource { if strings.HasPrefix(data, "http://") || strings.HasPrefix(data, "https://") { @@ -409,6 +414,15 @@ func (c *ClaudeRequest) GetTools() []any { } } +func (c *ClaudeRequest) GetEfforts() string { + var OutputConfig OutputConfigForEffort + if err := json.Unmarshal(c.OutputConfig, &OutputConfig); err == nil { + effort := OutputConfig.Effort + return effort + } + return "" +} + // ProcessTools 处理工具列表,支持类型断言 func ProcessTools(tools []any) ([]*Tool, []*ClaudeWebSearchTool) { var normalTools []*Tool diff --git a/relay/channel/openai/adaptor.go b/relay/channel/openai/adaptor.go index ed2c70c1e..59fadedac 100644 --- a/relay/channel/openai/adaptor.go +++ b/relay/channel/openai/adaptor.go @@ -298,6 +298,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn } reasoning := openrouter.RequestReasoning{ + Enabled: true, MaxTokens: *thinking.BudgetTokens, } diff --git a/relay/channel/openrouter/dto.go b/relay/channel/openrouter/dto.go index a32499852..73a1e445a 100644 --- a/relay/channel/openrouter/dto.go +++ b/relay/channel/openrouter/dto.go @@ -3,6 +3,7 @@ package openrouter import "encoding/json" type RequestReasoning struct { + Enabled bool `json:"enabled"` // One of the following (not both): Effort string `json:"effort,omitempty"` // Can be "high", "medium", or "low" (OpenAI-style) MaxTokens int `json:"max_tokens,omitempty"` // Specific token limit (Anthropic-style) diff --git a/service/convert.go b/service/convert.go index f249981b5..ad2854b44 100644 --- a/service/convert.go +++ b/service/convert.go @@ -34,16 +34,30 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re isOpenRouter := info.ChannelType == constant.ChannelTypeOpenRouter - if claudeRequest.Thinking != nil && claudeRequest.Thinking.Type == "enabled" { + if claudeRequest.Thinking != nil && (claudeRequest.Thinking.Type == "enabled" || claudeRequest.Thinking.Type == "adaptive") { if isOpenRouter { - reasoning := openrouter.RequestReasoning{ - MaxTokens: claudeRequest.Thinking.GetBudgetTokens(), + if effort := claudeRequest.GetEfforts(); effort != "" { + effortBytes, _ := json.Marshal(effort) + openAIRequest.Verbosity = effortBytes } - reasoningJSON, err := json.Marshal(reasoning) - if err != nil { - return nil, fmt.Errorf("failed to marshal reasoning: %w", err) + if claudeRequest.Thinking != nil { + var reasoning openrouter.RequestReasoning + if claudeRequest.Thinking.Type == "enabled" { + reasoning = openrouter.RequestReasoning{ + Enabled: true, + MaxTokens: claudeRequest.Thinking.GetBudgetTokens(), + } + } else if claudeRequest.Thinking.Type == "adaptive" { + reasoning = openrouter.RequestReasoning{ + Enabled: true, + } + } + reasoningJSON, err := json.Marshal(reasoning) + if err != nil { + return nil, fmt.Errorf("failed to marshal reasoning: %w", err) + } + openAIRequest.Reasoning = reasoningJSON } - openAIRequest.Reasoning = reasoningJSON } else { thinkingSuffix := "-thinking" if strings.HasSuffix(info.OriginModelName, thinkingSuffix) &&