CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 2d agoView on Hugging Face
0likes1.1kdownloads
chat-auto-parser-helpers.cpp364 linesDownload Raw Back to common
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