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:
@@ -7,17 +7,18 @@ type ClaudeMetadata struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClaudeMediaMessage struct {
|
type ClaudeMediaMessage struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Text *string `json:"text,omitempty"`
|
Text *string `json:"text,omitempty"`
|
||||||
Model string `json:"model,omitempty"`
|
Model string `json:"model,omitempty"`
|
||||||
Source *ClaudeMessageSource `json:"source,omitempty"`
|
Source *ClaudeMessageSource `json:"source,omitempty"`
|
||||||
Usage *ClaudeUsage `json:"usage,omitempty"`
|
Usage *ClaudeUsage `json:"usage,omitempty"`
|
||||||
StopReason *string `json:"stop_reason,omitempty"`
|
StopReason *string `json:"stop_reason,omitempty"`
|
||||||
PartialJson *string `json:"partial_json,omitempty"`
|
PartialJson *string `json:"partial_json,omitempty"`
|
||||||
Role string `json:"role,omitempty"`
|
Role string `json:"role,omitempty"`
|
||||||
Thinking string `json:"thinking,omitempty"`
|
Thinking string `json:"thinking,omitempty"`
|
||||||
Signature string `json:"signature,omitempty"`
|
Signature string `json:"signature,omitempty"`
|
||||||
Delta string `json:"delta,omitempty"`
|
Delta string `json:"delta,omitempty"`
|
||||||
|
CacheControl json.RawMessage `json:"cache_control,omitempty"`
|
||||||
// tool_calls
|
// tool_calls
|
||||||
Id string `json:"id,omitempty"`
|
Id string `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ type GeneralOpenAIRequest struct {
|
|||||||
MaxTokens uint `json:"max_tokens,omitempty"`
|
MaxTokens uint `json:"max_tokens,omitempty"`
|
||||||
MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
|
MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
|
||||||
ReasoningEffort string `json:"reasoning_effort,omitempty"`
|
ReasoningEffort string `json:"reasoning_effort,omitempty"`
|
||||||
//Reasoning json.RawMessage `json:"reasoning,omitempty"`
|
|
||||||
Temperature *float64 `json:"temperature,omitempty"`
|
Temperature *float64 `json:"temperature,omitempty"`
|
||||||
TopP float64 `json:"top_p,omitempty"`
|
TopP float64 `json:"top_p,omitempty"`
|
||||||
TopK int `json:"top_k,omitempty"`
|
TopK int `json:"top_k,omitempty"`
|
||||||
@@ -56,6 +55,8 @@ type GeneralOpenAIRequest struct {
|
|||||||
EnableThinking any `json:"enable_thinking,omitempty"` // ali
|
EnableThinking any `json:"enable_thinking,omitempty"` // ali
|
||||||
ExtraBody any `json:"extra_body,omitempty"`
|
ExtraBody any `json:"extra_body,omitempty"`
|
||||||
WebSearchOptions *WebSearchOptions `json:"web_search_options,omitempty"`
|
WebSearchOptions *WebSearchOptions `json:"web_search_options,omitempty"`
|
||||||
|
// OpenRouter Params
|
||||||
|
Reasoning json.RawMessage `json:"reasoning,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GeneralOpenAIRequest) ToMap() map[string]any {
|
func (r *GeneralOpenAIRequest) ToMap() map[string]any {
|
||||||
@@ -125,6 +126,8 @@ type MediaContent struct {
|
|||||||
InputAudio any `json:"input_audio,omitempty"`
|
InputAudio any `json:"input_audio,omitempty"`
|
||||||
File any `json:"file,omitempty"`
|
File any `json:"file,omitempty"`
|
||||||
VideoUrl any `json:"video_url,omitempty"`
|
VideoUrl any `json:"video_url,omitempty"`
|
||||||
|
// OpenRouter Params
|
||||||
|
CacheControl json.RawMessage `json:"cache_control,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MediaContent) GetImageMedia() *MessageImageUrl {
|
func (m *MediaContent) GetImageMedia() *MessageImageUrl {
|
||||||
|
|||||||
9
relay/channel/openrouter/dto.go
Normal file
9
relay/channel/openrouter/dto.go
Normal 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
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
"one-api/dto"
|
"one-api/dto"
|
||||||
|
"one-api/relay/channel/openrouter"
|
||||||
relaycommon "one-api/relay/common"
|
relaycommon "one-api/relay/common"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -18,10 +19,24 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re
|
|||||||
Stream: claudeRequest.Stream,
|
Stream: claudeRequest.Stream,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOpenRouter := info.ChannelType == common.ChannelTypeOpenRouter
|
||||||
|
|
||||||
if claudeRequest.Thinking != nil {
|
if claudeRequest.Thinking != nil {
|
||||||
if strings.HasSuffix(info.OriginModelName, "-thinking") &&
|
if isOpenRouter {
|
||||||
!strings.HasSuffix(claudeRequest.Model, "-thinking") {
|
reasoning := openrouter.RequestReasoning{
|
||||||
openAIRequest.Model = openAIRequest.Model + "-thinking"
|
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 {
|
} else {
|
||||||
systems := claudeRequest.ParseSystem()
|
systems := claudeRequest.ParseSystem()
|
||||||
if len(systems) > 0 {
|
if len(systems) > 0 {
|
||||||
systemStr := ""
|
|
||||||
openAIMessage := dto.Message{
|
openAIMessage := dto.Message{
|
||||||
Role: "system",
|
Role: "system",
|
||||||
}
|
}
|
||||||
for _, system := range systems {
|
isOpenRouterClaude := isOpenRouter && strings.HasPrefix(info.UpstreamModelName, "anthropic/claude")
|
||||||
if system.Text != nil {
|
if isOpenRouterClaude {
|
||||||
systemStr += *system.Text
|
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)
|
||||||
}
|
}
|
||||||
openAIMessage.SetStringContent(systemStr)
|
|
||||||
openAIMessages = append(openAIMessages, openAIMessage)
|
openAIMessages = append(openAIMessages, openAIMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,8 +126,9 @@ func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.Re
|
|||||||
switch mediaMsg.Type {
|
switch mediaMsg.Type {
|
||||||
case "text":
|
case "text":
|
||||||
message := dto.MediaContent{
|
message := dto.MediaContent{
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Text: mediaMsg.GetText(),
|
Text: mediaMsg.GetText(),
|
||||||
|
CacheControl: mediaMsg.CacheControl,
|
||||||
}
|
}
|
||||||
mediaMessages = append(mediaMessages, message)
|
mediaMessages = append(mediaMessages, message)
|
||||||
case "image":
|
case "image":
|
||||||
|
|||||||
Reference in New Issue
Block a user