From 2a34ef048c6640a83aa2fa79180bd5920bba7a4f Mon Sep 17 00:00:00 2001 From: Badgerbees Date: Sat, 21 Mar 2026 17:50:58 +0700 Subject: [PATCH] fix(providers): add thinking and reasoning fallback for OpenAI-compatible providers (Ollama) --- pkg/providers/common/common.go | 25 +++++++++++++- pkg/providers/openai_compat/provider.go | 44 ++++++++++++++++++------- pkg/providers/protocoltypes/types.go | 5 ++- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/pkg/providers/common/common.go b/pkg/providers/common/common.go index 23680a1bf..ca06b2bfc 100644 --- a/pkg/providers/common/common.go +++ b/pkg/providers/common/common.go @@ -73,6 +73,8 @@ type openaiMessage struct { Role string `json:"role"` Content string `json:"content"` ReasoningContent string `json:"reasoning_content,omitempty"` + Reasoning string `json:"reasoning,omitempty"` + Thinking string `json:"thinking,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"` } @@ -89,6 +91,8 @@ func SerializeMessages(messages []Message) []any { Role: m.Role, Content: m.Content, ReasoningContent: m.ReasoningContent, + Reasoning: m.Reasoning, + Thinking: m.Thinking, ToolCalls: m.ToolCalls, ToolCallID: m.ToolCallID, }) @@ -127,6 +131,12 @@ func SerializeMessages(messages []Message) []any { if m.ReasoningContent != "" { msg["reasoning_content"] = m.ReasoningContent } + if m.Reasoning != "" { + msg["reasoning"] = m.Reasoning + } + if m.Thinking != "" { + msg["thinking"] = m.Thinking + } out = append(out, msg) } return out @@ -142,6 +152,7 @@ func ParseResponse(body io.Reader) (*LLMResponse, error) { Content string `json:"content"` ReasoningContent string `json:"reasoning_content"` Reasoning string `json:"reasoning"` + Thinking string `json:"thinking"` ReasoningDetails []ReasoningDetail `json:"reasoning_details"` ToolCalls []struct { ID string `json:"id"` @@ -208,10 +219,22 @@ func ParseResponse(body io.Reader) (*LLMResponse, error) { toolCalls = append(toolCalls, toolCall) } + content := choice.Message.Content + if content == "" { + if choice.Message.Thinking != "" { + content = choice.Message.Thinking + } else if choice.Message.Reasoning != "" { + content = choice.Message.Reasoning + } else if choice.Message.ReasoningContent != "" { + content = choice.Message.ReasoningContent + } + } + return &LLMResponse{ - Content: choice.Message.Content, + Content: content, ReasoningContent: choice.Message.ReasoningContent, Reasoning: choice.Message.Reasoning, + Thinking: choice.Message.Thinking, ReasoningDetails: choice.Message.ReasoningDetails, ToolCalls: toolCalls, FinishReason: choice.FinishReason, diff --git a/pkg/providers/openai_compat/provider.go b/pkg/providers/openai_compat/provider.go index 938e4ea8b..0f2930bd0 100644 --- a/pkg/providers/openai_compat/provider.go +++ b/pkg/providers/openai_compat/provider.go @@ -241,6 +241,9 @@ func parseStreamResponse( onChunk func(accumulated string), ) (*LLMResponse, error) { var textContent strings.Builder + var reasoningContent strings.Builder + var reasoning strings.Builder + var thinking strings.Builder var finishReason string var usage *UsageInfo @@ -273,8 +276,11 @@ func parseStreamResponse( var chunk struct { Choices []struct { Delta struct { - Content string `json:"content"` - ToolCalls []struct { + Content string `json:"content"` + ReasoningContent string `json:"reasoning_content"` + Reasoning string `json:"reasoning"` + Thinking string `json:"thinking"` + ToolCalls []struct { Index int `json:"index"` ID string `json:"id"` Function *struct { @@ -301,13 +307,24 @@ func parseStreamResponse( } choice := chunk.Choices[0] + delta := choice.Delta - // Accumulate text content - if choice.Delta.Content != "" { - textContent.WriteString(choice.Delta.Content) - if onChunk != nil { - onChunk(textContent.String()) - } + // Accumulate text content with fallback to reasoning/thinking + if delta.Content != "" { + textContent.WriteString(delta.Content) + } else if delta.Thinking != "" { + thinking.WriteString(delta.Thinking) + textContent.WriteString(delta.Thinking) // Surface reasoning to user + } else if delta.Reasoning != "" { + reasoning.WriteString(delta.Reasoning) + textContent.WriteString(delta.Reasoning) // Surface reasoning to user + } else if delta.ReasoningContent != "" { + reasoningContent.WriteString(delta.ReasoningContent) + textContent.WriteString(delta.ReasoningContent) // Surface reasoning to user + } + + if onChunk != nil { + onChunk(textContent.String()) } // Accumulate tool call deltas @@ -366,10 +383,13 @@ func parseStreamResponse( } return &LLMResponse{ - Content: textContent.String(), - ToolCalls: toolCalls, - FinishReason: finishReason, - Usage: usage, + Content: textContent.String(), + ReasoningContent: reasoningContent.String(), + Reasoning: reasoning.String(), + Thinking: thinking.String(), + ToolCalls: toolCalls, + FinishReason: finishReason, + Usage: usage, }, nil } diff --git a/pkg/providers/protocoltypes/types.go b/pkg/providers/protocoltypes/types.go index 194c1aa6f..0ad2e3b24 100644 --- a/pkg/providers/protocoltypes/types.go +++ b/pkg/providers/protocoltypes/types.go @@ -27,10 +27,11 @@ type FunctionCall struct { type LLMResponse struct { Content string `json:"content"` ReasoningContent string `json:"reasoning_content,omitempty"` + Reasoning string `json:"reasoning,omitempty"` + Thinking string `json:"thinking,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` FinishReason string `json:"finish_reason"` Usage *UsageInfo `json:"usage,omitempty"` - Reasoning string `json:"reasoning"` ReasoningDetails []ReasoningDetail `json:"reasoning_details"` } @@ -67,6 +68,8 @@ type Message struct { Content string `json:"content"` Media []string `json:"media,omitempty"` ReasoningContent string `json:"reasoning_content,omitempty"` + Reasoning string `json:"reasoning,omitempty"` + Thinking string `json:"thinking,omitempty"` SystemParts []ContentBlock `json:"system_parts,omitempty"` // structured system blocks for cache-aware adapters ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"`