add unit tests for ollama thinking fallback (streaming and non-streaming)

This commit is contained in:
Badgerbees 2026-03-21 18:04:42 +07:00
parent 2a34ef048c
commit 43de821b08
2 changed files with 136 additions and 0 deletions

View file

@ -556,3 +556,65 @@ func TestParseResponse_WithThoughtSignature(t *testing.T) {
out.ToolCalls[0].ExtraContent.Google.ThoughtSignature, "sig123") out.ToolCalls[0].ExtraContent.Google.ThoughtSignature, "sig123")
} }
} }
func TestSerializeMessages_WithReasoningFallbackFields(t *testing.T) {
messages := []Message{
{
Role: "assistant",
Content: "final answer",
ReasoningContent: "deepseek thinking",
Reasoning: "ollama reasoning",
Thinking: "ollama thinking",
},
}
result := SerializeMessages(messages)
data, _ := json.Marshal(result)
var msgs []map[string]any
json.Unmarshal(data, &msgs)
if msgs[0]["reasoning_content"] != "deepseek thinking" {
t.Errorf("reasoning_content mismatch, got %v", msgs[0]["reasoning_content"])
}
if msgs[0]["reasoning"] != "ollama reasoning" {
t.Errorf("reasoning mismatch, got %v", msgs[0]["reasoning"])
}
if msgs[0]["thinking"] != "ollama thinking" {
t.Errorf("thinking mismatch, got %v", msgs[0]["thinking"])
}
}
func TestParseResponse_WithOllamaThinkingFallback(t *testing.T) {
// Test thinking fallback
body := `{"choices":[{"message":{"content":"","thinking":"I am thinking..."},"finish_reason":"stop"}]}`
out, err := ParseResponse(strings.NewReader(body))
if err != nil {
t.Fatalf("ParseResponse() error = %v", err)
}
if out.Content != "I am thinking..." {
t.Errorf("Content fallback to thinking failed, got %q", out.Content)
}
if out.Thinking != "I am thinking..." {
t.Errorf("Thinking field not preserved, got %q", out.Thinking)
}
// Test reasoning fallback
body = `{"choices":[{"message":{"content":"","reasoning":"Ollama reasoning text"},"finish_reason":"stop"}]}`
out, err = ParseResponse(strings.NewReader(body))
if err != nil {
t.Fatalf("ParseResponse() error = %v", err)
}
if out.Content != "Ollama reasoning text" {
t.Errorf("Content fallback to reasoning failed, got %q", out.Content)
}
// Test priority: content > thinking > reasoning
body = `{"choices":[{"message":{"content":"real content","thinking":"hidden thinking"},"finish_reason":"stop"}]}`
out, err = ParseResponse(strings.NewReader(body))
if err != nil {
t.Fatalf("ParseResponse() error = %v", err)
}
if out.Content != "real content" {
t.Errorf("Content should have priority, got %q", out.Content)
}
}

View file

@ -1089,3 +1089,77 @@ func TestSerializeMessages_StripsSystemParts(t *testing.T) {
t.Fatal("system_parts should not appear in serialized output") t.Fatal("system_parts should not appear in serialized output")
} }
} }
func TestParseStreamResponse_ThinkingFallback(t *testing.T) {
// Mock SSE stream with thinking/reasoning chunks
stream := `data: {"choices":[{"delta":{"thinking":"Let me "},"index":0}]}
data: {"choices":[{"delta":{"thinking":"think... "},"index":0}]}
data: {"choices":[{"delta":{"content":"The answer is "},"index":0}]}
data: {"choices":[{"delta":{"content":"42"},"index":0}]}
data: [DONE]
`
var accumulated string
onChunk := func(acc string) {
accumulated = acc
}
resp, err := parseStreamResponse(t.Context(), strings.NewReader(stream), onChunk)
if err != nil {
t.Fatalf("parseStreamResponse() error = %v", err)
}
// Verify Thinking field is populated
if resp.Thinking != "Let me think... " {
t.Errorf("resp.Thinking = %q, want %q", resp.Thinking, "Let me think... ")
}
// Verify Content contains BOTH thinking (as fallback/prefix) and content
expectedContent := "Let me think... The answer is 42"
if resp.Content != expectedContent {
t.Errorf("resp.Content = %q, want %q", resp.Content, expectedContent)
}
// Verify onChunk received the intermediate states
if accumulated != expectedContent {
t.Errorf("last onChunk = %q, want %q", accumulated, expectedContent)
}
}
func TestParseStreamResponse_ReasoningFallback(t *testing.T) {
// Mock SSE stream with reasoning (Ollama style)
stream := `data: {"choices":[{"delta":{"reasoning":"I will calculate "},"index":0}]}
data: {"choices":[{"delta":{"reasoning":"the sum."},"index":0}]}
data: [DONE]
`
resp, err := parseStreamResponse(t.Context(), strings.NewReader(stream), nil)
if err != nil {
t.Fatalf("parseStreamResponse() error = %v", err)
}
if resp.Content != "I will calculate the sum." {
t.Errorf("resp.Content = %q, want %q", resp.Content, "I will calculate the sum.")
}
}
func TestParseStreamResponse_MixedContent(t *testing.T) {
// Verify that if both thinking and content are present, they are both captured correctly.
// Some models might start with thinking and then send content.
stream := `data: {"choices":[{"delta":{"thinking":"Wait, "},"index":0}]}
data: {"choices":[{"delta":{"content":"Hello"},"index":0}]}
data: [DONE]
`
resp, err := parseStreamResponse(t.Context(), strings.NewReader(stream), nil)
if err != nil {
t.Fatalf("parseStreamResponse() error = %v", err)
}
if resp.Thinking != "Wait, " {
t.Errorf("Thinking = %q", resp.Thinking)
}
if resp.Content != "Wait, Hello" {
t.Errorf("Content = %q", resp.Content)
}
}