char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
d9f918d2d
char/aliceai-t5-moe
1#pragma once 2 3#include "chat.h" 4#include "common.h" 5#include "jinja/caps.h" 6#include "peg-parser.h" 7#include "json.h" 8 9#include <chrono> 10#include <optional> 11#include <string> 12#include <utility> 13#include <vector> 14 15using json = common_json ; 16 17class common_chat_peg_builder ; 18 19// ============================================================================ 20// Parameters for template application (low-level, used by diff analysis) 21// ============================================================================ 22struct template_params { 23json messages ; 24json tools ; 25bool add_generation_prompt = false; 26bool enable_thinking = true; 27std ::optional < json > extra_context = std ::nullopt ; 28}; 29 30struct diff_split { 31std ::string prefix ; 32std ::string suffix ; 33std ::string left ; 34std ::string right ; 35 36bool operator == (struct diff_split & other )const { 37return prefix == other .prefix && suffix == other .suffix && left == other .left && right == other .right ; 38 } 39}; 40 41// Result of compare_variants containing diff and original outputs 42struct compare_variants_result { 43diff_split diff ; 44std ::string output_A ; 45std ::string output_B ; 46}; 47 48namespace autoparser { 49 50// ============================================================================ 51// High-level params for parser generation 52// ============================================================================ 53 54struct generation_params { 55json messages ; 56json tools ; 57common_chat_tool_choice tool_choice = COMMON_CHAT_TOOL_CHOICE_AUTO ; 58json json_schema ; 59bool parallel_tool_calls = true; 60common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_AUTO ; 61bool stream = true; 62std ::string grammar ; 63bool add_generation_prompt = false; 64common_chat_continuation continue_final_message = COMMON_CHAT_CONTINUATION_NONE ; 65common_chat_msg continue_msg ; 66bool enable_thinking = true; 67std ::chrono ::system_clock ::time_point now = std ::chrono ::system_clock ::now (); 68json extra_context ; 69bool add_bos = false; 70bool add_eos = false; 71bool is_inference = true; 72bool add_inference = false; 73bool mark_input = true;// whether to mark input strings in the jinja context 74 75bool has_continuation ()const { 76return continue_final_message != COMMON_CHAT_CONTINUATION_NONE && !continue_msg .empty (); 77 } 78}; 79 80// ============================================================================ 81// Analysis Result Enums 82// ============================================================================ 83 84// Reasoning handling mode (derived from R1-R3 comparisons) 85enum class reasoning_mode { 86NONE ,// No reasoning markers detected 87TAG_BASED ,// Tag-based: <think>...</think> (start can be empty for delimiter-style) 88TOOLS_ONLY // Only reason on tool calls, not on normal content 89}; 90 91inline std ::ostream & operator <<(std ::ostream & os ,const reasoning_mode & mode ) { 92switch (mode ) { 93case reasoning_mode ::NONE : 94return os <<"NONE" ; 95case reasoning_mode ::TAG_BASED : 96return os <<"TAG_BASED" ; 97case reasoning_mode ::TOOLS_ONLY : 98return os <<"TOOLS_ONLY" ; 99default : 100return os <<"UNKNOWN" ; 101 } 102} 103 104// Content wrapping mode (derived from C1 comparison) 105enum class content_mode { 106PLAIN ,// No content markers 107ALWAYS_WRAPPED ,// Content always wrapped with markers 108WRAPPED_WITH_REASONING ,// Content wrapped only when reasoning present 109}; 110 111inline std ::ostream & operator <<(std ::ostream & os ,const content_mode & mode ) { 112switch (mode ) { 113case content_mode ::PLAIN : 114return os <<"PLAIN" ; 115case content_mode ::ALWAYS_WRAPPED : 116return os <<"ALWAYS_WRAPPED" ; 117case content_mode ::WRAPPED_WITH_REASONING : 118return os <<"WRAPPED_WITH_REASONING" ; 119default : 120return os <<"UNKNOWN" ; 121 } 122} 123 124// Call ID position in tool calls (for non-JSON formats) 125enum class call_id_position { 126NONE ,// No call ID support detected 127PRE_FUNC_NAME ,// Call ID before function name: [CALL_ID]id[FUNC]name{args} 128BETWEEN_FUNC_AND_ARGS ,// Call ID between function and args: [FUNC]name[CALL_ID]id{args} 129POST_ARGS ,// Call ID after arguments: [FUNC]name{args}[CALL_ID]id 130}; 131 132inline std ::ostream & operator <<(std ::ostream & os ,const call_id_position & pos ) { 133switch (pos ) { 134case call_id_position ::NONE : 135return os <<"NONE" ; 136case call_id_position ::PRE_FUNC_NAME : 137return os <<"PRE_FUNC_NAME" ; 138case call_id_position ::BETWEEN_FUNC_AND_ARGS : 139return os <<"BETWEEN_FUNC_AND_ARGS" ; 140case call_id_position ::POST_ARGS : 141return os <<"POST_ARGS" ; 142default : 143return os <<"UNKNOWN" ; 144 } 145} 146 147// Tool call format classification (derived from T1-T5, A1-A3 comparisons) 148enum class tool_format { 149NONE ,// No tool support detected 150JSON_NATIVE ,// Pure JSON: {"name": "X", "arguments": {...}} 151TAG_WITH_JSON ,// Tag-based with JSON args: <function=X>{...}</function> 152TAG_WITH_TAGGED ,// Tag-based with tagged args: <param=key>value</param> 153}; 154 155inline std ::ostream & operator <<(std ::ostream & os ,const tool_format & format ) { 156switch (format ) { 157case tool_format ::NONE : 158return os <<"NONE" ; 159case tool_format ::JSON_NATIVE : 160return os <<"JSON_NATIVE" ; 161case tool_format ::TAG_WITH_JSON : 162return os <<"TAG_WITH_JSON" ; 163case tool_format ::TAG_WITH_TAGGED : 164return os <<"TAG_WITH_TAGGED" ; 165default : 166return os <<"UNKNOWN" ; 167 } 168} 169 170// ============================================================================ 171// Sub-structs for tool analysis 172// ============================================================================ 173 174struct tool_format_analysis { 175tool_format mode = tool_format ::NONE ; 176 177std ::string section_start ;// e.g., "<tool_call>", "[TOOL_CALLS]", "" 178std ::string section_end ;// e.g., "</tool_call>", "" 179std ::string per_call_start ;// e.g., "<|tool_call_begin|>", "" (for multi-call templates) 180std ::string per_call_end ;// e.g., "<|tool_call_end|>", "" 181 182bool fun_name_is_key = false;// In JSON format function name is JSON key, i.e. { "<funname>": { ... arguments ... } } 183bool tools_array_wrapped = false;// Tool calls wrapped in JSON array [...] 184bool openai_wrapper_trigger = false;// model emits the OpenAI function wrapper, trigger on it 185 186std ::string function_field = "function "; 187std ::string name_field = "name "; 188std ::string args_field = "arguments "; 189std ::string id_field ; 190std ::string gen_id_field ; 191std ::vector < std ::string > parameter_order ; 192}; 193 194struct tool_function_analysis { 195std ::string name_prefix ;// e.g., "<function=", "\"name\": \"", "functions." 196std ::string name_suffix ;// e.g., ">", "\"", ":0" 197std ::string args_separator ;// e.g., "<tool_sep>" (marker between function name and arguments) 198std ::string close ;// e.g., "</function>", "" (for tag-based) 199}; 200 201struct tool_arguments_analysis { 202std ::string start ;// e.g., "<|tool_call_argument_begin|>", "<args>" 203std ::string end ;// e.g., "<|tool_call_argument_end|>", "</args>" 204std ::string name_prefix ;// e.g., "<param=", "<arg_key>", "\"" 205std ::string name_suffix ;// e.g., ">", "</arg_key>", "\":" 206std ::string value_prefix ;// e.g., "", "<arg_value>", "" 207std ::string value_suffix ;// e.g., "</param>", "</arg_value>", "" 208std ::string separator ;// e.g., "", "\n", "," 209bool tolerate_intertag_whitespace = false;// Laguna: accept optional whitespace between arg tags 210}; 211 212struct tool_id_analysis { 213call_id_position pos = call_id_position ::NONE ; 214 215std ::string prefix ;// e.g., "[CALL_ID]" (marker before call ID value) 216std ::string suffix ;// e.g., "" (marker after call ID value, before next section) 217}; 218 219// ============================================================================ 220// Parser build context (shared interface for build_parser methods) 221// ============================================================================ 222 223struct analyze_content ; 224struct analyze_reasoning ; 225 226struct parser_build_context { 227common_chat_peg_builder & p ; 228const generation_params & inputs ; 229common_peg_parser reasoning_parser ; 230bool extracting_reasoning = false; 231const analyze_reasoning * reasoning = nullptr; 232const analyze_content * content = nullptr; 233 234parser_build_context (common_chat_peg_builder & p ,const generation_params & inputs ); 235}; 236 237// ============================================================================ 238// Base class for analyzers with parser building 239// ============================================================================ 240 241struct analyze_base { 242virtual ~analyze_base ()= default ; 243virtual common_peg_parser build_parser (parser_build_context & ctx )const = 0 ; 244 245protected : 246const common_chat_template * tmpl = nullptr; 247 248analyze_base ()= default ; 249explicit analyze_base (const common_chat_template & tmpl ) :tmpl (& tmpl ) {} 250}; 251 252// ============================================================================ 253// Reasoning analyzer 254// ============================================================================ 255 256struct analyze_reasoning :analyze_base { 257reasoning_mode mode = reasoning_mode ::NONE ; 258 259std ::string start ;// e.g., "<think>", "[THINK]", "<|START_THINKING|>", "" 260std ::string end ;// e.g., "</think>", "[BEGIN FINAL RESPONSE]", "<|END_THINKING|>" 261 262analyze_reasoning ()= default ; 263analyze_reasoning (const common_chat_template & tmpl ,bool supports_tools ); 264analyze_reasoning (std ::string start_ ,std ::string end_ ) :start (std ::move (start_ )),end (std ::move (end_ )) {} 265 266common_peg_parser build_parser (parser_build_context & ctx )const override ; 267 268private : 269// Look for reasoning markers in rendered content 270void compare_reasoning_presence (); 271 272// Compare generation prompt with enable_thinking=true vs false 273void compare_thinking_enabled (); 274 275// Check if reasoning is always possible or only in tool calls 276void compare_reasoning_scope (); 277}; 278 279// ============================================================================ 280// Content analyzer 281// ============================================================================ 282 283struct analyze_content :analyze_base { 284content_mode mode = content_mode ::PLAIN ; 285 286std ::string start ;// e.g., "<response>", ">>>all\n", "" 287std ::string end ;// e.g., "</response>", "" 288 289bool requires_nonnull_content = false; 290 291analyze_content ()= default ; 292analyze_content (const common_chat_template & tmpl ,const analyze_reasoning & reasoning ); 293 294common_peg_parser build_parser (parser_build_context & ctx )const override ; 295 296bool is_always_wrapped ()const ; 297common_peg_parser build_optional_wrapped (parser_build_context & ctx )const ; 298}; 299 300// ============================================================================ 301// Tool analyzer 302// ============================================================================ 303 304struct analyze_tools :analyze_base { 305tool_format_analysis format ; 306tool_function_analysis function ; 307tool_arguments_analysis arguments ; 308tool_id_analysis call_id ; 309 310analyze_tools ()= default ; 311analyze_tools (const common_chat_template & tmpl , 312const jinja ::caps & caps , 313const analyze_reasoning & reasoning ); 314 315common_peg_parser build_parser (parser_build_context & ctx )const override ; 316 317private : 318// Extract tool calling 'haystack' for further analysis and delegate further analysis based on format 319void analyze_tool_calls (const analyze_reasoning & reasoning ,bool supports_parallel_tool_calls ); 320 321// Analyze format based on position of function and argument name in needle 322void analyze_tool_call_format (const std ::string & haystack , 323const std ::string & fun_name_needle , 324const std ::string & arg_name_needle , 325const analyze_reasoning & reasoning , 326bool supports_parallel_tool_calls ); 327 328// Analyze specifics of JSON native format (entire tool call is a JSON object) 329void analyze_tool_call_format_json_native (const std ::string & clean_haystack , 330const std ::string & fun_name_needle , 331const std ::string & arg_name_needle ); 332 333// Check if parallel calls in JSON native format array wrapped or tag wrapped 334void analyze_json_native_parallel_calls (); 335 336// Analyze specifics of non-JSON native format (tags for function name or for function name and arguments) 337void analyze_tool_call_format_non_json (const std ::string & clean_haystack , 338const std ::string & fun_name_needle ); 339 340// Check for and extract specific per-call markers for non-native-JSON templates with parallel call support 341void check_per_call_markers (); 342 343// Extract function name markers 344void extract_function_markers (); 345 346// Delegates to separate functions for: separator analysis, argument name analysis, argument value analysis 347void analyze_arguments (); 348 349// Extract argument name markers 350void extract_argument_name_markers (); 351 352// Extract argument value markers 353void extract_argument_value_markers (); 354 355// Extract argument separator, if specified (eg. <arg=foo>...</arg><sep><arg=bar>...</arg>) 356void extract_argument_separator (); 357 358// Extract argument wrapper markers, if present (eg. '<args><arg=foo>...</arg><arg=bar>...</arg></args>') 359void extract_args_markers (); 360 361// Extract call ID markers, if present 362void extract_call_id_markers (); 363 364// Per-format tool parser builders 365common_peg_parser build_tool_parser_json_native (parser_build_context & ctx )const ; 366common_peg_parser build_tool_parser_tag_json (parser_build_context & ctx )const ; 367common_peg_parser build_tool_parser_tag_tagged (parser_build_context & ctx )const ; 368 369// Shared helper: builds func_parser from open+call_id+args, handling atomic wrapping and close. 370// atomic_peek: if present, used as the peek expression in the third atomicity branch. 371common_peg_parser build_func_parser (common_chat_peg_builder & p ,const std ::string & name , 372const common_peg_parser & call_id_section ,bool have_call_id , 373const common_peg_parser & args , 374std ::optional < common_peg_parser > atomic_peek )const ; 375}; 376 377// ============================================================================ 378// Main autoparser class 379// ============================================================================ 380 381struct autoparser { 382jinja ::caps jinja_caps ; 383std ::string user_start ; 384std ::string assistant_start ; 385analyze_reasoning reasoning ; 386analyze_content content ; 387analyze_tools tools ; 388bool analysis_complete = false; 389 390// Preserved tokens for tokenizer (union of all non-empty markers) 391std ::vector < std ::string > preserved_tokens ; 392std ::vector < std ::string > additional_stops ;// literal stop strings (e.g. Laguna </assistant>) caught however tokenized 393 394autoparser ()= default ; 395 396// Find the starting marker for the user message and assistant message 397std ::string detect_user_start_marker (const common_chat_template & tmpl ); 398std ::string detect_assistant_start_marker (const common_chat_template & tmpl ); 399 400// Run full differential analysis on a template 401void analyze_template (const common_chat_template & tmpl ); 402 403// Build the PEG parser for this template 404common_peg_arena build_parser (const generation_params & inputs ,const std ::string & generation_prompt )const ; 405 406private : 407// Collect tokens from entire analysis to preserve 408void collect_preserved_tokens (); 409}; 410 411// ============================================================================ 412// Parser generator 413// ============================================================================ 414 415class peg_generator { 416public : 417static common_chat_params generate_parser (const common_chat_template & tmpl , 418const struct generation_params & inputs ); 419 420static common_chat_params generate_parser (const common_chat_template & tmpl , 421const struct generation_params & inputs , 422const autoparser & autoparser ); 423}; 424 425}// namespace autoparser 426 427enum segment_type {TEXT ,MARKER }; 428 429inline std ::ostream & operator <<(std ::ostream & os ,const segment_type & type ) { 430switch (type ) { 431case segment_type ::TEXT : 432return os <<"TEXT" ; 433case segment_type ::MARKER : 434return os <<"MARKER" ; 435default : 436return os <<"UNKNOWN" ; 437 } 438} 439 440struct segment { 441segment_type type ; 442std ::string value ; 443 444segment (segment_type type ,std ::string value ) :type (type ),value (std ::move (value )) {} 445 446bool operator == (const segment & other )const { 447return type == other .type && value == other .value ; 448 } 449 450bool operator != (const segment & other )const { 451return !(* this == other ); 452 } 453};