feat: support claude cache and thinking for upstream [OpenRouter] (#983)

* feat: support claude cache for upstream [OpenRouter]

* feat: support claude thinking for upstream [OpenRouter]

* feat: reasoning is common params for OpenRouter
This commit is contained in:
neotf
2025-06-05 17:35:48 +08:00
committed by GitHub
parent a8f4ae2a73
commit 3665ad672e
4 changed files with 65 additions and 22 deletions

View File

@@ -18,6 +18,7 @@ type ClaudeMediaMessage struct {
Thinking string `json:"thinking,omitempty"`
Signature string `json:"signature,omitempty"`
Delta string `json:"delta,omitempty"`
CacheControl json.RawMessage `json:"cache_control,omitempty"`
// tool_calls
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`

View File

@@ -29,7 +29,6 @@ type GeneralOpenAIRequest struct {
MaxTokens uint `json:"max_tokens,omitempty"`
MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"`
//Reasoning json.RawMessage `json:"reasoning,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
@@ -56,6 +55,8 @@ type GeneralOpenAIRequest struct {
EnableThinking any `json:"enable_thinking,omitempty"` // ali
ExtraBody any `json:"extra_body,omitempty"`
WebSearchOptions *WebSearchOptions `json:"web_search_options,omitempty"`
// OpenRouter Params
Reasoning json.RawMessage `json:"reasoning,omitempty"`
}
func (r *GeneralOpenAIRequest) ToMap() map[string]any {
@@ -125,6 +126,8 @@ type MediaContent struct {
InputAudio any `json:"input_audio,omitempty"`
File any `json:"file,omitempty"`
VideoUrl any `json:"video_url,omitempty"`
// OpenRouter Params
CacheControl json.RawMessage `json:"cache_control,omitempty"`
}
func (m *MediaContent) GetImageMedia() *MessageImageUrl {

View File

@@ -0,0 +1,9 @@
package openrouter
type RequestReasoning struct {
// 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)
// Optional: Default is false. All models support this.
Exclude bool `json:"exclude,omitempty"` // Set to true to exclude reasoning tokens from response
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"one-api/common"
"one-api/dto"
"one-api/relay/channel/openrouter"
relaycommon "one-api/relay/common"
"strings"
)
@@ -18,10 +19,24 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re
Stream: claudeRequest.Stream,
}
isOpenRouter := info.ChannelType == common.ChannelTypeOpenRouter
if claudeRequest.Thinking != nil {
if strings.HasSuffix(info.OriginModelName, "-thinking") &&
!strings.HasSuffix(claudeRequest.Model, "-thinking") {
openAIRequest.Model = openAIRequest.Model + "-thinking"
if isOpenRouter {
reasoning := openrouter.RequestReasoning{
MaxTokens: claudeRequest.Thinking.BudgetTokens,
}
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
}
}
}
@@ -62,16 +77,30 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re
} else {
systems := claudeRequest.ParseSystem()
if len(systems) > 0 {
systemStr := ""
openAIMessage := dto.Message{
Role: "system",
}
isOpenRouterClaude := isOpenRouter && strings.HasPrefix(info.UpstreamModelName, "anthropic/claude")
if isOpenRouterClaude {
systemMediaMessages := make([]dto.MediaContent, 0, len(systems))
for _, system := range systems {
message := dto.MediaContent{
Type: "text",
Text: system.GetText(),
CacheControl: system.CacheControl,
}
systemMediaMessages = append(systemMediaMessages, message)
}
openAIMessage.SetMediaContent(systemMediaMessages)
} else {
systemStr := ""
for _, system := range systems {
if system.Text != nil {
systemStr += *system.Text
}
}
openAIMessage.SetStringContent(systemStr)
}
openAIMessages = append(openAIMessages, openAIMessage)
}
}
@@ -99,6 +128,7 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re
message := dto.MediaContent{
Type: "text",
Text: mediaMsg.GetText(),
CacheControl: mediaMsg.CacheControl,
}
mediaMessages = append(mediaMessages, message)
case "image":