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..7efaba6cf 100644 --- a/service/convert.go +++ b/service/convert.go @@ -34,22 +34,34 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re isOpenRouter := info.ChannelType == constant.ChannelTypeOpenRouter - if claudeRequest.Thinking != nil && claudeRequest.Thinking.Type == "enabled" { - if isOpenRouter { - reasoning := openrouter.RequestReasoning{ - MaxTokens: claudeRequest.Thinking.GetBudgetTokens(), + if isOpenRouter { + if effort := claudeRequest.GetEfforts(); effort != "" { + effortBytes, _ := json.Marshal(effort) + openAIRequest.Verbosity = effortBytes + } + 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 - } else { - thinkingSuffix := "-thinking" - if strings.HasSuffix(info.OriginModelName, thinkingSuffix) && - !strings.HasSuffix(openAIRequest.Model, thinkingSuffix) { - openAIRequest.Model = openAIRequest.Model + thinkingSuffix - } + } + } else { + thinkingSuffix := "-thinking" + if strings.HasSuffix(info.OriginModelName, thinkingSuffix) && + !strings.HasSuffix(openAIRequest.Model, thinkingSuffix) { + openAIRequest.Model = openAIRequest.Model + thinkingSuffix } }