@@ -625,6 +625,7 @@ const char * common_chat_format_name(common_chat_format format) {
625
625
case COMMON_CHAT_FORMAT_CONTENT_ONLY: return " Content-only" ;
626
626
case COMMON_CHAT_FORMAT_GENERIC: return " Generic" ;
627
627
case COMMON_CHAT_FORMAT_MISTRAL_NEMO: return " Mistral Nemo" ;
628
+ case COMMON_CHAT_FORMAT_MAGISTRAL: return " Magistral" ;
628
629
case COMMON_CHAT_FORMAT_LLAMA_3_X: return " Llama 3.x" ;
629
630
case COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS: return " Llama 3.x with builtin tools" ;
630
631
case COMMON_CHAT_FORMAT_DEEPSEEK_R1: return " DeepSeek R1" ;
@@ -984,6 +985,65 @@ static common_chat_params common_chat_params_init_mistral_nemo(const common_chat
984
985
data.format = COMMON_CHAT_FORMAT_MISTRAL_NEMO;
985
986
return data;
986
987
}
988
+
989
+ static common_chat_params common_chat_params_init_magistral (const common_chat_template & tmpl, const struct templates_params & inputs) {
990
+ common_chat_params data;
991
+ data.prompt = apply (tmpl, inputs);
992
+ data.format = COMMON_CHAT_FORMAT_MAGISTRAL;
993
+ data.preserved_tokens = {
994
+ " [THINK]" ,
995
+ " [/THINK]" ,
996
+ };
997
+
998
+ if (inputs.tools .is_array () && !inputs.tools .empty ()) {
999
+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
1000
+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1001
+ auto schemas = json::array ();
1002
+ foreach_function (inputs.tools , [&](const json & tool) {
1003
+ const auto & function = tool.at (" function" );
1004
+ schemas.push_back ({
1005
+ {" type" , " object" },
1006
+ {" properties" , {
1007
+ {" name" , {
1008
+ {" type" , " string" },
1009
+ {" const" , function.at (" name" )},
1010
+ }},
1011
+ {" arguments" , function.at (" parameters" )},
1012
+ {" id" , {
1013
+ {" type" , " string" },
1014
+ {" pattern" , " ^[a-zA-Z0-9]{9}$" },
1015
+ }},
1016
+ }},
1017
+ {" required" , json::array ({" name" , " arguments" , " id" })},
1018
+ });
1019
+ });
1020
+ auto schema = json {
1021
+ {" type" , " array" },
1022
+ {" items" , schemas.size () == 1 ? schemas[0 ] : json {{" anyOf" , schemas}}},
1023
+ {" minItems" , 1 },
1024
+ };
1025
+ if (!inputs.parallel_tool_calls ) {
1026
+ schema[" maxItems" ] = 1 ;
1027
+ }
1028
+ builder.add_rule (" root" , " \" [TOOL_CALLS]\" " + builder.add_schema (" tool_calls" , schema));
1029
+ });
1030
+ data.grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " [TOOL_CALLS]" });
1031
+ data.preserved_tokens .push_back (" [TOOL_CALLS]" );
1032
+ } else {
1033
+ data.grammar_lazy = false ;
1034
+ if (!inputs.json_schema .is_null ()) {
1035
+ if (!inputs.grammar .empty ()) {
1036
+ throw std::runtime_error (" Either \" json_schema\" or \" grammar\" can be specified, but not both" );
1037
+ }
1038
+ data.grammar = json_schema_to_grammar (inputs.json_schema );
1039
+ } else {
1040
+ data.grammar = inputs.grammar ;
1041
+ }
1042
+ }
1043
+
1044
+ return data;
1045
+ }
1046
+
987
1047
static void common_chat_parse_mistral_nemo (common_chat_msg_parser & builder) {
988
1048
if (!builder.syntax ().parse_tool_calls ) {
989
1049
builder.add_content (builder.consume_rest ());
@@ -994,6 +1054,18 @@ static void common_chat_parse_mistral_nemo(common_chat_msg_parser & builder) {
994
1054
parse_prefixed_json_tool_call_array (builder, prefix);
995
1055
}
996
1056
1057
+ static void common_chat_parse_magistral (common_chat_msg_parser & builder) {
1058
+ builder.try_parse_reasoning (" [THINK]" , " [/THINK]" );
1059
+
1060
+ if (!builder.syntax ().parse_tool_calls ) {
1061
+ builder.add_content (builder.consume_rest ());
1062
+ return ;
1063
+ }
1064
+
1065
+ static const common_regex prefix (regex_escape (" [TOOL_CALLS]" ));
1066
+ parse_prefixed_json_tool_call_array (builder, prefix);
1067
+ }
1068
+
997
1069
static common_chat_params common_chat_params_init_command_r7b (const common_chat_template & tmpl, const struct templates_params & inputs) {
998
1070
common_chat_params data;
999
1071
@@ -2702,6 +2774,10 @@ static common_chat_params common_chat_templates_apply_jinja(
2702
2774
return common_chat_params_init_llama_3_x (tmpl, params, allow_python_tag_builtin_tools);
2703
2775
}
2704
2776
2777
+ if (src.find (" [THINK]" ) != std::string::npos && src.find (" [/THINK]" ) != std::string::npos) {
2778
+ return common_chat_params_init_magistral (tmpl, params);
2779
+ }
2780
+
2705
2781
// Plain handler (no tools)
2706
2782
if (params.tools .is_null () || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
2707
2783
return common_chat_params_init_without_tools (tmpl, params);
@@ -2802,6 +2878,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
2802
2878
case COMMON_CHAT_FORMAT_MISTRAL_NEMO:
2803
2879
common_chat_parse_mistral_nemo (builder);
2804
2880
break ;
2881
+ case COMMON_CHAT_FORMAT_MAGISTRAL:
2882
+ common_chat_parse_magistral (builder);
2883
+ break ;
2805
2884
case COMMON_CHAT_FORMAT_LLAMA_3_X:
2806
2885
common_chat_parse_llama_3_1 (builder);
2807
2886
break ;
0 commit comments