From 1cefa749aa7b0cd3e330a95f0b03e98dc0a914bd Mon Sep 17 00:00:00 2001 From: Alfredo Matas Date: Fri, 27 Mar 2026 19:28:34 +0100 Subject: [PATCH] model/parsers: close think block if tool block starts in Qwen3.5 (#15022) --- model/parsers/qwen35.go | 9 +++++++++ model/parsers/qwen35_test.go | 22 ++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/model/parsers/qwen35.go b/model/parsers/qwen35.go index b3e672c5d..6a6f54b69 100644 --- a/model/parsers/qwen35.go +++ b/model/parsers/qwen35.go @@ -21,6 +21,7 @@ const ( const ( qwen35ThinkingOpenTag = "" qwen35ThinkingCloseTag = "" + qwen35ToolCallOpenTag = "" ) // Qwen35Parser handles qwen3.5 reasoning extraction and delegates post-thinking @@ -202,6 +203,14 @@ func (p *Qwen35Parser) eat() ([]qwen35Event, bool) { events = append(events, qwen35EventThinkingContent{content: unambiguous}) } return events, false + } else if strings.Contains(acc, qwen35ToolCallOpenTag) { + // qwen3.5:9b model forgets sometimes to use tag before the block starts + // this condition ends the Think block and continues with the when the tag + // is found + thinking, tooling := p.splitAtTag(qwen35ToolCallOpenTag, true) + p.buffer.Reset() + p.buffer.WriteString(thinking + qwen35ThinkingCloseTag + qwen35ToolCallOpenTag + tooling) + return events, true } whitespaceLen := trailingWhitespaceLen(acc) diff --git a/model/parsers/qwen35_test.go b/model/parsers/qwen35_test.go index 94a3f26fe..abedfe8f4 100644 --- a/model/parsers/qwen35_test.go +++ b/model/parsers/qwen35_test.go @@ -108,7 +108,7 @@ func TestQwen35ParserAssistantPrefillStartsInContent(t *testing.T) { } } -func TestQwen35ParserToolCallEmittedInThinkingIsNotParsed(t *testing.T) { +func TestQwen35ParserToolCallEmittedInThinkingIsParsed(t *testing.T) { parser := ParserForName("qwen3.5") if parser == nil { t.Fatal("expected qwen3.5 parser") @@ -141,14 +141,20 @@ SF if content != "" { t.Fatalf("expected empty content, got %q", content) } - expectedThinking := `Need weather lookup -SF -` - if thinking != expectedThinking { - t.Fatalf("expected thinking %q, got %q", expectedThinking, thinking) + if thinking != "Need weather lookup" { + t.Fatalf("expected thinking %q, got %q", "Need weather lookup", thinking) } - if len(calls) != 0 { - t.Fatalf("expected no tool calls before , got %d", len(calls)) + if len(calls) != 1 { + t.Fatalf("expected 1 tool call, got %d", len(calls)) + } + + if calls[0].Function.Name != "get_weather" { + t.Fatalf("expected tool name %q, got %q", "get_weather", calls[0].Function.Name) + } + + location, ok := calls[0].Function.Arguments.Get("location") + if !ok || location != "SF" { + t.Fatalf("expected location %q, got %v", "SF", location) } }