char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
acecd5603
char/aliceai-t5-moe
1#include "chat-auto-parser-helpers.h" 2#include "chat-auto-parser.h" 3#include "chat-peg-parser.h" 4#include "chat.h" 5#include "common.h" 6#include "json-schema-to-grammar.h" 7#include "log.h" 8#include "parsers/parsers.h" 9#include "peg-parser.h" 10 11#include <stdexcept> 12#include <string> 13 14using json = common_json ; 15 16namespace autoparser { 17 18parser_build_context::parser_build_context (common_chat_peg_builder & p ,const generation_params & inputs ) : 19p (p ), 20inputs (inputs ), 21reasoning_parser (p .eps ()) {} 22 23common_chat_params peg_generator::generate_parser (const common_chat_template & tmpl , 24const struct generation_params & inputs ) { 25// Run differential analysis to extract template structure 26struct autoparser autoparser ; 27autoparser .analyze_template (tmpl ); 28return generate_parser (tmpl ,inputs ,autoparser ); 29} 30 31common_chat_params peg_generator::generate_parser (const common_chat_template & tmpl , 32const struct generation_params & inputs , 33const autoparser & autoparser ) { 34// Create the result structure 35common_chat_params data ; 36data .prompt = common_chat_template_direct_apply (tmpl ,inputs ); 37data .generation_prompt = common_chat_template_generation_prompt (tmpl ,inputs ); 38data .format = COMMON_CHAT_FORMAT_PEG_NATIVE ; 39data .preserved_tokens = autoparser .preserved_tokens ; 40data .additional_stops .insert (data .additional_stops .end (), 41autoparser .additional_stops .begin (),autoparser .additional_stops .end ()); 42 43 std::string parser_generation_prompt = data .generation_prompt ; 44 45if (inputs .continue_final_message != COMMON_CHAT_CONTINUATION_NONE && !inputs .continue_msg .empty ()) { 46// Build up generation prompt manually 47const auto & msg = inputs .continue_msg ; 48 49if (!autoparser .reasoning .start .empty ()) { 50data .generation_prompt = data .generation_prompt .substr (0 ,data .generation_prompt .find (autoparser .reasoning .start )); 51data .generation_prompt += autoparser .reasoning .start + msg .reasoning_content ; 52if (inputs .continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT ) { 53data .generation_prompt += autoparser .reasoning .end ; 54 } 55 } 56 57if (inputs .continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT ) { 58data .generation_prompt += msg .render_content (); 59 } 60 61data .prompt += data .generation_prompt ; 62 } 63 64auto parser = autoparser .build_parser (inputs ,parser_generation_prompt ); 65data .parser = parser .save (); 66 67// Build grammar if tools are present 68bool has_tools = 69autoparser .tools .format .mode != tool_format::NONE && inputs .tools .is_array ()&& !inputs .tools .empty (); 70 std::string trigger_marker = !autoparser .tools .format .section_start .empty () ?autoparser .tools .format .section_start : 71autoparser .tools .format .per_call_start ; 72 73bool has_response_format = !inputs .json_schema .empty ()&& inputs .json_schema .is_object (); 74bool include_grammar = has_response_format || (has_tools && 75 ((inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO && !trigger_marker .empty ())|| 76inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED )); 77 78if (include_grammar ) { 79data .grammar_lazy = !has_response_format && inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO ; 80data .grammar = build_grammar ([& ](const common_grammar_builder & builder ) { 81parser .build_grammar (builder ,data .grammar_lazy ); 82 }); 83 84// Set grammar triggers based on tool section markers (fall back to per-call markers) 85if (data .grammar_lazy ) { 86data .grammar_triggers = { 87 {COMMON_GRAMMAR_TRIGGER_TYPE_WORD ,trigger_marker } 88 }; 89if (autoparser .tools .format .openai_wrapper_trigger ) { 90// model emits the OpenAI function wrapper, trigger on it 91data .grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD ,"{\"type\": \"function\"," }); 92 } 93 } 94 } 95 96return data ; 97} 98 99common_peg_arena autoparser::build_parser (const generation_params & inputs ,const std::string & generation_prompt )const { 100if (!analysis_complete ) { 101throw std::invalid_argument ("Cannot call build_parser on autoparser without performing analysis first, call analyze_template(...)" ); 102 } 103return build_chat_peg_parser ([& ](common_chat_peg_builder & p ) { 104parser_build_context ctx (p ,inputs ); 105bool extract_reasoning = inputs .reasoning_format != COMMON_REASONING_FORMAT_NONE ; 106 107ctx .extracting_reasoning = extract_reasoning && reasoning .mode != reasoning_mode::NONE ; 108ctx .content = & content ; 109ctx .reasoning = & reasoning ; 110 111// Build reasoning parser 112ctx .reasoning_parser = reasoning .build_parser (ctx ); 113 114auto parser = p .eps (); 115 116bool has_tools = inputs .tools .is_array ()&& !inputs .tools .empty (); 117bool has_response_format = inputs .json_schema .is_object ()&& !inputs .json_schema .empty (); 118bool pure_content = reasoning .mode == reasoning_mode::NONE ; 119 120if (has_response_format ) { 121auto response_format = p .rule ("response-format" ,p .content (p .schema (p .json (),"response-format-schema" ,inputs .json_schema ))); 122parser = ctx .reasoning_parser + p .space ()+ p .choice ({ 123p .literal ("```json" )+ p .space ()+ response_format + p .space ()+ p .literal ("```" ), 124p .space ()+ response_format + p .space () 125 })+ p .end (); 126pure_content = false; 127 }else if (has_tools && inputs .tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE && jinja_caps .supports_tool_calls ) { 128parser = tools .build_parser (ctx ); 129pure_content = false; 130 }else { 131parser = content .build_parser (ctx ); 132 } 133const std::string reasoning_start = trim_whitespace (reasoning .start ); 134return pure_content ?p .prefix (generation_prompt ,reasoning_start )+ parser :p .prefix (generation_prompt ,reasoning_start ) <<parser ; 135 }); 136} 137 138common_peg_parser analyze_reasoning::build_parser (parser_build_context & ctx )const { 139auto & p = ctx .p ; 140 141if (!ctx .extracting_reasoning ) { 142return p .eps (); 143 } 144 145if (mode == reasoning_mode::TAG_BASED || mode == reasoning_mode::TOOLS_ONLY ) { 146if (!end .empty ()) { 147if (!start .empty ()) { 148// Standard tag-based: optional(<think>reasoning</think>) 149return p .optional (p .optspace (start )+ p .reasoning (p .until (trim_whitespace (end )))+ p .optspace (end )); 150 } 151// Delimiter-style (empty start) 152return p .optional (p .reasoning (p .until (trim_whitespace (end )))+ p .optspace (end )); 153 } 154 } 155 156return p .eps (); 157} 158 159common_peg_parser analyze_content::build_parser (parser_build_context & ctx )const { 160auto & p = ctx .p ; 161 162if (is_always_wrapped ()) { 163if (ctx .extracting_reasoning ) { 164return ctx .reasoning_parser + start + p .content (p .until (end ))+ end + p .end (); 165 } 166return p .content (p .until (start ))+ start + p .content (p .until (end ))+ end + p .end (); 167 } 168return ctx .reasoning_parser + p .content (p .rest ())+ p .end (); 169} 170 171common_peg_parser analyze_content::build_optional_wrapped (parser_build_context & ctx )const { 172auto & p = ctx .p ; 173 174if (is_always_wrapped ()) { 175return p .optional (start + p .content (p .until (end ))+ end ); 176 } 177return p .eps (); 178} 179 180common_peg_parser analyze_tools::build_parser (parser_build_context & ctx )const { 181switch (format .mode ) { 182case tool_format::JSON_NATIVE : 183return build_tool_parser_json_native (ctx ); 184case tool_format::TAG_WITH_JSON : 185return build_tool_parser_tag_json (ctx ); 186case tool_format::TAG_WITH_TAGGED : 187return build_tool_parser_tag_tagged (ctx ); 188default : 189LOG_ERR ("[ERROR] Template seems to support tool calls, but failed to determine tool format. Tool calling will not work properly. " 190"Check for a fixed template for your model in the models/templates directory of your llama.cpp installation or " 191"report an issue at https://github.com/ggml-org/llama.cpp/issues\n" ); 192return ctx .p .eps (); 193 } 194} 195 196common_peg_parser analyze_tools::build_tool_parser_json_native (parser_build_context & ctx )const { 197auto & p = ctx .p ; 198const auto & inputs = ctx .inputs ; 199 200// Build effective field names with dot notation if function_field is set 201 std::string name_field = format .name_field ; 202 std::string args_field = format .args_field ; 203 204if (!format .function_field .empty ()&& format .function_field != "function" && 205name_field .find ('.' )== std::string::npos ) { 206name_field = format .function_field + "." + name_field ; 207args_field = format .function_field + "." + args_field ; 208 } 209 210auto tools_parser = p .eps (); 211if (format .section_start .empty ()&& !format .per_call_start .empty ()) { 212auto single_tool_parser = p .standard_json_tools ( 213format .per_call_start ,format .per_call_end ,inputs .tools ,inputs .parallel_tool_calls , 214inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ,name_field ,args_field ,format .tools_array_wrapped , 215format .fun_name_is_key ,format .id_field ,format .gen_id_field ,format .parameter_order ,format .openai_wrapper_trigger ); 216tools_parser = p .trigger_rule ("tool-calls" ,p .one_or_more (single_tool_parser + p .space ())); 217 }else { 218tools_parser = p .standard_json_tools ( 219format .section_start ,format .section_end ,inputs .tools ,inputs .parallel_tool_calls , 220inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ,name_field ,args_field ,format .tools_array_wrapped , 221format .fun_name_is_key ,format .id_field ,format .gen_id_field ,format .parameter_order ,format .openai_wrapper_trigger ); 222 } 223 224// Handle content wrappers if present 225if (ctx .content && ctx .content -> is_always_wrapped ()) { 226auto wrapped_content = ctx .content -> build_optional_wrapped (ctx ); 227return ctx .reasoning_parser + wrapped_content + tools_parser + p .end (); 228 } 229 230 std::string tool_start = "{" ; 231if (!format .section_start .empty ()) { 232tool_start = format .section_start ; 233 }else if (!format .per_call_start .empty ()) { 234tool_start = format .per_call_start ; 235 } 236 237return ctx .reasoning_parser + p .optional (p .content (p .until (tool_start )))+ tools_parser + p .end (); 238} 239 240common_peg_parser analyze_tools::build_func_parser (common_chat_peg_builder & p ,const std::string & name , 241const common_peg_parser & call_id_section ,bool have_call_id , 242const common_peg_parser & args , 243 std::optional < common_peg_parser > atomic_peek )const { 244auto open = p .tool_open (function .name_prefix + p .tool_name (p .literal (name ))+ function .name_suffix ); 245bool matched_atomic = false; 246common_peg_parser func_parser = p .eps (); 247 248if (!function .args_separator .empty ()) { 249open = open + p .space ()+ p .literal (function .args_separator ); 250 } 251 252if (!function .name_suffix .empty ()) { 253func_parser = open + call_id_section + p .space ()+ args ; 254matched_atomic = true; 255 }else if (have_call_id ) { 256func_parser = p .atomic (open + call_id_section )+ p .space ()+ args ; 257matched_atomic = true; 258 }else if (atomic_peek .has_value ()) { 259func_parser = p .atomic (open + call_id_section + p .space ()+ * atomic_peek )+ args ; 260matched_atomic = true; 261 }else { 262func_parser = open + call_id_section + p .space ()+ args ; 263 } 264 265if (!function .close .empty ()) { 266func_parser = func_parser + p .space ()+ p .tool_close (p .literal (function .close )); 267 }else if (!format .per_call_end .empty ()) { 268// When there's no func_close but there is a per_call_end marker, use peek() to ensure 269// we only emit tool_close when we can actually see the closing marker. This prevents 270// premature closing during partial parsing when we've seen e.g. "</" which could be 271// either "</tool_call>" (end) or "<arg_key>" prefix that failed to match. 272// Laguna (v4): the model may emit whitespace between the last </arg_value> and 273// </tool_call> even though the template renders them tight. Tolerate optional 274// leading space in the close lookahead so the tool call still closes. 275auto close_peek = arguments .tolerate_intertag_whitespace 276 ?p .peek (p .space ()+ p .literal (format .per_call_end )) 277 :p .peek (p .literal (format .per_call_end )); 278func_parser = func_parser + p .tool_close (close_peek ); 279 }else { 280func_parser = func_parser + p .tool_close (p .space ());// force this to process tool closing callbacks in mapper 281 } 282if (!matched_atomic ) { 283func_parser = p .atomic (func_parser ); 284 } 285return func_parser ; 286} 287 288common_peg_parser analyze_tools::build_tool_parser_tag_json (parser_build_context & ctx )const { 289auto & p = ctx .p ; 290const auto & inputs = ctx .inputs ; 291 292common_peg_parser tool_choice = p .choice (); 293 294foreach_function (inputs .tools , [& ](const json & tool ) { 295const auto & func = tool .at ("function" ); 296 std::string name = func .at ("name" ); 297const auto schema = common_chat_tool_parameters (func ); 298 299// Build call_id parser based on position (if supported) 300bool have_call_id = false; 301common_peg_parser call_id_section = p .eps (); 302if (call_id .pos == call_id_position::BETWEEN_FUNC_AND_ARGS && !call_id .prefix .empty ()&& 303 (!call_id .suffix .empty ()|| !arguments .start .empty ())) { 304if (!call_id .suffix .empty ()) { 305call_id_section = p .optional (call_id .prefix + p .tool_id (p .until (call_id .suffix )))+ call_id .suffix ; 306 }else { 307call_id_section = p .optional (call_id .prefix + p .tool_id (p .until (arguments .start ))); 308 } 309have_call_id = true; 310 } 311auto args_parser = p .tool_args (p .schema (p .json (),"tool-" + name + "-schema" ,schema )); 312if (!arguments .start .empty ()) { 313args_parser = p .literal (arguments .start )+ args_parser ; 314 } 315if (!arguments .end .empty ()) { 316args_parser = args_parser + p .literal (arguments .end ); 317 } 318 319auto atomic_peek = !arguments .start .empty () ? std::optional (p .peek (p .literal (arguments .start ))) : std::nullopt ; 320auto func_parser = build_func_parser (p ,name ,call_id_section ,have_call_id ,args_parser ,atomic_peek ); 321tool_choice |=p .rule ("tool-" + name ,func_parser ); 322 }); 323 324auto require_calls = inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ; 325 326common_peg_parser tool_calls = p .eps (); 327 328if (!format .per_call_start .empty ()) { 329auto wrapped_call = format .per_call_start + tool_choice + format .per_call_end ; 330if (inputs .parallel_tool_calls ) { 331tool_calls = p .trigger_rule ("tool-call" ,wrapped_call + p .zero_or_more (p .space ()+ wrapped_call )); 332 }else { 333tool_calls = p .trigger_rule ("tool-call" ,wrapped_call ); 334 } 335if (!format .section_start .empty ()) { 336tool_calls = p .trigger_rule ("tool-calls" , 337p .literal (format .section_start )+ p .space ()+ tool_calls + p .space ()+ 338 (format .section_end .empty () ?p .end () :p .literal (format .section_end ))); 339 } 340 }else { 341 std::string separator = ", " ;// Default 342if (inputs .parallel_tool_calls ) { 343tool_calls = p .trigger_rule ("tool-call" ,format .section_start + tool_choice + 344p .zero_or_more (separator + tool_choice )+ format .section_end ); 345 }else { 346tool_calls = p .trigger_rule ("tool-call" ,format .section_start + tool_choice + format .section_end ); 347 } 348 } 349 350if (!require_calls ) { 351tool_calls = p .optional (tool_calls ); 352 } 353 354 std::string trigger_marker = !format .section_start .empty () ?format .section_start :format .per_call_start ; 355auto content_before_tools = trigger_marker .empty () ?p .eps () :p .until (trigger_marker ); 356return ctx .reasoning_parser + p .optional (p .content (content_before_tools ))+ tool_calls + p .end (); 357} 358 359common_peg_parser analyze_tools::build_tool_parser_tag_tagged (parser_build_context & ctx )const { 360auto & p = ctx .p ; 361const auto & inputs = ctx .inputs ; 362 363auto until_suffix = p .rule ("until-suffix" ,p .until (arguments .value_suffix )); 364 365common_peg_parser tool_choice = p .choice (); 366 367foreach_function (inputs .tools , [& ](const json & tool ) { 368const auto & func = tool .at ("function" ); 369 std::string name = func .at ("name" ); 370 371// Build parser for each argument, separating required and optional 372 std::vector < common_peg_parser > required_parsers ; 373 std::vector < common_peg_parser > optional_parsers ; 374foreach_parameter (func , [& ](const common_chat_schema_property & param ,const common_chat_schema_document_ptr & doc ) { 375auto arg = 376p .tool_arg (p .tool_arg_open (arguments .name_prefix + p .tool_arg_name (p .literal (param .name ))+ 377arguments .name_suffix )+ 378arguments .value_prefix + 379 (param .schema -> may_be_string () ? 380p .ac (p .tool_arg_string_value (until_suffix )+ 381p .tool_arg_close (p .literal (arguments .value_suffix )),arguments .value_suffix ) : 382 (p .tool_arg_json_value (p .schema ( 383p .json (),"tool-" + name + "-arg-" + param .name + "-schema" ,doc ,* param .schema ))+ 384p .tool_arg_close (p .literal (arguments .value_suffix ))))); 385 386auto named_arg = p .rule ("tool-" + name + "-arg-" + param .name ,arg ); 387if (param .required ) { 388required_parsers .push_back (named_arg ); 389 }else { 390optional_parsers .push_back (named_arg ); 391 } 392 }); 393 394// Build required arg sequence in definition order 395common_peg_parser args_seq = p .eps (); 396for (size_t i = 0 ;i < required_parsers .size ();i ++ ) { 397if (i > 0 ) { 398args_seq = args_seq + p .space (); 399 } 400args_seq = args_seq + required_parsers [i ]; 401 } 402 403// Build optional args with flexible ordering 404if (!optional_parsers .empty ()) { 405common_peg_parser any_opt = p .choice (); 406for (const auto & opt :optional_parsers ) { 407any_opt |=opt ; 408 } 409args_seq = args_seq + p .repeat (p .space ()+ any_opt ,0 ,-1 ); 410 } 411 412if (!arguments .start .empty ()) { 413args_seq = p .literal (arguments .start )+ args_seq ; 414 } 415if (!arguments .end .empty ()) { 416args_seq = args_seq + p .literal (arguments .end ); 417 } 418 419// Build call_id parser based on position (if supported) 420common_peg_parser call_id_section = p .eps (); 421bool have_call_id = false; 422if (call_id .pos == call_id_position::BETWEEN_FUNC_AND_ARGS && !call_id .prefix .empty ()&& 423 (!call_id .suffix .empty ()|| !arguments .start .empty ())) { 424have_call_id = true; 425if (!call_id .suffix .empty ()) { 426call_id_section = p .optional (call_id .prefix + p .tool_id (p .until (call_id .suffix ))+ call_id .suffix ); 427 }else { 428call_id_section = p .optional (call_id .prefix + p .tool_id (p .until (arguments .start ))); 429 } 430 } 431 432// Only peek for an arg tag when there are required args that must follow. 433// When all args are optional, the model may emit no arg tags at all (#20650). 434auto atomic_peek = (!arguments .name_prefix .empty ()&& !required_parsers .empty ()) ? 435 std::optional (p .peek (p .literal (arguments .name_prefix ))) : std::nullopt ; 436auto func_parser = build_func_parser (p ,name ,call_id_section ,have_call_id ,args_seq ,atomic_peek ); 437tool_choice |=p .rule ("tool-" + name ,func_parser ); 438 }); 439 440auto require_tools = inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ; 441 442common_peg_parser tool_calls = p .eps (); 443 444if (!format .per_call_start .empty ()) { 445auto wrapped_call = format .per_call_start + p .space ()+ tool_choice + p .space ()+ format .per_call_end ; 446if (inputs .parallel_tool_calls ) { 447tool_calls = p .trigger_rule ("tool-call" ,wrapped_call + p .zero_or_more (p .space ()+ wrapped_call )+ p .space ()); 448 }else { 449tool_calls = p .trigger_rule ("tool-call" ,wrapped_call + p .space ()); 450 } 451if (!format .section_start .empty ()) { 452tool_calls = p .trigger_rule ("tool-calls" , 453p .literal (format .section_start )+ p .space ()+ tool_calls + p .space ()+ 454 (format .section_end .empty () ?p .end () :p .literal (format .section_end )+ p .space ())); 455 } 456 }else { 457 std::string separator = ", " ;// Default 458 459if (inputs .parallel_tool_calls ) { 460tool_calls = p .trigger_rule ("tool-call" ,format .section_start + p .space ()+ tool_choice + 461p .zero_or_more (separator + tool_choice )+ p .space ()+ 462format .section_end ); 463 }else { 464tool_calls = p .trigger_rule ( 465"tool-call" ,format .section_start + p .space ()+ tool_choice + p .space ()+ format .section_end ); 466 } 467 } 468 469if (!require_tools ) { 470tool_calls = p .optional (tool_calls ); 471 } 472 473 std::string trigger_marker = !format .section_start .empty () ?format .section_start :format .per_call_start ; 474auto content_before_tools = trigger_marker .empty () ?p .eps () :p .until (trigger_marker ); 475return ctx .reasoning_parser + p .optional (p .content (content_before_tools ))+ tool_calls + p .end (); 476} 477 478}// namespace autoparser