CanIToolCall caniuse.com for tool calling

Qwen XML tool calls (Qwen3-Coder, Qwen3.5/3.6/3.8) on vllm 0.30.0

fail 44% strict pass 24 pass · 25 soft pass · 5 fail

Run

Engine
vllm 0.30.0
Run
to
Platform
linux-x86_64, Python 3.12.14, canitoolcall 0.1.0.dev0
Strategies
one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8 (normalization soft-v1)
Fixtures digest
a1e7b55367e195e8aa6e2ef746b2cc141b44f37caad0887ff8f30b104616f836 the fixture corpus used for this site matches the run
Engine details
{
  "huggingface-hub": "1.33.0",
  "mistral-common": "1.12.0",
  "openai-harmony": "0.0.8",
  "pinned_version": "0.30.0",
  "tokenizers": "0.23.2",
  "tokens_per_step": "many",
  "torch": "2.14.0+cpu",
  "transformers": "5.17.0",
  "wheel": "vllm-0.30.0-cp38-abi3-manylinux_2_28_x86_64.whl",
  "wheel_sha256": "ef52ee58c410ead0b8afb190838fa4cbcb52075596f67862a03859d984966ac4"
}
Results file
data/vllm-0.30.0.json

Checks

Per fixture, the worst result of each check over the non-streaming parse and every realistic chunking strategy.
CheckPassSoft FailErrorStrict pass rate
expected_match 24 25 4 0 45%
expected_error 0 0 1 0 0%
stream_equals_nonstream 45 5 4 0 83%
split_invariance 54 0 0 0 100%
no_leakage 54 0 0 0 100%
arguments_json 46 0 3 0 94%
arguments_schema 46 0 3 0 94%
parallel_order 7 0 0 0 100%

Fixtures needing attention

fail qwen3-xml/bug-coder-text-after-call expected_match, stream_equals_nonstream
Checks that did not pass for qwen3-xml/bug-coder-text-after-call
CheckStrategyResultDetail
expected_matchnonstream failcontent: expected 'beforeafter', got 'before'
stream_equals_nonstreamone failcontent: nonstream 'before', got 'beforeafter'
stream_equals_nonstreamspecial failcontent: nonstream 'before', got 'beforeafter'
stream_equals_nonstreamtoken failcontent: nonstream 'before', got 'beforeafter'
stream_equals_nonstreamrand:1:8 failcontent: nonstream 'before', got 'beforeafter'
stream_equals_nonstreamrand:2:8 failcontent: nonstream 'before', got 'beforeafter'
stream_equals_nonstreamrand:3:8 failcontent: nonstream 'before', got 'beforeafter'
stream_equals_nonstreamrand:4:8 failcontent: nonstream 'before', got 'beforeafter'
stream_equals_nonstreamrand:5:8 failcontent: nonstream 'before', got 'beforeafter'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/imported.jsonl --id qwen3-xml/bug-coder-text-after-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 19 of fixtures/qwen3-xml/imported.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

--- expected
+++ observed
@@ -1,4 +1,4 @@
 {
-  "content": "beforeafter",
+  "content": "before",
   "reasoning_content": null,
   "tool_calls": [

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

Matches the expected parse.

{
  "content": "beforeafter",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "city": "Paris"
      },
      "name": "get_weather"
    }
  ]
}

Fixture

Provenance: bug_report, https://github.com/sgl-project/sglang/issues/40739. Tags: single-call, text-before-call, text-after-call, regression, x-no-newlines.

Raw output

before<tool_call><function=get_weather><parameter=city>Paris</parameter></function></tool_call>after

Expected parse

{
  "content": "beforeafter",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "city": "Paris"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/bug-coder-text-after-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3-Coder-30B-A3B-Instruct", "Qwen/Qwen3-Coder-480B-A35B-Instruct"], "spec_version": "0.1", "provenance": {"kind": "bug_report", "source_url": "https://github.com/sgl-project/sglang/issues/40739", "revision": "issue-40739", "license": "NOASSERTION", "generator": "scripts/fixtures/qwen3-xml/import_qwen3_xml.py"}, "tools": [{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}], "raw_output": "before<tool_call><function=get_weather><parameter=city>Paris</parameter></function></tool_call>after", "output_token_ids": [14801, 151657, 27, 1688, 28280, 69364, 1784, 16181, 28, 8926, 29, 59604, 522, 16181, 1472, 1688, 29, 151658, 10694], "tokenizer": {"repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct", "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n", "expected": {"content": "beforeafter", "reasoning_content": null, "tool_calls": [{"name": "get_weather", "arguments": {"city": "Paris"}}]}, "tags": ["single-call", "text-before-call", "text-after-call", "regression", "x-no-newlines"], "notes": "The issue's input string (no newlines inside the call). Its expected behaviour: streaming and non-streaming both return content 'beforeafter' and the call."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
  "model_rule": "Qwen3-Coder",
  "model_type": "qwen3_moe",
  "parser_source": "Qwen3-Coder model cards: --tool-call-parser qwen3_coder",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "end|>\n<|im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n",
  "reasoning_parser": null,
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "5a38bfa05833266240066aedc497decc9b00cc0d3e3b8cceea98cf530196ab06",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
    "requested_mode": "auto",
    "resolved_revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
fail qwen3-xml/bug-missing-close-parameter-before-function expected_match, stream_equals_nonstream, arguments_json, arguments_schema
Checks that did not pass for qwen3-xml/bug-missing-close-parameter-before-function
CheckStrategyResultDetail
expected_matchnonstream failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{}'
expected_matchone failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
expected_matchspecial failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
expected_matchtoken failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
expected_matchrand:1:8 failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
expected_matchrand:2:8 failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
expected_matchrand:3:8 failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
expected_matchrand:4:8 failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
expected_matchrand:5:8 failtool_calls[0].arguments: expected '{"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}', got '{"body": '
stream_equals_nonstreamone failtool_calls[0].arguments: nonstream '{}', got '{"body": '
stream_equals_nonstreamspecial failtool_calls[0].arguments: nonstream '{}', got '{"body": '
stream_equals_nonstreamtoken failtool_calls[0].arguments: nonstream '{}', got '{"body": '
stream_equals_nonstreamrand:1:8 failtool_calls[0].arguments: nonstream '{}', got '{"body": '
stream_equals_nonstreamrand:2:8 failtool_calls[0].arguments: nonstream '{}', got '{"body": '
stream_equals_nonstreamrand:3:8 failtool_calls[0].arguments: nonstream '{}', got '{"body": '
stream_equals_nonstreamrand:4:8 failtool_calls[0].arguments: nonstream '{}', got '{"body": '
stream_equals_nonstreamrand:5:8 failtool_calls[0].arguments: nonstream '{}', got '{"body": '
arguments_jsonone fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_jsonspecial fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_jsontoken fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_jsonrand:1:8 fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_jsonrand:2:8 fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_jsonrand:3:8 fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_jsonrand:4:8 fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_jsonrand:5:8 fail[0] ThinQ_Connect: arguments are not valid JSON text ('{"body": ': Expecting value: line 1 column 10 (char 9))
arguments_schemanonstream fail[0] ThinQ_Connect: $: 'body' is a required property
arguments_schemaone fail[0] ThinQ_Connect: arguments are not a JSON object; not validated
arguments_schemaspecial fail[0] ThinQ_Connect: arguments are not a JSON object; not validated
arguments_schematoken fail[0] ThinQ_Connect: arguments are not a JSON object; not validated
arguments_schemarand:1:8 fail[0] ThinQ_Connect: arguments are not a JSON object; not validated
arguments_schemarand:2:8 fail[0] ThinQ_Connect: arguments are not a JSON object; not validated
arguments_schemarand:3:8 fail[0] ThinQ_Connect: arguments are not a JSON object; not validated
arguments_schemarand:4:8 fail[0] ThinQ_Connect: arguments are not a JSON object; not validated
arguments_schemarand:5:8 fail[0] ThinQ_Connect: arguments are not a JSON object; not validated

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/imported.jsonl --id qwen3-xml/bug-missing-close-parameter-before-function --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 17 of fixtures/qwen3-xml/imported.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

--- expected
+++ observed
@@ -4,11 +4,5 @@
   "tool_calls": [
     {
-      "arguments": {
-        "body": {
-          "airConJobMode": "AIR_CLEAN",
-          "monitoringEnabled": true,
-          "windStrength": "HIGH"
-        }
-      },
+      "arguments": {},
       "name": "ThinQ_Connect"
     }

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -5,9 +5,5 @@
     {
       "arguments": {
-        "body": {
-          "airConJobMode": "AIR_CLEAN",
-          "monitoringEnabled": true,
-          "windStrength": "HIGH"
-        }
+        "<arguments_raw, not valid JSON>": "{\"body\": "
       },
       "name": "ThinQ_Connect"

Fixture

Provenance: bug_report, https://github.com/vllm-project/vllm/issues/57699. Tags: single-call, malformed, nested-json, regression, x-missing-close-param.

Raw output

<tool_call>
<function=ThinQ_Connect>
<parameter=body>
{"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "body": {
          "airConJobMode": "AIR_CLEAN",
          "monitoringEnabled": true,
          "windStrength": "HIGH"
        }
      },
      "name": "ThinQ_Connect"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/bug-missing-close-parameter-before-function", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B"], "spec_version": "0.1", "provenance": {"kind": "bug_report", "source_url": "https://github.com/vllm-project/vllm/issues/57699", "revision": "issue-57699", "license": "NOASSERTION", "generator": "scripts/fixtures/qwen3-xml/import_qwen3_xml.py"}, "tools": [{"type": "function", "function": {"name": "ThinQ_Connect", "parameters": {"type": "object", "properties": {"body": {"type": "object"}}, "required": ["body"]}}}], "raw_output": "<tool_call>\n<function=ThinQ_Connect>\n<parameter=body>\n{\"airConJobMode\": \"AIR_CLEAN\", \"windStrength\": \"HIGH\", \"monitoringEnabled\": true}\n</function>\n</tool_call>", "output_token_ids": [248058, 198, 27, 1628, 28, 89882, 48, 14659, 2245, 29, 198, 27, 15704, 28, 2513, 29, 198, 4754, 1264, 1073, 11895, 3513, 763, 328, 55247, 76916, 487, 328, 18574, 51079, 763, 328, 87116, 487, 328, 31162, 286, 5290, 763, 804, 92, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n\n</think>\n\n", "thinking": false, "expected": {"content": null, "reasoning_content": null, "tool_calls": [{"name": "ThinQ_Connect", "arguments": {"body": {"airConJobMode": "AIR_CLEAN", "windStrength": "HIGH", "monitoringEnabled": true}}}]}, "tags": ["single-call", "malformed", "nested-json", "regression", "x-missing-close-param"], "notes": "Raw text from the issue (Qwen3.8-27B, BFCL live_simple_40-17-0, temperature 0). The last parameter is closed by </function> without </parameter>; the call is complete, so the value must be kept (vLLM returns {} non-streaming). The issue's tool schema is not shown; 'body' is typed as an object here. The thinking mode of the request is not stated; the fixture assumes the no-think prompt."}
Parser configuration
{
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n\n</think>\n\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "ser\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
fail qwen3-xml/coder-marker-in-arguments expected_match, stream_equals_nonstream, arguments_json, arguments_schema
Checks that did not pass for qwen3-xml/coder-marker-in-arguments
CheckStrategyResultDetail
expected_matchnonstream failtool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchone failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchspecial failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchtoken failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchrand:1:8 failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchrand:2:8 failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchrand:3:8 failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchrand:4:8 failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
expected_matchrand:5:8 failcontent: expected None, got '; open with '; tool_calls: expected ['write_file'], got ['write_file', 'x']
stream_equals_nonstreamone failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
stream_equals_nonstreamspecial failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
stream_equals_nonstreamtoken failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
stream_equals_nonstreamrand:1:8 failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
stream_equals_nonstreamrand:2:8 failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
stream_equals_nonstreamrand:3:8 failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
stream_equals_nonstreamrand:4:8 failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
stream_equals_nonstreamrand:5:8 failcontent: nonstream None, got '; open with '; tool_calls[0].arguments: nonstream '{"path": "x.md"}', got '{"path": "x.md", "content": "Close with '
arguments_jsonone fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_jsonspecial fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_jsontoken fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_jsonrand:1:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_jsonrand:2:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_jsonrand:3:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_jsonrand:4:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_jsonrand:5:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "x.md", "content": "Close with ': Unterminated string starting at: line 1 column 29 (char 28))
arguments_schemanonstream fail[0] write_file: $: 'content' is a required property; [1] 'x' is not an offered tool
arguments_schemaone fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool
arguments_schemaspecial fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool
arguments_schematoken fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool
arguments_schemarand:1:8 fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool
arguments_schemarand:2:8 fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool
arguments_schemarand:3:8 fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool
arguments_schemarand:4:8 fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool
arguments_schemarand:5:8 fail[0] write_file: arguments are not a JSON object; not validated; [1] 'x' is not an offered tool

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/coder-marker-in-arguments --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 32 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

--- expected
+++ observed
@@ -5,8 +5,11 @@
     {
       "arguments": {
-        "content": "Close with </function> then </tool_call>; open with <function=x>.",
         "path": "x.md"
       },
       "name": "write_file"
+    },
+    {
+      "arguments": {},
+      "name": "x"
     }
   ]

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,12 +1,15 @@
 {
-  "content": null,
+  "content": "; open with ",
   "reasoning_content": null,
   "tool_calls": [
     {
       "arguments": {
-        "content": "Close with </function> then </tool_call>; open with <function=x>.",
-        "path": "x.md"
+        "<arguments_raw, not valid JSON>": "{\"path\": \"x.md\", \"content\": \"Close with "
       },
       "name": "write_file"
+    },
+    {
+      "arguments": {},
+      "name": "x"
     }
   ]

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3-Coder-30B-A3B-Instruct/blob/b2cff646eb4bb1d68355c01b18ae02e7cf42d120/chat_template.jinja. Tags: single-call, marker-in-arguments.

Raw output

<tool_call>
<function=write_file>
<parameter=path>
x.md
</parameter>
<parameter=content>
Close with </function> then </tool_call>; open with <function=x>.
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "content": "Close with </function> then </tool_call>; open with <function=x>.",
        "path": "x.md"
      },
      "name": "write_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/coder-marker-in-arguments", "family": "qwen3-xml", "models": ["Qwen/Qwen3-Coder-30B-A3B-Instruct", "Qwen/Qwen3-Coder-480B-A35B-Instruct"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3-Coder-30B-A3B-Instruct/blob/b2cff646eb4bb1d68355c01b18ae02e7cf42d120/chat_template.jinja", "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "5a38bfa05833266240066aedc497decc9b00cc0d3e3b8cceea98cf530196ab06"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "<tool_call>\n<function=write_file>\n<parameter=path>\nx.md\n</parameter>\n<parameter=content>\nClose with </function> then </tool_call>; open with <function=x>.\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [151657, 198, 27, 1688, 28, 4934, 2458, 397, 27, 16181, 81940, 397, 87, 21324, 198, 522, 16181, 397, 27, 16181, 28, 1796, 397, 7925, 448, 690, 1688, 29, 1221, 220, 151658, 26, 1787, 448, 366, 1688, 25387, 29816, 522, 16181, 397, 522, 1688, 397, 151658], "tokenizer": {"repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct", "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n", "expected": {"content": null, "reasoning_content": null, "tool_calls": [{"name": "write_file", "arguments": {"path": "x.md", "content": "Close with </function> then </tool_call>; open with <function=x>."}}]}, "tags": ["single-call", "marker-in-arguments"], "notes": "History render of the official Qwen/Qwen3-Coder-30B-A3B-Instruct chat template. XML-looking markers inside a value, not at line starts. '</tool_call>' is an added token, so it appears as its marker id in output_token_ids."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
  "model_rule": "Qwen3-Coder",
  "model_type": "qwen3_moe",
  "parser_source": "Qwen3-Coder model cards: --tool-call-parser qwen3_coder",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "end|>\n<|im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n",
  "reasoning_parser": null,
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "5a38bfa05833266240066aedc497decc9b00cc0d3e3b8cceea98cf530196ab06",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
    "requested_mode": "auto",
    "resolved_revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
fail qwen3-xml/q38-marker-in-arguments expected_match
Checks that did not pass for qwen3-xml/q38-marker-in-arguments
CheckStrategyResultDetail
expected_matchnonstream failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchone failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchspecial failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchtoken failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchrand:1:8 failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchrand:2:8 failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchrand:3:8 failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchrand:4:8 failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'
expected_matchrand:5:8 failreasoning_content (whitespace only): expected 'Write the note.', got 'Write the note.\n'; tool_calls[0].arguments: expected '{"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}', got '{"path": "notes.md", "content": "Close tags inline: "}'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-marker-in-arguments --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 7 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,9 +1,9 @@
 {
   "content": null,
-  "reasoning_content": "Write the note.",
+  "reasoning_content": "Write the note.\n",
   "tool_calls": [
     {
       "arguments": {
-        "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here.",
+        "content": "Close tags inline: ",
         "path": "notes.md"
       },

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, marker-in-arguments.

Raw output

Write the note.
</think>

<tool_call>
<function=write_file>
<parameter=path>
notes.md
</parameter>
<parameter=content>
Close tags inline: </parameter> and </function> and <parameter=path> are text here.
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Write the note.",
  "tool_calls": [
    {
      "arguments": {
        "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here.",
        "path": "notes.md"
      },
      "name": "write_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-marker-in-arguments", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Write the note.\n</think>\n\n<tool_call>\n<function=write_file>\n<parameter=path>\nnotes.md\n</parameter>\n<parameter=content>\nClose tags inline: </parameter> and </function> and <parameter=path> are text here.\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [7734, 279, 5020, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 4775, 2378, 29, 198, 27, 15704, 79114, 29, 198, 17742, 20668, 198, 510, 15704, 29, 198, 27, 15704, 28, 1733, 29, 198, 7675, 9212, 7147, 25, 672, 15704, 29, 321, 672, 1628, 29, 321, 361, 15704, 79114, 29, 513, 1414, 1532, 13, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Write the note.", "tool_calls": [{"name": "write_file", "arguments": {"path": "notes.md", "content": "Close tags inline: </parameter> and </function> and <parameter=path> are text here."}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "marker-in-arguments"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. The value contains XML-looking markers that are not at a line start. The structural delimiter is '\\n</parameter>\\n', so a parser that splits on a bare '</parameter>' cuts the value short."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
fail qwen3-xml/q38-truncated-in-parameter expected_error, stream_equals_nonstream, arguments_json, arguments_schema
Checks that did not pass for qwen3-xml/q38-truncated-in-parameter
CheckStrategyResultDetail
expected_errornonstream failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errorone failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errorspecial failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errortoken failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errorrand:1:8 failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errorrand:2:8 failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errorrand:3:8 failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errorrand:4:8 failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
expected_errorrand:5:8 failreturned 1 tool call(s) ['write_file'] for Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).
stream_equals_nonstreamone failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
stream_equals_nonstreamspecial failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
stream_equals_nonstreamtoken failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
stream_equals_nonstreamrand:1:8 failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
stream_equals_nonstreamrand:2:8 failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
stream_equals_nonstreamrand:3:8 failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
stream_equals_nonstreamrand:4:8 failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
stream_equals_nonstreamrand:5:8 failtool_calls[0].arguments: nonstream '{"path": "/tmp/build.sh"}', got '{"path": "/tmp/build.sh", "content": "rm -rf /'
arguments_jsonone fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_jsonspecial fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_jsontoken fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_jsonrand:1:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_jsonrand:2:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_jsonrand:3:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_jsonrand:4:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_jsonrand:5:8 fail[0] write_file: arguments are not valid JSON text ('{"path": "/tmp/build.sh", "content": "rm -rf /': Unterminated string starting at: line 1 column 38 (char 37))
arguments_schemanonstream fail[0] write_file: $: 'content' is a required property
arguments_schemaone fail[0] write_file: arguments are not a JSON object; not validated
arguments_schemaspecial fail[0] write_file: arguments are not a JSON object; not validated
arguments_schematoken fail[0] write_file: arguments are not a JSON object; not validated
arguments_schemarand:1:8 fail[0] write_file: arguments are not a JSON object; not validated
arguments_schemarand:2:8 fail[0] write_file: arguments are not a JSON object; not validated
arguments_schemarand:3:8 fail[0] write_file: arguments are not a JSON object; not validated
arguments_schemarand:4:8 fail[0] write_file: arguments are not a JSON object; not validated
arguments_schemarand:5:8 fail[0] write_file: arguments are not a JSON object; not validated

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-truncated-in-parameter --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 22 of fixtures/qwen3-xml/rendered.jsonl.

Observed

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

{
  "content": null,
  "reasoning_content": "Write the script.\n",
  "tool_calls": [
    {
      "arguments": {
        "path": "/tmp/build.sh"
      },
      "name": "write_file"
    }
  ]
}

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

{
  "content": null,
  "reasoning_content": "Write the script.\n",
  "tool_calls": [
    {
      "arguments": {
        "<arguments_raw, not valid JSON>": "{\"path\": \"/tmp/build.sh\", \"content\": \"rm -rf /"
      },
      "name": "write_file"
    }
  ]
}

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: truncated, reasoning, reasoning-prefilled.

Expected graceful failure: Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699). (accept: no_tool_calls, content_passthrough, exception).

Raw output

Write the script.
</think>

<tool_call>
<function=write_file>
<parameter=path>
/tmp/build.sh
</parameter>
<parameter=content>
rm -rf /
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-truncated-in-parameter", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Write the script.\n</think>\n\n<tool_call>\n<function=write_file>\n<parameter=path>\n/tmp/build.sh\n</parameter>\n<parameter=content>\nrm -rf /", "output_token_ids": [7734, 279, 5148, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 4775, 2378, 29, 198, 27, 15704, 79114, 29, 198, 55101, 29594, 2318, 198, 510, 15704, 29, 198, 27, 15704, 28, 1733, 29, 198, 8454, 471, 7799, 593], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected_error": {"reason": "Output stopped (max_tokens) inside a parameter value, before </parameter> and </function>. The call is incomplete; a truncated 'rm -rf /tmp/build' must not become a complete 'rm -rf /' (vLLM #57699).", "accept": ["no_tool_calls", "content_passthrough", "exception"]}, "tags": ["truncated", "reasoning", "reasoning-prefilled"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/bug-coder-missing-tool-call-opener expected_match, stream_equals_nonstream
Checks that did not pass for qwen3-xml/bug-coder-missing-tool-call-opener
CheckStrategyResultDetail
expected_matchone soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
expected_matchspecial soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
expected_matchtoken soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
expected_matchrand:1:8 soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
expected_matchrand:2:8 soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
expected_matchrand:3:8 soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
expected_matchrand:4:8 soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
expected_matchrand:5:8 soft passcontent (whitespace only): expected 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamone soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamspecial soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamtoken soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamrand:1:8 soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamrand:2:8 soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamrand:3:8 soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamrand:4:8 soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'
stream_equals_nonstreamrand:5:8 soft passcontent (whitespace only): nonstream 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.', got 'I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/imported.jsonl --id qwen3-xml/bug-coder-missing-tool-call-opener --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 18 of fixtures/qwen3-xml/imported.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

Matches the expected parse.

{
  "content": "I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "pattern": "**/*.md"
      },
      "name": "glob"
    }
  ]
}

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,4 +1,4 @@
 {
-  "content": "I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.",
+  "content": "I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n",
   "reasoning_content": null,
   "tool_calls": [

Fixture

Provenance: bug_report, https://github.com/ollama/ollama/issues/18530. Tags: single-call, text-before-call, malformed, regression, x-bare-function.

Raw output

I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.

<function=glob>
<parameter=pattern>
**/*.md
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": "I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "pattern": "**/*.md"
      },
      "name": "glob"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/bug-coder-missing-tool-call-opener", "family": "qwen3-xml", "models": ["Qwen/Qwen3-Coder-30B-A3B-Instruct", "Qwen/Qwen3-Coder-480B-A35B-Instruct"], "spec_version": "0.1", "provenance": {"kind": "bug_report", "source_url": "https://github.com/ollama/ollama/issues/18530", "revision": "issue-18530", "license": "NOASSERTION", "generator": "scripts/fixtures/qwen3-xml/import_qwen3_xml.py"}, "tools": [{"type": "function", "function": {"name": "glob", "parameters": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}, "description": "Find files by glob pattern"}}], "raw_output": "I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.\n\n<function=glob>\n<parameter=pattern>\n**/*.md\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [40, 686, 1156, 1779, 369, 678, 73192, 3542, 304, 279, 1482, 6220, 323, 1181, 1186, 75701, 11, 323, 1221, 1140, 1105, 1667, 279, 13206, 5392, 382, 27, 1688, 37162, 1684, 397, 27, 16181, 28, 14339, 397, 334, 23540, 2277, 198, 522, 16181, 397, 522, 1688, 397, 151658], "tokenizer": {"repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct", "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n", "expected": {"content": "I will first check for all Markdown files in the current directory and its subdirectories, and then list them using the glob tool.", "reasoning_content": null, "tool_calls": [{"name": "glob", "arguments": {"pattern": "**/*.md"}}]}, "tags": ["single-call", "text-before-call", "malformed", "regression", "x-bare-function"], "notes": "A real qwen3-coder:30b generation captured by the reporter with raw: true (temperature 0, seed 1): the model omitted the <tool_call> opener after a prose preamble."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
  "model_rule": "Qwen3-Coder",
  "model_type": "qwen3_moe",
  "parser_source": "Qwen3-Coder model cards: --tool-call-parser qwen3_coder",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "end|>\n<|im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n",
  "reasoning_parser": null,
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "5a38bfa05833266240066aedc497decc9b00cc0d3e3b8cceea98cf530196ab06",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
    "requested_mode": "auto",
    "resolved_revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/coder-text-before-call expected_match, stream_equals_nonstream
Checks that did not pass for qwen3-xml/coder-text-before-call
CheckStrategyResultDetail
expected_matchone soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
expected_matchspecial soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
expected_matchtoken soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
expected_matchrand:1:8 soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
expected_matchrand:2:8 soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
expected_matchrand:3:8 soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
expected_matchrand:4:8 soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
expected_matchrand:5:8 soft passcontent (whitespace only): expected "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamone soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamspecial soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamtoken soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamrand:1:8 soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamrand:2:8 soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamrand:3:8 soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamrand:4:8 soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"
stream_equals_nonstreamrand:5:8 soft passcontent (whitespace only): nonstream "I'll create the file now.", got "I'll create the file now.\n\n"

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/coder-text-before-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 28 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

Matches the expected parse.

{
  "content": "I'll create the file now.",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "content": "def greet(name: str) -> str:\n    \"\"\"Say hi.\"\"\"\n    return f\"Hello, {name}!\"\n\n\nprint(greet(\"Zoë\"))",
        "path": "hello.py"
      },
      "name": "write_file"
    }
  ]
}

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,4 +1,4 @@
 {
-  "content": "I'll create the file now.",
+  "content": "I'll create the file now.\n\n",
   "reasoning_content": null,
   "tool_calls": [

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3-Coder-30B-A3B-Instruct/blob/b2cff646eb4bb1d68355c01b18ae02e7cf42d120/chat_template.jinja. Tags: single-call, text-before-call, x-multiline-string.

Raw output

I'll create the file now.

<tool_call>
<function=write_file>
<parameter=path>
hello.py
</parameter>
<parameter=content>
def greet(name: str) -> str:
    """Say hi."""
    return f"Hello, {name}!"


print(greet("Zoë"))
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": "I'll create the file now.",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "content": "def greet(name: str) -> str:\n    \"\"\"Say hi.\"\"\"\n    return f\"Hello, {name}!\"\n\n\nprint(greet(\"Zoë\"))",
        "path": "hello.py"
      },
      "name": "write_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/coder-text-before-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3-Coder-30B-A3B-Instruct", "Qwen/Qwen3-Coder-480B-A35B-Instruct"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3-Coder-30B-A3B-Instruct/blob/b2cff646eb4bb1d68355c01b18ae02e7cf42d120/chat_template.jinja", "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "5a38bfa05833266240066aedc497decc9b00cc0d3e3b8cceea98cf530196ab06"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "I'll create the file now.\n\n<tool_call>\n<function=write_file>\n<parameter=path>\nhello.py\n</parameter>\n<parameter=content>\ndef greet(name: str) -> str:\n    \"\"\"Say hi.\"\"\"\n    return f\"Hello, {name}!\"\n\n\nprint(greet(\"Zoë\"))\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [40, 3278, 1855, 279, 1034, 1431, 382, 151657, 198, 27, 1688, 28, 4934, 2458, 397, 27, 16181, 81940, 397, 14990, 7197, 198, 522, 16181, 397, 27, 16181, 28, 1796, 397, 750, 40786, 3153, 25, 607, 8, 1464, 607, 510, 262, 4210, 45764, 15588, 14442, 262, 470, 282, 1, 9707, 11, 314, 606, 92, 8958, 1406, 1350, 3268, 3744, 445, 57, 78, 12179, 5455, 522, 16181, 397, 522, 1688, 397, 151658], "tokenizer": {"repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct", "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n", "expected": {"content": "I'll create the file now.", "reasoning_content": null, "tool_calls": [{"name": "write_file", "arguments": {"path": "hello.py", "content": "def greet(name: str) -> str:\n    \"\"\"Say hi.\"\"\"\n    return f\"Hello, {name}!\"\n\n\nprint(greet(\"Zoë\"))"}}]}, "tags": ["single-call", "text-before-call", "x-multiline-string"], "notes": "History render of the official Qwen/Qwen3-Coder-30B-A3B-Instruct chat template. The Qwen3-Coder template writes '\\n' + content + '\\n' and then '\\n<tool_call>', so the content is followed by '\\n\\n' of markup."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
  "model_rule": "Qwen3-Coder",
  "model_type": "qwen3_moe",
  "parser_source": "Qwen3-Coder model cards: --tool-call-parser qwen3_coder",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "end|>\n<|im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n",
  "reasoning_parser": null,
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "5a38bfa05833266240066aedc497decc9b00cc0d3e3b8cceea98cf530196ab06",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
    "requested_mode": "auto",
    "resolved_revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/llamacpp-q35-call-inside-think expected_match
Checks that did not pass for qwen3-xml/llamacpp-q35-call-inside-think
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Need to inspect the current directory.', got 'Need to inspect the current directory.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/imported.jsonl --id qwen3-xml/llamacpp-q35-call-inside-think --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 6 of fixtures/qwen3-xml/imported.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Need to inspect the current directory.",
+  "reasoning_content": "Need to inspect the current directory.\n",
   "tool_calls": [
     {

Fixture

Provenance: engine_test, https://github.com/ggml-org/llama.cpp/blob/7fe450e19305b828c199d602c23a8337aaa1f03b/tests/test-chat.cpp#L2323-L2340. Tags: single-call, reasoning, reasoning-prefilled, malformed, x-call-inside-think.

Raw output

Need to inspect the current directory.
<tool_call>
<function=run_in_terminal>
<parameter=command>
pwd
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Need to inspect the current directory.",
  "tool_calls": [
    {
      "arguments": {
        "command": "pwd"
      },
      "name": "run_in_terminal"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/llamacpp-q35-call-inside-think", "family": "qwen3-xml", "models": ["Qwen/Qwen3.5-9B"], "spec_version": "0.1", "provenance": {"kind": "engine_test", "source_url": "https://github.com/ggml-org/llama.cpp/blob/7fe450e19305b828c199d602c23a8337aaa1f03b/tests/test-chat.cpp#L2323-L2340", "revision": "7fe450e19305b828c199d602c23a8337aaa1f03b", "license": "MIT", "generator": "scripts/fixtures/qwen3-xml/import_qwen3_xml.py", "attribution": "Copyright (c) 2023-2026 The ggml authors (MIT)."}, "tools": [{"type": "function", "function": {"name": "run_in_terminal", "parameters": {"type": "object", "properties": {"command": {"type": "string", "description": "Shell command to run"}}, "required": ["command"]}, "description": "Run a shell command."}}], "raw_output": "Need to inspect the current directory.\n<tool_call>\n<function=run_in_terminal>\n<parameter=command>\npwd\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [22916, 310, 23331, 279, 1428, 6025, 13, 198, 248058, 198, 27, 1628, 28, 5917, 1201, 62138, 29, 198, 27, 15704, 28, 5454, 29, 198, 24751, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.5-9B", "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Need to inspect the current directory.", "tool_calls": [{"name": "run_in_terminal", "arguments": {"command": "pwd"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "malformed", "x-call-inside-think"], "notes": "The model starts a tool call without closing the prefilled <think> block. llama.cpp's test ('a tool call ends the prefilled thinking block, with or without a closing </think>') treats <tool_call> as ending the reasoning. This is the most-reported failure for the family: https://github.com/ggml-org/llama.cpp/issues/20837, https://github.com/vllm-project/vllm/issues/39056."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.5-9B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.5-9B",
    "requested_mode": "auto",
    "resolved_revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/llamacpp-q35-reasoning-content-call expected_match, stream_equals_nonstream
Checks that did not pass for qwen3-xml/llamacpp-q35-reasoning-content-call
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchone soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchspecial soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchtoken soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchrand:1:8 soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchrand:2:8 soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchrand:3:8 soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchrand:4:8 soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
expected_matchrand:5:8 soft passcontent (whitespace only): expected 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'; reasoning_content (whitespace only): expected 'I should inspect the directory.', got 'I should inspect the directory.\n'
stream_equals_nonstreamone soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'
stream_equals_nonstreamspecial soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'
stream_equals_nonstreamtoken soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'
stream_equals_nonstreamrand:1:8 soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'
stream_equals_nonstreamrand:2:8 soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'
stream_equals_nonstreamrand:3:8 soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'
stream_equals_nonstreamrand:4:8 soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'
stream_equals_nonstreamrand:5:8 soft passcontent (whitespace only): nonstream 'Let me inspect it now.', got '\n\nLet me inspect it now.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/imported.jsonl --id qwen3-xml/llamacpp-q35-reasoning-content-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 8 of fixtures/qwen3-xml/imported.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": "Let me inspect it now.",
-  "reasoning_content": "I should inspect the directory.",
+  "reasoning_content": "I should inspect the directory.\n",
   "tool_calls": [
     {

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
-  "content": "Let me inspect it now.",
-  "reasoning_content": "I should inspect the directory.",
+  "content": "\n\nLet me inspect it now.\n",
+  "reasoning_content": "I should inspect the directory.\n",
   "tool_calls": [
     {

Fixture

Provenance: engine_test, https://github.com/ggml-org/llama.cpp/blob/7fe450e19305b828c199d602c23a8337aaa1f03b/tests/test-chat.cpp#L2459-L2480. Tags: single-call, reasoning, reasoning-prefilled, text-before-call.

Raw output

I should inspect the directory.
</think>

Let me inspect it now.
<tool_call>
<function=run_in_terminal>
<parameter=command>
pwd
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": "Let me inspect it now.",
  "reasoning_content": "I should inspect the directory.",
  "tool_calls": [
    {
      "arguments": {
        "command": "pwd"
      },
      "name": "run_in_terminal"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/llamacpp-q35-reasoning-content-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3.5-9B"], "spec_version": "0.1", "provenance": {"kind": "engine_test", "source_url": "https://github.com/ggml-org/llama.cpp/blob/7fe450e19305b828c199d602c23a8337aaa1f03b/tests/test-chat.cpp#L2459-L2480", "revision": "7fe450e19305b828c199d602c23a8337aaa1f03b", "license": "MIT", "generator": "scripts/fixtures/qwen3-xml/import_qwen3_xml.py", "attribution": "Copyright (c) 2023-2026 The ggml authors (MIT)."}, "tools": [{"type": "function", "function": {"name": "run_in_terminal", "parameters": {"type": "object", "properties": {"command": {"type": "string", "description": "Shell command to run"}}, "required": ["command"]}, "description": "Run a shell command."}}], "raw_output": "I should inspect the directory.\n</think>\n\nLet me inspect it now.\n<tool_call>\n<function=run_in_terminal>\n<parameter=command>\npwd\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [40, 1220, 23331, 279, 6025, 13, 198, 248069, 271, 9764, 728, 23331, 424, 1381, 13, 198, 248058, 198, 27, 1628, 28, 5917, 1201, 62138, 29, 198, 27, 15704, 28, 5454, 29, 198, 24751, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.5-9B", "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": "Let me inspect it now.", "reasoning_content": "I should inspect the directory.", "tool_calls": [{"name": "run_in_terminal", "arguments": {"command": "pwd"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "text-before-call"]}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.5-9B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.5-9B",
    "requested_mode": "auto",
    "resolved_revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/llamacpp-q35-reasoning-then-call expected_match
Checks that did not pass for qwen3-xml/llamacpp-q35-reasoning-then-call
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchone soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchspecial soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchtoken soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected "I'm\nthinking", got "I'm\nthinking\n"

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/imported.jsonl --id qwen3-xml/llamacpp-q35-reasoning-then-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 1 of fixtures/qwen3-xml/imported.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "I'm\nthinking",
+  "reasoning_content": "I'm\nthinking\n",
   "tool_calls": [
     {

Fixture

Provenance: engine_test, https://github.com/ggml-org/llama.cpp/blob/7fe450e19305b828c199d602c23a8337aaa1f03b/tests/test-chat.cpp#L2164-L2174. Tags: single-call, reasoning, reasoning-prefilled, numeric-arguments.

Raw output

I'm
thinking
</think>

<tool_call>
<function=special_function>
<parameter=arg1>
1
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "I'm\nthinking",
  "tool_calls": [
    {
      "arguments": {
        "arg1": 1
      },
      "name": "special_function"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/llamacpp-q35-reasoning-then-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3.5-9B"], "spec_version": "0.1", "provenance": {"kind": "engine_test", "source_url": "https://github.com/ggml-org/llama.cpp/blob/7fe450e19305b828c199d602c23a8337aaa1f03b/tests/test-chat.cpp#L2164-L2174", "revision": "7fe450e19305b828c199d602c23a8337aaa1f03b", "license": "MIT", "generator": "scripts/fixtures/qwen3-xml/import_qwen3_xml.py", "attribution": "Copyright (c) 2023-2026 The ggml authors (MIT)."}, "tools": [{"type": "function", "function": {"name": "special_function", "parameters": {"type": "object", "properties": {"arg1": {"type": "integer", "description": "The arg."}}, "required": ["arg1"]}, "description": "I'm special"}}], "raw_output": "I'm\nthinking\n</think>\n\n<tool_call>\n<function=special_function>\n<parameter=arg1>\n1\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [40, 2688, 198, 79420, 198, 248069, 271, 248058, 198, 27, 1628, 13744, 2869, 8901, 29, 198, 27, 15704, 28, 832, 16, 29, 198, 16, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.5-9B", "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "I'm\nthinking", "tool_calls": [{"name": "special_function", "arguments": {"arg1": 1}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "numeric-arguments"]}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.5-9B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.5-9B",
    "requested_mode": "auto",
    "resolved_revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q35-python-literals expected_match
Checks that did not pass for qwen3-xml/q35-python-literals
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Set a repeating alarm.', got 'Set a repeating alarm.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q35-python-literals --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 24 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Set a repeating alarm.",
+  "reasoning_content": "Set a repeating alarm.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.5-9B/blob/c202236235762e1c871ad0ccb60c8ee5ba337b9a/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, numeric-arguments, x-python-literals.

Raw output

Set a repeating alarm.
</think>

<tool_call>
<function=set_alarm>
<parameter=hour>
6
</parameter>
<parameter=minute>
0
</parameter>
<parameter=repeat>
True
</parameter>
<parameter=label>
gym
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Set a repeating alarm.",
  "tool_calls": [
    {
      "arguments": {
        "hour": 6,
        "label": "gym",
        "minute": 0,
        "repeat": true
      },
      "name": "set_alarm"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q35-python-literals", "family": "qwen3-xml", "models": ["Qwen/Qwen3.5-9B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.5-9B/blob/c202236235762e1c871ad0ccb60c8ee5ba337b9a/chat_template.jinja", "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Set a repeating alarm.\n</think>\n\n<tool_call>\n<function=set_alarm>\n<parameter=hour>\n6\n</parameter>\n<parameter=minute>\n0\n</parameter>\n<parameter=repeat>\nTrue\n</parameter>\n<parameter=label>\ngym\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [1591, 264, 38495, 16136, 13, 198, 248069, 271, 248058, 198, 27, 1628, 66473, 64049, 29, 198, 27, 15704, 28, 23899, 29, 198, 21, 198, 510, 15704, 29, 198, 27, 15704, 28, 41143, 29, 198, 15, 198, 510, 15704, 29, 198, 27, 15704, 28, 29625, 29, 198, 2434, 198, 510, 15704, 29, 198, 27, 15704, 95277, 29, 198, 70, 1544, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.5-9B", "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Set a repeating alarm.", "tool_calls": [{"name": "set_alarm", "arguments": {"hour": 6, "minute": 0, "repeat": true, "label": "gym"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "numeric-arguments", "x-python-literals"], "notes": "History render of the official Qwen/Qwen3.5-9B chat template. The Qwen3.5-9B template writes Python literals ('True'); with a boolean schema the value is true."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.5-9B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.5-9B",
    "requested_mode": "auto",
    "resolved_revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q35-single-call expected_match
Checks that did not pass for qwen3-xml/q35-single-call
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Weather lookup.', got 'Weather lookup.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q35-single-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 25 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Weather lookup.",
+  "reasoning_content": "Weather lookup.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.5-9B/blob/c202236235762e1c871ad0ccb60c8ee5ba337b9a/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, unicode.

Raw output

Weather lookup.
</think>

<tool_call>
<function=get_weather>
<parameter=city>
Zürich
</parameter>
<parameter=unit>
c
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Weather lookup.",
  "tool_calls": [
    {
      "arguments": {
        "city": "Zürich",
        "unit": "c"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q35-single-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3.5-9B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.5-9B/blob/c202236235762e1c871ad0ccb60c8ee5ba337b9a/chat_template.jinja", "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Weather lookup.\n</think>\n\n<tool_call>\n<function=get_weather>\n<parameter=city>\nZürich\n</parameter>\n<parameter=unit>\nc\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [28034, 18054, 13, 198, 248069, 271, 248058, 198, 27, 1628, 27362, 67017, 29, 198, 27, 15704, 28, 8656, 29, 198, 57, 5021, 693, 198, 510, 15704, 29, 198, 27, 15704, 28, 3715, 29, 198, 66, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.5-9B", "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Weather lookup.", "tool_calls": [{"name": "get_weather", "arguments": {"city": "Zürich", "unit": "c"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "unicode"], "notes": "History render of the official Qwen/Qwen3.5-9B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.5-9B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.5-9B",
    "requested_mode": "auto",
    "resolved_revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "revision": "c202236235762e1c871ad0ccb60c8ee5ba337b9a",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-empty-arguments expected_match
Checks that did not pass for qwen3-xml/q38-empty-arguments
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Get the time.', got 'Get the time.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-empty-arguments --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 6 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Get the time.",
+  "reasoning_content": "Get the time.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, empty-arguments.

Raw output

Get the time.
</think>

<tool_call>
<function=get_time>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Get the time.",
  "tool_calls": [
    {
      "arguments": {},
      "name": "get_time"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-empty-arguments", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Get the time.\n</think>\n\n<tool_call>\n<function=get_time>\n</function>\n</tool_call>", "output_token_ids": [1882, 279, 854, 13, 198, 248069, 271, 248058, 198, 27, 1628, 27362, 2914, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Get the time.", "tool_calls": [{"name": "get_time", "arguments": {}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "empty-arguments"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-empty-reasoning expected_match
Checks that did not pass for qwen3-xml/q38-empty-reasoning
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchone soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected None, got '\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected None, got '\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-empty-reasoning --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 17 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": null,
+  "reasoning_content": "\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, empty-arguments, reasoning-prefilled.

Raw output

</think>

<tool_call>
<function=get_time>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {},
      "name": "get_time"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-empty-reasoning", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "\n</think>\n\n<tool_call>\n<function=get_time>\n</function>\n</tool_call>", "output_token_ids": [198, 248069, 271, 248058, 198, 27, 1628, 27362, 2914, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": null, "tool_calls": [{"name": "get_time", "arguments": {}}]}, "tags": ["single-call", "empty-arguments", "reasoning-prefilled"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. The prompt/completion boundary fell inside a merged BPE token, so the rendered text was sliced after the generation prompt and the remainder re-encoded. Thinking enabled but the reasoning is empty: the completion starts with '\\n</think>'."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-long-arguments expected_match
Checks that did not pass for qwen3-xml/q38-long-arguments
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Write the module.', got 'Write the module.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-long-arguments --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 16 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Write the module.",
+  "reasoning_content": "Write the module.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, long-arguments, x-multiline-string.

Raw output

Write the module.
</think>

<tool_call>
<function=write_file>
<parameter=path>
steps.py
</parameter>
<parameter=content>
"""Generated module used as a long tool argument."""

from __future__ import annotations

def step_00(x: int) -> int:
    return x + 0

def step_01(x: int) -> int:
    return x + 1

def step_02(x: int) -> int:
    return x + 2

def step_03(x: int) -> int:
    return x + 3

def step_04(x: int) -> int:
    return x + 4

def step_05(x: int) -> int:
    return x + 5

def step_06(x: int) -> int:
    return x + 6

def step_07(x: int) -> int:
    return x + 7

def step_08(x: int) -> int:
    return x + 8

def step_09(x: int) -> int:
    return x + 9

def step_10(x: int) -> int:
    return x + 10

def step_11(x: int) -> int:
    return x + 11

def step_12(x: int) -> int:
    return x + 12

def step_13(x: int) -> int:
    return x + 13

def step_14(x: int) -> int:
    return x + 14

def step_15(x: int) -> int:
    return x + 15

def step_16(x: int) -> int:
    return x + 16

def step_17(x: int) -> int:
    return x + 17

def step_18(x: int) -> int:
    return x + 18

def step_19(x: int) -> int:
    return x + 19

def step_20(x: int) -> int:
    return x + 20

def step_21(x: int) -> int:
    return x + 21

def step_22(x: int) -> int:
    return x + 22

def step_23(x: int) -> int:
    return x + 23

def step_24(x: int) -> int:
    return x + 24

def step_25(x: int) -> int:
    return x + 25

def step_26(x: int) -> int:
    return x + 26

def step_27(x: int) -> int:
    return x + 27

def step_28(x: int) -> int:
    return x + 28

def step_29(x: int) -> int:
    return x + 29

def step_30(x: int) -> int:
    return x + 30

def step_31(x: int) -> int:
    return x + 31

def step_32(x: int) -> int:
    return x + 32

def step_33(x: int) -> int:
    return x + 33

def step_34(x: int) -> int:
    return x + 34

def step_35(x: int) -> int:
    return x + 35

def step_36(x: int) -> int:
    return x + 36

def step_37(x: int) -> int:
    return x + 37

def step_38(x: int) -> int:
    return x + 38

def step_39(x: int) -> int:
    return x + 39

def step_40(x: int) -> int:
    return x + 40

def step_41(x: int) -> int:
    return x + 41

def step_42(x: int) -> int:
    return x + 42

def step_43(x: int) -> int:
    return x + 43

def step_44(x: int) -> int:
    return x + 44

def step_45(x: int) -> int:
    return x + 45

def step_46(x: int) -> int:
    return x + 46

def step_47(x: int) -> int:
    return x + 47

def step_48(x: int) -> int:
    return x + 48

def step_49(x: int) -> int:
    return x + 49

def step_50(x: int) -> int:
    return x + 50

def step_51(x: int) -> int:
    return x + 51

def step_52(x: int) -> int:
    return x + 52

def step_53(x: int) -> int:
    return x + 53

def step_54(x: int) -> int:
    return x + 54

def step_55(x: int) -> int:
    return x + 55

def step_56(x: int) -> int:
    return x + 56

def step_57(x: int) -> int:
    return x + 57

def step_58(x: int) -> int:
    return x + 58

def step_59(x: int) -> int:
    return x + 59

def step_60(x: int) -> int:
    return x + 60

def step_61(x: int) -> int:
    return x + 61

def step_62(x: int) -> int:
    return x + 62

def step_63(x: int) -> int:
    return x + 63

def step_64(x: int) -> int:
    return x + 64

def step_65(x: int) -> int:
    return x + 65

def step_66(x: int) -> int:
    return x + 66

def step_67(x: int) -> int:
    return x + 67

def step_68(x: int) -> int:
    return x + 68

def step_69(x: int) -> int:
    return x + 69

def step_70(x: int) -> int:
    return x + 70

def step_71(x: int) -> int:
    return x + 71

def step_72(x: int) -> int:
    return x + 72

def step_73(x: int) -> int:
    return x + 73

def step_74(x: int) -> int:
    return x + 74

def step_75(x: int) -> int:
    return x + 75

def step_76(x: int) -> int:
    return x + 76

def step_77(x: int) -> int:
    return x + 77

def step_78(x: int) -> int:
    return x + 78

def step_79(x: int) -> int:
    return x + 79

</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Write the module.",
  "tool_calls": [
    {
      "arguments": {
        "content": "\"\"\"Generated module used as a long tool argument.\"\"\"\n\nfrom __future__ import annotations\n\ndef step_00(x: int) -> int:\n    return x + 0\n\ndef step_01(x: int) -> int:\n    return x + 1\n\ndef step_02(x: int) -> int:\n    return x + 2\n\ndef step_03(x: int) -> int:\n    return x + 3\n\ndef step_04(x: int) -> int:\n    return x + 4\n\ndef step_05(x: int) -> int:\n    return x + 5\n\ndef step_06(x: int) -> int:\n    return x + 6\n\ndef step_07(x: int) -> int:\n    return x + 7\n\ndef step_08(x: int) -> int:\n    return x + 8\n\ndef step_09(x: int) -> int:\n    return x + 9\n\ndef step_10(x: int) -> int:\n    return x + 10\n\ndef step_11(x: int) -> int:\n    return x + 11\n\ndef step_12(x: int) -> int:\n    return x + 12\n\ndef step_13(x: int) -> int:\n    return x + 13\n\ndef step_14(x: int) -> int:\n    return x + 14\n\ndef step_15(x: int) -> int:\n    return x + 15\n\ndef step_16(x: int) -> int:\n    return x + 16\n\ndef step_17(x: int) -> int:\n    return x + 17\n\ndef step_18(x: int) -> int:\n    return x + 18\n\ndef step_19(x: int) -> int:\n    return x + 19\n\ndef step_20(x: int) -> int:\n    return x + 20\n\ndef step_21(x: int) -> int:\n    return x + 21\n\ndef step_22(x: int) -> int:\n    return x + 22\n\ndef step_23(x: int) -> int:\n    return x + 23\n\ndef step_24(x: int) -> int:\n    return x + 24\n\ndef step_25(x: int) -> int:\n    return x + 25\n\ndef step_26(x: int) -> int:\n    return x + 26\n\ndef step_27(x: int) -> int:\n    return x + 27\n\ndef step_28(x: int) -> int:\n    return x + 28\n\ndef step_29(x: int) -> int:\n    return x + 29\n\ndef step_30(x: int) -> int:\n    return x + 30\n\ndef step_31(x: int) -> int:\n    return x + 31\n\ndef step_32(x: int) -> int:\n    return x + 32\n\ndef step_33(x: int) -> int:\n    return x + 33\n\ndef step_34(x: int) -> int:\n    return x + 34\n\ndef step_35(x: int) -> int:\n    return x + 35\n\ndef step_36(x: int) -> int:\n    return x + 36\n\ndef step_37(x: int) -> int:\n    return x + 37\n\ndef step_38(x: int) -> int:\n    return x + 38\n\ndef step_39(x: int) -> int:\n    return x + 39\n\ndef step_40(x: int) -> int:\n    return x + 40\n\ndef step_41(x: int) -> int:\n    return x + 41\n\ndef step_42(x: int) -> int:\n    return x + 42\n\ndef step_43(x: int) -> int:\n    return x + 43\n\ndef step_44(x: int) -> int:\n    return x + 44\n\ndef step_45(x: int) -> int:\n    return x + 45\n\ndef step_46(x: int) -> int:\n    return x + 46\n\ndef step_47(x: int) -> int:\n    return x + 47\n\ndef step_48(x: int) -> int:\n    return x + 48\n\ndef step_49(x: int) -> int:\n    return x + 49\n\ndef step_50(x: int) -> int:\n    return x + 50\n\ndef step_51(x: int) -> int:\n    return x + 51\n\ndef step_52(x: int) -> int:\n    return x + 52\n\ndef step_53(x: int) -> int:\n    return x + 53\n\ndef step_54(x: int) -> int:\n    return x + 54\n\ndef step_55(x: int) -> int:\n    return x + 55\n\ndef step_56(x: int) -> int:\n    return x + 56\n\ndef step_57(x: int) -> int:\n    return x + 57\n\ndef step_58(x: int) -> int:\n    return x + 58\n\ndef step_59(x: int) -> int:\n    return x + 59\n\ndef step_60(x: int) -> int:\n    return x + 60\n\ndef step_61(x: int) -> int:\n    return x + 61\n\ndef step_62(x: int) -> int:\n    return x + 62\n\ndef step_63(x: int) -> int:\n    return x + 63\n\ndef step_64(x: int) -> int:\n    return x + 64\n\ndef step_65(x: int) -> int:\n    return x + 65\n\ndef step_66(x: int) -> int:\n    return x + 66\n\ndef step_67(x: int) -> int:\n    return x + 67\n\ndef step_68(x: int) -> int:\n    return x + 68\n\ndef step_69(x: int) -> int:\n    return x + 69\n\ndef step_70(x: int) -> int:\n    return x + 70\n\ndef step_71(x: int) -> int:\n    return x + 71\n\ndef step_72(x: int) -> int:\n    return x + 72\n\ndef step_73(x: int) -> int:\n    return x + 73\n\ndef step_74(x: int) -> int:\n    return x + 74\n\ndef step_75(x: int) -> int:\n    return x + 75\n\ndef step_76(x: int) -> int:\n    return x + 76\n\ndef step_77(x: int) -> int:\n    return x + 77\n\ndef step_78(x: int) -> int:\n    return x + 78\n\ndef step_79(x: int) -> int:\n    return x + 79\n",
        "path": "steps.py"
      },
      "name": "write_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-long-arguments", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Write the module.\n</think>\n\n<tool_call>\n<function=write_file>\n<parameter=path>\nsteps.py\n</parameter>\n<parameter=content>\n\"\"\"Generated module used as a long tool argument.\"\"\"\n\nfrom __future__ import annotations\n\ndef step_00(x: int) -> int:\n    return x + 0\n\ndef step_01(x: int) -> int:\n    return x + 1\n\ndef step_02(x: int) -> int:\n    return x + 2\n\ndef step_03(x: int) -> int:\n    return x + 3\n\ndef step_04(x: int) -> int:\n    return x + 4\n\ndef step_05(x: int) -> int:\n    return x + 5\n\ndef step_06(x: int) -> int:\n    return x + 6\n\ndef step_07(x: int) -> int:\n    return x + 7\n\ndef step_08(x: int) -> int:\n    return x + 8\n\ndef step_09(x: int) -> int:\n    return x + 9\n\ndef step_10(x: int) -> int:\n    return x + 10\n\ndef step_11(x: int) -> int:\n    return x + 11\n\ndef step_12(x: int) -> int:\n    return x + 12\n\ndef step_13(x: int) -> int:\n    return x + 13\n\ndef step_14(x: int) -> int:\n    return x + 14\n\ndef step_15(x: int) -> int:\n    return x + 15\n\ndef step_16(x: int) -> int:\n    return x + 16\n\ndef step_17(x: int) -> int:\n    return x + 17\n\ndef step_18(x: int) -> int:\n    return x + 18\n\ndef step_19(x: int) -> int:\n    return x + 19\n\ndef step_20(x: int) -> int:\n    return x + 20\n\ndef step_21(x: int) -> int:\n    return x + 21\n\ndef step_22(x: int) -> int:\n    return x + 22\n\ndef step_23(x: int) -> int:\n    return x + 23\n\ndef step_24(x: int) -> int:\n    return x + 24\n\ndef step_25(x: int) -> int:\n    return x + 25\n\ndef step_26(x: int) -> int:\n    return x + 26\n\ndef step_27(x: int) -> int:\n    return x + 27\n\ndef step_28(x: int) -> int:\n    return x + 28\n\ndef step_29(x: int) -> int:\n    return x + 29\n\ndef step_30(x: int) -> int:\n    return x + 30\n\ndef step_31(x: int) -> int:\n    return x + 31\n\ndef step_32(x: int) -> int:\n    return x + 32\n\ndef step_33(x: int) -> int:\n    return x + 33\n\ndef step_34(x: int) -> int:\n    return x + 34\n\ndef step_35(x: int) -> int:\n    return x + 35\n\ndef step_36(x: int) -> int:\n    return x + 36\n\ndef step_37(x: int) -> int:\n    return x + 37\n\ndef step_38(x: int) -> int:\n    return x + 38\n\ndef step_39(x: int) -> int:\n    return x + 39\n\ndef step_40(x: int) -> int:\n    return x + 40\n\ndef step_41(x: int) -> int:\n    return x + 41\n\ndef step_42(x: int) -> int:\n    return x + 42\n\ndef step_43(x: int) -> int:\n    return x + 43\n\ndef step_44(x: int) -> int:\n    return x + 44\n\ndef step_45(x: int) -> int:\n    return x + 45\n\ndef step_46(x: int) -> int:\n    return x + 46\n\ndef step_47(x: int) -> int:\n    return x + 47\n\ndef step_48(x: int) -> int:\n    return x + 48\n\ndef step_49(x: int) -> int:\n    return x + 49\n\ndef step_50(x: int) -> int:\n    return x + 50\n\ndef step_51(x: int) -> int:\n    return x + 51\n\ndef step_52(x: int) -> int:\n    return x + 52\n\ndef step_53(x: int) -> int:\n    return x + 53\n\ndef step_54(x: int) -> int:\n    return x + 54\n\ndef step_55(x: int) -> int:\n    return x + 55\n\ndef step_56(x: int) -> int:\n    return x + 56\n\ndef step_57(x: int) -> int:\n    return x + 57\n\ndef step_58(x: int) -> int:\n    return x + 58\n\ndef step_59(x: int) -> int:\n    return x + 59\n\ndef step_60(x: int) -> int:\n    return x + 60\n\ndef step_61(x: int) -> int:\n    return x + 61\n\ndef step_62(x: int) -> int:\n    return x + 62\n\ndef step_63(x: int) -> int:\n    return x + 63\n\ndef step_64(x: int) -> int:\n    return x + 64\n\ndef step_65(x: int) -> int:\n    return x + 65\n\ndef step_66(x: int) -> int:\n    return x + 66\n\ndef step_67(x: int) -> int:\n    return x + 67\n\ndef step_68(x: int) -> int:\n    return x + 68\n\ndef step_69(x: int) -> int:\n    return x + 69\n\ndef step_70(x: int) -> int:\n    return x + 70\n\ndef step_71(x: int) -> int:\n    return x + 71\n\ndef step_72(x: int) -> int:\n    return x + 72\n\ndef step_73(x: int) -> int:\n    return x + 73\n\ndef step_74(x: int) -> int:\n    return x + 74\n\ndef step_75(x: int) -> int:\n    return x + 75\n\ndef step_76(x: int) -> int:\n    return x + 76\n\ndef step_77(x: int) -> int:\n    return x + 77\n\ndef step_78(x: int) -> int:\n    return x + 78\n\ndef step_79(x: int) -> int:\n    return x + 79\n\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [7734, 279, 4536, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 4775, 2378, 29, 198, 27, 15704, 79114, 29, 198, 23326, 6971, 198, 510, 15704, 29, 198, 27, 15704, 28, 1733, 29, 198, 12237, 15280, 4536, 1429, 430, 264, 1248, 5224, 5515, 71483, 271, 1445, 1259, 20409, 548, 1120, 31144, 271, 727, 2923, 62, 15, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 15, 271, 727, 2923, 62, 15, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 271, 727, 2923, 62, 15, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 271, 727, 2923, 62, 15, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 271, 727, 2923, 62, 15, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 271, 727, 2923, 62, 15, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 271, 727, 2923, 62, 15, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 271, 727, 2923, 62, 15, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 271, 727, 2923, 62, 15, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 23, 271, 727, 2923, 62, 15, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 24, 271, 727, 2923, 62, 16, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 15, 271, 727, 2923, 62, 16, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 16, 271, 727, 2923, 62, 16, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 17, 271, 727, 2923, 62, 16, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 18, 271, 727, 2923, 62, 16, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 19, 271, 727, 2923, 62, 16, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 20, 271, 727, 2923, 62, 16, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 21, 271, 727, 2923, 62, 16, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 22, 271, 727, 2923, 62, 16, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 23, 271, 727, 2923, 62, 16, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 16, 24, 271, 727, 2923, 62, 17, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 15, 271, 727, 2923, 62, 17, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 16, 271, 727, 2923, 62, 17, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 17, 271, 727, 2923, 62, 17, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 18, 271, 727, 2923, 62, 17, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 19, 271, 727, 2923, 62, 17, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 20, 271, 727, 2923, 62, 17, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 21, 271, 727, 2923, 62, 17, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 22, 271, 727, 2923, 62, 17, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 23, 271, 727, 2923, 62, 17, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 17, 24, 271, 727, 2923, 62, 18, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 15, 271, 727, 2923, 62, 18, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 16, 271, 727, 2923, 62, 18, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 17, 271, 727, 2923, 62, 18, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 18, 271, 727, 2923, 62, 18, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 19, 271, 727, 2923, 62, 18, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 20, 271, 727, 2923, 62, 18, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 21, 271, 727, 2923, 62, 18, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 22, 271, 727, 2923, 62, 18, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 23, 271, 727, 2923, 62, 18, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 18, 24, 271, 727, 2923, 62, 19, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 15, 271, 727, 2923, 62, 19, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 16, 271, 727, 2923, 62, 19, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 17, 271, 727, 2923, 62, 19, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 18, 271, 727, 2923, 62, 19, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 19, 271, 727, 2923, 62, 19, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 20, 271, 727, 2923, 62, 19, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 21, 271, 727, 2923, 62, 19, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 22, 271, 727, 2923, 62, 19, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 23, 271, 727, 2923, 62, 19, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 19, 24, 271, 727, 2923, 62, 20, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 15, 271, 727, 2923, 62, 20, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 16, 271, 727, 2923, 62, 20, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 17, 271, 727, 2923, 62, 20, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 18, 271, 727, 2923, 62, 20, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 19, 271, 727, 2923, 62, 20, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 20, 271, 727, 2923, 62, 20, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 21, 271, 727, 2923, 62, 20, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 22, 271, 727, 2923, 62, 20, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 23, 271, 727, 2923, 62, 20, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 20, 24, 271, 727, 2923, 62, 21, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 15, 271, 727, 2923, 62, 21, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 16, 271, 727, 2923, 62, 21, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 17, 271, 727, 2923, 62, 21, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 18, 271, 727, 2923, 62, 21, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 19, 271, 727, 2923, 62, 21, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 20, 271, 727, 2923, 62, 21, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 21, 271, 727, 2923, 62, 21, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 22, 271, 727, 2923, 62, 21, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 23, 271, 727, 2923, 62, 21, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 21, 24, 271, 727, 2923, 62, 22, 15, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 15, 271, 727, 2923, 62, 22, 16, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 16, 271, 727, 2923, 62, 22, 17, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 17, 271, 727, 2923, 62, 22, 18, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 18, 271, 727, 2923, 62, 22, 19, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 19, 271, 727, 2923, 62, 22, 20, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 20, 271, 727, 2923, 62, 22, 21, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 21, 271, 727, 2923, 62, 22, 22, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 22, 271, 727, 2923, 62, 22, 23, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 23, 271, 727, 2923, 62, 22, 24, 2007, 25, 514, 8, 1411, 514, 25, 198, 262, 460, 830, 478, 220, 22, 24, 271, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Write the module.", "tool_calls": [{"name": "write_file", "arguments": {"path": "steps.py", "content": "\"\"\"Generated module used as a long tool argument.\"\"\"\n\nfrom __future__ import annotations\n\ndef step_00(x: int) -> int:\n    return x + 0\n\ndef step_01(x: int) -> int:\n    return x + 1\n\ndef step_02(x: int) -> int:\n    return x + 2\n\ndef step_03(x: int) -> int:\n    return x + 3\n\ndef step_04(x: int) -> int:\n    return x + 4\n\ndef step_05(x: int) -> int:\n    return x + 5\n\ndef step_06(x: int) -> int:\n    return x + 6\n\ndef step_07(x: int) -> int:\n    return x + 7\n\ndef step_08(x: int) -> int:\n    return x + 8\n\ndef step_09(x: int) -> int:\n    return x + 9\n\ndef step_10(x: int) -> int:\n    return x + 10\n\ndef step_11(x: int) -> int:\n    return x + 11\n\ndef step_12(x: int) -> int:\n    return x + 12\n\ndef step_13(x: int) -> int:\n    return x + 13\n\ndef step_14(x: int) -> int:\n    return x + 14\n\ndef step_15(x: int) -> int:\n    return x + 15\n\ndef step_16(x: int) -> int:\n    return x + 16\n\ndef step_17(x: int) -> int:\n    return x + 17\n\ndef step_18(x: int) -> int:\n    return x + 18\n\ndef step_19(x: int) -> int:\n    return x + 19\n\ndef step_20(x: int) -> int:\n    return x + 20\n\ndef step_21(x: int) -> int:\n    return x + 21\n\ndef step_22(x: int) -> int:\n    return x + 22\n\ndef step_23(x: int) -> int:\n    return x + 23\n\ndef step_24(x: int) -> int:\n    return x + 24\n\ndef step_25(x: int) -> int:\n    return x + 25\n\ndef step_26(x: int) -> int:\n    return x + 26\n\ndef step_27(x: int) -> int:\n    return x + 27\n\ndef step_28(x: int) -> int:\n    return x + 28\n\ndef step_29(x: int) -> int:\n    return x + 29\n\ndef step_30(x: int) -> int:\n    return x + 30\n\ndef step_31(x: int) -> int:\n    return x + 31\n\ndef step_32(x: int) -> int:\n    return x + 32\n\ndef step_33(x: int) -> int:\n    return x + 33\n\ndef step_34(x: int) -> int:\n    return x + 34\n\ndef step_35(x: int) -> int:\n    return x + 35\n\ndef step_36(x: int) -> int:\n    return x + 36\n\ndef step_37(x: int) -> int:\n    return x + 37\n\ndef step_38(x: int) -> int:\n    return x + 38\n\ndef step_39(x: int) -> int:\n    return x + 39\n\ndef step_40(x: int) -> int:\n    return x + 40\n\ndef step_41(x: int) -> int:\n    return x + 41\n\ndef step_42(x: int) -> int:\n    return x + 42\n\ndef step_43(x: int) -> int:\n    return x + 43\n\ndef step_44(x: int) -> int:\n    return x + 44\n\ndef step_45(x: int) -> int:\n    return x + 45\n\ndef step_46(x: int) -> int:\n    return x + 46\n\ndef step_47(x: int) -> int:\n    return x + 47\n\ndef step_48(x: int) -> int:\n    return x + 48\n\ndef step_49(x: int) -> int:\n    return x + 49\n\ndef step_50(x: int) -> int:\n    return x + 50\n\ndef step_51(x: int) -> int:\n    return x + 51\n\ndef step_52(x: int) -> int:\n    return x + 52\n\ndef step_53(x: int) -> int:\n    return x + 53\n\ndef step_54(x: int) -> int:\n    return x + 54\n\ndef step_55(x: int) -> int:\n    return x + 55\n\ndef step_56(x: int) -> int:\n    return x + 56\n\ndef step_57(x: int) -> int:\n    return x + 57\n\ndef step_58(x: int) -> int:\n    return x + 58\n\ndef step_59(x: int) -> int:\n    return x + 59\n\ndef step_60(x: int) -> int:\n    return x + 60\n\ndef step_61(x: int) -> int:\n    return x + 61\n\ndef step_62(x: int) -> int:\n    return x + 62\n\ndef step_63(x: int) -> int:\n    return x + 63\n\ndef step_64(x: int) -> int:\n    return x + 64\n\ndef step_65(x: int) -> int:\n    return x + 65\n\ndef step_66(x: int) -> int:\n    return x + 66\n\ndef step_67(x: int) -> int:\n    return x + 67\n\ndef step_68(x: int) -> int:\n    return x + 68\n\ndef step_69(x: int) -> int:\n    return x + 69\n\ndef step_70(x: int) -> int:\n    return x + 70\n\ndef step_71(x: int) -> int:\n    return x + 71\n\ndef step_72(x: int) -> int:\n    return x + 72\n\ndef step_73(x: int) -> int:\n    return x + 73\n\ndef step_74(x: int) -> int:\n    return x + 74\n\ndef step_75(x: int) -> int:\n    return x + 75\n\ndef step_76(x: int) -> int:\n    return x + 76\n\ndef step_77(x: int) -> int:\n    return x + 77\n\ndef step_78(x: int) -> int:\n    return x + 78\n\ndef step_79(x: int) -> int:\n    return x + 79\n"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "long-arguments", "x-multiline-string"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-multiline-string expected_match
Checks that did not pass for qwen3-xml/q38-multiline-string
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Write the script.', got 'Write the script.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-multiline-string --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 11 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Write the script.",
+  "reasoning_content": "Write the script.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, string-escapes, x-multiline-string.

Raw output

Write the script.
</think>

<tool_call>
<function=write_file>
<parameter=path>
greet.py
</parameter>
<parameter=content>
def greet(name: str) -> str:
    """Say hi."""
    return f"Hello, {name}!"


print(greet("Zoë"))
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Write the script.",
  "tool_calls": [
    {
      "arguments": {
        "content": "def greet(name: str) -> str:\n    \"\"\"Say hi.\"\"\"\n    return f\"Hello, {name}!\"\n\n\nprint(greet(\"Zoë\"))",
        "path": "greet.py"
      },
      "name": "write_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-multiline-string", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Write the script.\n</think>\n\n<tool_call>\n<function=write_file>\n<parameter=path>\ngreet.py\n</parameter>\n<parameter=content>\ndef greet(name: str) -> str:\n    \"\"\"Say hi.\"\"\"\n    return f\"Hello, {name}!\"\n\n\nprint(greet(\"Zoë\"))\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [7734, 279, 5148, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 4775, 2378, 29, 198, 27, 15704, 79114, 29, 198, 70, 3617, 6971, 198, 510, 15704, 29, 198, 27, 15704, 28, 1733, 29, 198, 727, 39440, 3052, 25, 592, 8, 1411, 592, 25, 198, 262, 4071, 44240, 15131, 71483, 198, 262, 460, 281, 1, 9419, 11, 313, 591, 92, 8687, 1358, 1302, 3163, 3617, 437, 230123, 11831, 2673, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Write the script.", "tool_calls": [{"name": "write_file", "arguments": {"path": "greet.py", "content": "def greet(name: str) -> str:\n    \"\"\"Say hi.\"\"\"\n    return f\"Hello, {name}!\"\n\n\nprint(greet(\"Zoë\"))"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "string-escapes", "x-multiline-string"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-nested-json expected_match
Checks that did not pass for qwen3-xml/q38-nested-json
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Search with structured filters.', got 'Search with structured filters.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-nested-json --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 5 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Search with structured filters.",
+  "reasoning_content": "Search with structured filters.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, nested-json.

Raw output

Search with structured filters.
</think>

<tool_call>
<function=search>
<parameter=query>
hotels
</parameter>
<parameter=filters>
{"price": {"min": 50, "max": 120.5}, "tags": ["pool", "wifi"], "open": null}
</parameter>
<parameter=sites>
["a.example", "b.example"]
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Search with structured filters.",
  "tool_calls": [
    {
      "arguments": {
        "filters": {
          "open": null,
          "price": {
            "max": 120.5,
            "min": 50
          },
          "tags": [
            "pool",
            "wifi"
          ]
        },
        "query": "hotels",
        "sites": [
          "a.example",
          "b.example"
        ]
      },
      "name": "search"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-nested-json", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Search with structured filters.\n</think>\n\n<tool_call>\n<function=search>\n<parameter=query>\nhotels\n</parameter>\n<parameter=filters>\n{\"price\": {\"min\": 50, \"max\": 120.5}, \"tags\": [\"pool\", \"wifi\"], \"open\": null}\n</parameter>\n<parameter=sites>\n[\"a.example\", \"b.example\"]\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [5708, 440, 31838, 13019, 13, 198, 248069, 271, 248058, 198, 27, 1628, 93260, 29, 198, 27, 15704, 76478, 29, 198, 10307, 1943, 198, 510, 15704, 29, 198, 27, 15704, 17572, 8349, 29, 198, 4754, 6352, 763, 5046, 1030, 763, 220, 20, 15, 11, 328, 2721, 763, 220, 16, 17, 15, 13, 20, 2069, 328, 13679, 763, 4241, 9979, 487, 328, 65011, 7664, 328, 2428, 763, 819, 92, 198, 510, 15704, 29, 198, 27, 15704, 13744, 3490, 29, 198, 1143, 64, 7479, 487, 328, 65, 7479, 1293, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Search with structured filters.", "tool_calls": [{"name": "search", "arguments": {"query": "hotels", "filters": {"price": {"min": 50, "max": 120.5}, "tags": ["pool", "wifi"], "open": null}, "sites": ["a.example", "b.example"]}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "nested-json"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. Object and array values are rendered with tojson; the parser must decode them as JSON."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-no-call expected_match
Checks that did not pass for qwen3-xml/q38-no-call
CheckStrategyResultDetail
expected_matchnonstream soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchone soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchspecial soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchtoken soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchrand:1:8 soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchrand:2:8 soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchrand:3:8 soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchrand:4:8 soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'
expected_matchrand:5:8 soft passcontent (whitespace only): expected 'Hello! How can I help you today?', got '\n\nHello! How can I help you today?'; reasoning_content (whitespace only): expected 'A simple greeting; no tool is needed.', got 'A simple greeting; no tool is needed.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-no-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 10 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
-  "content": "Hello! How can I help you today?",
-  "reasoning_content": "A simple greeting; no tool is needed.",
+  "content": "\n\nHello! How can I help you today?",
+  "reasoning_content": "A simple greeting; no tool is needed.\n",
   "tool_calls": []
 }

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: no-call, reasoning, reasoning-prefilled.

Raw output

A simple greeting; no tool is needed.
</think>

Hello! How can I help you today?

Expected parse

{
  "content": "Hello! How can I help you today?",
  "reasoning_content": "A simple greeting; no tool is needed.",
  "tool_calls": []
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-no-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "A simple greeting; no tool is needed.\n</think>\n\nHello! How can I help you today?", "output_token_ids": [32, 4145, 40719, 26, 874, 5224, 369, 4221, 13, 198, 248069, 271, 9419, 0, 2500, 628, 353, 1438, 488, 3242, 30], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": "Hello! How can I help you today?", "reasoning_content": "A simple greeting; no tool is needed.", "tool_calls": []}, "tags": ["no-call", "reasoning", "reasoning-prefilled"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-numeric-and-literal-arguments expected_match
Checks that did not pass for qwen3-xml/q38-numeric-and-literal-arguments
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Set the alarm.', got 'Set the alarm.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-numeric-and-literal-arguments --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 13 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Set the alarm.",
+  "reasoning_content": "Set the alarm.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, numeric-arguments, x-schema-coercion.

Raw output

Set the alarm.
</think>

<tool_call>
<function=set_alarm>
<parameter=hour>
7
</parameter>
<parameter=minute>
30
</parameter>
<parameter=volume>
0.75
</parameter>
<parameter=repeat>
true
</parameter>
<parameter=label>
null
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Set the alarm.",
  "tool_calls": [
    {
      "arguments": {
        "hour": 7,
        "label": null,
        "minute": 30,
        "repeat": true,
        "volume": 0.75
      },
      "name": "set_alarm"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-numeric-and-literal-arguments", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Set the alarm.\n</think>\n\n<tool_call>\n<function=set_alarm>\n<parameter=hour>\n7\n</parameter>\n<parameter=minute>\n30\n</parameter>\n<parameter=volume>\n0.75\n</parameter>\n<parameter=repeat>\ntrue\n</parameter>\n<parameter=label>\nnull\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [1591, 279, 16136, 13, 198, 248069, 271, 248058, 198, 27, 1628, 66473, 64049, 29, 198, 27, 15704, 28, 23899, 29, 198, 22, 198, 510, 15704, 29, 198, 27, 15704, 28, 41143, 29, 198, 18, 15, 198, 510, 15704, 29, 198, 27, 15704, 28, 24266, 29, 198, 15, 13, 22, 20, 198, 510, 15704, 29, 198, 27, 15704, 28, 29625, 29, 198, 1802, 198, 510, 15704, 29, 198, 27, 15704, 95277, 29, 198, 2827, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Set the alarm.", "tool_calls": [{"name": "set_alarm", "arguments": {"hour": 7, "minute": 30, "volume": 0.75, "repeat": true, "label": null}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "numeric-arguments", "x-schema-coercion"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. Scalars are raw text; integer/number/boolean/null come back only through the tool's JSON schema."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-numeric-looking-strings expected_match
Checks that did not pass for qwen3-xml/q38-numeric-looking-strings
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Look it up.', got 'Look it up.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-numeric-looking-strings --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 14 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Look it up.",
+  "reasoning_content": "Look it up.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, x-schema-coercion.

Raw output

Look it up.
</think>

<tool_call>
<function=lookup>
<parameter=zip>
02139
</parameter>
<parameter=account_id>
123456789012345678901
</parameter>
<parameter=flag>
true
</parameter>
<parameter=payload>
{"a": 1}
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Look it up.",
  "tool_calls": [
    {
      "arguments": {
        "account_id": "123456789012345678901",
        "flag": "true",
        "payload": "{\"a\": 1}",
        "zip": "02139"
      },
      "name": "lookup"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-numeric-looking-strings", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Look it up.\n</think>\n\n<tool_call>\n<function=lookup>\n<parameter=zip>\n02139\n</parameter>\n<parameter=account_id>\n123456789012345678901\n</parameter>\n<parameter=flag>\ntrue\n</parameter>\n<parameter=payload>\n{\"a\": 1}\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [10071, 424, 685, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 20377, 29, 198, 27, 15704, 28, 9669, 29, 198, 15, 17, 16, 18, 24, 198, 510, 15704, 29, 198, 27, 15704, 28, 4459, 816, 29, 198, 16, 17, 18, 19, 20, 21, 22, 23, 24, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 15, 16, 198, 510, 15704, 29, 198, 27, 15704, 28, 9610, 29, 198, 1802, 198, 510, 15704, 29, 198, 27, 15704, 16874, 6771, 29, 198, 4754, 64, 763, 220, 16, 92, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Look it up.", "tool_calls": [{"name": "lookup", "arguments": {"zip": "02139", "account_id": "123456789012345678901", "flag": "true", "payload": "{\"a\": 1}"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "x-schema-coercion"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. Every parameter is typed 'string' in the schema, so '02139', 'true' and '{\"a\": 1}' must stay strings."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-parallel-calls expected_match
Checks that did not pass for qwen3-xml/q38-parallel-calls
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'I should call the tools.', got 'I should call the tools.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-parallel-calls --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 2 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "I should call the tools.",
+  "reasoning_content": "I should call the tools.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: parallel-calls, reasoning, reasoning-prefilled, unicode, nested-json, string-escapes.

Raw output

I should call the tools.
</think>

<tool_call>
<function=get_weather>
<parameter=city>
Zürich
</parameter>
<parameter=unit>
c
</parameter>
</function>
</tool_call>
<tool_call>
<function=search>
<parameter=query>
café "best"
</parameter>
<parameter=filters>
{"tags": ["a", "b"], "max": 3}
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "I should call the tools.",
  "tool_calls": [
    {
      "arguments": {
        "city": "Zürich",
        "unit": "c"
      },
      "name": "get_weather"
    },
    {
      "arguments": {
        "filters": {
          "max": 3,
          "tags": [
            "a",
            "b"
          ]
        },
        "query": "café \"best\""
      },
      "name": "search"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-parallel-calls", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "I should call the tools.\n</think>\n\n<tool_call>\n<function=get_weather>\n<parameter=city>\nZürich\n</parameter>\n<parameter=unit>\nc\n</parameter>\n</function>\n</tool_call>\n<tool_call>\n<function=search>\n<parameter=query>\ncafé \"best\"\n</parameter>\n<parameter=filters>\n{\"tags\": [\"a\", \"b\"], \"max\": 3}\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [40, 1220, 1562, 279, 7141, 13, 198, 248069, 271, 248058, 198, 27, 1628, 27362, 67017, 29, 198, 27, 15704, 28, 8656, 29, 198, 57, 5021, 693, 198, 510, 15704, 29, 198, 27, 15704, 28, 3715, 29, 198, 66, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059, 198, 248058, 198, 27, 1628, 93260, 29, 198, 27, 15704, 76478, 29, 198, 895, 56868, 328, 15395, 1, 198, 510, 15704, 29, 198, 27, 15704, 17572, 8349, 29, 198, 4754, 13679, 763, 4241, 64, 487, 328, 65, 7664, 328, 2721, 763, 220, 18, 92, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "I should call the tools.", "tool_calls": [{"name": "get_weather", "arguments": {"city": "Zürich", "unit": "c"}}, {"name": "search", "arguments": {"query": "café \"best\"", "filters": {"tags": ["a", "b"], "max": 3}}}]}, "tags": ["parallel-calls", "reasoning", "reasoning-prefilled", "unicode", "nested-json", "string-escapes"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-single-call expected_match
Checks that did not pass for qwen3-xml/q38-single-call
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-single-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 1 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.",
+  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled.

Raw output

The user wants the weather in Paris. I should call get_weather.
</think>

<tool_call>
<function=get_weather>
<parameter=city>
Paris
</parameter>
<parameter=unit>
c
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.",
  "tool_calls": [
    {
      "arguments": {
        "city": "Paris",
        "unit": "c"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-single-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "The user wants the weather in Paris. I should call get_weather.\n</think>\n\n<tool_call>\n<function=get_weather>\n<parameter=city>\nParis\n</parameter>\n<parameter=unit>\nc\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [760, 1156, 6587, 279, 8831, 303, 11751, 13, 353, 1220, 1562, 615, 67017, 13, 198, 248069, 271, 248058, 198, 27, 1628, 27362, 67017, 29, 198, 27, 15704, 28, 8656, 29, 198, 57590, 198, 510, 15704, 29, 198, 27, 15704, 28, 3715, 29, 198, 66, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "The user wants the weather in Paris. I should call get_weather.", "tool_calls": [{"name": "get_weather", "arguments": {"city": "Paris", "unit": "c"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-string-escapes expected_match
Checks that did not pass for qwen3-xml/q38-string-escapes
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Write it.', got 'Write it.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-string-escapes --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 15 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Write it.",
+  "reasoning_content": "Write it.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, string-escapes.

Raw output

Write it.
</think>

<tool_call>
<function=write_file>
<parameter=path>
C:\tmp\x.txt
</parameter>
<parameter=content>
a "quote", a \backslash, <tag> & ampersand	tab
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Write it.",
  "tool_calls": [
    {
      "arguments": {
        "content": "a \"quote\", a \\backslash, <tag> & ampersand\ttab",
        "path": "C:\\tmp\\x.txt"
      },
      "name": "write_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-string-escapes", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Write it.\n</think>\n\n<tool_call>\n<function=write_file>\n<parameter=path>\nC:\\tmp\\x.txt\n</parameter>\n<parameter=content>\na \"quote\", a \\backslash, <tag> & ampersand\ttab\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [7734, 424, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 4775, 2378, 29, 198, 27, 15704, 79114, 29, 198, 34, 6964, 5008, 3351, 3778, 198, 510, 15704, 29, 198, 27, 15704, 28, 1733, 29, 198, 64, 328, 2855, 487, 264, 1088, 1371, 48569, 11, 361, 4429, 29, 594, 22050, 382, 429, 56196, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Write it.", "tool_calls": [{"name": "write_file", "arguments": {"path": "C:\\tmp\\x.txt", "content": "a \"quote\", a \\backslash, <tag> & ampersand\ttab"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "string-escapes"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. String values are inserted verbatim with no escaping."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-text-before-call expected_match, stream_equals_nonstream
Checks that did not pass for qwen3-xml/q38-text-before-call
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchone soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchspecial soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchtoken soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:1:8 soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:2:8 soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:3:8 soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:4:8 soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:5:8 soft passcontent (whitespace only): expected 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'; reasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
stream_equals_nonstreamone soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'
stream_equals_nonstreamspecial soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'
stream_equals_nonstreamtoken soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'
stream_equals_nonstreamrand:1:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'
stream_equals_nonstreamrand:2:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'
stream_equals_nonstreamrand:3:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'
stream_equals_nonstreamrand:4:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'
stream_equals_nonstreamrand:5:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you.', got '\n\nLet me check the weather for you.\n\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-text-before-call --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 9 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": "Let me check the weather for you.",
-  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.",
+  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.\n",
   "tool_calls": [
     {

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
-  "content": "Let me check the weather for you.",
-  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.",
+  "content": "\n\nLet me check the weather for you.\n\n",
+  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, text-before-call.

Raw output

The user wants the weather in Paris. I should call get_weather.
</think>

Let me check the weather for you.

<tool_call>
<function=get_weather>
<parameter=city>
Paris
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": "Let me check the weather for you.",
  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.",
  "tool_calls": [
    {
      "arguments": {
        "city": "Paris"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-text-before-call", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "The user wants the weather in Paris. I should call get_weather.\n</think>\n\nLet me check the weather for you.\n\n<tool_call>\n<function=get_weather>\n<parameter=city>\nParis\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [760, 1156, 6587, 279, 8831, 303, 11751, 13, 353, 1220, 1562, 615, 67017, 13, 198, 248069, 271, 9764, 728, 1716, 279, 8831, 364, 488, 13, 271, 248058, 198, 27, 1628, 27362, 67017, 29, 198, 27, 15704, 28, 8656, 29, 198, 57590, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": "Let me check the weather for you.", "reasoning_content": "The user wants the weather in Paris. I should call get_weather.", "tool_calls": [{"name": "get_weather", "arguments": {"city": "Paris"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "text-before-call"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. Qwen3.5+ templates join content and the first <tool_call> with '\\n\\n', which is markup."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-three-parallel-calls expected_match
Checks that did not pass for qwen3-xml/q38-three-parallel-calls
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Three cities, three calls.', got 'Three cities, three calls.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-three-parallel-calls --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 3 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Three cities, three calls.",
+  "reasoning_content": "Three cities, three calls.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: parallel-calls, reasoning, reasoning-prefilled.

Raw output

Three cities, three calls.
</think>

<tool_call>
<function=get_weather>
<parameter=city>
Paris
</parameter>
</function>
</tool_call>
<tool_call>
<function=get_weather>
<parameter=city>
Berlin
</parameter>
</function>
</tool_call>
<tool_call>
<function=get_weather>
<parameter=city>
Tokyo
</parameter>
<parameter=unit>
f
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Three cities, three calls.",
  "tool_calls": [
    {
      "arguments": {
        "city": "Paris"
      },
      "name": "get_weather"
    },
    {
      "arguments": {
        "city": "Berlin"
      },
      "name": "get_weather"
    },
    {
      "arguments": {
        "city": "Tokyo",
        "unit": "f"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-three-parallel-calls", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Three cities, three calls.\n</think>\n\n<tool_call>\n<function=get_weather>\n<parameter=city>\nParis\n</parameter>\n</function>\n</tool_call>\n<tool_call>\n<function=get_weather>\n<parameter=city>\nBerlin\n</parameter>\n</function>\n</tool_call>\n<tool_call>\n<function=get_weather>\n<parameter=city>\nTokyo\n</parameter>\n<parameter=unit>\nf\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [19041, 9432, 11, 2250, 6526, 13, 198, 248069, 271, 248058, 198, 27, 1628, 27362, 67017, 29, 198, 27, 15704, 28, 8656, 29, 198, 57590, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059, 198, 248058, 198, 27, 1628, 27362, 67017, 29, 198, 27, 15704, 28, 8656, 29, 198, 91149, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059, 198, 248058, 198, 27, 1628, 27362, 67017, 29, 198, 27, 15704, 28, 8656, 29, 198, 51076, 15560, 198, 510, 15704, 29, 198, 27, 15704, 28, 3715, 29, 198, 69, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Three cities, three calls.", "tool_calls": [{"name": "get_weather", "arguments": {"city": "Paris"}}, {"name": "get_weather", "arguments": {"city": "Berlin"}}, {"name": "get_weather", "arguments": {"city": "Tokyo", "unit": "f"}}]}, "tags": ["parallel-calls", "reasoning", "reasoning-prefilled"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-tool-call-token-in-arguments expected_match
Checks that did not pass for qwen3-xml/q38-tool-call-token-in-arguments
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Document the format.', got 'Document the format.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-tool-call-token-in-arguments --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 8 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Document the format.",
+  "reasoning_content": "Document the format.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, marker-in-arguments.

Raw output

Document the format.
</think>

<tool_call>
<function=write_file>
<parameter=path>
fmt.md
</parameter>
<parameter=content>
Qwen wraps calls in <tool_call> ... </tool_call> blocks.
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Document the format.",
  "tool_calls": [
    {
      "arguments": {
        "content": "Qwen wraps calls in <tool_call> ... </tool_call> blocks.",
        "path": "fmt.md"
      },
      "name": "write_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-tool-call-token-in-arguments", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Document the format.\n</think>\n\n<tool_call>\n<function=write_file>\n<parameter=path>\nfmt.md\n</parameter>\n<parameter=content>\nQwen wraps calls in <tool_call> ... </tool_call> blocks.\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [7288, 279, 3443, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 4775, 2378, 29, 198, 27, 15704, 79114, 29, 198, 12143, 20668, 198, 510, 15704, 29, 198, 27, 15704, 28, 1733, 29, 198, 48, 16451, 38394, 6526, 303, 220, 248058, 2423, 220, 248059, 9714, 13, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Document the format.", "tool_calls": [{"name": "write_file", "arguments": {"path": "fmt.md", "content": "Qwen wraps calls in <tool_call> ... </tool_call> blocks."}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "marker-in-arguments"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. '<tool_call>' and '</tool_call>' are added tokens, so the tokenizer maps them to their marker ids even inside the argument value (see output_token_ids). Only the XML structure tells them apart."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-truncated-at-tool-call-open expected_match
Checks that did not pass for qwen3-xml/q38-truncated-at-tool-call-open
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'The user wants the weather in Paris. I should call get_weather.', got 'The user wants the weather in Paris. I should call get_weather.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-truncated-at-tool-call-open --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 23 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.",
+  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.\n",
   "tool_calls": []
 }

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: truncated, reasoning, reasoning-prefilled, x-stop-at-open-marker.

Raw output

The user wants the weather in Paris. I should call get_weather.
</think>

<tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "The user wants the weather in Paris. I should call get_weather.",
  "tool_calls": []
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-truncated-at-tool-call-open", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "The user wants the weather in Paris. I should call get_weather.\n</think>\n\n<tool_call>", "output_token_ids": [760, 1156, 6587, 279, 8831, 303, 11751, 13, 353, 1220, 1562, 615, 67017, 13, 198, 248069, 271, 248058], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "The user wants the weather in Paris. I should call get_weather.", "tool_calls": []}, "tags": ["truncated", "reasoning", "reasoning-prefilled", "x-stop-at-open-marker"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. Output stopped right after the <tool_call> opener. The reasoning is complete and must be kept; there is no call yet and the marker must not leak into content (SGLang #35565: https://github.com/sgl-project/sglang/issues/35565)."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-unicode-emoji expected_match
Checks that did not pass for qwen3-xml/q38-unicode-emoji
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchone soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected '用户想知道东京的天气。🌦️', got '用户想知道东京的天气。🌦️\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-unicode-emoji --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 4 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "用户想知道东京的天气。🌦️",
+  "reasoning_content": "用户想知道东京的天气。🌦️\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, unicode.

Raw output

用户想知道东京的天气。🌦️
</think>

<tool_call>
<function=search>
<parameter=query>
東京の天気 ☀️🌧️ — «prévisions» für Zürich 🇨🇭
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "用户想知道东京的天气。🌦️",
  "tool_calls": [
    {
      "arguments": {
        "query": "東京の天気 ☀️🌧️ — «prévisions» für Zürich 🇨🇭"
      },
      "name": "search"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-unicode-emoji", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "用户想知道东京的天气。🌦️\n</think>\n\n<tool_call>\n<function=search>\n<parameter=query>\n東京の天気 ☀️🌧️ — «prévisions» für Zürich 🇨🇭\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [97237, 102487, 110799, 131802, 1710, 9008, 234, 99, 29545, 198, 248069, 271, 248058, 198, 27, 1628, 93260, 29, 198, 27, 15704, 76478, 29, 198, 115197, 15303, 247570, 24329, 222, 29545, 9008, 234, 100, 29545, 1892, 12129, 631, 933, 38685, 12620, 6954, 194323, 10838, 229, 101, 9008, 229, 255, 198, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "用户想知道东京的天气。🌦️", "tool_calls": [{"name": "search", "arguments": {"query": "東京の天気 ☀️🌧️ — «prévisions» für Zürich 🇨🇭"}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "unicode"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/q38-whitespace-significant expected_match
Checks that did not pass for qwen3-xml/q38-whitespace-significant
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchone soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchspecial soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchrand:1:8 soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchrand:2:8 soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchrand:3:8 soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchrand:4:8 soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'
expected_matchrand:5:8 soft passreasoning_content (whitespace only): expected 'Replace the block exactly.', got 'Replace the block exactly.\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/rendered.jsonl --id qwen3-xml/q38-whitespace-significant --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 12 of fixtures/qwen3-xml/rendered.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream, one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,5 +1,5 @@
 {
   "content": null,
-  "reasoning_content": "Replace the block exactly.",
+  "reasoning_content": "Replace the block exactly.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja. Tags: single-call, reasoning, reasoning-prefilled, x-whitespace-significant-string.

Raw output

Replace the block exactly.
</think>

<tool_call>
<function=edit_file>
<parameter=path>
a.py
</parameter>
<parameter=old_string>
    if x:
        return 1

</parameter>
<parameter=new_string>
  two  spaces  
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": null,
  "reasoning_content": "Replace the block exactly.",
  "tool_calls": [
    {
      "arguments": {
        "new_string": "  two  spaces  ",
        "old_string": "    if x:\n        return 1\n",
        "path": "a.py"
      },
      "name": "edit_file"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/q38-whitespace-significant", "family": "qwen3-xml", "models": ["Qwen/Qwen3.8-27B", "Qwen/Qwen3.6-35B-A3B"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/Qwen/Qwen3.8-27B/blob/1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0/chat_template.jinja", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/render_qwen3_xml.py", "template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"}, "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["c", "f"]}}, "required": ["city"]}}}, {"type": "function", "function": {"name": "search", "description": "Search the web.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}, "filters": {"type": "object"}, "sites": {"type": "array", "items": {"type": "string"}}}, "required": ["query"]}}}, {"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time.", "parameters": {"type": "object", "properties": {}, "required": []}}}, {"type": "function", "function": {"name": "write_file", "description": "Write text to a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}}}, {"type": "function", "function": {"name": "edit_file", "description": "Replace an exact string in a file.", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}, "required": ["path", "old_string", "new_string"]}}}, {"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm.", "parameters": {"type": "object", "properties": {"hour": {"type": "integer"}, "minute": {"type": "integer"}, "volume": {"type": "number"}, "repeat": {"type": "boolean"}, "label": {"type": ["string", "null"]}}, "required": ["hour", "minute"]}}}, {"type": "function", "function": {"name": "lookup", "description": "Look up a record by its identifiers.", "parameters": {"type": "object", "properties": {"zip": {"type": "string"}, "account_id": {"type": "string"}, "flag": {"type": "string"}, "payload": {"type": "string"}}, "required": ["zip"]}}}], "raw_output": "Replace the block exactly.\n</think>\n\n<tool_call>\n<function=edit_file>\n<parameter=path>\na.py\n</parameter>\n<parameter=old_string>\n    if x:\n        return 1\n\n</parameter>\n<parameter=new_string>\n  two  spaces  \n</parameter>\n</function>\n</tool_call>", "output_token_ids": [22386, 279, 2424, 6681, 13, 198, 248069, 271, 248058, 198, 27, 1628, 28, 3468, 2378, 29, 198, 27, 15704, 79114, 29, 198, 64, 6971, 198, 510, 15704, 29, 198, 27, 15704, 28, 787, 3773, 29, 198, 262, 413, 830, 25, 198, 285, 460, 220, 16, 271, 510, 15704, 29, 198, 27, 15704, 8083, 3773, 29, 198, 220, 1330, 220, 12258, 2228, 510, 15704, 29, 198, 510, 1628, 29, 198, 248059], "tokenizer": {"repo": "Qwen/Qwen3.8-27B", "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n<think>\n", "expected": {"content": null, "reasoning_content": "Replace the block exactly.", "tool_calls": [{"name": "edit_file", "arguments": {"path": "a.py", "old_string": "    if x:\n        return 1\n", "new_string": "  two  spaces  "}}]}, "tags": ["single-call", "reasoning", "reasoning-prefilled", "x-whitespace-significant-string"], "notes": "History render of the official Qwen/Qwen3.8-27B chat template. Leading indentation and a trailing newline are part of the values. Exactly one '\\n' on each side of a value is markup (vLLM #48753: https://github.com/vllm-project/vllm/issues/48753)."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n<think>\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3.8-27B",
  "model_rule": "Qwen3\\.[5-9]",
  "model_type": "qwen3_5",
  "parser_source": "Qwen3.5+ model cards: --tool-call-parser qwen3_coder --reasoning-parser qwen3",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n<think>\n",
  "reasoning_parser": "qwen3",
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3.8-27B",
    "requested_mode": "auto",
    "resolved_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}
soft pass qwen3-xml/vllm-coder-missing-close-parameter expected_match, stream_equals_nonstream
Checks that did not pass for qwen3-xml/vllm-coder-missing-close-parameter
CheckStrategyResultDetail
expected_matchone soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
expected_matchspecial soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
expected_matchtoken soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
expected_matchrand:1:8 soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
expected_matchrand:2:8 soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
expected_matchrand:3:8 soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
expected_matchrand:4:8 soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
expected_matchrand:5:8 soft passcontent (whitespace only): expected 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamone soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamspecial soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamtoken soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamrand:1:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamrand:2:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamrand:3:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamrand:4:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'
stream_equals_nonstreamrand:5:8 soft passcontent (whitespace only): nonstream 'Let me check the weather for you:', got 'Let me check the weather for you:\n'

Minimal repro

uv run canitoolcall run --engine vllm --fixtures fixtures/qwen3-xml/imported.jsonl --id qwen3-xml/vllm-coder-missing-close-parameter --strategy one --strategy rand:1:8 --strategy rand:2:8 --strategy rand:3:8 --strategy rand:4:8 --strategy rand:5:8 --strategy special --strategy token --observed all

Set up the engine first with scripts/engines/vllm.sh; this run used vllm 0.30.0. The fixture is line 16 of fixtures/qwen3-xml/imported.jsonl.

Observed vs expected

Identical parses are grouped. Empty strings are shown as null, as in strict comparison.

Strategies: nonstream

Matches the expected parse.

{
  "content": "Let me check the weather for you:",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "city": "Dallas",
        "state": "TX",
        "unit": "fahrenheit"
      },
      "name": "get_current_weather"
    }
  ]
}

Strategies: one, special, token, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8

--- expected
+++ observed
@@ -1,4 +1,4 @@
 {
-  "content": "Let me check the weather for you:",
+  "content": "Let me check the weather for you:\n",
   "reasoning_content": null,
   "tool_calls": [

Fixture

Provenance: engine_test, https://github.com/vllm-project/vllm/blob/ced6857afa0ea7b2e3f0846a62e1394e90f15607/tests/tool_parsers/test_qwen3coder_tool_parser.py#L932-L971. Tags: single-call, text-before-call, malformed, x-missing-close-param.

Raw output

Let me check the weather for you:
<tool_call>
<function=get_current_weather>
<parameter=city>
Dallas
<parameter=state>
TX
</parameter>
<parameter=unit>
fahrenheit
</parameter>
</function>
</tool_call>

Expected parse

{
  "content": "Let me check the weather for you:",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "city": "Dallas",
        "state": "TX",
        "unit": "fahrenheit"
      },
      "name": "get_current_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "qwen3-xml/vllm-coder-missing-close-parameter", "family": "qwen3-xml", "models": ["Qwen/Qwen3-Coder-30B-A3B-Instruct", "Qwen/Qwen3-Coder-480B-A35B-Instruct"], "spec_version": "0.1", "provenance": {"kind": "engine_test", "source_url": "https://github.com/vllm-project/vllm/blob/ced6857afa0ea7b2e3f0846a62e1394e90f15607/tests/tool_parsers/test_qwen3coder_tool_parser.py#L932-L971", "revision": "ced6857afa0ea7b2e3f0846a62e1394e90f15607", "license": "Apache-2.0", "generator": "scripts/fixtures/qwen3-xml/import_qwen3_xml.py", "attribution": "Copyright contributors to the vLLM project (Apache-2.0)."}, "tools": [{"type": "function", "function": {"name": "get_current_weather", "parameters": {"type": "object", "properties": {"city": {"type": "string", "description": "The city name"}, "state": {"type": "string", "description": "The state code"}, "unit": {"type": "string", "enum": ["fahrenheit", "celsius"]}}, "required": ["city", "state"]}, "description": "Get the current weather"}}, {"type": "function", "function": {"name": "calculate_area", "parameters": {"type": "object", "properties": {"shape": {"type": "string"}, "dimensions": {"type": "object"}, "precision": {"type": "integer"}}}, "description": "Calculate area of a shape"}}], "raw_output": "Let me check the weather for you:\n<tool_call>\n<function=get_current_weather>\n<parameter=city>\nDallas\n<parameter=state>\nTX\n</parameter>\n<parameter=unit>\nfahrenheit\n</parameter>\n</function>\n</tool_call>", "output_token_ids": [10061, 752, 1779, 279, 9104, 369, 498, 510, 151657, 198, 27, 1688, 28280, 11080, 69364, 397, 27, 16181, 28, 8926, 397, 86514, 198, 27, 16181, 28, 2454, 397, 22867, 198, 522, 16181, 397, 27, 16181, 28, 3843, 397, 69, 47910, 198, 522, 16181, 397, 522, 1688, 397, 151658], "tokenizer": {"repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct", "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120", "mode": "hf"}, "generation_prompt": "<|im_start|>assistant\n", "expected": {"content": "Let me check the weather for you:", "reasoning_content": null, "tool_calls": [{"name": "get_current_weather", "arguments": {"city": "Dallas", "state": "TX", "unit": "fahrenheit"}}]}, "tags": ["single-call", "text-before-call", "malformed", "x-missing-close-param"], "notes": "test_extract_tool_calls_missing_closing_parameter_tag: the next <parameter= ends the unclosed value."}
Parser configuration
{
  "chat_template_kwargs": {},
  "detokenizer": "vllm.v1.engine.detokenizer.IncrementalDetokenizer.from_new_request",
  "detokenizer_class": "vllm.v1.engine.detokenizer.FastIncrementalDetokenizer",
  "enable_auto_tools": true,
  "engine": "vllm",
  "entrypoint": "vllm.parser.parser_manager.ParserManager.get_parser",
  "generation_prompt": "<|im_start|>assistant\n",
  "generation_prompt_match": true,
  "is_harmony": false,
  "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
  "model_rule": "Qwen3-Coder",
  "model_type": "qwen3_moe",
  "parser_source": "Qwen3-Coder model cards: --tool-call-parser qwen3_coder",
  "pinned_version": "0.30.0",
  "prompt_messages": [
    {
      "content": "Use the tools to answer.",
      "role": "user"
    }
  ],
  "prompt_tail": "end|>\n<|im_start|>user\nUse the tools to answer.<|im_end|>\n<|im_start|>assistant\n",
  "reasoning_parser": null,
  "stop_token_in_final_delta": false,
  "template": {
    "sha256": "5a38bfa05833266240066aedc497decc9b00cc0d3e3b8cceea98cf530196ab06",
    "source": "tokenizer chat template (HF repo)"
  },
  "tokenizer": {
    "mode": "hf",
    "repo": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
    "requested_mode": "auto",
    "resolved_revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "revision": "b2cff646eb4bb1d68355c01b18ae02e7cf42d120",
    "tokenizer_class": "transformers.models.qwen2.tokenization_qwen2.Qwen2Tokenizer"
  },
  "tool_choice": "auto (tools and tool_choice omitted when the fixture offers no tools)",
  "tool_parser": "qwen3_coder",
  "units_source": "fixture.output_token_ids"
}