Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
ollama
Commits
0a2d9208
Unverified
Commit
0a2d9208
authored
Oct 29, 2025
by
Grace
Committed by
GitHub
Oct 29, 2025
Browse files
Removing whitespace between Thinking and Content in Qwen3VL (#12838)
Eats extra whitespace at the end/beginning of content
parent
c8864710
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
553 additions
and
17 deletions
+553
-17
model/parsers/qwen3vl.go
model/parsers/qwen3vl.go
+37
-17
model/parsers/qwen3vl_nonthinking_test.go
model/parsers/qwen3vl_nonthinking_test.go
+186
-0
model/parsers/qwen3vl_thinking_test.go
model/parsers/qwen3vl_thinking_test.go
+330
-0
No files found.
model/parsers/qwen3vl.go
View file @
0a2d9208
...
@@ -16,6 +16,8 @@ const (
...
@@ -16,6 +16,8 @@ const (
CollectingThinkingContent
qwenParserState
=
iota
CollectingThinkingContent
qwenParserState
=
iota
CollectingContent
CollectingContent
CollectingToolContent
CollectingToolContent
ThinkingDoneEatingWhitespace
ToolCallDoneEatingWhitespace
)
)
const
(
const
(
...
@@ -111,17 +113,28 @@ func (p *Qwen3VLParser) parseEvents() []qwenEvent {
...
@@ -111,17 +113,28 @@ func (p *Qwen3VLParser) parseEvents() []qwenEvent {
return
all
return
all
}
}
func
emitContentBefore
Tag
(
p
*
Qwen3VLParser
,
events
[]
qwenEvent
,
tag
string
)
[]
qwenEvent
{
func
splitAt
Tag
(
p
*
Qwen3VLParser
,
tag
string
,
trimAfter
bool
)
(
string
,
string
)
{
split
:=
strings
.
SplitN
(
p
.
buffer
.
String
(),
tag
,
2
)
split
:=
strings
.
SplitN
(
p
.
buffer
.
String
(),
tag
,
2
)
before
:=
split
[
0
]
before
:=
split
[
0
]
before
=
strings
.
TrimRightFunc
(
before
,
unicode
.
IsSpace
)
before
=
strings
.
TrimRightFunc
(
before
,
unicode
.
IsSpace
)
if
len
(
before
)
>
0
{
events
=
append
(
events
,
qwenEventContent
{
content
:
before
})
}
after
:=
split
[
1
]
after
:=
split
[
1
]
if
trimAfter
{
after
=
strings
.
TrimLeftFunc
(
after
,
unicode
.
IsSpace
)
}
p
.
buffer
.
Reset
()
p
.
buffer
.
Reset
()
p
.
buffer
.
WriteString
(
after
)
p
.
buffer
.
WriteString
(
after
)
return
events
return
before
,
after
// return events
}
func
(
p
*
Qwen3VLParser
)
eatLeadingWhitespaceAndTransitionTo
(
nextState
qwenParserState
)
([]
qwenEvent
,
bool
)
{
trimmed
:=
strings
.
TrimLeftFunc
(
p
.
buffer
.
String
(),
unicode
.
IsSpace
)
p
.
buffer
.
Reset
()
if
trimmed
==
""
{
return
nil
,
false
}
p
.
state
=
nextState
p
.
buffer
.
WriteString
(
trimmed
)
return
nil
,
true
}
}
func
(
p
*
Qwen3VLParser
)
eat
()
([]
qwenEvent
,
bool
)
{
func
(
p
*
Qwen3VLParser
)
eat
()
([]
qwenEvent
,
bool
)
{
...
@@ -130,7 +143,11 @@ func (p *Qwen3VLParser) eat() ([]qwenEvent, bool) {
...
@@ -130,7 +143,11 @@ func (p *Qwen3VLParser) eat() ([]qwenEvent, bool) {
switch
p
.
state
{
switch
p
.
state
{
case
CollectingContent
:
case
CollectingContent
:
if
strings
.
Contains
(
p
.
buffer
.
String
(),
toolOpenTag
)
{
if
strings
.
Contains
(
p
.
buffer
.
String
(),
toolOpenTag
)
{
events
=
emitContentBeforeTag
(
p
,
events
,
toolOpenTag
)
// events = emitContentBeforeTag(p, events, toolOpenTag)
before
,
_
:=
splitAtTag
(
p
,
toolOpenTag
,
false
)
if
len
(
before
)
>
0
{
events
=
append
(
events
,
qwenEventContent
{
content
:
before
})
}
p
.
state
=
CollectingToolContent
p
.
state
=
CollectingToolContent
return
events
,
true
return
events
,
true
}
else
if
overlapLen
:=
overlap
(
p
.
buffer
.
String
(),
toolOpenTag
);
overlapLen
>
0
{
}
else
if
overlapLen
:=
overlap
(
p
.
buffer
.
String
(),
toolOpenTag
);
overlapLen
>
0
{
...
@@ -167,27 +184,26 @@ func (p *Qwen3VLParser) eat() ([]qwenEvent, bool) {
...
@@ -167,27 +184,26 @@ func (p *Qwen3VLParser) eat() ([]qwenEvent, bool) {
slog
.
Warn
(
"qwen tool call closing tag found but no content before it"
)
slog
.
Warn
(
"qwen tool call closing tag found but no content before it"
)
}
}
after
:=
s
trings
.
TrimLeftFunc
(
split
[
1
],
unicode
.
IsSpace
)
after
:=
s
plit
[
1
]
events
=
append
(
events
,
qwenEventRawToolCall
{
raw
:
before
})
events
=
append
(
events
,
qwenEventRawToolCall
{
raw
:
before
})
p
.
buffer
.
Reset
()
p
.
buffer
.
Reset
()
p
.
buffer
.
WriteString
(
after
)
p
.
buffer
.
WriteString
(
after
)
p
.
state
=
CollectingContent
p
.
state
=
ToolCallDoneEatingWhitespace
return
events
,
true
return
events
,
true
}
else
{
}
else
{
return
events
,
false
return
events
,
false
}
}
case
CollectingThinkingContent
:
case
CollectingThinkingContent
:
if
strings
.
Contains
(
p
.
buffer
.
String
(),
thinkingCloseTag
)
{
if
strings
.
Contains
(
p
.
buffer
.
String
(),
thinkingCloseTag
)
{
split
:=
strings
.
SplitN
(
p
.
buffer
.
String
(),
thinkingCloseTag
,
2
)
thinking
,
remaining
:=
splitAtTag
(
p
,
thinkingCloseTag
,
true
)
// before := split[0]
if
len
(
thinking
)
>
0
{
before
:=
strings
.
TrimRightFunc
(
split
[
0
],
unicode
.
IsSpace
)
events
=
append
(
events
,
qwenEventThinkingContent
{
content
:
thinking
})
after
:=
strings
.
TrimLeftFunc
(
split
[
1
],
unicode
.
IsSpace
)
}
if
len
(
before
)
>
0
{
if
remaining
==
""
{
events
=
append
(
events
,
qwenEventThinkingContent
{
content
:
before
})
p
.
state
=
ThinkingDoneEatingWhitespace
}
else
{
p
.
state
=
CollectingContent
}
}
p
.
buffer
.
Reset
()
p
.
buffer
.
WriteString
(
after
)
p
.
state
=
CollectingContent
return
events
,
true
return
events
,
true
}
else
if
overlapLen
:=
overlap
(
p
.
buffer
.
String
(),
thinkingCloseTag
);
overlapLen
>
0
{
}
else
if
overlapLen
:=
overlap
(
p
.
buffer
.
String
(),
thinkingCloseTag
);
overlapLen
>
0
{
beforePartialTag
:=
p
.
buffer
.
String
()[
:
len
(
p
.
buffer
.
String
())
-
overlapLen
]
beforePartialTag
:=
p
.
buffer
.
String
()[
:
len
(
p
.
buffer
.
String
())
-
overlapLen
]
...
@@ -215,6 +231,10 @@ func (p *Qwen3VLParser) eat() ([]qwenEvent, bool) {
...
@@ -215,6 +231,10 @@ func (p *Qwen3VLParser) eat() ([]qwenEvent, bool) {
}
}
return
events
,
false
return
events
,
false
}
}
case
ThinkingDoneEatingWhitespace
:
return
p
.
eatLeadingWhitespaceAndTransitionTo
(
CollectingContent
)
case
ToolCallDoneEatingWhitespace
:
return
p
.
eatLeadingWhitespaceAndTransitionTo
(
CollectingContent
)
default
:
default
:
panic
(
"unreachable"
)
panic
(
"unreachable"
)
}
}
...
...
model/parsers/qwen3vl_nonthinking_test.go
View file @
0a2d9208
...
@@ -653,3 +653,189 @@ func TestQwen3VLNonThinkingToolParser(t *testing.T) {
...
@@ -653,3 +653,189 @@ func TestQwen3VLNonThinkingToolParser(t *testing.T) {
}
}
}
}
}
}
func
TestQwen3VLNonThinkingToolCallWhitespaceHandling
(
t
*
testing
.
T
)
{
type
step
struct
{
input
string
wantEvents
[]
qwenEvent
}
cases
:=
[]
struct
{
desc
string
steps
[]
step
only
bool
}{
{
desc
:
"whitespace inside tool call preserves trailing space"
,
steps
:
[]
step
{
{
input
:
"before<tool_call> tool content </tool_call>after"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"before"
},
qwenEventRawToolCall
{
raw
:
" tool content "
},
qwenEventContent
{
content
:
"after"
},
},
},
},
},
{
desc
:
"whitespace inside tool call preserves trailing space"
,
steps
:
[]
step
{
{
input
:
"
\n
\n
\n
\n
\n
\n
blahhhhhhhhhh blahhhh blahhhh
\n\n\n\t\t
<tool_call> tool content </tool_call>
\n\n\n\n\n\n\n
after"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"
\n
\n
\n
\n
\n
\n
blahhhhhhhhhh blahhhh blahhhh"
},
qwenEventRawToolCall
{
raw
:
" tool content "
},
qwenEventContent
{
content
:
"after"
},
},
},
},
},
{
desc
:
"whitespace inside tool call preserves trailing space"
,
steps
:
[]
step
{
{
input
:
"<tool_call> tool content </tool_call> "
,
wantEvents
:
[]
qwenEvent
{
qwenEventRawToolCall
{
raw
:
" tool content "
},
},
},
{
input
:
"
\n
\n
\n
\n
\n
\n
blahhhhhhhhhh blahhhh blahhhh
\n\n\n\t\t
<tool_call> anotha one </tool_call>
\n\n\n\n\n\n\n
after
\n\n\n\n\n\n
blep"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"blahhhhhhhhhh blahhhh blahhhh"
},
qwenEventRawToolCall
{
raw
:
" anotha one "
},
qwenEventContent
{
content
:
"after
\n\n\n\n\n\n
blep"
},
},
},
},
},
{
desc
:
"whitespace between content and tool call"
,
steps
:
[]
step
{
{
input
:
"content
\n
<tool_call>tool</tool_call>
\n
more content"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"content"
},
qwenEventRawToolCall
{
raw
:
"tool"
},
qwenEventContent
{
content
:
"more content"
},
},
},
},
},
{
desc
:
"consecutive tool calls with whitespace"
,
steps
:
[]
step
{
{
input
:
"<tool_call>first</tool_call>
\n
<tool_call>second</tool_call>
\n
<tool_call>third</tool_call>"
,
wantEvents
:
[]
qwenEvent
{
qwenEventRawToolCall
{
raw
:
"first"
},
qwenEventRawToolCall
{
raw
:
"second"
},
qwenEventRawToolCall
{
raw
:
"third"
},
},
},
},
},
{
desc
:
"whitespace before and after tool open tag"
,
steps
:
[]
step
{
{
input
:
"text
\n
<tool_call>content</tool_call>"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"text"
},
qwenEventRawToolCall
{
raw
:
"content"
},
},
},
},
},
{
desc
:
"unicode whitespace around tool calls"
,
steps
:
[]
step
{
{
input
:
"text
\u00a0\u3000
<tool_call>content</tool_call>
\u00a0\u3000
text"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"text"
},
qwenEventRawToolCall
{
raw
:
"content"
},
qwenEventContent
{
content
:
"text"
},
},
},
},
},
{
desc
:
"empty tool call with surrounding whitespace"
,
steps
:
[]
step
{
{
input
:
"before <tool_call></tool_call> after"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"before"
},
qwenEventRawToolCall
{
raw
:
""
},
qwenEventContent
{
content
:
"after"
},
},
},
},
},
{
desc
:
"whitespace in tool call split across chunks"
,
steps
:
[]
step
{
{
input
:
"before<tool_call> "
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"before"
}},
},
{
input
:
"tool"
,
wantEvents
:
[]
qwenEvent
{},
},
{
input
:
" </tool_call>after"
,
wantEvents
:
[]
qwenEvent
{
qwenEventRawToolCall
{
raw
:
" tool "
},
qwenEventContent
{
content
:
"after"
},
},
},
},
},
{
desc
:
"mixed whitespace types between tool calls"
,
steps
:
[]
step
{
{
input
:
"<tool_call>first</tool_call>
\t\n\r
<tool_call>second</tool_call>"
,
wantEvents
:
[]
qwenEvent
{
qwenEventRawToolCall
{
raw
:
"first"
},
qwenEventRawToolCall
{
raw
:
"second"
},
},
},
},
},
}
anyOnlies
:=
false
for
_
,
tc
:=
range
cases
{
if
tc
.
only
{
anyOnlies
=
true
}
}
for
_
,
tc
:=
range
cases
{
if
anyOnlies
&&
!
tc
.
only
{
continue
}
t
.
Run
(
tc
.
desc
,
func
(
t
*
testing
.
T
)
{
parser
:=
Qwen3VLParser
{
hasThinkingSupport
:
false
}
parser
.
Init
([]
api
.
Tool
{},
nil
)
for
i
,
step
:=
range
tc
.
steps
{
parser
.
buffer
.
WriteString
(
step
.
input
)
gotEvents
:=
parser
.
parseEvents
()
if
len
(
gotEvents
)
==
0
&&
len
(
step
.
wantEvents
)
==
0
{
continue
}
if
!
reflect
.
DeepEqual
(
gotEvents
,
step
.
wantEvents
)
{
t
.
Errorf
(
"step %d: input %q: got events %#v, want %#v"
,
i
,
step
.
input
,
gotEvents
,
step
.
wantEvents
)
}
}
})
}
}
model/parsers/qwen3vl_thinking_test.go
View file @
0a2d9208
...
@@ -546,3 +546,333 @@ func TestQwen3VLThinkingParserStreamingAssistantPrefillContent(t *testing.T) {
...
@@ -546,3 +546,333 @@ func TestQwen3VLThinkingParserStreamingAssistantPrefillContent(t *testing.T) {
}
}
}
}
}
}
func
TestQwen3VLThinkingWhitespaceHandling
(
t
*
testing
.
T
)
{
type
step
struct
{
input
string
wantEvents
[]
qwenEvent
}
cases
:=
[]
struct
{
desc
string
steps
[]
step
only
bool
}{
{
desc
:
"whitespace after thinking tag is trimmed"
,
steps
:
[]
step
{
{
input
:
"thinking content</think>
\n\t
content starts here"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking content"
},
qwenEventContent
{
content
:
"content starts here"
},
},
},
},
},
{
desc
:
"whitespace after thinking tag split across chunks"
,
steps
:
[]
step
{
{
input
:
"thinking content</think> "
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking content"
}},
},
{
input
:
"
\n\t
"
,
wantEvents
:
[]
qwenEvent
{},
},
{
input
:
"content"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"content"
},
},
},
},
},
{
desc
:
"only whitespace after thinking tag"
,
steps
:
[]
step
{
{
input
:
"thinking content</think>
\n\t
"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking content"
}},
},
},
},
{
desc
:
"multiple spaces and tabs after thinking"
,
steps
:
[]
step
{
{
input
:
"think</think>
\t\t\n\n
text"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"think"
},
qwenEventContent
{
content
:
"text"
},
},
},
},
},
{
desc
:
"trailing whitespace before thinking tag is preserved in content"
,
steps
:
[]
step
{
{
input
:
"thinking with spaces </think>text"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking with spaces"
},
qwenEventContent
{
content
:
"text"
},
},
},
},
},
{
desc
:
"whitespace between thinking and tool call"
,
steps
:
[]
step
{
{
input
:
"thinking</think>
\n
<tool_call>{
\"
name
\"
:
\"
test
\"
}</tool_call>"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking"
},
qwenEventRawToolCall
{
raw
:
"{
\"
name
\"
:
\"
test
\"
}"
},
},
},
},
},
{
desc
:
"no whitespace after thinking tag"
,
steps
:
[]
step
{
{
input
:
"thinking</think>content"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking"
},
qwenEventContent
{
content
:
"content"
},
},
},
},
},
{
desc
:
"unicode whitespace after thinking tag"
,
steps
:
[]
step
{
{
input
:
"thinking</think>
\u00a0\u3000
content"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking"
},
qwenEventContent
{
content
:
"content"
},
},
},
},
},
{
desc
:
"whitespace split with partial thinking tag"
,
steps
:
[]
step
{
{
input
:
"thinking</th"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking"
}},
},
{
input
:
"ink>
\n
"
,
wantEvents
:
[]
qwenEvent
{},
},
{
input
:
" content"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"content"
},
},
},
},
},
{
desc
:
"empty thinking tag with whitespace after"
,
steps
:
[]
step
{
{
input
:
"</think>
\n
content"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"content"
},
},
},
},
},
{
desc
:
"whitespace inside tool call preserves trailing space"
,
steps
:
[]
step
{
{
input
:
"bruh</think>
\n
\n
\n
\n
\n
\n
blahhhhhhhhhh blahhhh blahhhh
\n\n\n\t\t
<tool_call> tool content </tool_call>
\n\n\n\n\n\n\n
after"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"bruh"
},
qwenEventContent
{
content
:
"blahhhhhhhhhh blahhhh blahhhh"
},
qwenEventRawToolCall
{
raw
:
" tool content "
},
qwenEventContent
{
content
:
"after"
},
},
},
},
},
{
desc
:
"whitespace inside tool call preserves trailing space"
,
steps
:
[]
step
{
{
input
:
"bruh</think> shdjfhksdhfj "
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"bruh"
},
qwenEventContent
{
content
:
"shdjfhksdhfj"
},
},
},
{
input
:
"another word "
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
" another word"
},
},
},
{
input
:
"<tool_call> tool content </tool_call> "
,
wantEvents
:
[]
qwenEvent
{
qwenEventRawToolCall
{
raw
:
" tool content "
},
},
},
{
input
:
"
\n
\n
\n
\n
\n
\n
blahhhhhhhhhh blahhhh blahhhh
\n\n\n\t\t
<tool_call> anotha one </tool_call>
\n\n\n\n\n\n\n
after
\n\n\n\n\n\n
blep"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"blahhhhhhhhhh blahhhh blahhhh"
},
qwenEventRawToolCall
{
raw
:
" anotha one "
},
qwenEventContent
{
content
:
"after
\n\n\n\n\n\n
blep"
},
},
},
},
},
}
anyOnlies
:=
false
for
_
,
tc
:=
range
cases
{
if
tc
.
only
{
anyOnlies
=
true
}
}
for
_
,
tc
:=
range
cases
{
if
anyOnlies
&&
!
tc
.
only
{
continue
}
t
.
Run
(
tc
.
desc
,
func
(
t
*
testing
.
T
)
{
parser
:=
Qwen3VLParser
{
hasThinkingSupport
:
true
}
parser
.
Init
([]
api
.
Tool
{},
nil
)
for
i
,
step
:=
range
tc
.
steps
{
parser
.
buffer
.
WriteString
(
step
.
input
)
gotEvents
:=
parser
.
parseEvents
()
if
len
(
gotEvents
)
==
0
&&
len
(
step
.
wantEvents
)
==
0
{
continue
}
if
!
reflect
.
DeepEqual
(
gotEvents
,
step
.
wantEvents
)
{
t
.
Errorf
(
"step %d: input %q: got events %#v, want %#v"
,
i
,
step
.
input
,
gotEvents
,
step
.
wantEvents
)
}
}
})
}
}
func
TestQwen3VLToolCallWhitespaceHandling
(
t
*
testing
.
T
)
{
type
step
struct
{
input
string
wantEvents
[]
qwenEvent
}
cases
:=
[]
struct
{
desc
string
steps
[]
step
only
bool
prefillMsg
*
api
.
Message
// allows starting in content mode instead of thinking mode
}{
{
desc
:
"whitespace inside tool call is fully preserved (with content prefill)"
,
prefillMsg
:
&
api
.
Message
{
Role
:
"assistant"
,
Content
:
"prefill"
},
steps
:
[]
step
{
{
input
:
"before<tool_call> tool content </tool_call>
\n
after"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"before"
},
qwenEventRawToolCall
{
raw
:
" tool content "
},
qwenEventContent
{
content
:
"after"
},
},
},
},
},
{
desc
:
"whitespace after tool call trimmed across chunks (with content prefill)"
,
prefillMsg
:
&
api
.
Message
{
Role
:
"assistant"
,
Content
:
"prefill"
},
steps
:
[]
step
{
{
input
:
"before<tool_call>tool</tool_call> "
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"before"
},
qwenEventRawToolCall
{
raw
:
"tool"
},
},
},
{
input
:
"
\n\t
"
,
wantEvents
:
[]
qwenEvent
{},
},
{
input
:
"after
\n
this is a song"
,
wantEvents
:
[]
qwenEvent
{
qwenEventContent
{
content
:
"after
\n
this is a song"
},
},
},
},
},
{
desc
:
"multiple tool calls with whitespace between (with content prefill)"
,
prefillMsg
:
&
api
.
Message
{
Role
:
"assistant"
,
Content
:
"prefill"
},
steps
:
[]
step
{
{
input
:
"<tool_call>first</tool_call>
\n
<tool_call>second</tool_call>"
,
wantEvents
:
[]
qwenEvent
{
qwenEventRawToolCall
{
raw
:
"first"
},
qwenEventRawToolCall
{
raw
:
"second"
},
},
},
},
},
{
desc
:
"thinking with whitespace then tool call"
,
steps
:
[]
step
{
{
input
:
"thinking</think>
\n
<tool_call>tool</tool_call>
\n
content"
,
wantEvents
:
[]
qwenEvent
{
qwenEventThinkingContent
{
content
:
"thinking"
},
qwenEventRawToolCall
{
raw
:
"tool"
},
qwenEventContent
{
content
:
"content"
},
},
},
},
},
}
anyOnlies
:=
false
for
_
,
tc
:=
range
cases
{
if
tc
.
only
{
anyOnlies
=
true
}
}
for
_
,
tc
:=
range
cases
{
if
anyOnlies
&&
!
tc
.
only
{
continue
}
t
.
Run
(
tc
.
desc
,
func
(
t
*
testing
.
T
)
{
parser
:=
Qwen3VLParser
{
hasThinkingSupport
:
true
}
parser
.
Init
([]
api
.
Tool
{},
tc
.
prefillMsg
)
for
i
,
step
:=
range
tc
.
steps
{
parser
.
buffer
.
WriteString
(
step
.
input
)
gotEvents
:=
parser
.
parseEvents
()
if
len
(
gotEvents
)
==
0
&&
len
(
step
.
wantEvents
)
==
0
{
continue
}
if
!
reflect
.
DeepEqual
(
gotEvents
,
step
.
wantEvents
)
{
t
.
Errorf
(
"step %d: input %q: got events %#v, want %#v"
,
i
,
step
.
input
,
gotEvents
,
step
.
wantEvents
)
}
}
})
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment