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"`
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,

View file

@ -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
@ -274,6 +277,9 @@ func parseStreamResponse(
Choices []struct {
Delta 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"`
@ -301,14 +307,25 @@ func parseStreamResponse(
}
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 {
onChunk(textContent.String())
}
}
// Accumulate tool call deltas
for _, tc := range choice.Delta.ToolCalls {
@ -367,6 +384,9 @@ func parseStreamResponse(
return &LLMResponse{
Content: textContent.String(),
ReasoningContent: reasoningContent.String(),
Reasoning: reasoning.String(),
Thinking: thinking.String(),
ToolCalls: toolCalls,
FinishReason: finishReason,
Usage: usage,

View file

@ -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"`