fix(providers): add thinking and reasoning fallback for OpenAI-compatible providers (Ollama)

This commit is contained in:
Badgerbees 2026-03-21 17:50:58 +07:00
parent 6148ccc529
commit 2a34ef048c
3 changed files with 60 additions and 14 deletions

View file

@ -73,6 +73,8 @@ type openaiMessage struct {
Role string `json:"role"` Role string `json:"role"`
Content string `json:"content"` Content string `json:"content"`
ReasoningContent string `json:"reasoning_content,omitempty"` ReasoningContent string `json:"reasoning_content,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
Thinking string `json:"thinking,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"`
} }
@ -89,6 +91,8 @@ func SerializeMessages(messages []Message) []any {
Role: m.Role, Role: m.Role,
Content: m.Content, Content: m.Content,
ReasoningContent: m.ReasoningContent, ReasoningContent: m.ReasoningContent,
Reasoning: m.Reasoning,
Thinking: m.Thinking,
ToolCalls: m.ToolCalls, ToolCalls: m.ToolCalls,
ToolCallID: m.ToolCallID, ToolCallID: m.ToolCallID,
}) })
@ -127,6 +131,12 @@ func SerializeMessages(messages []Message) []any {
if m.ReasoningContent != "" { if m.ReasoningContent != "" {
msg["reasoning_content"] = 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) out = append(out, msg)
} }
return out return out
@ -142,6 +152,7 @@ func ParseResponse(body io.Reader) (*LLMResponse, error) {
Content string `json:"content"` Content string `json:"content"`
ReasoningContent string `json:"reasoning_content"` ReasoningContent string `json:"reasoning_content"`
Reasoning string `json:"reasoning"` Reasoning string `json:"reasoning"`
Thinking string `json:"thinking"`
ReasoningDetails []ReasoningDetail `json:"reasoning_details"` ReasoningDetails []ReasoningDetail `json:"reasoning_details"`
ToolCalls []struct { ToolCalls []struct {
ID string `json:"id"` ID string `json:"id"`
@ -208,10 +219,22 @@ func ParseResponse(body io.Reader) (*LLMResponse, error) {
toolCalls = append(toolCalls, toolCall) 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{ return &LLMResponse{
Content: choice.Message.Content, Content: content,
ReasoningContent: choice.Message.ReasoningContent, ReasoningContent: choice.Message.ReasoningContent,
Reasoning: choice.Message.Reasoning, Reasoning: choice.Message.Reasoning,
Thinking: choice.Message.Thinking,
ReasoningDetails: choice.Message.ReasoningDetails, ReasoningDetails: choice.Message.ReasoningDetails,
ToolCalls: toolCalls, ToolCalls: toolCalls,
FinishReason: choice.FinishReason, FinishReason: choice.FinishReason,

View file

@ -241,6 +241,9 @@ func parseStreamResponse(
onChunk func(accumulated string), onChunk func(accumulated string),
) (*LLMResponse, error) { ) (*LLMResponse, error) {
var textContent strings.Builder var textContent strings.Builder
var reasoningContent strings.Builder
var reasoning strings.Builder
var thinking strings.Builder
var finishReason string var finishReason string
var usage *UsageInfo var usage *UsageInfo
@ -274,6 +277,9 @@ func parseStreamResponse(
Choices []struct { Choices []struct {
Delta struct { Delta struct {
Content string `json:"content"` Content string `json:"content"`
ReasoningContent string `json:"reasoning_content"`
Reasoning string `json:"reasoning"`
Thinking string `json:"thinking"`
ToolCalls []struct { ToolCalls []struct {
Index int `json:"index"` Index int `json:"index"`
ID string `json:"id"` ID string `json:"id"`
@ -301,14 +307,25 @@ func parseStreamResponse(
} }
choice := chunk.Choices[0] choice := chunk.Choices[0]
delta := choice.Delta
// 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
}
// Accumulate text content
if choice.Delta.Content != "" {
textContent.WriteString(choice.Delta.Content)
if onChunk != nil { if onChunk != nil {
onChunk(textContent.String()) onChunk(textContent.String())
} }
}
// Accumulate tool call deltas // Accumulate tool call deltas
for _, tc := range choice.Delta.ToolCalls { for _, tc := range choice.Delta.ToolCalls {
@ -367,6 +384,9 @@ func parseStreamResponse(
return &LLMResponse{ return &LLMResponse{
Content: textContent.String(), Content: textContent.String(),
ReasoningContent: reasoningContent.String(),
Reasoning: reasoning.String(),
Thinking: thinking.String(),
ToolCalls: toolCalls, ToolCalls: toolCalls,
FinishReason: finishReason, FinishReason: finishReason,
Usage: usage, Usage: usage,

View file

@ -27,10 +27,11 @@ type FunctionCall struct {
type LLMResponse struct { type LLMResponse struct {
Content string `json:"content"` Content string `json:"content"`
ReasoningContent string `json:"reasoning_content,omitempty"` ReasoningContent string `json:"reasoning_content,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
Thinking string `json:"thinking,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"`
FinishReason string `json:"finish_reason"` FinishReason string `json:"finish_reason"`
Usage *UsageInfo `json:"usage,omitempty"` Usage *UsageInfo `json:"usage,omitempty"`
Reasoning string `json:"reasoning"`
ReasoningDetails []ReasoningDetail `json:"reasoning_details"` ReasoningDetails []ReasoningDetail `json:"reasoning_details"`
} }
@ -67,6 +68,8 @@ type Message struct {
Content string `json:"content"` Content string `json:"content"`
Media []string `json:"media,omitempty"` Media []string `json:"media,omitempty"`
ReasoningContent string `json:"reasoning_content,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 SystemParts []ContentBlock `json:"system_parts,omitempty"` // structured system blocks for cache-aware adapters
ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"` ToolCallID string `json:"tool_call_id,omitempty"`