@@ -2324,6 +2324,16 @@ static common_chat_params common_chat_params_init_granite(const common_chat_temp
23242324 data.prompt = apply (tmpl, inputs, /* messages_override= */ std::nullopt , /* tools_override= */ std::nullopt , additional_context);
23252325 data.format = COMMON_CHAT_FORMAT_GRANITE;
23262326
2327+ const auto & src = tmpl.source ();
2328+ const bool has_pipe_tool_call = src.find (" <|tool_call|>" ) != std::string::npos;
2329+ const bool has_plain_tool_call = src.find (" <tool_call>" ) != std::string::npos;
2330+ const bool has_plain_tool_call_close = src.find (" </tool_call>" ) != std::string::npos;
2331+ const bool use_plain_tool_call = !has_pipe_tool_call && has_plain_tool_call;
2332+
2333+ const std::string tool_call_tag = use_plain_tool_call ? " <tool_call>" : " <|tool_call|>" ;
2334+ const std::string tool_call_close_tag =
2335+ use_plain_tool_call && has_plain_tool_call_close ? " </tool_call>" : " " ;
2336+
23272337 if (string_ends_with (data.prompt , " <think>\n " ) || string_ends_with (data.prompt , " <think>" )) {
23282338 if (!inputs.enable_thinking ) {
23292339 data.prompt += " </think>" ;
@@ -2333,9 +2343,23 @@ static common_chat_params common_chat_params_init_granite(const common_chat_temp
23332343 }
23342344
23352345 if (!inputs.tools .is_null ()) {
2336- // Granite uses <|tool_call|> followed by JSON list
2346+ // Granite uses a sentinel tag followed by a JSON list of tool calls
23372347 data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
23382348 data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
2349+ const auto quote_literal = [](const std::string & literal) {
2350+ std::string escaped;
2351+ escaped.reserve (literal.size () * 2 + 2 );
2352+ escaped.push_back (' "' );
2353+ for (const char ch : literal) {
2354+ if (ch == ' "' || ch == ' \\ ' ) {
2355+ escaped.push_back (' \\ ' );
2356+ }
2357+ escaped.push_back (ch);
2358+ }
2359+ escaped.push_back (' "' );
2360+ return escaped;
2361+ };
2362+
23392363 std::vector<std::string> tool_rules;
23402364 foreach_function (inputs.tools , [&](const json & tool) {
23412365 const auto & function = tool.at (" function" );
@@ -2356,24 +2380,37 @@ static common_chat_params common_chat_params_init_granite(const common_chat_temp
23562380 auto tool_call = builder.add_rule (" tool_call" , string_join (tool_rules, " | " ));
23572381 auto tool_list = builder.add_rule (" tool_list" , " \" [\" space " + tool_call + " (\" ,\" space " + tool_call + " )* space \" ]\" " );
23582382
2383+ const auto tool_call_literal = quote_literal (tool_call_tag);
2384+ const auto tool_call_close_literal =
2385+ tool_call_close_tag.empty () ? std::string{} : quote_literal (tool_call_close_tag);
2386+ const auto optional_close_segment = tool_call_close_literal.empty ()
2387+ ? std::string{}
2388+ : " (space " + tool_call_close_literal + " )?" ;
2389+
23592390 if (data.thinking_forced_open ) {
2360- builder.add_rule (" root" , " \" </think>\" space \" <response>\" space [^<]* \" </response>\" space \" <|tool_call|>\" space " + tool_list);
2391+ builder.add_rule (
2392+ " root" ,
2393+ " \" </think>\" space \" <response>\" space [^<]* \" </response>\" space " +
2394+ tool_call_literal + " space " + tool_list + optional_close_segment);
23612395 } else {
2362- builder.add_rule (" root" , " \" <|tool_call|> \" space " + tool_list);
2396+ builder.add_rule (" root" , tool_call_literal + " space " + tool_list + optional_close_segment );
23632397 }
23642398
23652399 data.grammar_triggers .push_back ({
23662400 COMMON_GRAMMAR_TRIGGER_TYPE_WORD,
2367- " <|tool_call|> "
2401+ tool_call_tag
23682402 });
23692403
23702404 data.preserved_tokens = {
23712405 " <think>" ,
23722406 " </think>" ,
23732407 " <response>" ,
23742408 " </response>" ,
2375- " <|tool_call|> " ,
2409+ tool_call_tag ,
23762410 };
2411+ if (!tool_call_close_tag.empty ()) {
2412+ data.preserved_tokens .push_back (tool_call_close_tag);
2413+ }
23772414 });
23782415 } else {
23792416 // Handle thinking tags for non-tool responses
@@ -2426,17 +2463,30 @@ static void common_chat_parse_granite(common_chat_msg_parser & builder) {
24262463 }
24272464
24282465 // Look for tool calls
2429- static const common_regex tool_call_regex (regex_escape (" <|tool_call|>" ));
2430- if (auto res = builder.try_find_regex (tool_call_regex)) {
2431- builder.move_to (res->groups [0 ].end );
2466+ static const common_regex tool_call_regex_legacy (regex_escape (" <|tool_call|>" ));
2467+ static const common_regex tool_call_regex_plain (regex_escape (" <tool_call>" ));
24322468
2433- // Expect JSON array of tool calls
2434- if (auto tool_call = builder.try_consume_json_with_dumped_args ({{{" arguments" }}})) {
2435- if (!builder.add_tool_calls (tool_call->value ) || tool_call->is_partial ) {
2436- throw common_chat_msg_partial_exception (" incomplete tool call" );
2469+ const auto try_parse_tool_calls = [&](const common_regex & regex, const std::string & close_tag) {
2470+ if (auto res = builder.try_find_regex (regex)) {
2471+ builder.move_to (res->groups [0 ].end );
2472+
2473+ // Expect JSON array of tool calls
2474+ if (auto tool_call = builder.try_consume_json_with_dumped_args ({{{" arguments" }}})) {
2475+ if (!builder.add_tool_calls (tool_call->value ) || tool_call->is_partial ) {
2476+ throw common_chat_msg_partial_exception (" incomplete tool call" );
2477+ }
2478+ builder.consume_spaces ();
2479+ if (!close_tag.empty ()) {
2480+ builder.try_consume_literal (close_tag);
2481+ }
24372482 }
2483+ return true ;
24382484 }
2439- } else {
2485+ return false ;
2486+ };
2487+
2488+ if (!try_parse_tool_calls (tool_call_regex_legacy, " " ) &&
2489+ !try_parse_tool_calls (tool_call_regex_plain, " </tool_call>" )) {
24402490 builder.add_content (builder.consume_rest ());
24412491 }
24422492}
@@ -2719,7 +2769,8 @@ static common_chat_params common_chat_templates_apply_jinja(
27192769 }
27202770
27212771 // Granite (IBM) - detects thinking / tools support
2722- if (src.find (" elif thinking" ) != std::string::npos && src.find (" <|tool_call|>" ) != std::string::npos) {
2772+ if ((src.find (" elif thinking" ) != std::string::npos || src.find (" tools_system_message_prefix" ) != std::string::npos) &&
2773+ (src.find (" <|tool_call|>" ) != std::string::npos || src.find (" <tool_call>" ) != std::string::npos)) {
27232774 return common_chat_params_init_granite (tmpl, params);
27242775 }
27252776
0 commit comments