Add <function=name> format support to Qwen tool parser#281
Conversation
db1ec93 to
32c6bdd
Compare
Thump604
left a comment
There was a problem hiding this comment.
I found one blocker in the new streaming recovery path.
The parser now tries to recover false positives by re-emitting buffered text when a partial <function prefix turns out not to be a tool call. But the new server-side safety net swallows that recovered content again whenever it still contains <function. That means plain text like <functional or any literal content ending in <function at end-of-stream can be lost instead of re-emitted.
Minimal reproduction from the current logic:
- delta 1:
Look at <function-> parser buffers and emits nothing - delta 2:
al interface-> parser correctly recovers"<functional interface" - server safety net sees
<functionin recovered content and suppresses it again - next delta only emits the later suffix, so the original recovered prefix is dropped
There is a second edge case at end-of-stream: a literal final "<function" with no = is buffered and then never flushed, because the fallback only checks for actual tool markers like <function=.
I would keep the parser-side buffering/recovery, but narrow or remove the server-side <function suppression so it does not fight the parser's recovery path. A focused regression test around a false-positive literal like <functional across a chunk boundary would catch this.
32e4751 to
fdc6735
Compare
|
Updated based on review feedback:
The diff is now clean: 3 files, +311/-3 lines (rebased on latest |
|
@Thump604 Updated based on review feedback — removed the server-side safety net, added regression tests for false positives like |
|
@Thump604 Review feedback addressed — ready for re-review. |
|
I rechecked the updated streaming path and there is still one blocker in the false-positive recovery logic. Minimal repro on the current branch:
That preserves the buffered marker prefix, but it still drops the earlier buffered content from the same first chunk ( The current regression test does not catch this because it only asserts that I would keep the general direction, but the recovery branch needs to re-emit the full buffered suffix from the point buffering began, not only the matched partial marker fragment. |
fdc6735 to
0ccb90f
Compare
|
Addressed the second review feedback — content before the partial marker is now preserved: What changed:
All 23 Qwen tests pass. Ready for re-review @Thump604. |
- Parse <function=name><parameter=key>value</parameter></function> format natively generated by Qwen3.5 models (both parameter tags and JSON body) - Add streaming partial-marker buffering for <function, [Calling tool, and <tool_call prefixes to prevent raw markup from leaking to clients - Content-preserving buffering: only the marker suffix is held back; text before it in the same delta is emitted immediately - False-positive recovery: when a buffered prefix like <function turns out to be normal text (e.g. <functional), re-emit the buffered prefix together with the next delta - Widen server-side end-of-stream fallback to catch incomplete <function markers (without requiring the = sign) - Add comprehensive tests: 4 non-streaming format tests + 5 streaming buffering/recovery tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0ccb90f to
d67fec4
Compare
Thump604
left a comment
There was a problem hiding this comment.
Rechecked the false-positive streaming case that blocked this earlier. The updated buffering logic now preserves the pre-marker content, the parser tests pass locally, and the server-side suppression conflict is gone. This looks good to merge.
Summary
<function=name><parameter=key>value</parameter></function>format, but theqwenparser only handled<tool_call>JSON</tool_call>and[Calling tool: name({...})]— causing tool calls to be returned as raw text instead of structuredtool_callsFUNCTION_PATTERNandPARAM_PATTERNto parse both<parameter>tags and inline JSON arguments<functionbefore=arrives) as text content to the clientserver.pyAnthropic streaming paths to suppress any<functioncontent that slips through the streaming parserFiles changed
qwen_tool_parser.pyFUNCTION_PATTERN,PARAM_PATTERN, partial-marker streaming bufferingserver.py<functionin Anthropic streaming,<function=in fallback detectionContext
When using
--tool-call-parser qwenwith Qwen3.5 models, the model outputs:This format was not recognized, so responses had
tool_calls: nulland the raw XML leaked as text content. Clients (e.g. Claude Code) reported "Invalid tool parameters" errors.Streaming issue
Without partial-marker buffering, early tokens like
<function(before=arrives) leaked as text content deltas. The fix buffers potential marker prefixes and re-emits them if they turn out not to be tool calls. An additional safety net inserver.pycatches any edge cases where markers slip through the streaming parser (e.g. due to multi-token MTP deltas or reasoning parser splits).Test plan
tool_callscorrectly populated withnameandargumentstool_usecontent blocks withinput_json_delta, no leaked text<tool_call>, bracket[Calling tool:])<function>tags parsed correctly<parameter>tags parsed into single arguments object<functionsuppressed until=confirms tool call<div>) re-emit buffered prefix<functionin content is suppressed before emission<function=triggers tool call extraction🤖 Generated with Claude Code