Felipe97/llama-cpp-compiled
01.1k
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) {13 size_t start = 0;14 while (start < str.length() && std::isspace(static_cast<unsigned char>(str[start]))) {15 start++;16 }17 18 if (start == str.length()) {19 return "";20 }21 22 size_t end = str.length() - 1;23 while (end > start && std::isspace(static_cast<unsigned char>(str[end]))) {24 end--;25 }26 27 return str.substr(start, end - start + 1);28}29 30std::string trim_leading_whitespace(const std::string & str) {31 size_t start = 0;32 while (start < str.length() && std::isspace(static_cast<unsigned char>(str[start]))) {33 start++;34 }35 36 return str.substr(start);37}38 39std::string trim_trailing_whitespace(const std::string & str) {40 if (str.empty()) {41 return "";42 }43 44 size_t end = str.length() - 1;45 while (end > 0 && std::isspace(static_cast<unsigned char>(str[end]))) {46 end--;47 }48 49 // If first char is also whitespace, return empty string50 if (end == 0 && std::isspace(static_cast<unsigned char>(str[0]))) {51 return "";52 }53 54 return str.substr(0, end + 1);55}56 57std::string trim_trailing_newlines(const std::string & str) {58 size_t end = str.length();59 while (end > 0 && str[end - 1] == '\n') {60 end--;61 }62 63 return str.substr(0, end);64}65 66static size_t common_prefix_len(const std::string & left, const std::string & right) {67 size_t prefix_len = 0;68 size_t min_len = std::min(left.length(), right.length());69 while (prefix_len < min_len && left[prefix_len] == right[prefix_len]) {70 prefix_len++;71 }72 return prefix_len;73}74 75static size_t common_suffix_len(const std::string & left, const std::string & right) {76 size_t suffix_len = 0;77 size_t min_len = std::min(left.length(), right.length());78 while (suffix_len < min_len && left[left.length() - 1 - suffix_len] == right[right.length() - 1 - suffix_len]) {79 suffix_len++;80 }81 return suffix_len;82}83 84diff_split calculate_diff_split(const std::string & left, const std::string & right) {85 diff_split result;86 87 auto left_seg = segmentize_markers(left);88 auto right_seg = segmentize_markers(right);89 90 if (left_seg.empty()) {91 result.right = right;92 return result;93 }94 if (right_seg.empty()) {95 result.left = left;96 return result;97 }98 99 auto left_start = left_seg.begin();100 auto left_end = --left_seg.end();101 auto right_start = right_seg.begin();102 auto right_end = --right_seg.end();103 104 auto test = [&] () {105 return left_start != left_end && right_start != right_end;106 };107 108 bool left_fully_consumed = false;109 bool right_fully_consumed = false;110 111 while (test()) {112 bool advanced = false;113 if (*left_start == *right_start) {114 result.prefix.append(left_start->value);115 left_start++;116 right_start++;117 advanced = true;118 }119 if (*left_end == *right_end) {120 result.suffix = left_end->value + result.suffix;121 if (left_start != left_end) {122 left_end--;123 } else {124 left_fully_consumed = true;125 }126 if (right_start != right_end) {127 right_end--;128 } else {129 right_fully_consumed = true;130 }131 advanced = true;132 }133 if (!advanced) {134 break;135 }136 }137 138 if (left_start == left_end && right_start != right_end) {139 if (*left_start == *right_end) {140 result.suffix = right_end->value + result.suffix;141 right_end--;142 left_fully_consumed = true;143 } else if (*left_start == *right_start) {144 result.prefix.append(right_start->value);145 right_start++;146 left_fully_consumed = true;147 }148 } else if (right_start == right_end && left_start != left_end) {149 if (*left_end == *right_start) {150 result.suffix = left_end->value + result.suffix;151 left_end--;152 right_fully_consumed = true;153 } else if (*left_start == *right_start) {154 result.prefix.append(left_start->value);155 left_start++;156 right_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) {159 result.prefix.append(right_start->value);160 left_fully_consumed = true;161 right_fully_consumed = true;162 }163 164 auto eat_segment = [](std::string str, const segment & seg) -> std::string { return std::move(str) + seg.value; };165 166 bool can_have_text_suffix = left_end->type == segment_type::TEXT && right_end->type == segment_type::TEXT;167 bool 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 172 size_t suffix_len = can_have_text_suffix ? common_suffix_len(remainder_left, remainder_right) : 0;173 // avoid overlaps between prefix and suffix174 size_t prefix_len = can_have_text_prefix ? common_prefix_len(remainder_left.substr(0, remainder_left.size() - suffix_len),175 remainder_right.substr(0, remainder_right.size() - suffix_len)) : 0;176 177 result.prefix.append(remainder_left.substr(0, prefix_len));178 result.suffix = remainder_left.substr(remainder_left.length() - suffix_len, suffix_len) + result.suffix;179 result.left = remainder_left.substr(prefix_len, remainder_left.length() - prefix_len - suffix_len);180 result.right = remainder_right.substr(prefix_len, remainder_right.length() - prefix_len - suffix_len);181 182 if (result.left == "" && result.right == "") {183 // degenerate case, no diff184 result.prefix = left;185 result.suffix = "";186 // pick prefix = all as representation187 }188 189 // When left has no unique content (result.left is empty), left is entirely190 // shared with right. The simultaneous prefix/suffix segment matching can191 // incorrectly consume trailing segments of left as suffix when those same192 // segments also appear at the end of right (e.g. "\n" at the end of both193 // the shared content and the generation prompt). This rotates the diff.194 // Fix: if left is a prefix of right, enforce that directly.195 if (result.left.empty() && !result.right.empty() &&196 left.size() <= right.size() &&197 right.substr(0, left.size()) == left) {198 result.prefix = left;199 result.suffix = "";200 result.right = right.substr(left.size());201 }202 203 return 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 right209 size_t common_prefix_len = 0;210 size_t min_len = std::min(left.length(), right.length());211 while (common_prefix_len < min_len && left[common_prefix_len] == right[common_prefix_len]) {212 common_prefix_len++;213 }214 215 // If there's no common prefix, return empty string216 if (common_prefix_len == 0) {217 return "";218 }219 220 // Find the common prefix in the full string221 std::string common_prefix = left.substr(0, common_prefix_len);222 size_t pos = full.find(common_prefix);223 224 // If not found, return empty string225 if (pos == std::string::npos) {226 return "";227 }228 229 // Return everything before the common prefix230 return 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)236 size_t common_suffix_len = 0;237 size_t min_len = std::min(left.length(), right.length());238 while (common_suffix_len < min_len &&239 left[left.length() - 1 - common_suffix_len] == right[right.length() - 1 - common_suffix_len]) {240 common_suffix_len++;241 }242 243 // If there's no common suffix, return empty string244 if (common_suffix_len == 0) {245 return "";246 }247 248 // Extract the common suffix249 std::string common_suffix = left.substr(left.length() - common_suffix_len);250 251 // Find the last occurrence of the common suffix in the full string252 size_t pos = full.rfind(common_suffix);253 254 // If not found, return empty string255 if (pos == std::string::npos) {256 return "";257 }258 259 // Return everything after the common suffix260 return 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 markers264// not too worried about that because it hasn't turned out as a problem anywhere, but noting here in case it will265// 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;268 bool in_marker = false;269 char marker_opener = '\0';270 271 auto is_marker_opener = [](char c) -> bool { return c == '<' || c == '['; };272 auto is_marker_closer = [](char op, char c) -> bool { return (op == '<' && c == '>') || (op == '[' && c == ']'); };273 274 size_t last_border = 0;275 276 for (size_t cur_pos = 0; cur_pos < text.length(); cur_pos++) {277 if (!in_marker && is_marker_opener(text[cur_pos])) {278 if (last_border < cur_pos) {279 retval.push_back(segment(segment_type::TEXT, text.substr(last_border, cur_pos - last_border)));280 }281 last_border = cur_pos;282 in_marker = true;283 marker_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 smaller286 retval.push_back(segment(segment_type::MARKER, text.substr(last_border, cur_pos - last_border + 1)));287 last_border = cur_pos + 1;288 in_marker = false;289 marker_opener = '\0';290 }291 }292 if (last_border < text.length()) {293 retval.push_back(segment(segment_type::TEXT, text.substr(last_border)));294 }295 return retval;296}297 298std::vector<segment> prune_whitespace_segments(const std::vector<segment> & segments) {299 std::vector<segment> result;300 for (const auto & seg : segments) {301 if (!trim_whitespace(seg.value).empty()) {302 result.push_back(seg);303 }304 }305 return 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) {313 generation_params tmpl_params;314 tmpl_params.messages = params.messages;315 tmpl_params.tools = params.tools;316 tmpl_params.add_generation_prompt = params.add_generation_prompt;317 tmpl_params.enable_thinking = params.enable_thinking;318 319 if (params.extra_context) {320 tmpl_params.extra_context = *params.extra_context;321 }322 tmpl_params.extra_context["enable_thinking"] = params.enable_thinking;323 324 try {325 return common_chat_template_direct_apply(tmpl, tmpl_params);326 } catch (const std::exception & e) {327 LOG_DBG("Template application failed: %s\n", e.what());328 return ERR_TMPL;329 }330}331 332std::optional<compare_variants_result> compare_variants(333 const common_chat_template & tmpl,334 const template_params & params_A,335 const std::function<void(template_params &)> & params_modifier) {336 // Create variant B by copying A337 template_params params_B = params_A;338 339 // Apply modifier to create variant B340 if (params_modifier) {341 params_modifier(params_B);342 }343 344 // Apply template to both variants345 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 failures349 if (output_A == ERR_TMPL || output_B == ERR_TMPL) {350 return std::nullopt;351 }352 353 // Calculate diff and return result with both outputs354 compare_variants_result result;355 result.diff = calculate_diff_split(output_A, output_B);356 result.output_A = output_A;357 result.output_B = output_B;358 359 return result;360}361 362} // namespace autoparser363 364 