@@ -623,6 +623,7 @@ const char * common_chat_format_name(common_chat_format format) {
623623 case COMMON_CHAT_FORMAT_GRANITE: return " Granite" ;
624624 case COMMON_CHAT_FORMAT_GPT_OSS: return " GPT-OSS" ;
625625 case COMMON_CHAT_FORMAT_SEED_OSS: return " Seed-OSS" ;
626+ case COMMON_CHAT_FORMAT_NEMOTRON_V2: return " Nemotron V2" ;
626627 default :
627628 throw std::runtime_error (" Unknown chat format" );
628629 }
@@ -1184,6 +1185,67 @@ static common_chat_params common_chat_params_init_llama_3_x(const common_chat_te
11841185 });
11851186 return data;
11861187}
1188+
1189+ static common_chat_params common_chat_params_init_nemotron_v2 (const common_chat_template & tmpl, const struct templates_params & inputs) {
1190+ common_chat_params data;
1191+
1192+ // Generate the prompt using the apply() function with the template
1193+ data.prompt = apply (tmpl, inputs);
1194+ data.format = COMMON_CHAT_FORMAT_NEMOTRON_V2;
1195+
1196+ // Handle thinking tags appropriately based on inputs.enable_thinking
1197+ if (string_ends_with (data.prompt , " <think>\n " )) {
1198+ if (!inputs.enable_thinking ) {
1199+ data.prompt += " </think>" ;
1200+ } else {
1201+ data.thinking_forced_open = true ;
1202+ }
1203+ }
1204+
1205+ // When tools are present, build grammar for the <TOOLCALL> format, similar to CommandR, but without tool call ID
1206+ if (!inputs.tools .is_null () && inputs.tools .is_array () && !inputs.tools .empty ()) {
1207+ data.grammar_lazy = true ;
1208+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1209+ auto schemas = json::array ();
1210+ foreach_function (inputs.tools , [&](const json & tool) {
1211+ const auto & function = tool.at (" function" );
1212+ schemas.push_back ({
1213+ { " type" , " object" },
1214+ { " properties" ,
1215+ {
1216+ { " name" ,
1217+ {
1218+ { " type" , " string" },
1219+ { " const" , function.at (" name" ) },
1220+ } },
1221+ { " arguments" , function.at (" parameters" ) },
1222+ } },
1223+ { " required" , json::array ({ " name" , " arguments" }) },
1224+ });
1225+ });
1226+ auto schema = json{
1227+ { " type" , " array" },
1228+ { " items" , schemas.size () == 1 ? schemas[0 ] : json{ { " anyOf" , schemas } } },
1229+ { " minItems" , 1 },
1230+ };
1231+ if (!inputs.parallel_tool_calls ) {
1232+ schema[" maxItems" ] = 1 ;
1233+ }
1234+ builder.add_rule (" root" ,
1235+ std::string (data.thinking_forced_open ? " ( \" </think>\" space )? " : " " ) +
1236+ " \" <TOOLCALL>\" " + builder.add_schema (" tool_calls" , schema) +
1237+ " \" </TOOLCALL>\" " );
1238+ });
1239+ data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL,
1240+ // If thinking_forced_open, then we capture the </think> tag in the grammar,
1241+ // (important for required tool choice) and in the trigger's first capture (decides what is sent to the grammar)
1242+ std::string (data.thinking_forced_open ?
1243+ " [\\ s\\ S]*?(</think>\\ s*)" :
1244+ " (?:<think>[\\ s\\ S]*?</think>\\ s*)?" ) +
1245+ " (<TOOLCALL>)[\\ s\\ S]*" });
1246+ }
1247+ return data;
1248+ }
11871249static void common_chat_parse_llama_3_1 (common_chat_msg_parser & builder, bool with_builtin_tools = false ) {
11881250 if (!builder.syntax ().parse_tool_calls ) {
11891251 builder.add_content (builder.consume_rest ());
@@ -1830,7 +1892,7 @@ static common_chat_params common_chat_params_init_hermes_2_pro(const common_chat
18301892 // If thinking_forced_open, then we capture the </think> tag in the grammar,
18311893 // (important for required tool choice) and in the trigger's first capture (decides what is sent to the grammar)
18321894 std::string (data.thinking_forced_open ? " [\\ s\\ S]*?(</think>\\ s*)" : " (?:<think>[\\ s\\ S]*?</think>\\ s*)?" ) + (
1833- " ( \\ s*"
1895+ " \\ s*( "
18341896 " (?:<tool_call>"
18351897 " |<function"
18361898 " |(?:```(?:json|xml)?\n\\ s*)?(?:<function_call>|<tools>|<xml><json>|<response>)?"
@@ -2060,6 +2122,33 @@ static void common_chat_parse_granite(common_chat_msg_parser & builder) {
20602122 }
20612123}
20622124
2125+ static void common_chat_parse_nemotron_v2 (common_chat_msg_parser & builder) {
2126+ // Parse thinking tags
2127+ builder.try_parse_reasoning (" <think>" , " </think>" );
2128+ if (!builder.syntax ().parse_tool_calls ) {
2129+ builder.add_content (builder.consume_rest ());
2130+ return ;
2131+ }
2132+
2133+ // Look for tool calls
2134+ static const common_regex tool_call_regex (regex_escape (" <TOOLCALL>" ));
2135+ if (auto res = builder.try_find_regex (tool_call_regex)) {
2136+ builder.move_to (res->groups [0 ].end );
2137+
2138+ // Expect JSON array of tool calls
2139+ auto tool_calls_data = builder.consume_json ();
2140+ if (tool_calls_data.json .is_array ()) {
2141+ if (!builder.try_consume_literal (" </TOOLCALL>" )) {
2142+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2143+ }
2144+ builder.add_tool_calls (tool_calls_data.json );
2145+ } else {
2146+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2147+ }
2148+ }
2149+ builder.add_content (builder.consume_rest ());
2150+ }
2151+
20632152static void common_chat_parse_seed_oss (common_chat_msg_parser & builder) {
20642153 // Parse thinking tags first - this handles the main reasoning content
20652154 builder.try_parse_reasoning (" <seed:think>" , " </seed:think>" );
@@ -2293,6 +2382,11 @@ static common_chat_params common_chat_templates_apply_jinja(
22932382 return common_chat_params_init_seed_oss (tmpl, params, inputs);
22942383 }
22952384
2385+ // Nemotron v2
2386+ if (src.find (" <SPECIAL_10>" ) != std::string::npos) {
2387+ return common_chat_params_init_nemotron_v2 (tmpl, params);
2388+ }
2389+
22962390 // Use generic handler when mixing tools + JSON schema.
22972391 // TODO: support that mix in handlers below.
22982392 if ((params.tools .is_array () && params.json_schema .is_object ())) {
@@ -2454,6 +2548,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
24542548 case COMMON_CHAT_FORMAT_SEED_OSS:
24552549 common_chat_parse_seed_oss (builder);
24562550 break ;
2551+ case COMMON_CHAT_FORMAT_NEMOTRON_V2:
2552+ common_chat_parse_nemotron_v2 (builder);
2553+ break ;
24572554 default :
24582555 throw std::runtime_error (std::string (" Unsupported format: " ) + common_chat_format_name (builder.syntax ().format ));
24592556 }
0 commit comments