Skip to content

Commit 9744ba6

Browse files
committed
fix Gemma 4 thinking generation prompt
1 parent b0df4c0 commit 9744ba6

3 files changed

Lines changed: 63 additions & 8 deletions

File tree

models/templates/google-gemma-4-31B-it-interleaved.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@
276276
{%- if ns.prev_message_type != 'tool_response' -%}
277277
{{- '<|turn>model\n' -}}
278278
{%- endif -%}
279-
{%- if not enable_thinking | default(false) -%}
280-
{{- '<|channel>thought\n<channel|>' -}}
279+
{%- if enable_thinking | default(false) -%}
280+
{{- '<|channel>thought\n' -}}
281281
{%- endif -%}
282282
{%- endif -%}

models/templates/google-gemma-4-31B-it.jinja

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@
340340
{%- if add_generation_prompt -%}
341341
{%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}
342342
{{- '<|turn>model\n' -}}
343-
{%- if not enable_thinking | default(false) -%}
344-
{{- '<|channel>thought\n<channel|>' -}}
343+
{%- if enable_thinking | default(false) -%}
344+
{{- '<|channel>thought\n' -}}
345345
{%- endif -%}
346346
{%- endif -%}
347347
{%- endif -%}

tests/test-chat.cpp

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2519,53 +2519,60 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
25192519
// Google Gemma 4 (tool calling with Gemma4 dict format)
25202520
auto tst = peg_tester("models/templates/google-gemma-4-31B-it.jinja");
25212521

2522-
tst.test("Hello, world!").expect(simple_assist_msg("Hello, world!")).run();
2522+
tst.test("Hello, world!").enable_thinking(false).expect(simple_assist_msg("Hello, world!")).run();
25232523

25242524
// Reasoning and content
25252525
tst.test(
2526-
"<|channel>thought\nI'm\nthinking<channel|>Hello, world!\nWhat's up?")
2526+
"I'm\nthinking<channel|>Hello, world!\nWhat's up?")
2527+
.enable_thinking(true)
25272528
.reasoning_format(COMMON_REASONING_FORMAT_AUTO)
25282529
.expect(message_assist_thoughts)
25292530
.run();
25302531

25312532
// Empty reasoning (budget=0: sampler forces end tag before newline)
25322533
tst.test(
2533-
"<|channel>thought<channel|>Hello, world!\nWhat's up?")
2534+
"<channel|>Hello, world!\nWhat's up?")
2535+
.enable_thinking(true)
25342536
.reasoning_format(COMMON_REASONING_FORMAT_AUTO)
25352537
.expect(simple_assist_msg("Hello, world!\nWhat's up?", ""))
25362538
.run();
25372539

25382540
// Reasoning and content with reasoning_format = none
25392541
tst.test(
25402542
"<|channel>thought\nI'm\nthinking<channel|>Hello, world!\nWhat's up?")
2543+
.enable_thinking(false)
25412544
.reasoning_format(COMMON_REASONING_FORMAT_NONE)
25422545
.expect_content("<|channel>thought\nI'm\nthinking<channel|>Hello, world!\nWhat's up?")
25432546
.run();
25442547

25452548
// Simple tool call with string argument
25462549
tst.test(
25472550
"<|tool_call>call:get_time{city:<|\"|>London<|\"|>}<tool_call|>")
2551+
.enable_thinking(false)
25482552
.tools({ get_time_tool })
25492553
.expect(message_with_tool_calls("get_time", R"({"city": "London"})"))
25502554
.run();
25512555

25522556
// Tool call with string argument containing special chars
25532557
tst.test(
25542558
"<|tool_call>call:get_time{city:<|\"|>San Francisco<|\"|>}<tool_call|>")
2559+
.enable_thinking(false)
25552560
.tools({ get_time_tool })
25562561
.expect(message_with_tool_calls("get_time", R"({"city": "San Francisco"})"))
25572562
.run();
25582563

25592564
// Tool call with empty args
25602565
tst.test(
25612566
"<|tool_call>call:empty_args{}<tool_call|>")
2567+
.enable_thinking(false)
25622568
.tools({ empty_args_tool })
25632569
.expect(message_with_tool_calls("empty_args", "{}"))
25642570
.run();
25652571

25662572
// Tool call with string and content
25672573
tst.test(
25682574
"Hello, world!\nWhat's up?<|tool_call>call:get_time{city:<|\"|>Paris<|\"|>}<tool_call|>")
2575+
.enable_thinking(false)
25692576
.tools({ get_time_tool })
25702577
.expect(message_with_content_and_tool_call("Hello, world!\nWhat's up?", "get_time", R"({"city": "Paris"})"))
25712578
.run();
@@ -2574,6 +2581,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
25742581
tst.test(
25752582
"<|tool_call>call:get_time{city:<|\"|>London<|\"|>}<tool_call|>"
25762583
"<|tool_call>call:get_weather{city:<|\"|>Paris<|\"|>}<tool_call|>")
2584+
.enable_thinking(false)
25772585
.tools({ get_time_tool, get_weather_tool })
25782586
.parallel_tool_calls(true)
25792587
.expect_tool_calls({
@@ -2585,101 +2593,116 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
25852593
// Tool call with integer argument (number type)
25862594
tst.test(
25872595
"<|tool_call>call:special_function{arg1:42}<tool_call|>")
2596+
.enable_thinking(false)
25882597
.tools({ special_function_tool })
25892598
.expect(message_with_tool_calls("special_function", R"({"arg1": 42})"))
25902599
.run();
25912600

25922601
// Tool call with negative number argument
25932602
tst.test(
25942603
"<|tool_call>call:special_function{arg1:-7}<tool_call|>")
2604+
.enable_thinking(false)
25952605
.tools({ special_function_tool })
25962606
.expect(message_with_tool_calls("special_function", R"({"arg1": -7})"))
25972607
.run();
25982608

25992609
// Tool call with decimal number argument
26002610
tst.test(
26012611
"<|tool_call>call:amount{orig:3.14}<tool_call|>")
2612+
.enable_thinking(false)
26022613
.tools({ amount_tool })
26032614
.expect(message_with_tool_calls("amount", R"({"orig": 3.14})"))
26042615
.run();
26052616

26062617
// Tool call with boolean argument (true)
26072618
tst.test(
26082619
"<|tool_call>call:toggle{enabled:true}<tool_call|>")
2620+
.enable_thinking(false)
26092621
.tools({ toggle_tool })
26102622
.expect(message_with_tool_calls("toggle", R"({"enabled": true})"))
26112623
.run();
26122624

26132625
// Tool call with boolean argument (false)
26142626
tst.test(
26152627
"<|tool_call>call:toggle{enabled:false}<tool_call|>")
2628+
.enable_thinking(false)
26162629
.tools({ toggle_tool })
26172630
.expect(message_with_tool_calls("toggle", R"({"enabled": false})"))
26182631
.run();
26192632

26202633
// Tool call with null argument
26212634
tst.test(
26222635
"<|tool_call>call:set_nullable{value:null}<tool_call|>")
2636+
.enable_thinking(false)
26232637
.tools({ nullable_tool })
26242638
.expect(message_with_tool_calls("set_nullable", R"({"value": null})"))
26252639
.run();
26262640

26272641
// Tool call with array argument (todo list)
26282642
tst.test(
26292643
"<|tool_call>call:todo_list{todos:[<|\"|>buy milk<|\"|>,<|\"|>walk dog<|\"|>]}<tool_call|>")
2644+
.enable_thinking(false)
26302645
.tools({ todo_list })
26312646
.expect(message_with_tool_calls("todo_list", R"({"todos":["buy milk","walk dog"]})"))
26322647
.run();
26332648

26342649
// Tool call with object/dict argument
26352650
tst.test(
26362651
"<|tool_call>call:set_config{config:{theme:<|\"|>dark<|\"|>,count:3}}<tool_call|>")
2652+
.enable_thinking(false)
26372653
.tools({ config_tool })
26382654
.expect(message_with_tool_calls("set_config", R"({"config":{"theme":"dark","count":3}})"))
26392655
.run();
26402656

26412657
// Tool call with empty array
26422658
tst.test(
26432659
"<|tool_call>call:todo_list{todos:[]}<tool_call|>")
2660+
.enable_thinking(false)
26442661
.tools({ todo_list })
26452662
.expect(message_with_tool_calls("todo_list", R"({"todos":[]})"))
26462663
.run();
26472664

26482665
// Tool call with empty dict
26492666
tst.test(
26502667
"<|tool_call>call:set_config{config:{}}<tool_call|>")
2668+
.enable_thinking(false)
26512669
.tools({ config_tool })
26522670
.expect(message_with_tool_calls("set_config", R"({"config":{}})"))
26532671
.run();
26542672

26552673
// Tool call with scientific notation number
26562674
tst.test(
26572675
"<|tool_call>call:amount{orig:1.5e10}<tool_call|>")
2676+
.enable_thinking(false)
26582677
.tools({ amount_tool })
26592678
.expect(message_with_tool_calls("amount", R"({"orig": 1.5e10})"))
26602679
.run();
26612680

26622681
// Edge cases
26632682
tst.test(
26642683
"<|channel>thought\n<channel|>Hello, world!\nWhat's up?<channel|>")
2684+
.enable_thinking(false)
26652685
.reasoning_format(COMMON_REASONING_FORMAT_AUTO)
26662686
.expect(message_assist)
26672687
.run();
26682688

26692689
tst.test(
26702690
"<|channel>thought\n<channel|>Hello, world!\nWhat's up?<|channel>thought\n<channel|>")
2691+
.enable_thinking(false)
26712692
.reasoning_format(COMMON_REASONING_FORMAT_AUTO)
26722693
.expect(message_assist)
26732694
.run();
26742695

26752696
tst.test(
26762697
"<|channel>thought\n<channel|>Hello, world!\nWhat's up?<|channel>thought\n<channel|><channel|>")
2698+
.enable_thinking(false)
26772699
.reasoning_format(COMMON_REASONING_FORMAT_AUTO)
26782700
.expect(message_assist)
26792701
.run();
26802702

26812703
tst.test(
26822704
"<|channel><|channel>thought\n<channel|>Hello, world!\nWhat's up?")
2705+
.enable_thinking(false)
26832706
.reasoning_format(COMMON_REASONING_FORMAT_AUTO)
26842707
.expect(message_assist)
26852708
.run();
@@ -2723,11 +2746,15 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
27232746
inputs.messages = { message_user, tool_call_msg, tool_msg };
27242747
inputs.tools = { special_function_tool };
27252748
inputs.add_generation_prompt = true;
2749+
inputs.enable_thinking = false;
27262750

27272751
auto params = common_chat_templates_apply(tmpls.get(), inputs);
27282752

27292753
if (!string_ends_with(params.prompt, "<turn|>\n<|turn>model\n")) {
2730-
throw std::runtime_error("Missing generation prompt for Gemma 4");
2754+
throw std::runtime_error("Missing non-thinking generation prompt for Gemma 4");
2755+
}
2756+
if (params.prompt.find("<|channel>thought\n<channel|>") != std::string::npos) {
2757+
throw std::runtime_error("Gemma 4 rendered an empty thought block with thinking disabled");
27312758
}
27322759
}
27332760

@@ -2744,6 +2771,34 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
27442771
}
27452772
}
27462773
}
2774+
2775+
for (const char * template_path : {
2776+
"models/templates/google-gemma-4-31B-it.jinja",
2777+
"models/templates/google-gemma-4-31B-it-interleaved.jinja",
2778+
}) {
2779+
auto tmpls = read_templates(template_path);
2780+
2781+
common_chat_templates_inputs inputs;
2782+
inputs.messages = { message_user };
2783+
inputs.add_generation_prompt = true;
2784+
inputs.enable_thinking = true;
2785+
2786+
auto params = common_chat_templates_apply(tmpls.get(), inputs);
2787+
2788+
if (params.prompt.rfind("<|turn>model\n<|channel>thought\n") == std::string::npos) {
2789+
throw std::runtime_error(std::string("Missing thinking generation prompt for ") + template_path);
2790+
}
2791+
2792+
inputs.enable_thinking = false;
2793+
params = common_chat_templates_apply(tmpls.get(), inputs);
2794+
2795+
if (!string_ends_with(params.prompt, "<|turn>model\n")) {
2796+
throw std::runtime_error(std::string("Missing non-thinking generation prompt for ") + template_path);
2797+
}
2798+
if (params.prompt.find("<|channel>thought\n<channel|>") != std::string::npos) {
2799+
throw std::runtime_error(std::string("Gemma 4 rendered an empty thought block with thinking disabled for ") + template_path);
2800+
}
2801+
}
27472802
}
27482803

27492804
{

0 commit comments

Comments
 (0)