package llm
import (
"strings"
"testing"
)
func run(chunks ...string) (string, string) {
var f thinkFilter
var streamed strings.Builder
for _, c := range chunks {
streamed.WriteString(f.push(c))
}
streamed.WriteString(f.finish())
return streamed.String(), f.out.String()
}
func TestThinkFilter(t *testing.T) {
cases := []struct {
name string
chunks []string
want string
}{
{"plain", []string{"Hallo ", "Welt"}, "Hallo Welt"},
{"block", []string{"reasoning\n\n\nHallo!"}, "Hallo!"},
{"split tags", []string{"
abc | ", "\n\nHal", "lo"}, "Hallo"},
{"char by char", strings.Split("xJa", ""), "Ja"},
{"stray close", []string{"Hallo!\n\nHallo!"}, "Hallo!\n\nHallo!"},
{"unclosed with gap (Dahl)", []string{"The user asks.\n\n\nHallo! Wie geht's?"}, "Hallo! Wie geht's?"},
{"gap streams live", []string{"a\n", "\n", "\nAntw", "ort"}, "Antwort"},
{"gap outside block kept", []string{"A\n\n\nB"}, "A\n\n\nB"},
{"unclosed no gap", []string{"nur Gedanken"}, "nur Gedanken"},
{"lt not a tag", []string{"a c <", "3"}, "a c <3"},
{"trailing partial", []string{"x The user"}}]}
data: {"choices":[{"delta":{"content":" greets.\n\n\nHallo!"}}]}
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]
`
var deltas []string
resp, err := parseStream(strings.NewReader(sse), func(s string) { deltas = append(deltas, s) })
if err != nil || resp.Content != "Hallo!" || strings.Join(deltas, "") != "Hallo!" {
t.Fatalf("%q %v %v", resp.Content, deltas, err)
}
}
// The answer after a swallowed must be streamed incrementally, not only at the end.
func TestThinkGapStreamsIncrementally(t *testing.T) {
var f thinkFilter
if v := f.push("Überlegung\n\n\nHal"); v != "Hal" {
t.Fatalf("got %q", v)
}
if v := f.push("lo"); v != "lo" {
t.Fatalf("got %q", v)
}
}