diff --git a/dto/openai_request.go b/dto/openai_request.go index 6f12a19e..812e14a5 100644 --- a/dto/openai_request.go +++ b/dto/openai_request.go @@ -99,6 +99,7 @@ type Message struct { Name *string `json:"name,omitempty"` Prefix *bool `json:"prefix,omitempty"` ReasoningContent string `json:"reasoning_content,omitempty"` + Reasoning string `json:"reasoning,omitempty"` ToolCalls json.RawMessage `json:"tool_calls,omitempty"` ToolCallId string `json:"tool_call_id,omitempty"` parsedContent []MediaContent diff --git a/dto/openai_response.go b/dto/openai_response.go index 56fac585..a1d728fe 100644 --- a/dto/openai_response.go +++ b/dto/openai_response.go @@ -64,6 +64,7 @@ type ChatCompletionsStreamResponseChoice struct { type ChatCompletionsStreamResponseChoiceDelta struct { Content *string `json:"content,omitempty"` ReasoningContent *string `json:"reasoning_content,omitempty"` + Reasoning *string `json:"reasoning,omitempty"` Role string `json:"role,omitempty"` ToolCalls []ToolCallResponse `json:"tool_calls,omitempty"` } @@ -80,14 +81,18 @@ func (c *ChatCompletionsStreamResponseChoiceDelta) GetContentString() string { } func (c *ChatCompletionsStreamResponseChoiceDelta) GetReasoningContent() string { - if c.ReasoningContent == nil { + if c.ReasoningContent == nil && c.Reasoning == nil { return "" } - return *c.ReasoningContent + if c.ReasoningContent != nil { + return *c.ReasoningContent + } + return *c.Reasoning } func (c *ChatCompletionsStreamResponseChoiceDelta) SetReasoningContent(s string) { c.ReasoningContent = &s + c.Reasoning = &s } type ToolCallResponse struct { diff --git a/relay/channel/openai/relay-openai.go b/relay/channel/openai/relay-openai.go index 0afe3f51..223ddd3d 100644 --- a/relay/channel/openai/relay-openai.go +++ b/relay/channel/openai/relay-openai.go @@ -4,10 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/bytedance/gopkg/util/gopool" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - "github.com/pkg/errors" "io" "math" "mime/multipart" @@ -21,6 +17,11 @@ import ( "one-api/service" "os" "strings" + + "github.com/bytedance/gopkg/util/gopool" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/pkg/errors" ) func sendStreamData(c *gin.Context, info *relaycommon.RelayInfo, data string, forceFormat bool, thinkToContent bool) error { @@ -42,10 +43,15 @@ func sendStreamData(c *gin.Context, info *relaycommon.RelayInfo, data string, fo } hasThinkingContent := false + hasContent := false + var thinkingContent strings.Builder for _, choice := range lastStreamResponse.Choices { if len(choice.Delta.GetReasoningContent()) > 0 { hasThinkingContent = true - break + thinkingContent.WriteString(choice.Delta.GetReasoningContent()) + } + if len(choice.Delta.GetContentString()) > 0 { + hasContent = true } } @@ -54,13 +60,13 @@ func sendStreamData(c *gin.Context, info *relaycommon.RelayInfo, data string, fo if hasThinkingContent { response := lastStreamResponse.Copy() for i := range response.Choices { - response.Choices[i].Delta.SetContentString("\n") - response.Choices[i].Delta.SetReasoningContent("") + // send `think` tag with thinking content + response.Choices[i].Delta.SetContentString("\n" + thinkingContent.String()) + response.Choices[i].Delta.ReasoningContent = nil + response.Choices[i].Delta.Reasoning = nil } info.ThinkingContentInfo.IsFirstThinkingContent = false return helper.ObjectData(c, response) - } else { - return helper.ObjectData(c, lastStreamResponse) } } @@ -71,11 +77,12 @@ func sendStreamData(c *gin.Context, info *relaycommon.RelayInfo, data string, fo // Process each choice for i, choice := range lastStreamResponse.Choices { // Handle transition from thinking to content - if len(choice.Delta.GetContentString()) > 0 && !info.ThinkingContentInfo.SendLastThinkingContent { + if hasContent && !info.ThinkingContentInfo.SendLastThinkingContent { response := lastStreamResponse.Copy() for j := range response.Choices { - response.Choices[j].Delta.SetContentString("\n\n\n") - response.Choices[j].Delta.SetReasoningContent("") + response.Choices[j].Delta.SetContentString("\n\n") + response.Choices[j].Delta.ReasoningContent = nil + response.Choices[j].Delta.Reasoning = nil } info.ThinkingContentInfo.SendLastThinkingContent = true helper.ObjectData(c, response) @@ -84,7 +91,12 @@ func sendStreamData(c *gin.Context, info *relaycommon.RelayInfo, data string, fo // Convert reasoning content to regular content if len(choice.Delta.GetReasoningContent()) > 0 { lastStreamResponse.Choices[i].Delta.SetContentString(choice.Delta.GetReasoningContent()) - lastStreamResponse.Choices[i].Delta.SetReasoningContent("") + lastStreamResponse.Choices[i].Delta.ReasoningContent = nil + lastStreamResponse.Choices[i].Delta.Reasoning = nil + } else if !hasThinkingContent && !hasContent { + // flush thinking content + lastStreamResponse.Choices[i].Delta.ReasoningContent = nil + lastStreamResponse.Choices[i].Delta.Reasoning = nil } } @@ -178,7 +190,10 @@ func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel //} for _, choice := range streamResponse.Choices { responseTextBuilder.WriteString(choice.Delta.GetContentString()) + + // handle both reasoning_content and reasoning responseTextBuilder.WriteString(choice.Delta.GetReasoningContent()) + if choice.Delta.ToolCalls != nil { if len(choice.Delta.ToolCalls) > toolCount { toolCount = len(choice.Delta.ToolCalls) @@ -199,7 +214,7 @@ func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel //} for _, choice := range streamResponse.Choices { responseTextBuilder.WriteString(choice.Delta.GetContentString()) - responseTextBuilder.WriteString(choice.Delta.GetReasoningContent()) + responseTextBuilder.WriteString(choice.Delta.GetReasoningContent()) // This will handle both reasoning_content and reasoning if choice.Delta.ToolCalls != nil { if len(choice.Delta.ToolCalls) > toolCount { toolCount = len(choice.Delta.ToolCalls) @@ -291,7 +306,7 @@ func OpenaiHandler(c *gin.Context, resp *http.Response, promptTokens int, model if simpleResponse.Usage.TotalTokens == 0 || (simpleResponse.Usage.PromptTokens == 0 && simpleResponse.Usage.CompletionTokens == 0) { completionTokens := 0 for _, choice := range simpleResponse.Choices { - ctkm, _ := service.CountTextToken(choice.Message.StringContent()+choice.Message.ReasoningContent, model) + ctkm, _ := service.CountTextToken(choice.Message.StringContent()+choice.Message.ReasoningContent+choice.Message.Reasoning, model) completionTokens += ctkm } simpleResponse.Usage = dto.Usage{