Commit f072b1037 for llama.cpp
commit f072b103714dfa1eee531f80b24512faf38e3dd2
Author: Aldehir Rojas <hello@alde.dev>
Date: Sat Sep 19 18:59:43 2026 -0500
chat : fix gemma4 required tool grammar (#29115)
diff --git a/common/parsers/gemma4.cpp b/common/parsers/gemma4.cpp
index ad48226e6..f43c5ae46 100644
--- a/common/parsers/gemma4.cpp
+++ b/common/parsers/gemma4.cpp
@@ -272,6 +272,10 @@ common_chat_params common_chat_params_init_gemma4(const common_chat_template &
/* max = */ inputs.parallel_tool_calls ? -1 : 1
));
+ if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
+ return start + thought + tool_call;
+ }
+
auto scan_to_toolcall = p.rule("scan-to-toolcall", p.until("<|tool_call>"));
auto content = p.rule("content", p.content(p.until_one_of({"<|channel>", "<channel|>", "<|tool_call>"})));
auto message = p.rule("message", thought + content);
diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp
index 4566571e3..13733c1d9 100644
--- a/tests/test-chat.cpp
+++ b/tests/test-chat.cpp
@@ -3032,6 +3032,26 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
.expect(message_with_content_and_tool_call("Hello, world!\nWhat's up?", "get_time", R"({"city": "Paris"})"))
.run();
+ // Required tool call
+ tst.test(
+ "<|tool_call>call:get_time{city:<|\"|>Paris<|\"|>}<tool_call|>")
+ .tools({ get_time_tool })
+ .tool_choice(COMMON_CHAT_TOOL_CHOICE_REQUIRED)
+ .expect(message_with_tool_calls("get_time", R"({"city": "Paris"})"))
+ .run();
+
+ // Required tool call after reasoning
+ tst.test(
+ "<|channel>thought\nI'm\nthinking<channel|><|tool_call>call:get_time{city:<|\"|>Paris<|\"|>}<tool_call|>")
+ .reasoning_format(COMMON_REASONING_FORMAT_AUTO)
+ .tools({ get_time_tool })
+ .tool_choice(COMMON_CHAT_TOOL_CHOICE_REQUIRED)
+ .expect_reasoning("I'm\nthinking")
+ .expect_tool_calls({
+ { "get_time", R"({"city": "Paris"})", {} },
+ })
+ .run();
+
// Parallel tool calls
tst.test(
"<|tool_call>call:get_time{city:<|\"|>London<|\"|>}<tool_call|>"