Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
package tools
|
|
|
|
import "testing"
|
|
|
|
// feedAll runs the splitter over a sequence of chunks and concatenates the
|
|
// think / answer outputs (including the final Flush), mirroring how the agent
|
|
// stream router consumes it.
|
|
func feedAll(sp *ThinkStreamSplitter, chunks []string) (think, answer string) {
|
|
for _, c := range chunks {
|
|
tk, ans := sp.Feed(c)
|
|
think += tk
|
|
answer += ans
|
|
}
|
|
tk, ans := sp.Flush()
|
|
think += tk
|
|
answer += ans
|
|
return think, answer
|
|
}
|
|
|
|
func TestThinkStreamSplitter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
chunks []string
|
|
wantThink string
|
|
wantAnswer string
|
|
}{
|
|
{
|
|
name: "no think tags - all answer",
|
|
chunks: []string{"Hello ", "world"},
|
|
wantThink: "",
|
|
wantAnswer: "Hello world",
|
|
},
|
|
{
|
|
name: "single think block then answer",
|
|
chunks: []string{"<think>reasoning</think>The answer is 42."},
|
|
wantThink: "reasoning",
|
|
wantAnswer: "The answer is 42.",
|
|
},
|
|
{
|
|
name: "open tag split across chunks",
|
|
chunks: []string{"<thi", "nk>secret</think>visible"},
|
|
wantThink: "secret",
|
|
wantAnswer: "visible",
|
|
},
|
|
{
|
|
name: "close tag split across chunks",
|
|
chunks: []string{"<think>think part</thi", "nk>answer part"},
|
|
wantThink: "think part",
|
|
wantAnswer: "answer part",
|
|
},
|
|
{
|
|
name: "think content streamed across multiple chunks",
|
|
chunks: []string{"<think>a", "b", "c</think>", "done"},
|
|
wantThink: "abc",
|
|
wantAnswer: "done",
|
|
},
|
|
{
|
|
name: "answer before think block",
|
|
chunks: []string{"prefix <think>mid</think> suffix"},
|
|
wantThink: "mid",
|
|
wantAnswer: "prefix suffix",
|
|
},
|
|
{
|
|
name: "two think blocks",
|
|
chunks: []string{"<think>one</think>A<think>two</think>B"},
|
|
wantThink: "onetwo",
|
|
wantAnswer: "AB",
|
|
},
|
|
{
|
|
name: "unterminated think treated as think on flush",
|
|
chunks: []string{"<think>still thinking"},
|
|
wantThink: "still thinking",
|
|
wantAnswer: "",
|
|
},
|
|
{
|
|
name: "literal less-than in answer is preserved",
|
|
chunks: []string{"if a < ", "b then"},
|
|
wantThink: "",
|
|
wantAnswer: "if a < b then",
|
|
},
|
|
{
|
|
name: "empty chunks are no-ops",
|
|
chunks: []string{"", "answer", ""},
|
|
wantThink: "",
|
|
wantAnswer: "answer",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sp := NewThinkStreamSplitter()
|
|
think, answer := feedAll(sp, tt.chunks)
|
|
if think != tt.wantThink {
|
|
t.Errorf("think = %q, want %q", think, tt.wantThink)
|
|
}
|
|
if answer != tt.wantAnswer {
|
|
t.Errorf("answer = %q, want %q", answer, tt.wantAnswer)
|
|
}
|
|
})
|
|
}
|
|
}
|