diff --git a/common/parsers/gemma4.cpp b/common/parsers/gemma4.cpp index ad48226e61..f43c5ae46d 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>", "", "<|tool_call>"}))); auto message = p.rule("message", thought + content); diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp index 4566571e33..13733c1d9c 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<|\"|>}") + .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<|tool_call>call:get_time{city:<|\"|>Paris<|\"|>}") + .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<|\"|>}"