CanIToolCall caniuse.com for tool calling

Gemma 4 (<|tool_call>call:NAME{...} object notation) on llamacpp a25c9865

fail 73% strict pass 35 pass · 5 soft pass · 8 fail

Run

Engine
llamacpp a25c9865 (a25c9865fe03c954c93fd755b5d79ae86ba99750)
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); not counted for this engine: one, special, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8
Fixtures digest
a1e7b55367e195e8aa6e2ef746b2cc141b44f37caad0887ff8f30b104616f836 the fixture corpus used for this site matches the run
Engine details
{
  "build_info": "b1-a25c986",
  "detokenizer": "common_token_to_piece(vocab-only GGUF, special = id in preserved_tokens)",
  "harness": "harnesses/llamacpp/replay.cpp",
  "harness_protocol": 1,
  "pinned_version": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "reasoning_format": "deepseek",
  "template_source": "gguf",
  "tokens_per_step": "one"
}
Results file
data/llamacpp-a25c9865.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 35 5 4 0 80%
expected_error 2 0 2 0 50%
stream_equals_nonstream 46 0 2 0 96%
no_leakage 48 0 0 0 100%
arguments_json 36 0 3 0 92%
arguments_schema 36 0 3 0 92%
parallel_order 4 0 0 0 100%

8 fixture(s) failed only a synthetic strategy: char:* (which can split special tokens), or a multi-token strategy this engine never produces because its server streams one token per event (one, special, rand:1:8, rand:2:8, rand:3:8, rand:4:8, rand:5:8). These are listed below but never count toward the status.

Fixtures needing attention

fail gemma4/sglang-text-around-call expected_match
Checks that did not pass for gemma4/sglang-text-around-call
CheckStrategyResultDetail
expected_matchnonstream failcontent: expected 'Some text before after', got 'Some text before '
expected_matchtoken failcontent: expected 'Some text before after', got 'Some text before '

Synthetic strategies (not counted): expected_match@one fail, expected_match@special fail, expected_match@rand:1:8 fail, expected_match@rand:2:8 fail, expected_match@rand:3:8 fail, expected_match@rand:4:8 fail, expected_match@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/engine-tests.jsonl --id gemma4/sglang-text-around-call --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 14 of fixtures/gemma4/engine-tests.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,4 +1,4 @@
 {
-  "content": "Some text before  after",
+  "content": "Some text before ",
   "reasoning_content": null,
   "tool_calls": [

Fixture

Provenance: engine_test, https://github.com/sgl-project/sglang/blob/94602c9c2b7cbdb8efd5c52802dac6a1c180089e/test/registered/unit/function_call/test_function_call_parser.py#L5546. Tags: single-call, text-before-call, text-after-call.

Raw output

Some text before <|tool_call>call:get_weather{location:<|"|>Tokyo<|"|>}<tool_call|> after

Expected parse

{
  "content": "Some text before  after",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "location": "Tokyo"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/sglang-text-around-call", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "engine_test", "source_url": "https://github.com/sgl-project/sglang/blob/94602c9c2b7cbdb8efd5c52802dac6a1c180089e/test/registered/unit/function_call/test_function_call_parser.py#L5546", "revision": "94602c9c2b7cbdb8efd5c52802dac6a1c180089e", "license": "Apache-2.0", "attribution": "Copyright SGLang Team"}, "tools": [{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"location": {"type": "string"}, "unit": {"type": "string"}}}}}], "raw_output": "Some text before <|tool_call>call:get_weather{location:<|\"|>Tokyo<|\"|>}<tool_call|> after", "output_token_ids": [9401, 1816, 1680, 236743, 48, 6639, 236787, 828, 236779, 19323, 236782, 7125, 236787, 52, 89265, 52, 236783, 49, 1308], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected": {"content": "Some text before  after", "reasoning_content": null, "tool_calls": [{"name": "get_weather", "arguments": {"location": "Tokyo"}}]}, "tags": ["single-call", "text-before-call", "text-after-call"], "notes": "The streamed chunks of the test, joined. Content is all text outside the call. output_token_ids: tokenizer.encode(raw_output)."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "Some text before ",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": [
          {
            "arguments_raw": "{\"location\":\"Tokyo\"}",
            "name": "get_weather"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "476794947c0d7e966548e561d15ac40a035481bded34e57deaa698d9d68fe141",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
fail gemma4/text-after-call expected_match
Checks that did not pass for gemma4/text-after-call
CheckStrategyResultDetail
expected_matchnonstream failcontent: expected 'Let me check the current conditions in London.', got None
expected_matchtoken failcontent: expected 'Let me check the current conditions in London.', got None

Synthetic strategies (not counted): expected_match@one fail, expected_match@special fail, expected_match@rand:1:8 fail, expected_match@rand:2:8 fail, expected_match@rand:3:8 fail, expected_match@rand:4:8 fail, expected_match@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/basic.jsonl --id gemma4/text-after-call --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 5 of fixtures/gemma4/basic.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,4 +1,4 @@
 {
-  "content": "Let me check the current conditions in London.",
+  "content": null,
   "reasoning_content": null,
   "tool_calls": [

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: single-call, text-after-call.

Raw output

<|tool_call>call:get_weather{city:<|"|>London<|"|>}<tool_call|>Let me check the current conditions in London.

Expected parse

{
  "content": "Let me check the current conditions in London.",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "city": "London"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/text-after-call", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"]}}}], "raw_output": "<|tool_call>call:get_weather{city:<|\"|>London<|\"|>}<tool_call|>Let me check the current conditions in London.", "output_token_ids": [48, 6639, 236787, 828, 236779, 19323, 236782, 13319, 236787, 52, 27822, 52, 236783, 49, 6481, 786, 2426, 506, 1873, 3439, 528, 5860, 236761], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected": {"content": "Let me check the current conditions in London.", "reasoning_content": null, "tool_calls": [{"name": "get_weather", "arguments": {"city": "London"}}]}, "tags": ["single-call", "text-after-call"], "notes": "The official template renders an assistant message's text content AFTER its tool calls. History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": [
          {
            "arguments_raw": "{\"city\":\"London\"}",
            "name": "get_weather"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "7716fb8476727a5f17f8972a92c7654097007431aac0c586cdac9a95d6a510af",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
fail gemma4/text-after-call-thinking expected_match
Checks that did not pass for gemma4/text-after-call-thinking
CheckStrategyResultDetail
expected_matchnonstream failcontent: expected 'Let me check the current conditions in London.', got None; reasoning_content (whitespace only): expected 'I need live weather data for London.', got 'I need live weather data for London.\n'
expected_matchtoken failcontent: expected 'Let me check the current conditions in London.', got None; reasoning_content (whitespace only): expected 'I need live weather data for London.', got 'I need live weather data for London.\n'

Synthetic strategies (not counted): expected_match@one fail, expected_match@special fail, expected_match@rand:1:8 fail, expected_match@rand:2:8 fail, expected_match@rand:3:8 fail, expected_match@rand:4:8 fail, expected_match@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/basic.jsonl --id gemma4/text-after-call-thinking --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 6 of fixtures/gemma4/basic.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": "Let me check the current conditions in London.",
-  "reasoning_content": "I need live weather data for London.",
+  "content": null,
+  "reasoning_content": "I need live weather data for London.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: single-call, text-after-call, reasoning.

Raw output

<|channel>thought
I need live weather data for London.
<channel|><|tool_call>call:get_weather{city:<|"|>London<|"|>}<tool_call|>Let me check the current conditions in London.

Expected parse

{
  "content": "Let me check the current conditions in London.",
  "reasoning_content": "I need live weather data for London.",
  "tool_calls": [
    {
      "arguments": {
        "city": "London"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/text-after-call-thinking", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"]}}}], "raw_output": "<|channel>thought\nI need live weather data for London.\n<channel|><|tool_call>call:get_weather{city:<|\"|>London<|\"|>}<tool_call|>Let me check the current conditions in London.", "output_token_ids": [100, 45518, 107, 236777, 1202, 3892, 7606, 1262, 573, 5860, 236761, 107, 101, 48, 6639, 236787, 828, 236779, 19323, 236782, 13319, 236787, 52, 27822, 52, 236783, 49, 6481, 786, 2426, 506, 1873, 3439, 528, 5860, 236761], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n", "thinking": true, "expected": {"content": "Let me check the current conditions in London.", "reasoning_content": "I need live weather data for London.", "tool_calls": [{"name": "get_weather", "arguments": {"city": "London"}}]}, "tags": ["single-call", "text-after-call", "reasoning"], "notes": "History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": true,
    "thinking": true
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": true,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": "I need live weather data for London.\n",
        "tool_calls": [
          {
            "arguments_raw": "{\"city\":\"London\"}",
            "name": "get_weather"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "7716fb8476727a5f17f8972a92c7654097007431aac0c586cdac9a95d6a510af",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
fail gemma4/truncated-before-close expected_error, arguments_json, arguments_schema
Checks that did not pass for gemma4/truncated-before-close
CheckStrategyResultDetail
expected_errornonstream failreturned 1 tool call(s) ['get_weather'] for Output cut by max_tokens inside the tool call: no complete call exists.
expected_errortoken failreturned 1 tool call(s) ['get_weather'] for Output cut by max_tokens inside the tool call: no complete call exists.
arguments_jsonnonstream fail[0] get_weather: arguments are not valid JSON text ('{"city":"Paris"': Expecting ',' delimiter: line 1 column 16 (char 15))
arguments_jsontoken fail[0] get_weather: arguments are not valid JSON text ('{"city":"Paris"': Expecting ',' delimiter: line 1 column 16 (char 15))
arguments_schemanonstream fail[0] get_weather: arguments are not a JSON object; not validated
arguments_schematoken fail[0] get_weather: arguments are not a JSON object; not validated

Synthetic strategies (not counted): expected_error@one fail, expected_error@special fail, expected_error@rand:1:8 fail, expected_error@rand:2:8 fail, expected_error@rand:3:8 fail, expected_error@rand:4:8 fail, expected_error@rand:5:8 fail, arguments_json@one fail, arguments_json@special fail, arguments_json@rand:1:8 fail, arguments_json@rand:2:8 fail, arguments_json@rand:3:8 fail, arguments_json@rand:4:8 fail, arguments_json@rand:5:8 fail, arguments_schema@one fail, arguments_schema@special fail, arguments_schema@rand:1:8 fail, arguments_schema@rand:2:8 fail, arguments_schema@rand:3:8 fail, arguments_schema@rand:4:8 fail, arguments_schema@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/truncated.jsonl --id gemma4/truncated-before-close --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 3 of fixtures/gemma4/truncated.jsonl.

Observed

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

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "<arguments_raw, not valid JSON>": "{\"city\":\"Paris\""
      },
      "name": "get_weather"
    }
  ]
}

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: truncated.

Expected graceful failure: Output cut by max_tokens inside the tool call: no complete call exists. (accept: no_tool_calls, content_passthrough, exception).

Raw output

<|tool_call>call:get_weather{city:<|"|>Paris<|"|>
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/truncated-before-close", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"]}}}], "raw_output": "<|tool_call>call:get_weather{city:<|\"|>Paris<|\"|>", "output_token_ids": [48, 6639, 236787, 828, 236779, 19323, 236782, 13319, 236787, 52, 50429, 52], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected_error": {"reason": "Output cut by max_tokens inside the tool call: no complete call exists.", "accept": ["no_tool_calls", "content_passthrough", "exception"]}, "tags": ["truncated"], "notes": "Cut before the closing '}' and <tool_call|>. Token prefix of the full render, cut right after 'Paris<|\"|>' appears. History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": null,
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": [
          {
            "arguments_raw": "{\"city\":\"Paris\"",
            "name": "get_weather"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "7716fb8476727a5f17f8972a92c7654097007431aac0c586cdac9a95d6a510af",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
fail gemma4/truncated-in-name stream_equals_nonstream
Checks that did not pass for gemma4/truncated-in-name
CheckStrategyResultDetail
stream_equals_nonstreamtoken failcontent: nonstream '<|tool_call>call:get', got None

Synthetic strategies (not counted): stream_equals_nonstream@one fail, stream_equals_nonstream@special fail, stream_equals_nonstream@rand:1:8 fail, stream_equals_nonstream@rand:2:8 fail, stream_equals_nonstream@rand:3:8 fail, stream_equals_nonstream@rand:4:8 fail, stream_equals_nonstream@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/truncated.jsonl --id gemma4/truncated-in-name --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 2 of fixtures/gemma4/truncated.jsonl.

Observed

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

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

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": []
}

Strategies: nonstream

{
  "content": "<|tool_call>call:get",
  "reasoning_content": null,
  "tool_calls": []
}

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: truncated.

Expected graceful failure: Output cut by max_tokens inside the tool call: no complete call exists. (accept: no_tool_calls, content_passthrough, exception).

Raw output

<|tool_call>call:get
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/truncated-in-name", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"]}}}], "raw_output": "<|tool_call>call:get", "output_token_ids": [48, 6639, 236787, 828], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected_error": {"reason": "Output cut by max_tokens inside the tool call: no complete call exists.", "accept": ["no_tool_calls", "content_passthrough", "exception"]}, "tags": ["truncated"], "notes": "Token prefix of the full render, cut right after 'call:get' appears. History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": null,
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "<|tool_call>call:get",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": []
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "7716fb8476727a5f17f8972a92c7654097007431aac0c586cdac9a95d6a510af",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
fail gemma4/truncated-mid-string expected_error, arguments_json, arguments_schema
Checks that did not pass for gemma4/truncated-mid-string
CheckStrategyResultDetail
expected_errornonstream failreturned 1 tool call(s) ['get_weather'] for Output cut by max_tokens inside the tool call: no complete call exists.
expected_errortoken failreturned 1 tool call(s) ['get_weather'] for Output cut by max_tokens inside the tool call: no complete call exists.
arguments_jsonnonstream fail[0] get_weather: arguments are not valid JSON text ('{"city":"Paris': Unterminated string starting at: line 1 column 9 (char 8))
arguments_jsontoken fail[0] get_weather: arguments are not valid JSON text ('{"city":"Paris': Unterminated string starting at: line 1 column 9 (char 8))
arguments_schemanonstream fail[0] get_weather: arguments are not a JSON object; not validated
arguments_schematoken fail[0] get_weather: arguments are not a JSON object; not validated

Synthetic strategies (not counted): expected_error@one fail, expected_error@special fail, expected_error@rand:1:8 fail, expected_error@rand:2:8 fail, expected_error@rand:3:8 fail, expected_error@rand:4:8 fail, expected_error@rand:5:8 fail, arguments_json@one fail, arguments_json@special fail, arguments_json@rand:1:8 fail, arguments_json@rand:2:8 fail, arguments_json@rand:3:8 fail, arguments_json@rand:4:8 fail, arguments_json@rand:5:8 fail, arguments_schema@one fail, arguments_schema@special fail, arguments_schema@rand:1:8 fail, arguments_schema@rand:2:8 fail, arguments_schema@rand:3:8 fail, arguments_schema@rand:4:8 fail, arguments_schema@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/truncated.jsonl --id gemma4/truncated-mid-string --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 1 of fixtures/gemma4/truncated.jsonl.

Observed

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

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "<arguments_raw, not valid JSON>": "{\"city\":\"Paris"
      },
      "name": "get_weather"
    }
  ]
}

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: truncated.

Expected graceful failure: Output cut by max_tokens inside the tool call: no complete call exists. (accept: no_tool_calls, content_passthrough, exception).

Raw output

<|tool_call>call:get_weather{city:<|"|>Paris
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/truncated-mid-string", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"]}}}], "raw_output": "<|tool_call>call:get_weather{city:<|\"|>Paris", "output_token_ids": [48, 6639, 236787, 828, 236779, 19323, 236782, 13319, 236787, 52, 50429], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected_error": {"reason": "Output cut by max_tokens inside the tool call: no complete call exists.", "accept": ["no_tool_calls", "content_passthrough", "exception"]}, "tags": ["truncated"], "notes": "Token prefix of the full render, cut right after 'Par' appears. History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": null,
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": [
          {
            "arguments_raw": "{\"city\":\"Paris",
            "name": "get_weather"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "7716fb8476727a5f17f8972a92c7654097007431aac0c586cdac9a95d6a510af",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
fail gemma4/truncated-second-parallel-call expected_match, arguments_json, arguments_schema
Checks that did not pass for gemma4/truncated-second-parallel-call
CheckStrategyResultDetail
expected_matchnonstream failtool_calls: expected ['get_weather'], got ['get_weather', 'search']
expected_matchtoken failtool_calls: expected ['get_weather'], got ['get_weather', 'search']
arguments_jsonnonstream fail[1] search: arguments are not valid JSON text ('{"filters":{"max":3,"tags":["a","b"]},"query":': Expecting value: line 1 column 47 (char 46))
arguments_jsontoken fail[1] search: arguments are not valid JSON text ('{"filters":{"max":3,"tags":["a","b"]},"query":': Expecting value: line 1 column 47 (char 46))
arguments_schemanonstream fail[1] search: arguments are not a JSON object; not validated
arguments_schematoken fail[1] search: arguments are not a JSON object; not validated

Synthetic strategies (not counted): expected_match@one fail, expected_match@special fail, expected_match@rand:1:8 fail, expected_match@rand:2:8 fail, expected_match@rand:3:8 fail, expected_match@rand:4:8 fail, expected_match@rand:5:8 fail, arguments_json@one fail, arguments_json@special fail, arguments_json@rand:1:8 fail, arguments_json@rand:2:8 fail, arguments_json@rand:3:8 fail, arguments_json@rand:4:8 fail, arguments_json@rand:5:8 fail, arguments_schema@one fail, arguments_schema@special fail, arguments_schema@rand:1:8 fail, arguments_schema@rand:2:8 fail, arguments_schema@rand:3:8 fail, arguments_schema@rand:4:8 fail, arguments_schema@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/truncated.jsonl --id gemma4/truncated-second-parallel-call --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 5 of fixtures/gemma4/truncated.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
@@ -9,4 +9,10 @@
       },
       "name": "get_weather"
+    },
+    {
+      "arguments": {
+        "<arguments_raw, not valid JSON>": "{\"filters\":{\"max\":3,\"tags\":[\"a\",\"b\"]},\"query\":"
+      },
+      "name": "search"
     }
   ]

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: truncated, parallel-calls, unicode.

Raw output

<|tool_call>call:get_weather{city:<|"|>Zürich<|"|>,unit:<|"|>c<|"|>}<tool_call|><|tool_call>call:search{filters:{max:3,tags:[<|"|>a<|"|>,<|"|>b<|"|>]},query:

Expected parse

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "city": "Zürich",
        "unit": "c"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/truncated-second-parallel-call", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"}}, "required": ["query"]}}}], "raw_output": "<|tool_call>call:get_weather{city:<|\"|>Zürich<|\"|>,unit:<|\"|>c<|\"|>}<tool_call|><|tool_call>call:search{filters:{max:3,tags:[<|\"|>a<|\"|>,<|\"|>b<|\"|>]},query:", "output_token_ids": [48, 6639, 236787, 828, 236779, 19323, 236782, 13319, 236787, 52, 236953, 105214, 52, 236764, 6805, 236787, 52, 236755, 52, 236783, 49, 48, 6639, 236787, 2305, 236782, 28881, 29616, 2074, 236787, 236800, 236764, 19112, 24845, 52, 236746, 52, 236764, 52, 236763, 52, 59117, 3278, 236787], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected": {"content": null, "reasoning_content": null, "tool_calls": [{"name": "get_weather", "arguments": {"city": "Zürich", "unit": "c"}}]}, "tags": ["truncated", "parallel-calls", "unicode"], "notes": "The first call is complete; the second is cut. A correct parser keeps the complete call only. Token prefix of the full render, cut right after 'query:' appears. History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": null,
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": [
          {
            "arguments_raw": "{\"city\":\"Zürich\",\"unit\":\"c\"}",
            "name": "get_weather"
          },
          {
            "arguments_raw": "{\"filters\":{\"max\":3,\"tags\":[\"a\",\"b\"]},\"query\":",
            "name": "search"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "2309009459b4475c5ddada39a14bc9114e87e46a4469d34c5c361ffa5a0badaf",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
fail gemma4/vllm-malformed-no-brace stream_equals_nonstream
Checks that did not pass for gemma4/vllm-malformed-no-brace
CheckStrategyResultDetail
stream_equals_nonstreamtoken failcontent: nonstream '<|tool_call>call:bad_func no brace<tool_call|>', got None

Synthetic strategies (not counted): stream_equals_nonstream@one fail, stream_equals_nonstream@special fail, stream_equals_nonstream@rand:1:8 fail, stream_equals_nonstream@rand:2:8 fail, stream_equals_nonstream@rand:3:8 fail, stream_equals_nonstream@rand:4:8 fail, stream_equals_nonstream@rand:5:8 fail

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/malformed.jsonl --id gemma4/vllm-malformed-no-brace --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 1 of fixtures/gemma4/malformed.jsonl.

Observed

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

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

{
  "content": null,
  "reasoning_content": null,
  "tool_calls": []
}

Strategies: nonstream

{
  "content": "<|tool_call>call:bad_func no brace<tool_call|>",
  "reasoning_content": null,
  "tool_calls": []
}

Fixture

Provenance: engine_test, https://github.com/vllm-project/vllm/blob/ced6857afa0ea7b2e3f0846a62e1394e90f15607/tests/tool_parsers/test_gemma4_tool_parser.py#L942. Tags: malformed.

Expected graceful failure: The call has no '{...}' argument object and names no offered tool; it is not a valid call. (accept: no_tool_calls, content_passthrough).

Raw output

<|tool_call>call:bad_func no brace<tool_call|>
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/vllm-malformed-no-brace", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "engine_test", "source_url": "https://github.com/vllm-project/vllm/blob/ced6857afa0ea7b2e3f0846a62e1394e90f15607/tests/tool_parsers/test_gemma4_tool_parser.py#L942", "revision": "ced6857afa0ea7b2e3f0846a62e1394e90f15607", "license": "Apache-2.0", "attribution": "Copyright contributors to the vLLM project"}, "tools": [{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"location": {"type": "string"}, "unit": {"type": "string"}}}}}], "raw_output": "<|tool_call>call:bad_func no brace<tool_call|>", "output_token_ids": [48, 6639, 236787, 15242, 236779, 6823, 951, 24088, 49], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected_error": {"reason": "The call has no '{...}' argument object and names no offered tool; it is not a valid call.", "accept": ["no_tool_calls", "content_passthrough"]}, "tags": ["malformed"], "notes": "vLLM's test only asserts that the recovered name stays bounded by <tool_call|> (it returns a call named 'bad_func no brace'); per the format there is no valid call here. output_token_ids: tokenizer.encode(raw_output)."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "<|tool_call>call:bad_func no brace<tool_call|>",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": []
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "476794947c0d7e966548e561d15ac40a035481bded34e57deaa698d9d68fe141",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
soft pass gemma4/empty-arguments-thinking expected_match
Checks that did not pass for gemma4/empty-arguments-thinking
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'get_time takes no arguments.', got 'get_time takes no arguments.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'get_time takes no arguments.', got 'get_time takes no arguments.\n'

Synthetic strategies (not counted): expected_match@one soft pass, expected_match@special soft pass, expected_match@rand:1:8 soft pass, expected_match@rand:2:8 soft pass, expected_match@rand:3:8 soft pass, expected_match@rand:4:8 soft pass, expected_match@rand:5:8 soft pass

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/basic.jsonl --id gemma4/empty-arguments-thinking --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 8 of fixtures/gemma4/basic.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_time takes no arguments.",
+  "reasoning_content": "get_time takes no arguments.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: single-call, empty-arguments, reasoning.

Raw output

<|channel>thought
get_time takes no arguments.
<channel|><|tool_call>call:get_time{}<tool_call|>

Expected parse

{
  "content": null,
  "reasoning_content": "get_time takes no arguments.",
  "tool_calls": [
    {
      "arguments": {},
      "name": "get_time"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/empty-arguments-thinking", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "tools": [{"type": "function", "function": {"name": "get_time", "description": "Get the current UTC time. Takes no arguments.", "parameters": {"type": "object", "properties": {}}}}], "raw_output": "<|channel>thought\nget_time takes no arguments.\n<channel|><|tool_call>call:get_time{}<tool_call|>", "output_token_ids": [100, 45518, 107, 828, 236779, 2289, 4716, 951, 12130, 236761, 107, 101, 48, 6639, 236787, 828, 236779, 2289, 16454, 49], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n", "thinking": true, "expected": {"content": null, "reasoning_content": "get_time takes no arguments.", "tool_calls": [{"name": "get_time", "arguments": {}}]}, "tags": ["single-call", "empty-arguments", "reasoning"], "notes": "History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": true,
    "thinking": true
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": true,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": "get_time takes no arguments.\n",
        "tool_calls": [
          {
            "arguments_raw": "{}",
            "name": "get_time"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "087e3444fb4f8cb3c090a45e55e6dcb9c26e3c4c39bcf7b3d903f2154627877c",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
soft pass gemma4/no-call-thinking expected_match
Checks that did not pass for gemma4/no-call-thinking
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected 'The user is just greeting me. No tool is needed.', got 'The user is just greeting me. No tool is needed.\n'
expected_matchtoken soft passreasoning_content (whitespace only): expected 'The user is just greeting me. No tool is needed.', got 'The user is just greeting me. No tool is needed.\n'

Synthetic strategies (not counted): expected_match@one soft pass, expected_match@special soft pass, expected_match@rand:1:8 soft pass, expected_match@rand:2:8 soft pass, expected_match@rand:3:8 soft pass, expected_match@rand:4:8 soft pass, expected_match@rand:5:8 soft pass

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/basic.jsonl --id gemma4/no-call-thinking --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 4 of fixtures/gemma4/basic.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": "The user is just greeting me. No tool is needed.",
+  "reasoning_content": "The user is just greeting me. No tool is needed.\n",
   "tool_calls": []
 }

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: no-call, reasoning.

Raw output

<|channel>thought
The user is just greeting me. No tool is needed.
<channel|>Hello! How can I help you today?

Expected parse

{
  "content": "Hello! How can I help you today?",
  "reasoning_content": "The user is just greeting me. No tool is needed.",
  "tool_calls": []
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/no-call-thinking", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"]}}}], "raw_output": "<|channel>thought\nThe user is just greeting me. No tool is needed.\n<channel|>Hello! How can I help you today?", "output_token_ids": [100, 45518, 107, 818, 2430, 563, 1164, 26227, 786, 236761, 2301, 5904, 563, 4354, 236761, 107, 101, 9259, 236888, 2088, 740, 564, 1601, 611, 3124, 236881], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n", "thinking": true, "expected": {"content": "Hello! How can I help you today?", "reasoning_content": "The user is just greeting me. No tool is needed.", "tool_calls": []}, "tags": ["no-call", "reasoning"], "notes": "History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": true,
    "thinking": true
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": true,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "Hello! How can I help you today?",
        "exception": null,
        "reasoning_content": "The user is just greeting me. No tool is needed.\n",
        "tool_calls": []
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "7716fb8476727a5f17f8972a92c7654097007431aac0c586cdac9a95d6a510af",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
soft pass gemma4/parallel-two-calls-thinking expected_match
Checks that did not pass for gemma4/parallel-two-calls-thinking
CheckStrategyResultDetail
expected_matchnonstream 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'

Synthetic strategies (not counted): expected_match@one soft pass, expected_match@special soft pass, expected_match@rand:1:8 soft pass, expected_match@rand:2:8 soft pass, expected_match@rand:3:8 soft pass, expected_match@rand:4:8 soft pass, expected_match@rand:5:8 soft pass

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/parallel.jsonl --id gemma4/parallel-two-calls-thinking --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 1 of fixtures/gemma4/parallel.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/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: parallel-calls, reasoning, unicode, nested-json, string-escapes.

Raw output

<|channel>thought
I should call the tools.
<channel|><|tool_call>call:get_weather{city:<|"|>Zürich<|"|>,unit:<|"|>c<|"|>}<tool_call|><|tool_call>call:search{filters:{max:3,tags:[<|"|>a<|"|>,<|"|>b<|"|>]},query:<|"|>café "best"<|"|>}<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": "gemma4/parallel-two-calls-thinking", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"}}, "required": ["query"]}}}], "raw_output": "<|channel>thought\nI should call the tools.\n<channel|><|tool_call>call:get_weather{city:<|\"|>Zürich<|\"|>,unit:<|\"|>c<|\"|>}<tool_call|><|tool_call>call:search{filters:{max:3,tags:[<|\"|>a<|\"|>,<|\"|>b<|\"|>]},query:<|\"|>café \"best\"<|\"|>}<tool_call|>", "output_token_ids": [100, 45518, 107, 236777, 1374, 2246, 506, 6436, 236761, 107, 101, 48, 6639, 236787, 828, 236779, 19323, 236782, 13319, 236787, 52, 236953, 105214, 52, 236764, 6805, 236787, 52, 236755, 52, 236783, 49, 48, 6639, 236787, 2305, 236782, 28881, 29616, 2074, 236787, 236800, 236764, 19112, 24845, 52, 236746, 52, 236764, 52, 236763, 52, 59117, 3278, 236787, 52, 123125, 236859, 623, 9783, 236775, 52, 236783, 49], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n", "thinking": true, "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", "unicode", "nested-json", "string-escapes"], "notes": "History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": true,
    "thinking": true
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": true,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": "I should call the tools.\n",
        "tool_calls": [
          {
            "arguments_raw": "{\"city\":\"Zürich\",\"unit\":\"c\"}",
            "name": "get_weather"
          },
          {
            "arguments_raw": "{\"filters\":{\"max\":3,\"tags\":[\"a\",\"b\"]},\"query\":\"café \\\"best\\\"\"}",
            "name": "search"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "2309009459b4475c5ddada39a14bc9114e87e46a4469d34c5c361ffa5a0badaf",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
soft pass gemma4/single-call-thinking expected_match
Checks that did not pass for gemma4/single-call-thinking
CheckStrategyResultDetail
expected_matchnonstream soft passreasoning_content (whitespace only): expected "The user wants the current weather in Paris. I'll call get_weather.", got "The user wants the current weather in Paris. I'll call get_weather.\n"
expected_matchtoken soft passreasoning_content (whitespace only): expected "The user wants the current weather in Paris. I'll call get_weather.", got "The user wants the current weather in Paris. I'll call get_weather.\n"

Synthetic strategies (not counted): expected_match@one soft pass, expected_match@special soft pass, expected_match@rand:1:8 soft pass, expected_match@rand:2:8 soft pass, expected_match@rand:3:8 soft pass, expected_match@rand:4:8 soft pass, expected_match@rand:5:8 soft pass

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/basic.jsonl --id gemma4/single-call-thinking --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 2 of fixtures/gemma4/basic.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 current weather in Paris. I'll call get_weather.",
+  "reasoning_content": "The user wants the current weather in Paris. I'll call get_weather.\n",
   "tool_calls": [
     {

Fixture

Provenance: template_render, https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja. Tags: single-call, reasoning.

Raw output

<|channel>thought
The user wants the current weather in Paris. I'll call get_weather.
<channel|><|tool_call>call:get_weather{city:<|"|>Paris<|"|>,unit:<|"|>c<|"|>}<tool_call|>

Expected parse

{
  "content": null,
  "reasoning_content": "The user wants the current weather in Paris. I'll 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": "gemma4/single-call-thinking", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "template_render", "source_url": "https://huggingface.co/google/gemma-4-31B-it/blob/842da3794eaa0b77d5f08bae87a17459d91ff475/chat_template.jinja", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "license": "Apache-2.0", "generator": "scripts/fixtures/gemma4/build.py", "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4"}, "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"]}}}], "raw_output": "<|channel>thought\nThe user wants the current weather in Paris. I'll call get_weather.\n<channel|><|tool_call>call:get_weather{city:<|\"|>Paris<|\"|>,unit:<|\"|>c<|\"|>}<tool_call|>", "output_token_ids": [100, 45518, 107, 818, 2430, 8150, 506, 1873, 7606, 528, 9079, 236761, 564, 236789, 859, 2246, 974, 236779, 19323, 236761, 107, 101, 48, 6639, 236787, 828, 236779, 19323, 236782, 13319, 236787, 52, 50429, 52, 236764, 6805, 236787, 52, 236755, 52, 236783, 49], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n", "thinking": true, "expected": {"content": null, "reasoning_content": "The user wants the current weather in Paris. I'll call get_weather.", "tool_calls": [{"name": "get_weather", "arguments": {"city": "Paris", "unit": "c"}}]}, "tags": ["single-call", "reasoning"], "notes": "History render of the official template; generation stops on <|tool_response>/<turn|>."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": true,
    "thinking": true
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": true,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "",
        "exception": null,
        "reasoning_content": "The user wants the current weather in Paris. I'll call get_weather.\n",
        "tool_calls": [
          {
            "arguments_raw": "{\"city\":\"Paris\",\"unit\":\"c\"}",
            "name": "get_weather"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "7716fb8476727a5f17f8972a92c7654097007431aac0c586cdac9a95d6a510af",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}
soft pass gemma4/vllm-text-before-call expected_match
Checks that did not pass for gemma4/vllm-text-before-call
CheckStrategyResultDetail
expected_matchnonstream soft passcontent (whitespace only): expected 'Let me check the weather for you.', got 'Let me check the weather for you. '
expected_matchtoken soft passcontent (whitespace only): expected 'Let me check the weather for you.', got 'Let me check the weather for you. '

Synthetic strategies (not counted): expected_match@one soft pass, expected_match@special soft pass, expected_match@rand:1:8 soft pass, expected_match@rand:2:8 soft pass, expected_match@rand:3:8 soft pass, expected_match@rand:4:8 soft pass, expected_match@rand:5:8 soft pass

Minimal repro

uv run canitoolcall run --engine llamacpp --fixtures fixtures/gemma4/engine-tests.jsonl --id gemma4/vllm-text-before-call --strategy token --observed all

Set up the engine first with scripts/engines/llamacpp.sh; this run used llamacpp a25c9865. The fixture is line 4 of fixtures/gemma4/engine-tests.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,4 +1,4 @@
 {
-  "content": "Let me check the weather for you.",
+  "content": "Let me check the weather for you. ",
   "reasoning_content": null,
   "tool_calls": [

Fixture

Provenance: engine_test, https://github.com/vllm-project/vllm/blob/ced6857afa0ea7b2e3f0846a62e1394e90f15607/tests/tool_parsers/test_gemma4_tool_parser.py#L323. Tags: single-call, text-before-call.

Raw output

Let me check the weather for you. <|tool_call>call:get_weather{location:<|"|>Paris<|"|>}<tool_call|>

Expected parse

{
  "content": "Let me check the weather for you.",
  "reasoning_content": null,
  "tool_calls": [
    {
      "arguments": {
        "location": "Paris"
      },
      "name": "get_weather"
    }
  ]
}
Fixture record (JSONL, ready to vendor into an engine's tests)
{"id": "gemma4/vllm-text-before-call", "family": "gemma4", "models": ["google/gemma-4-31B-it", "google/gemma-4-26B-A4B-it", "google/gemma-4-12B-it"], "spec_version": "0.1", "provenance": {"kind": "engine_test", "source_url": "https://github.com/vllm-project/vllm/blob/ced6857afa0ea7b2e3f0846a62e1394e90f15607/tests/tool_parsers/test_gemma4_tool_parser.py#L323", "revision": "ced6857afa0ea7b2e3f0846a62e1394e90f15607", "license": "Apache-2.0", "attribution": "Copyright contributors to the vLLM project"}, "tools": [{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"location": {"type": "string"}, "unit": {"type": "string"}}}}}], "raw_output": "Let me check the weather for you. <|tool_call>call:get_weather{location:<|\"|>Paris<|\"|>}<tool_call|>", "output_token_ids": [6481, 786, 2426, 506, 7606, 573, 611, 236761, 236743, 48, 6639, 236787, 828, 236779, 19323, 236782, 7125, 236787, 52, 50429, 52, 236783, 49], "tokenizer": {"repo": "google/gemma-4-31B-it", "revision": "842da3794eaa0b77d5f08bae87a17459d91ff475", "mode": "hf"}, "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>", "thinking": false, "expected": {"content": "Let me check the weather for you.", "reasoning_content": null, "tool_calls": [{"name": "get_weather", "arguments": {"location": "Paris"}}]}, "tags": ["single-call", "text-before-call"], "notes": "The template renders text after calls, but models also emit text before them. output_token_ids: tokenizer.encode(raw_output)."}
Parser configuration
{
  "additional_stops": [],
  "chat_parser": "common_chat_templates_apply -> common_chat_parse (PEG)",
  "chat_template_kwargs": {
    "enable_thinking": false,
    "thinking": false
  },
  "detokenized_matches_raw_output": true,
  "enable_thinking": false,
  "end_token": "<eos>",
  "engine_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "eog_positions": [],
  "family_handler": "specialized handler (format peg-gemma4)",
  "fixture_generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "fixture_tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "format": "peg-gemma4",
  "generation_prompt": "<|turn>model\n<|channel>thought\n<channel|>",
  "model": "google/gemma-4-31B-it",
  "parallel_tool_calls": true,
  "preserved_tokens": [
    "<|channel>",
    "<channel|>",
    "<|tool_call>",
    "<tool_call|>",
    "<|turn>"
  ],
  "reasoning_format": "deepseek",
  "template_alternatives": {
    "llamacpp": {
      "available": true,
      "format": "peg-gemma4",
      "nonstream": {
        "content": "Let me check the weather for you. ",
        "exception": null,
        "reasoning_content": null,
        "tool_calls": [
          {
            "arguments_raw": "{\"location\":\"Paris\"}",
            "name": "get_weather"
          }
        ]
      },
      "path": "models/templates/google-gemma-4-31B-it.jinja",
      "sha256": "85a08664d16d8f3be4416c92427b3ac10df1024ac566cc0b4bc3bab409393f98"
    }
  },
  "template_identical_to": null,
  "template_path": "google--gemma-4-31B-it.vocab.gguf: tokenizer.chat_template",
  "template_sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
  "template_source": "gguf",
  "template_source_reason": null,
  "template_source_requested": "gguf",
  "template_supports_thinking": true,
  "template_variant": "default",
  "thinking_end_tags": [
    "<channel|>"
  ],
  "thinking_start_tag": "<|channel>thought",
  "tokenizer": "google/gemma-4-31B-it@842da3794eaa0b77d5f08bae87a17459d91ff475",
  "tokenizer_mode": "gguf-vocab-only",
  "tools_sha256": "476794947c0d7e966548e561d15ac40a035481bded34e57deaa698d9d68fe141",
  "vocab_gguf": "google--gemma-4-31B-it.vocab.gguf",
  "vocab_gguf_converter_env": "fallback: llama.cpp requirements with transformers 5.17.0",
  "vocab_gguf_converter_patches": null,
  "vocab_gguf_llama_cpp_commit": "a25c9865fe03c954c93fd755b5d79ae86ba99750",
  "vocab_gguf_sha256": "c45a756342ca921303a5689e6ea3e4c5133e6ebfe79d158d91616c3597803e31"
}