char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
d9f918d2d
char/aliceai-t5-moe
1#include "chat-auto-parser-helpers.h" 2 3#include "chat-auto-parser.h" 4#include "chat-peg-parser.h" 5#include "chat.h" 6#include "log.h" 7#include "peg-parser.h" 8 9#include <cctype> 10#include <numeric> 11 12std::string trim_whitespace (const std::string & str ) { 13size_t start = 0 ; 14while (start < str .length ()&& std::isspace (static_cast < unsigned char > (str [start ]))) { 15start ++ ; 16 } 17 18if (start == str .length ()) { 19return "" ; 20 } 21 22size_t end = str .length ()- 1 ; 23while (end > start && std::isspace (static_cast < unsigned char > (str [end ]))) { 24end -- ; 25 } 26 27return str .substr (start ,end - start + 1 ); 28} 29 30std::string trim_leading_whitespace (const std::string & str ) { 31size_t start = 0 ; 32while (start < str .length ()&& std::isspace (static_cast < unsigned char > (str [start ]))) { 33start ++ ; 34 } 35 36return str .substr (start ); 37} 38 39std::string trim_trailing_whitespace (const std::string & str ) { 40if (str .empty ()) { 41return "" ; 42 } 43 44size_t end = str .length ()- 1 ; 45while (end > 0 && std::isspace (static_cast < unsigned char > (str [end ]))) { 46end -- ; 47 } 48 49// If first char is also whitespace, return empty string 50if (end == 0 && std::isspace (static_cast < unsigned char > (str [0 ]))) { 51return "" ; 52 } 53 54return str .substr (0 ,end + 1 ); 55} 56 57std::string trim_trailing_newlines (const std::string & str ) { 58size_t end = str .length (); 59while (end > 0 && str [end - 1 ]== '\n' ) { 60end -- ; 61 } 62 63return str .substr (0 ,end ); 64} 65 66static size_t common_prefix_len (const std::string & left ,const std::string & right ) { 67size_t prefix_len = 0 ; 68size_t min_len = std::min (left .length (),right .length ()); 69while (prefix_len < min_len && left [prefix_len ]== right [prefix_len ]) { 70prefix_len ++ ; 71 } 72return prefix_len ; 73} 74 75static size_t common_suffix_len (const std::string & left ,const std::string & right ) { 76size_t suffix_len = 0 ; 77size_t min_len = std::min (left .length (),right .length ()); 78while (suffix_len < min_len && left [left .length ()- 1 - suffix_len ]== right [right .length ()- 1 - suffix_len ]) { 79suffix_len ++ ; 80 } 81return suffix_len ; 82} 83 84diff_split calculate_diff_split (const std::string & left ,const std::string & right ) { 85diff_split result ; 86 87auto left_seg = segmentize_markers (left ); 88auto right_seg = segmentize_markers (right ); 89 90if (left_seg .empty ()) { 91result .right = right ; 92return result ; 93 } 94if (right_seg .empty ()) { 95result .left = left ; 96return result ; 97 } 98 99auto left_start = left_seg .begin (); 100auto left_end = -- left_seg .end (); 101auto right_start = right_seg .begin (); 102auto right_end = -- right_seg .end (); 103 104auto test = [& ] () { 105return left_start != left_end && right_start != right_end ; 106 }; 107 108bool left_fully_consumed = false; 109bool right_fully_consumed = false; 110 111while (test ()) { 112bool advanced = false; 113if (* left_start == * right_start ) { 114result .prefix .append (left_start -> value ); 115left_start ++ ; 116right_start ++ ; 117advanced = true; 118 } 119if (* left_end == * right_end ) { 120result .suffix = left_end -> value + result .suffix ; 121if (left_start != left_end ) { 122left_end -- ; 123 }else { 124left_fully_consumed = true; 125 } 126if (right_start != right_end ) { 127right_end -- ; 128 }else { 129right_fully_consumed = true; 130 } 131advanced = true; 132 } 133if (!advanced ) { 134break ; 135 } 136 } 137 138if (left_start == left_end && right_start != right_end ) { 139if (* left_start == * right_end ) { 140result .suffix = right_end -> value + result .suffix ; 141right_end -- ; 142left_fully_consumed = true; 143 }else if (* left_start == * right_start ) { 144result .prefix .append (right_start -> value ); 145right_start ++ ; 146left_fully_consumed = true; 147 } 148 }else if (right_start == right_end && left_start != left_end ) { 149if (* left_end == * right_start ) { 150result .suffix = left_end -> value + result .suffix ; 151left_end -- ; 152right_fully_consumed = true; 153 }else if (* left_start == * right_start ) { 154result .prefix .append (left_start -> value ); 155left_start ++ ; 156right_fully_consumed = true; 157 } 158 }else if (left_start == left_end && right_start == right_end && * left_start == * right_start && left_start -> type == segment_type::MARKER ) { 159result .prefix .append (right_start -> value ); 160left_fully_consumed = true; 161right_fully_consumed = true; 162 } 163 164auto eat_segment = [](std::string str ,const segment & seg )-> std::string {return std::move (str )+ seg .value ; }; 165 166bool can_have_text_suffix = left_end -> type == segment_type::TEXT && right_end -> type == segment_type::TEXT ; 167bool can_have_text_prefix = right_start -> type == segment_type::TEXT && left_start -> type == segment_type::TEXT ; 168 169 std::string remainder_left = std::accumulate (left_start ,left_fully_consumed ?left_end :++ left_end , std::string (),eat_segment ); 170 std::string remainder_right = std::accumulate (right_start ,right_fully_consumed ?right_end :++ right_end , std::string (),eat_segment ); 171 172size_t suffix_len = can_have_text_suffix ?common_suffix_len (remainder_left ,remainder_right ) :0 ; 173// avoid overlaps between prefix and suffix 174size_t prefix_len = can_have_text_prefix ?common_prefix_len (remainder_left .substr (0 ,remainder_left .size ()- suffix_len ), 175remainder_right .substr (0 ,remainder_right .size ()- suffix_len )) :0 ; 176 177result .prefix .append (remainder_left .substr (0 ,prefix_len )); 178result .suffix = remainder_left .substr (remainder_left .length ()- suffix_len ,suffix_len )+ result .suffix ; 179result .left = remainder_left .substr (prefix_len ,remainder_left .length ()- prefix_len - suffix_len ); 180result .right = remainder_right .substr (prefix_len ,remainder_right .length ()- prefix_len - suffix_len ); 181 182if (result .left == "" && result .right == "" ) { 183// degenerate case, no diff 184result .prefix = left ; 185result .suffix = "" ; 186// pick prefix = all as representation 187 } 188 189// When left has no unique content (result.left is empty), left is entirely 190// shared with right. The simultaneous prefix/suffix segment matching can 191// incorrectly consume trailing segments of left as suffix when those same 192// segments also appear at the end of right (e.g. "\n" at the end of both 193// the shared content and the generation prompt). This rotates the diff. 194// Fix: if left is a prefix of right, enforce that directly. 195if (result .left .empty ()&& !result .right .empty ()&& 196left .size () <=right .size ()&& 197right .substr (0 ,left .size ())== left ) { 198result .prefix = left ; 199result .suffix = "" ; 200result .right = right .substr (left .size ()); 201 } 202 203return result ; 204} 205 206// Returns the prefix of `full` up until the first occurrence of the common prefix of `left` and `right` 207std::string until_common_prefix (const std::string & full ,const std::string & left ,const std::string & right ) { 208// Find the common prefix of left and right 209size_t common_prefix_len = 0 ; 210size_t min_len = std::min (left .length (),right .length ()); 211while (common_prefix_len < min_len && left [common_prefix_len ]== right [common_prefix_len ]) { 212common_prefix_len ++ ; 213 } 214 215// If there's no common prefix, return empty string 216if (common_prefix_len == 0 ) { 217return "" ; 218 } 219 220// Find the common prefix in the full string 221 std::string common_prefix = left .substr (0 ,common_prefix_len ); 222size_t pos = full .find (common_prefix ); 223 224// If not found, return empty string 225if (pos == std::string::npos ) { 226return "" ; 227 } 228 229// Return everything before the common prefix 230return full .substr (0 ,pos ); 231} 232 233// Returns the suffix of `full` after the last occurrence of the common suffix of `left` and `right` 234std::string after_common_suffix (const std::string & full ,const std::string & left ,const std::string & right ) { 235// Find the common suffix of left and right (compare from the end) 236size_t common_suffix_len = 0 ; 237size_t min_len = std::min (left .length (),right .length ()); 238while (common_suffix_len < min_len && 239left [left .length ()- 1 - common_suffix_len ]== right [right .length ()- 1 - common_suffix_len ]) { 240common_suffix_len ++ ; 241 } 242 243// If there's no common suffix, return empty string 244if (common_suffix_len == 0 ) { 245return "" ; 246 } 247 248// Extract the common suffix 249 std::string common_suffix = left .substr (left .length ()- common_suffix_len ); 250 251// Find the last occurrence of the common suffix in the full string 252size_t pos = full .rfind (common_suffix ); 253 254// If not found, return empty string 255if (pos == std::string::npos ) { 256return "" ; 257 } 258 259// Return everything after the common suffix 260return full .substr (pos + common_suffix_len ); 261} 262 263// TODO: segmentize will treat a JSON array inside tags as a tag: <calls>[{ "fun": { ... } }]</calls> will be three markers 264// not too worried about that because it hasn't turned out as a problem anywhere, but noting here in case it will 265// Might have to put some restrictions on tag contents as well (like "no { }") 266std::vector < segment > segmentize_markers (const std::string & text ) { 267 std::vector < segment > retval ; 268bool in_marker = false; 269char marker_opener = '\0' ; 270 271auto is_marker_opener = [](char c )-> bool {return c == '<' || c == '[' ; }; 272auto is_marker_closer = [](char op ,char c )-> bool {return (op == '<' && c == '>' )|| (op == '[' && c == ']' ); }; 273 274size_t last_border = 0 ; 275 276for (size_t cur_pos = 0 ;cur_pos < text .length ();cur_pos ++ ) { 277if (!in_marker && is_marker_opener (text [cur_pos ])) { 278if (last_border < cur_pos ) { 279retval .push_back (segment (segment_type::TEXT ,text .substr (last_border ,cur_pos - last_border ))); 280 } 281last_border = cur_pos ; 282in_marker = true; 283marker_opener = text [cur_pos ]; 284 }else if (in_marker && is_marker_closer (marker_opener ,text [cur_pos ])) { 285// no need to check because last_border will always be smaller 286retval .push_back (segment (segment_type::MARKER ,text .substr (last_border ,cur_pos - last_border + 1 ))); 287last_border = cur_pos + 1 ; 288in_marker = false; 289marker_opener = '\0' ; 290 } 291 } 292if (last_border < text .length ()) { 293retval .push_back (segment (segment_type::TEXT ,text .substr (last_border ))); 294 } 295return retval ; 296} 297 298std::vector < segment > prune_whitespace_segments (const std::vector < segment > & segments ) { 299 std::vector < segment > result ; 300for (const auto & seg :segments ) { 301if (!trim_whitespace (seg .value ).empty ()) { 302result .push_back (seg ); 303 } 304 } 305return result ; 306} 307 308namespace autoparser { 309 310static const std::string ERR_TMPL = "#**ERROR**#" ; 311 312std::string apply_template (const common_chat_template & tmpl ,const template_params & params ) { 313generation_params tmpl_params ; 314tmpl_params .messages = params .messages ; 315tmpl_params .tools = params .tools ; 316tmpl_params .add_generation_prompt = params .add_generation_prompt ; 317tmpl_params .enable_thinking = params .enable_thinking ; 318 319if (params .extra_context ) { 320tmpl_params .extra_context = * params .extra_context ; 321 } 322tmpl_params .extra_context ["enable_thinking" ]= params .enable_thinking ; 323 324try { 325return common_chat_template_direct_apply (tmpl ,tmpl_params ); 326 }catch (const std::exception & e ) { 327LOG_DBG ("Template application failed: %s\n" ,e .what ()); 328return ERR_TMPL ; 329 } 330} 331 332std::optional < compare_variants_result > compare_variants ( 333const common_chat_template & tmpl , 334const template_params & params_A , 335const std::function < void (template_params & )> & params_modifier ) { 336// Create variant B by copying A 337template_params params_B = params_A ; 338 339// Apply modifier to create variant B 340if (params_modifier ) { 341params_modifier (params_B ); 342 } 343 344// Apply template to both variants 345 std::string output_A = apply_template (tmpl ,params_A ); 346 std::string output_B = apply_template (tmpl ,params_B ); 347 348// Check for template application failures 349if (output_A == ERR_TMPL || output_B == ERR_TMPL ) { 350return std::nullopt ; 351 } 352 353// Calculate diff and return result with both outputs 354compare_variants_result result ; 355result .diff = calculate_diff_split (output_A ,output_B ); 356result .output_A = output_A ; 357result .output_B = output_B ; 358 359return result ; 360} 361 362}// namespace autoparser 363