char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
acecd5603
char/aliceai-t5-moe
1#include "parsers.h" 2 3common_chat_params common_chat_params_init_minimax_m3 (const common_chat_template & tmpl , 4const autoparser::generation_params & inputs ) { 5common_chat_params data ; 6 7data .prompt = common_chat_template_direct_apply_impl (tmpl ,inputs ); 8data .generation_prompt = common_chat_template_generation_prompt_impl (tmpl ,inputs ); 9data .format = COMMON_CHAT_FORMAT_PEG_MINIMAX_M3 ; 10data .supports_thinking = true; 11data .thinking_start_tag = "<mm:think>" ; 12data .thinking_end_tags = {"</mm:think>" }; 13 14// M3 prefixes every tool tag with the namespace token "]<]minimax[>["; 15// params use the parameter name as the tag (<file_path>...</file_path>). 16const std::string NS = "]<]minimax[>[" ; 17const std::string THINK_START = "<mm:think>" ; 18const std::string THINK_END = "</mm:think>" ; 19const std::string FC_START = NS + "<tool_call>" ; 20const std::string FC_END = NS + "</tool_call>" ; 21const std::string INVOKE_END = NS + "</invoke>" ; 22 23data .preserved_tokens = { 24NS , 25"<tool_call>" , 26"</tool_call>" , 27THINK_START , 28THINK_END , 29 }; 30 31data .message_delimiters = { 32 {COMMON_CHAT_ROLE_ASSISTANT ,"]~b]ai" }, 33 {COMMON_CHAT_ROLE_USER ,"]~b]user" }, 34 {COMMON_CHAT_ROLE_TOOL ,"]~b]tool" }, 35 {COMMON_CHAT_ROLE_SYSTEM ,"]~b]developer" }, 36 {COMMON_CHAT_ROLE_SYSTEM ,"]~b]system" }, 37 }; 38 39auto has_tools = inputs .tools .is_array ()&& !inputs .tools .empty (); 40auto has_response_format = !inputs .json_schema .is_null ()&& inputs .json_schema .is_object (); 41auto extract_reasoning = inputs .reasoning_format != COMMON_REASONING_FORMAT_NONE ; 42auto include_grammar = has_response_format || (has_tools && inputs .tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ); 43 44const std::string GEN_PROMPT = data .generation_prompt ; 45 46using mm3 = common_chat_peg_minimax_m3_mapper ; 47 48if (inputs .has_continuation ()) { 49const auto & msg = inputs .continue_msg ; 50 51data .generation_prompt = GEN_PROMPT + THINK_START + msg .reasoning_content ; 52if (inputs .continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT ) { 53data .generation_prompt += THINK_END + msg .render_content (); 54 } 55 56data .prompt += data .generation_prompt ; 57 } 58 59auto parser = build_chat_peg_parser ([& ](common_chat_peg_builder & p ) { 60auto generation_prompt = p .prefix (GEN_PROMPT ,THINK_START ); 61auto end = p .end (); 62 63auto reasoning = p .eps (); 64if (extract_reasoning ) { 65auto block = inputs .enable_thinking 66 ?p .literal (THINK_START )+ p .space ()+ 67p .ac (p .reasoning (p .until (THINK_END ))+ p .literal (THINK_END ),THINK_END ) 68 :p .literal (THINK_START )+ p .ac (p .until (THINK_END )+ p .literal (THINK_END ),THINK_END ); 69 70// A turn without reasoning is prefixed with a bare </mm:think>, written either by the 71// generation prompt (thinking_mode = "disabled") or by the model itself. 72reasoning = p .optional (p .choice ({block ,p .literal (THINK_END ) })); 73 } 74 75if (has_response_format ) { 76auto response_format = p .rule ("response-format" , 77p .literal ("```json" )+ p .space ()+ 78p .content (p .schema (p .json (),"response-format-schema" ,inputs .json_schema ))+ 79p .space ()+ p .literal ("```" )); 80return generation_prompt + reasoning + response_format + end ; 81 } 82 83if (!has_tools || inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE ) { 84return generation_prompt + reasoning + p .content (p .rest ())+ end ; 85 } 86 87auto tool_choice = p .choice (); 88foreach_function (inputs .tools , [& ](const json & tool ) { 89const auto & function = tool .at ("function" ); 90 std::string name = function .at ("name" ); 91auto params = common_chat_tool_parameters (function ); 92auto doc = std::make_shared < const common_chat_schema_document > (common_chat_schema_from_json (params )); 93 94// The template expands argument values recursively in XML (see the to_xml() macro) 95 std::function < common_peg_parser (const common_chat_schema & ,const std::string & ,const std::string & )> value_of ; 96 std::function < common_peg_parser (const common_chat_schema_object & ,const std::string & )> members_of ; 97 98auto element_of = [& ](const std::string & tag ,const common_chat_schema & schema ,const std::string & rule_name ) { 99const std::string close = NS + "</" + tag + ">" ; 100return p .rule (rule_name , 101p .tool_arg ( 102p .tool_arg_open ( 103p .literal (NS + "<" )+ 104p .tool_arg_name (p .literal (tag ))+ 105p .literal (">" ))+ 106value_of (schema ,rule_name ,close ))); 107 }; 108 109value_of = [& ](const common_chat_schema & schema , 110const std::string & rule_name , 111const std::string & close )-> common_peg_parser { 112auto close_tag = p .tool_arg_close (p .literal (close )); 113 114// A string accepts anything, so a union with a string alternative is a string 115if (schema .may_be_string ()) { 116return p .ac (p .tool_arg_string_value (p .until (close ))+ close_tag ,close ); 117 } 118 119if (schema .kind ()== common_chat_schema::KIND_ANY_OF ) { 120 std::vector < common_peg_parser > choices ; 121 122size_t index = 0 ; 123for (const auto & alternative :static_cast < const common_chat_schema_any_of &> (schema ).children ) { 124const std::string alt_name = rule_name + "-" + std::to_string (index ++ ); 125 126// There is a risk that this breaks streaming deltas, but that's a risk we 127// assume to provide tool arg streaming. 128choices .push_back (value_of (* alternative ,alt_name ,close )); 129 } 130 131return p .choice (choices ); 132 } 133 134if (schema .kind ()== common_chat_schema::KIND_OBJECT ) { 135const auto & object = static_cast < const common_chat_schema_object &> (schema ); 136if (!object .properties .empty ()) { 137return p .tag (mm3::TOOL_ARG_OBJECT ,members_of (object ,rule_name ))+ p .space ()+ close_tag ; 138 } 139 } 140 141if (schema .kind ()== common_chat_schema::KIND_ARRAY ) { 142const std::string item_close = NS + "</item>" ; 143auto item = p .rule (rule_name + "-item" , 144p .tag (mm3::TOOL_ARG_ITEM , 145p .literal (NS + "<item>" )+ 146value_of (* static_cast < const common_chat_schema_array &> (schema ).items ,rule_name + "-item" ,item_close ))); 147return p .tag (mm3::TOOL_ARG_ARRAY ,p .repeat (p .space ()+ item ,0 ,-1 ))+ p .space ()+ close_tag ; 148 } 149 150return p .tool_arg_json_value (p .schema (p .json (),rule_name + "-schema" ,doc ,schema ))+ close_tag ; 151 }; 152 153// Required properties in schema order, then any number of optional ones in any order. 154members_of = [& ](const common_chat_schema_object & object ,const std::string & rule_prefix )-> common_peg_parser { 155 std::vector < common_peg_parser > required_elements ; 156 std::vector < common_peg_parser > optional_elements ; 157for (const auto & prop :object .properties ) { 158auto element = element_of (prop .name ,* prop .schema ,rule_prefix + "-" + prop .name ); 159 (prop .required ?required_elements :optional_elements ).push_back (element ); 160 } 161 162common_peg_parser members = p .eps (); 163for (size_t i = 0 ;i < required_elements .size ();i ++ ) { 164if (i > 0 ) { 165members = members + p .space (); 166 } 167members = members + required_elements [i ]; 168 } 169 170if (!optional_elements .empty ()) { 171common_peg_parser any_optional = p .choice (); 172for (const auto & element :optional_elements ) { 173any_optional |=element ; 174 } 175members = members + p .repeat (p .space ()+ any_optional ,0 ,-1 ); 176 } 177 178return members ; 179 }; 180 181common_peg_parser invoke_body = p .eps (); 182if (doc -> root -> kind ()== common_chat_schema::KIND_OBJECT ) { 183invoke_body = members_of (static_cast < const common_chat_schema_object &> (* doc -> root ),"tool-" + name + "-arg" ); 184 } 185 186auto func_parser = p .tool ( 187p .tool_open (p .literal (NS + "<invoke name=\"" )+ 188p .tool_name (p .literal (name ))+ p .literal ("\">" ))+ 189p .space ()+ invoke_body + p .space ()+ 190p .tool_close (p .literal (INVOKE_END ))); 191 192tool_choice |=p .rule ("tool-" + name ,func_parser ); 193 }); 194 195auto require_tools = inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ; 196 197common_peg_parser tool_calls = p .eps (); 198if (inputs .parallel_tool_calls ) { 199tool_calls = p .trigger_rule ("tool-call" , 200p .literal (FC_START )+ p .space ()+ tool_choice + 201p .zero_or_more (p .space ()+ tool_choice )+ p .space ()+ p .literal (FC_END )); 202 }else { 203tool_calls = p .trigger_rule ("tool-call" , 204p .literal (FC_START )+ p .space ()+ tool_choice + p .space ()+ p .literal (FC_END )); 205 } 206 207if (!require_tools ) { 208tool_calls = p .optional (tool_calls ); 209 } 210 211auto content_before_tools = p .content (p .until (FC_START )); 212return generation_prompt + reasoning + content_before_tools + tool_calls + end ; 213 }); 214 215data .parser = parser .save (); 216 217if (include_grammar ) { 218data .grammar_lazy = !(has_response_format || (has_tools && inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED )); 219data .grammar = build_grammar ([& ](const common_grammar_builder & builder ) { 220parser .build_grammar (builder ,data .grammar_lazy ); 221 }); 222 223data .grammar_triggers = { 224 {COMMON_GRAMMAR_TRIGGER_TYPE_WORD ,FC_START }, 225 }; 226 } 227 228return data ; 229}