Team Ai
Datasetpublic

Brunobkr/llama.cpp_AlgMor24_github

ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes3kdownloads
chat-peg-parser.cpp1237 linesDownload Raw Back to common
1#include "chat-peg-parser.h"2 3#include "chat-auto-parser.h"4#include "ggml.h"5#include "peg-parser.h"6 7#include <nlohmann/json.hpp>8 9#include <cstdint>10#include <functional>11 12using ordered_json = nlohmann::ordered_json;13 14static std::string_view trim_trailing_space(std::string_view sv, int max = -1) {15    int count = 0;16    while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.back()))) {17        if (max != -1 && count >= max) {18            break;19        }20        sv.remove_suffix(1);21        count++;22    }23    return sv;24}25 26static std::string_view trim_leading_space(std::string_view sv, int max = -1) {27    int count = 0;28    while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.front()))) {29        if (max != -1 && count >= max) {30            break;31        }32        sv.remove_prefix(1);33        count++;34    }35    return sv;36}37 38static std::string_view trim(std::string_view sv) {39    return trim_trailing_space(trim_leading_space(sv, 1));40}41 42// Count the number of unclosed '{' braces in a JSON-like string,43// properly skipping braces inside quoted strings.44static int json_brace_depth(const std::string & s) {45    int  depth     = 0;46    bool in_string = false;47    bool escaped   = false;48    for (char c : s) {49        if (escaped) {50            escaped = false;51            continue;52        }53        if (c == '\\' && in_string) {54            escaped = true;55            continue;56        }57        if (c == '"') {58            in_string = !in_string;59            continue;60        }61        if (!in_string) {62            if (c == '{') {63                depth++;64            } else if (c == '}') {65                depth--;66            }67        }68    }69    return depth;70}71 72// JSON-escape a string and return the inner content (without surrounding quotes).73static std::string escape_json_string_inner(const std::string & s) {74    std::string escaped = ordered_json(s).dump();75    if (escaped.size() >= 2 && escaped.front() == '"' && escaped.back() == '"') {76        return escaped.substr(1, escaped.size() - 2);77    }78    return escaped;79}80 81// Convert Python-style single-quoted strings to JSON double-quoted strings82// Only converts outer string delimiters, properly handling escape sequences:83// - {'key': 'value'} -> {"key": "value"}84// - {'code': 'print(\'hello\')'} -> {"code": "print('hello')"}85// - {'msg': 'He said "hi"'} -> {"msg": "He said \"hi\""}86static std::string normalize_quotes_to_json(const std::string & input) {87    std::string result;88    result.reserve(input.size() + 16);  // May need extra space for escaping89 90    bool in_single_quoted = false;91    bool in_double_quoted = false;92 93    auto is_word_char = [](char ch) { return std::isalnum(static_cast<unsigned char>(ch)) || ch == '_'; };94 95    for (size_t i = 0; i < input.size(); ++i) {96        char c = input[i];97 98        // Handle escape sequences99        if (c == '\\' && i + 1 < input.size()) {100            char next = input[i + 1];101 102            if (in_single_quoted) {103                // Inside a single-quoted string being converted to double quotes104                if (next == '\'') {105                    // \' -> ' (escaped single quote becomes unescaped in double-quoted string)106                    result += '\'';107                    ++i;108                    continue;109                }110                if (next == '"') {111                    // \" stays as \" (already escaped, works in double-quoted string)112                    result += "\\\"";113                    ++i;114                    continue;115                }116                // Other escapes (\n, \\, etc.): pass through both characters117                result += c;118                result += next;119                ++i;120                continue;121            }122 123            if (in_double_quoted) {124                // Inside a double-quoted string - pass through escape sequences as-is125                result += c;126                result += next;127                ++i;128                continue;129            }130 131            // Outside any string - just pass through the backslash132            result += c;133            continue;134        }135 136        // Handle quote characters137        if (c == '"') {138            if (in_single_quoted) {139                // Unescaped double quote inside single-quoted string -> must escape for JSON140                result += "\\\"";141            } else {142                // Double quote as string delimiter or outside strings143                in_double_quoted = !in_double_quoted;144                result += c;145            }146        } else if (c == '\'') {147            if (in_double_quoted) {148                // Single quote inside double-quoted string -> pass through149                result += c;150            } else if (in_single_quoted) {151                // Closing single quote -> convert to double quote152                in_single_quoted = false;153                result += '"';154            } else {155                // Opening single quote -> convert to double quote156                in_single_quoted = true;157                result += '"';158            }159        } else if (!in_single_quoted && !in_double_quoted && (c == 'T' || c == 'F' || c == 'N') &&160                   (i == 0 || !is_word_char(input[i - 1]))) {161            // Python literals -> JSON; prefix match keeps streamed partials monotonic.162            static constexpr std::pair<std::string_view, std::string_view> literals[] = {163                { "True", "true" }, { "False", "false" }, { "None", "null" },164            };165            size_t n = 0;166            while (i + n < input.size() && is_word_char(input[i + n])) {167                ++n;168            }169            std::string_view token(input.data() + i, n);170            bool matched = false;171            for (const auto & [py, js] : literals) {172                if (py.substr(0, n) == token) {173                    result += js.substr(0, n);174                    i += n - 1;175                    matched = true;176                    break;177                }178            }179            if (!matched) {180                result += c;181            }182        } else {183            result += c;184        }185    }186 187    return result;188}189 190void tag_based_peg_mapper::from_ast(const common_peg_ast_arena & arena, const common_peg_parse_result & result) {191    arena.visit(result, [this](const common_peg_ast_node & node) {192        if (!node.tag.empty()) {193            tags[node.tag] = std::string(node.text);194        }195    });196}197 198tagged_parse_result tagged_peg_parser::parse_and_extract(const std::string & input, common_peg_parse_flags extra_flags) const {199    common_peg_parse_context ctx(input, flags | extra_flags);200    auto parse_result = arena.parse(ctx);201 202    tag_based_peg_mapper mapper;203    mapper.from_ast(ctx.ast, parse_result);204 205    return { std::move(parse_result), std::move(mapper.tags) };206}207 208tagged_parse_result tagged_peg_parser::parse_anywhere_and_extract(const std::string & input) const {209    if (input.empty()) {210        return parse_and_extract(input);211    }212    for (size_t i = 0; i < input.size(); i++) {213        common_peg_parse_context ctx(input, flags);214        auto parse_result = arena.parse(ctx, i);215        if (parse_result.success() || i == input.size() - 1) {216            tag_based_peg_mapper mapper;217            mapper.from_ast(ctx.ast, parse_result);218            return { std::move(parse_result), std::move(mapper.tags) };219        }220    }221    GGML_ABORT("Should not happen");222}223 224tagged_peg_parser build_tagged_peg_parser(225    const std::function<common_peg_parser(common_peg_parser_builder & builder)> & fn) {226    common_peg_parser_builder builder;227    builder.set_root(fn(builder));228    return { builder.build() };229}230 231common_peg_parser common_chat_peg_builder::tag_with_safe_content(const std::string &       tag_name,232                                                                 const std::string &       marker,233                                                                 const common_peg_parser & p) {234    if (marker.empty()) {235        return zero_or_more(choice({ p, rule(tag_name, content(any())) }));236    }237    auto content_chunk = rule(tag_name, content(negate(literal(marker)) + any() + until(marker)));238    return zero_or_more(choice({ p, content_chunk }));239}240 241common_peg_parser common_chat_peg_builder::permute(const std::string &                    rule_prefix,242                                                   const std::vector<common_peg_parser> & parsers) {243    if (parsers.empty()) {244        return eps();245    }246 247    if (parsers.size() == 1 || parsers.size() > COMMON_CHAT_MAX_PERMUTE) {248        return sequence(parsers);249    }250 251    std::map<uint32_t, common_peg_parser>      rules;252    std::function<common_peg_parser(uint32_t)> remaining_of;253 254    remaining_of = [&](uint32_t remaining) -> common_peg_parser {255        if (remaining == 0) {256            return eps();257        }258 259        auto cached = rules.find(remaining);260        if (cached != rules.end()) {261            return cached->second;262        }263 264        auto alternatives = choice();265        for (size_t i = 0; i < parsers.size(); i++) {266            const uint32_t bit = 1u << i;267            if (remaining & bit) {268                alternatives |= parsers[i] + remaining_of(remaining & ~bit);269            }270        }271 272        return rules.emplace(remaining, rule(rule_prefix + "-" + std::to_string(remaining), alternatives)).first->second;273    };274 275    return remaining_of((1u << parsers.size()) - 1);276}277 278std::string & common_chat_peg_mapper::args_target() {279    return (current_tool && !current_tool->name.empty()) ? current_tool->arguments : args_buffer;280}281 282std::string common_chat_peg_mapper::normalize_container_value(const std::string & input) {283    return normalize_quotes_to_json(input);284}285 286void common_chat_peg_mapper::from_ast(const common_peg_ast_arena &    arena,287                                      const common_peg_parse_result & parse_result_arg) {288    arena.visit(parse_result_arg, [this](const common_peg_ast_node & node) { map(node); });289    // Flush any pending tool call that was started but never got a name290    // This happens during partial parsing when the tool call is incomplete291    if (pending_tool_call.has_value() && !pending_tool_call->name.empty()) {292        if (!args_buffer.empty()) {293            pending_tool_call->arguments = args_buffer;294        }295        if (closing_quote_pending && !pending_tool_call->arguments.empty()) {296            pending_tool_call->arguments += "\"";297        }298        result.tool_calls.push_back(pending_tool_call.value());299        pending_tool_call.reset();300    }301 302    // Discard whitespace-only reasoning content (e.g. from <think></think> prefill)303    if (!result.reasoning_content.empty()) {304        bool all_whitespace = true;305        for (char c : result.reasoning_content) {306            if (c != ' ' && c != '\n' && c != '\r' && c != '\t') {307                all_whitespace = false;308                break;309            }310        }311        if (all_whitespace) {312            result.reasoning_content.clear();313        }314    }315}316 317void common_chat_peg_mapper::map(const common_peg_ast_node & node) {318    // Handle reasoning/content tags319    bool is_reasoning = node.tag == common_chat_peg_builder::REASONING;320    bool is_content   = node.tag == common_chat_peg_builder::CONTENT;321 322    if (is_reasoning) { // GPT OSS can have more than 1 reasoning block, so concatenate here323        result.reasoning_content += std::string(node.text);324    }325 326    if (is_content) {327        // Concatenate content from multiple content nodes (e.g., when reasoning markers328        // are preserved before content markers in reasoning_format=NONE mode)329        result.content += std::string(node.text);330    }331 332    // Handle tool-related tags (supporting both JSON and tagged formats)333    bool is_tool_open  = node.tag == common_chat_peg_builder::TOOL_OPEN;334    bool is_tool_close = node.tag == common_chat_peg_builder::TOOL_CLOSE;335    bool is_tool_name  = node.tag == common_chat_peg_builder::TOOL_NAME;336    bool is_tool_id    = node.tag == common_chat_peg_builder::TOOL_ID;337    bool is_tool_args  = node.tag == common_chat_peg_builder::TOOL_ARGS;338    bool is_arg_open   = node.tag == common_chat_peg_builder::TOOL_ARG_OPEN;339    bool is_arg_close  = node.tag == common_chat_peg_builder::TOOL_ARG_CLOSE;340    bool is_arg_name         = node.tag == common_chat_peg_builder::TOOL_ARG_NAME;341    bool is_arg_value        = node.tag == common_chat_peg_builder::TOOL_ARG_VALUE;342    bool is_arg_string_value = node.tag == common_chat_peg_builder::TOOL_ARG_STRING_VALUE;343 344    if (is_tool_open) {345        pending_tool_call     = common_chat_tool_call();346        current_tool          = &pending_tool_call.value();347        arg_count             = 0;348        args_buffer.clear();349        closing_quote_pending = false;350    }351 352    if (is_tool_id && current_tool) {353        auto text = trim_trailing_space(node.text);354        if (text.size() >= 2 && text.front() == '"' && text.back() == '"') {355            text = text.substr(1, text.size() - 2);356        }357        current_tool->id = std::string(text);358    }359 360    if (is_tool_name && current_tool) {361        current_tool->name = std::string(trim_trailing_space(node.text));362        // Now that we have the name, populate the arguments from the buffer363        if (!args_buffer.empty()) {364            current_tool->arguments = args_buffer;365            args_buffer.clear();366        } else if (current_tool->arguments.empty()) {367            current_tool->arguments = "{";368        }369        // Add the tool call to results so streaming can see it370        if (pending_tool_call.has_value()) {371            result.tool_calls.push_back(pending_tool_call.value());372            pending_tool_call.reset();373            current_tool = &result.tool_calls.back();374        }375    }376 377    if (is_tool_args && current_tool) {378        // For JSON format: arguments come as a complete JSON object379        // For tagged format: built up from individual arg_name/arg_value nodes380        auto text = trim_trailing_space(node.text);381        if (!text.empty() && text.front() == '{') {382            args_target() = std::string(text);383        }384    }385 386    if (is_arg_open) {387        closing_quote_pending = false;388    }389 390    if (is_arg_name && current_tool) {391        std::string arg_entry;392        if (arg_count > 0) {393            arg_entry = ",";394        }395        arg_entry += ordered_json(trim(node.text)).dump() + ":";396        ++arg_count;397 398        auto & target = args_target();399        if (target.empty()) {400            target = "{";401        }402        target += arg_entry;403    }404 405    if ((is_arg_value || is_arg_string_value) && current_tool) {406        std::string value_content = std::string(node.text);407 408        std::string value_to_add;409        if (value_content.empty() && is_arg_string_value) {410            // Empty string value - arg_close will add the closing quote411            value_to_add          = "\"";412            closing_quote_pending = true;413        } else if (!value_content.empty() && is_arg_string_value) {414            // Schema declares this as string type - always treat as literal string value415            if (!closing_quote_pending) {416                value_to_add          = "\"";417                closing_quote_pending = true;418            }419            value_to_add += escape_json_string_inner(value_content);420        } else if (!value_content.empty()) {421            // Pythonic scalars/containers -> JSON.422            value_to_add += normalize_container_value(value_content);423        }424 425        args_target() += value_to_add;426    }427 428    if (is_arg_close && current_tool) {429        if (closing_quote_pending) {430            args_target() += "\"";431            closing_quote_pending = false;432        }433    }434 435    if (is_tool_close && current_tool) {436        // Flush buffer to arguments if tool name was never seen437        if (current_tool->name.empty() && !args_buffer.empty()) {438            current_tool->arguments = args_buffer;439            args_buffer.clear();440        }441        // Close any pending string quote442        if (closing_quote_pending) {443            current_tool->arguments += "\"";444            closing_quote_pending = false;445        }446        // Close any unclosed braces (accounts for nested objects)447        for (int d = json_brace_depth(current_tool->arguments); d > 0; d--) {448            current_tool->arguments += "}";449        }450        // Add tool call to results if named; otherwise discard451        if (pending_tool_call.has_value()) {452            if (!current_tool->name.empty()) {453                result.tool_calls.push_back(pending_tool_call.value());454            }455            pending_tool_call.reset();456        }457    }458}459 460common_peg_parser common_chat_peg_builder::standard_constructed_tools(461    const std::map<std::string, std::string> & markers,462    const ordered_json &                       tools,463    bool                                       parallel_tool_calls,464    bool                                       force_tool_calls) {465    if (!tools.is_array() || tools.empty()) {466        return eps();467    }468 469    // Extract markers with defaults470    auto get_marker = [&markers](const std::string & key, const std::string & default_val = "") -> std::string {471        auto it = markers.find(key);472        return it != markers.end() ? it->second : default_val;473    };474 475    std::string section_start    = get_marker("tool_call_start_marker", "<tool_call>");476    std::string section_end      = get_marker("tool_call_end_marker", "</tool_call>");477    std::string func_opener      = get_marker("function_opener", "<function=");478    std::string func_name_suffix = get_marker("function_name_suffix", ">");479    std::string func_closer      = get_marker("function_closer", "</function>");480    std::string param_key_prefix = get_marker("parameter_key_prefix", "<param=");481    std::string param_key_suffix = get_marker("parameter_key_suffix", ">");482    std::string param_closer     = get_marker("parameter_closer", "</param>");483 484    // Build tool choices for tagged format485    auto tool_choices = choice();486 487    for (const auto & tool_def : tools) {488        if (!tool_def.contains("function")) {489            continue;490        }491        const auto &   function = tool_def.at("function");492        std::string    name     = function.at("name");493        ordered_json   params   = function.contains("parameters") ? function.at("parameters") : ordered_json::object();494 495        // Build argument parsers496        auto args = eps();497        if (params.contains("properties") && !params["properties"].empty()) {498            auto arg_choice = choice();499            for (const auto & el : params["properties"].items()) {500                const std::string & prop_name = el.key();501 502                auto arg_name_parser =503                    choice({ literal(prop_name), literal("\"" + prop_name + "\""), literal("'" + prop_name + "'") });504 505                auto arg_rule = tool_arg(tool_arg_open(literal(param_key_prefix)) + tool_arg_name(arg_name_parser) +506                                         literal(param_key_suffix) + tool_arg_value(until(param_closer)) +507                                         tool_arg_close(literal(param_closer)));508                arg_choice |= arg_rule;509            }510            args = zero_or_more(arg_choice + space());511        }512 513        // Build function parser: <function=name>args</function>514        auto tool_parser = tool(tool_open(literal(func_opener) + tool_name(literal(name)) + literal(func_name_suffix)) +515                                space() + tool_args(args) + space() + tool_close(literal(func_closer)));516 517        tool_choices |= rule("tool-" + name, tool_parser);518    }519 520    // Build the section with markers521    auto section =522        parallel_tool_calls ?523            trigger_rule("tool-call", literal(section_start) + space() + one_or_more(tool_choices + space()) +524                                          literal(section_end)) :525            trigger_rule("tool-call", literal(section_start) + space() + tool_choices + space() + literal(section_end));526 527    return force_tool_calls ? section : optional(section);528}529 530// Like python_value(), but the leaf also accepts JSON-cased true/false/null, used by LFM2/LFM2.5531common_peg_parser common_chat_peg_builder::python_or_json_value() {532    return rule("python-or-json-value", [this]() {533        auto ws    = space();534        auto value = python_or_json_value();535 536        auto member  = sequence({ python_string(), ws, literal(":"), ws, value });537        auto members = sequence({ member, zero_or_more(sequence({ ws, literal(","), ws, member })) });538        auto dict    = rule("python-or-json-dict", [&]() {539            return sequence({ literal("{"), ws, choice({ literal("}"), sequence({ members, ws, literal("}") }) }), ws });540        });541 542        auto elements = sequence({ value, zero_or_more(sequence({ literal(","), ws, value })) });543        auto array    = rule("python-or-json-array", [&]() {544            return sequence({ literal("["), ws, choice({ literal("]"), sequence({ elements, ws, literal("]") }) }), ws });545        });546 547        return choice({ dict, array, python_string(), python_number(),548                        python_bool(), python_null(), json_bool(), json_null() });549    });550}551 552// Python-style tool calls: name(arg1="value1", arg2=123)553// Used only by LFM2 for now, so we don't merge it into autoparser554common_peg_parser common_chat_peg_builder::python_style_tool_calls(555    const ordered_json & tools,556    bool                 parallel_tool_calls,557    bool                 allow_json_literals) {558    if (!tools.is_array() || tools.empty()) {559        return eps();560    }561 562    auto tool_choices = choice();563 564    for (const auto & tool_def : tools) {565        if (!tool_def.contains("function")) {566            continue;567        }568        const auto &   function = tool_def.at("function");569        std::string    name     = function.at("name");570        ordered_json   params   = function.contains("parameters") ? function.at("parameters") : ordered_json::object();571 572        auto args = eps();573        if (params.contains("properties") && !params["properties"].empty()) {574            auto arg_choice = choice();575            for (const auto & el : params["properties"].items()) {576                const std::string & prop_name = el.key();577                const auto & prop_def = el.value();578                bool is_string_type = (prop_def.contains("type") && prop_def["type"] == "string");579 580                auto arg_name_parser = literal(prop_name);581 582                common_peg_parser arg_value_parser = eps();583                // Quoted literal as a value: normalize_quotes_to_json preserves escapes.584                auto string_value_parser = tool_arg_value(choice({585                    literal("\"") + string_content('"') + literal("\""),586                    literal("'") + string_content('\'') + literal("'")587                }));588 589                if (is_string_type) {590                    arg_value_parser = string_value_parser;591                } else {592                    arg_value_parser = tool_arg_value(allow_json_literals ? python_or_json_value() : python_value());593                }594 595                // Full argument: name="value" or name=value596                auto arg_rule = tool_arg(597                    tool_arg_open(eps()) +598                    tool_arg_name(arg_name_parser) +599                    literal("=") +600                    arg_value_parser +601                    tool_arg_close(eps())602                );603                arg_choice |= arg_rule;604            }605 606            args = arg_choice + zero_or_more("," + space() + arg_choice);607        }608 609        auto tool_parser = tool(tool_open(tool_name(literal(name)) + literal("(")) +610            space() + tool_args(args) + space() + tool_close(literal(")"))611        );612 613        tool_choices |= rule("tool-" + name, tool_parser);614    }615 616    if (parallel_tool_calls) {617        return "[" + space() + tool_choices + zero_or_more("," + space() + tool_choices) + space() + "]";618    }619    return "[" + space() + tool_choices + space() + "]";620}621 622// Helper: Parse dot notation key into prefix and field name623static std::pair<std::string, std::string> parse_key_spec(const std::string & key) {624    auto dot_pos = key.find('.');625    if (dot_pos == std::string::npos) {626        return {"", key};  // Top-level field627    }628    return {key.substr(0, dot_pos), key.substr(dot_pos + 1)};629}630 631// Mode 1: function_is_key — parse {"function_name": {...}}632common_peg_parser common_chat_peg_builder::build_json_tools_function_is_key(633    const ordered_json & tools,634    const std::string &  args_key,635    const std::string &  effective_args_key,636    const std::string &  call_id_key,637    const std::string &  gen_call_id_key) {638 639    auto tool_choices = choice();640 641    for (const auto & tool_def : tools) {642        if (!tool_def.contains("function")) {643            continue;644        }645        const auto &   function = tool_def.at("function");646        std::string    name     = function.at("name");647        ordered_json   params   = function.contains("parameters") ? function.at("parameters") : ordered_json::object();648 649        // Build inner object fields650        std::vector<common_peg_parser> inner_fields;651 652        if (!call_id_key.empty()) {653            auto id_parser = atomic(654                literal("\"" + call_id_key + "\"") + space() + literal(":") + space() +655                literal("\"") + tool_id(string_content('"')) + literal("\"")656            );657            inner_fields.push_back(optional(id_parser + space() + optional(literal(",") + space())));658        }659 660        if (!gen_call_id_key.empty()) {661            auto gen_id_parser = atomic(662                literal("\"" + gen_call_id_key + "\"") + space() + literal(":") + space() +663                choice({664                    literal("\"") + tool_id(string_content('"')) + literal("\""),665                    tool_id(json_number())666                })667            );668            inner_fields.push_back(optional(gen_id_parser + space() + optional(literal(",") + space())));669        }670 671        // Arguments — either wrapped in args_key or parsed directly672        common_peg_parser args_parser = eps();673        if (args_key.empty()) {674            args_parser = tool_args(schema(json(), "tool-" + name + "-schema", params));675        } else {676            args_parser = literal("\"" + effective_args_key + "\"") + space() + literal(":") + space() +677                          tool_args(schema(json(), "tool-" + name + "-schema", params));678        }679        inner_fields.push_back(args_parser);680 681        // Build inner object parser682        common_peg_parser inner_object = eps();683        if (args_key.empty() && inner_fields.size() == 1) {684            inner_object = inner_fields[0];685        } else {686            inner_object = literal("{") + space();687            for (size_t i = 0; i < inner_fields.size(); i++) {688                inner_object = inner_object + inner_fields[i];689                if (i < inner_fields.size() - 1) {690                    inner_object = inner_object + space();691                }692            }693            inner_object = inner_object + space() + literal("}");694        }695 696        auto tool_parser = tool(697            tool_open(literal("{")) + space() +698            literal("\"") + tool_name(literal(name)) + literal("\"") +699            space() + literal(":") + space() +700            inner_object +701            space() + tool_close(literal("}"))702        );703 704        tool_choices |= rule("tool-" + name, tool_parser);705    }706 707    return tool_choices;708}709 710// Mode 2: Nested keys (dot notation like "function.name")711common_peg_parser common_chat_peg_builder::build_json_tools_nested_keys(712    const ordered_json & tools,713    const std::string &  effective_name_key,714    const std::string &  effective_args_key,715    const std::string &  call_id_key,716    const std::string &  gen_call_id_key) {717 718    auto tool_choices = choice();719 720    auto name_spec = parse_key_spec(effective_name_key);721    auto args_spec = parse_key_spec(effective_args_key);722 723    std::string nested_prefix     = !name_spec.first.empty() ? name_spec.first  : args_spec.first;724    std::string nested_name_field = !name_spec.first.empty() ? name_spec.second  : effective_name_key;725    std::string nested_args_field = !args_spec.first.empty() ? args_spec.second  : effective_args_key;726 727    for (const auto & tool_def : tools) {728        if (!tool_def.contains("function")) {729            continue;730        }731        const auto &   function = tool_def.at("function");732        std::string    name     = function.at("name");733        ordered_json   params   = function.contains("parameters") ? function.at("parameters") : ordered_json::object();734 735        auto nested_name = literal("\"" + nested_name_field + "\"") + space() + literal(":") + space() +736                          atomic(literal("\"") + tool_name(literal(name)) + literal("\""));737        auto nested_args = literal("\"" + nested_args_field + "\"") + space() + literal(":") + space() +738                          tool_args(schema(json(), "tool-" + name + "-schema", params));739 740        auto nested_object = literal("{") + space() +741                            nested_name + space() + literal(",") + space() +742                            nested_args +743                            space() + literal("}");744 745        // Format: { id?, "function": {...} }746        auto tool_parser_body = tool_open(literal("{")) + space();747 748        if (!call_id_key.empty()) {749            auto id_spec = parse_key_spec(call_id_key);750            if (id_spec.first.empty()) {751                auto id_parser = atomic(752                    literal("\"" + call_id_key + "\"") + space() + literal(":") + space() +753                    literal("\"") + tool_id(string_content('"')) + literal("\"")754                );755                tool_parser_body = tool_parser_body + optional(id_parser + space() + literal(",") + space());756            }757        }758 759        if (!gen_call_id_key.empty()) {760            auto gen_id_spec = parse_key_spec(gen_call_id_key);761            if (gen_id_spec.first.empty()) {762                auto gen_id_parser = atomic(763                    literal("\"" + gen_call_id_key + "\"") + space() + literal(":") + space() +764                    choice({765                        literal("\"") + tool_id(string_content('"')) + literal("\""),766                        tool_id(json_number())767                    })768                );769                tool_parser_body = tool_parser_body + optional(gen_id_parser + space() + literal(",") + space());770            }771        }772 773        auto nested_field = literal("\"" + nested_prefix + "\"") + space() + literal(":") + space() + nested_object;774        tool_parser_body = tool_parser_body + nested_field + space() + tool_close(literal("}"));775 776        tool_choices |= rule("tool-" + name, tool(tool_parser_body));777    }778 779    return tool_choices;780}781 782// Mode 3: Flat keys with optional ID fields and parameter ordering783common_peg_parser common_chat_peg_builder::build_json_tools_flat_keys(784    const ordered_json &             tools,785    const std::string &              effective_name_key,786    const std::string &              effective_args_key,787    const std::string &              call_id_key,788    const std::string &              gen_call_id_key,789    const std::vector<std::string> & parameters_order,790    bool                             accept_openai_wrapper) {791 792    auto tool_choices    = choice();793    auto name_key_parser = literal("\"" + effective_name_key + "\"");794    auto args_key_parser = literal("\"" + effective_args_key + "\"");795 796    for (const auto & tool_def : tools) {797        if (!tool_def.contains("function")) {798            continue;799        }800        const auto &   function = tool_def.at("function");801        std::string    name     = function.at("name");802        ordered_json   params   = function.contains("parameters") ? function.at("parameters") : ordered_json::object();803 804        auto tool_name_ = name_key_parser + space() + literal(":") + space() +805                         atomic(literal("\"") + tool_name(literal(name)) + literal("\""));806        auto tool_args_ = args_key_parser + space() + literal(":") + space() +807                         tool_args(schema(json(), "tool-" + name + "-schema", params));808 809        // Build ID parsers if keys are provided810        common_peg_parser id_parser = eps();811        if (!call_id_key.empty()) {812            id_parser = atomic(813                literal("\"" + call_id_key + "\"") + space() + literal(":") + space() +814                choice({815                    literal("\"") + tool_id(string_content('"')) + literal("\""),816                    tool_id(json_number())817                })818            );819        }820 821        common_peg_parser gen_id_parser = eps();822        if (!gen_call_id_key.empty()) {823            gen_id_parser = atomic(824                literal("\"" + gen_call_id_key + "\"") + space() + literal(":") + space() +825                choice({826                    literal("\"") + tool_id(string_content('"')) + literal("\""),827                    tool_id(json_number())828                })829            );830        }831 832        // Create (parser, key) pairs for all fields, then sort by parameters_order833        std::vector<std::pair<common_peg_parser, std::string>> parser_pairs;834        parser_pairs.emplace_back(tool_name_, effective_name_key);835        parser_pairs.emplace_back(tool_args_, effective_args_key);836        if (!call_id_key.empty()) {837            parser_pairs.emplace_back(optional(id_parser), call_id_key);838        }839        if (!gen_call_id_key.empty()) {840            parser_pairs.emplace_back(optional(gen_id_parser), gen_call_id_key);841        }842 843        std::sort(parser_pairs.begin(), parser_pairs.end(),844            [&parameters_order](const auto & a, const auto & b) {845                auto pos_a = std::find(parameters_order.begin(), parameters_order.end(), a.second);846                auto pos_b = std::find(parameters_order.begin(), parameters_order.end(), b.second);847                size_t idx_a = (pos_a == parameters_order.end()) ? parameters_order.size() : std::distance(parameters_order.begin(), pos_a);848                size_t idx_b = (pos_b == parameters_order.end()) ? parameters_order.size() : std::distance(parameters_order.begin(), pos_b);849                return idx_a < idx_b;850            });851 852        // accept an optional leading "type": "function" field when the model emits the OpenAI wrapper853        common_peg_parser type_field = eps();854        if (accept_openai_wrapper) {855            type_field = optional(literal("\"type\"") + space() + literal(":") + space() +856                                  literal("\"function\"") + space() + literal(",") + space());857        }858        auto ordered_body = tool_open(literal("{")) + space() + type_field;859        for (size_t i = 0; i < parser_pairs.size(); i++) {860            ordered_body = ordered_body + parser_pairs[i].first;861            if (i < parser_pairs.size() - 1) {862                ordered_body = ordered_body + space() + literal(",") + space();863            }864        }865        ordered_body = ordered_body + space() + tool_close(literal("}"));866 867        tool_choices |= rule("tool-" + name, tool(ordered_body));868    }869 870    return tool_choices;871}872 873common_peg_parser common_chat_peg_builder::prefix(const std::string & s, const std::string & delimiter) {874    if (s.empty()) {875        return eps();876    }877    if (delimiter.empty()) {878        return literal(s);879    }880    return literal(s.substr(0, s.find(delimiter)));881}882 883common_peg_parser common_chat_peg_builder::optspace(const std::string & tag) {884    auto parser = eps();885    size_t end_of_prefix_space = tag.size();886    size_t start_of_suffix_space = tag.size();887    for (size_t i = 0; i < tag.size(); i++) {888        if (!std::isspace(tag[i])) {889            end_of_prefix_space = i;890            break;891        }892    }893    for (size_t i = tag.size(); i > 0; i--) {894        if (!std::isspace(tag[i - 1])) {895            start_of_suffix_space = i;896            break;897        }898    }899    for (size_t i = 0; i < end_of_prefix_space; i++) {900        parser += optional(literal(std::string(1, tag[i])));901    }902    parser += literal(tag.substr(end_of_prefix_space, start_of_suffix_space - end_of_prefix_space));903    for (size_t i = start_of_suffix_space; i < tag.size(); i++) {904        parser += optional(literal(std::string(1, tag[i])));905    }906    return parser;907}908 909common_peg_parser common_chat_peg_builder::standard_json_tools(910                                                       const std::string &              section_start,911                                                       const std::string &              section_end,912                                                       const ordered_json &             tools,913                                                       bool                             parallel_tool_calls,914                                                       bool                             force_tool_calls,915                                                       const std::string &              name_key,916                                                       const std::string &              args_key,917                                                       bool                             array_wrapped,918                                                       bool                             function_is_key,919                                                       const std::string &              call_id_key,920                                                       const std::string &              gen_call_id_key,921                                                       const std::vector<std::string> & parameters_order,922                                                       bool                             accept_openai_wrapper) {923    if (!tools.is_array() || tools.empty()) {924        return eps();925    }926 927    std::string effective_name_key = name_key.empty() ? "name" : name_key;928    std::string effective_args_key = args_key.empty() ? "arguments" : args_key;929 930    // Dispatch to the appropriate builder based on the JSON layout mode931    common_peg_parser tool_choices = eps();932    if (function_is_key) {933        tool_choices = build_json_tools_function_is_key(tools, args_key, effective_args_key, call_id_key, gen_call_id_key);934    } else {935        auto name_spec = parse_key_spec(effective_name_key);936        auto args_spec = parse_key_spec(effective_args_key);937        if (!name_spec.first.empty() || !args_spec.first.empty()) {938            tool_choices = build_json_tools_nested_keys(tools, effective_name_key, effective_args_key, call_id_key, gen_call_id_key);939        } else {940            tool_choices = build_json_tools_flat_keys(tools, effective_name_key, effective_args_key, call_id_key, gen_call_id_key, parameters_order, accept_openai_wrapper);941        }942    }943 944    // Build the section with markers945    auto tool_calls = tool_choices;946    if (parallel_tool_calls) {947        tool_calls = tool_calls + zero_or_more(space() + literal(",") + space() + tool_choices);948    }949 950    if (array_wrapped) {951        tool_calls = literal("[") + space() + tool_calls + space() + literal("]");952    }953 954    auto section =955        trigger_rule("tool-call", literal(section_start) + space() + tool_calls + space() + literal(section_end));956 957    return force_tool_calls ? section : optional(section);958}959 960void common_chat_peg_gemma4_mapper::from_ast(const common_peg_ast_arena & arena, const common_peg_parse_result & result) {961    for (const auto & node : result.nodes) {962        visit(arena, node);963    }964}965 966static std::string gemma4_to_json(const common_peg_ast_arena & arena, common_peg_ast_id id) {967    const auto & node = arena.get(id);968 969    if (node.text.empty()) {970        return "";971    }972 973    if (node.rule == "gemma4-number" || node.rule == "gemma4-bool" || node.rule == "gemma4-null") {974        return std::string(node.text);975    }976 977    if (node.rule == "gemma4-string-content") {978        return escape_json_string_inner(std::string(node.text));979    }980 981    if (node.rule == "gemma4-string") {982        std::string result = "\"";983        if (!node.children.empty()) {984            result += gemma4_to_json(arena, node.children[0]);985            if (!node.is_partial) {986                result += "\"";987            }988        }989        return result;990    }991 992    if (node.rule == "gemma4-array") {993        std::string result = "[";994 995        bool add_comma = false;996        for (auto child_id : node.children) {997            if (add_comma) {998                result += ',';999            }1000            add_comma = true;1001            result += gemma4_to_json(arena, child_id);1002        }1003 1004        if (!node.is_partial) {1005            result += ']';1006        }1007        return result;1008    }1009 1010    if (node.rule == "gemma4-dict-key-name") {1011        return std::string(node.text);1012    }1013 1014    if (node.rule == "gemma4-dict-key") {1015        std::string result = "\"";1016        if (!node.children.empty()) {1017            result += escape_json_string_inner(gemma4_to_json(arena, node.children[0]));1018        }1019        if (!node.is_partial) {1020            result += "\":";1021        }1022        return result;1023    }1024 1025    if (node.rule == "gemma4-dict-kv") {1026        std::string result;1027        for (auto child_id : node.children) {1028            result += gemma4_to_json(arena, child_id);1029        }1030        return result;1031    }1032 1033    if (node.rule == "gemma4-dict") {1034        std::string result = "{";1035 1036        bool add_comma = false;1037        for (auto child_id : node.children) {1038            if (add_comma) {1039                result += ',';1040            }1041            add_comma = true;1042            result += gemma4_to_json(arena, child_id);1043        }1044 1045        if (!node.is_partial) {1046            result += '}';1047        }1048        return result;1049    }1050 1051    if (node.rule == "gemma4-value") {1052        if (!node.children.empty()) {1053            return gemma4_to_json(arena, node.children[0]);1054        }1055        return "";1056    }1057 1058    return "";1059}1060 1061void common_chat_peg_gemma4_mapper::visit(const common_peg_ast_arena & arena, common_peg_ast_id id) {1062    const auto & node = arena.get(id);1063 1064    if (node.tag == "reasoning") {1065        result.reasoning_content += std::string(node.text);1066        return;1067    }1068 1069    if (node.tag == "content") {1070        result.content += std::string(node.text);1071        return;1072    }1073 1074    if (node.tag == "tool") {1075        auto name_id = arena.find_by_tag(node, "tool-name");1076        auto args_id = arena.find_by_tag(node, "tool-args");1077 1078        if (name_id != COMMON_PEG_INVALID_AST_ID && args_id != COMMON_PEG_INVALID_AST_ID) {1079            const auto & name_node = arena.get(name_id);1080            const auto & args_node = arena.get(args_id);1081 1082            if (!name_node.is_partial) {1083                common_chat_tool_call call;1084                call.name = std::string(name_node.text);1085                if (!args_node.children.empty()) {1086                    call.arguments = gemma4_to_json(arena, args_node.children[0]);1087                }1088                result.tool_calls.push_back(call);1089            }1090        }1091 1092        return;1093    }1094 1095    for (auto child_id : node.children) {1096        visit(arena, child_id);1097    }1098}1099 1100static void minimax_m3_collect(const common_peg_ast_arena &     arena,1101                               const common_peg_ast_node &      node,1102                               const std::string &              tag,1103                               std::vector<common_peg_ast_id> & out) {1104    for (auto child_id : node.children) {1105        const auto & child = arena.get(child_id);1106        if (child.tag == tag) {1107            out.push_back(child_id);1108        } else {1109            minimax_m3_collect(arena, child, tag, out);1110        }1111    }1112}1113 1114static common_peg_ast_id minimax_m3_value_of(const common_peg_ast_arena & arena, const common_peg_ast_node & node) {1115    for (auto child_id : node.children) {1116        const auto & tag = arena.get(child_id).tag;1117        if (tag == common_chat_peg_builder::TOOL_ARG_VALUE ||1118            tag == common_chat_peg_builder::TOOL_ARG_STRING_VALUE ||1119            tag == common_chat_peg_minimax_m3_mapper::TOOL_ARG_OBJECT ||1120            tag == common_chat_peg_minimax_m3_mapper::TOOL_ARG_ARRAY) {1121            return child_id;1122        }1123    }1124    return COMMON_PEG_INVALID_AST_ID;1125}1126 1127static std::string minimax_m3_value_to_json(const common_peg_ast_arena & arena, common_peg_ast_id id, bool closed);1128 1129static std::string minimax_m3_member_to_json(const common_peg_ast_arena & arena, const common_peg_ast_node & node) {1130    auto name_id = arena.find_by_tag(node, common_chat_peg_builder::TOOL_ARG_NAME);1131    if (name_id == COMMON_PEG_INVALID_AST_ID) {1132        return "";1133    }1134 1135    return ordered_json(arena.get(name_id).text).dump() + ":" +1136           minimax_m3_value_to_json(arena, minimax_m3_value_of(arena, node), !node.is_partial);1137}1138 1139static std::string minimax_m3_container_to_json(const common_peg_ast_arena & arena,1140                                                const common_peg_ast_node & node,1141                                                bool                        is_object,1142                                                bool                        closed) {1143    const std::string tag = is_object ? common_chat_peg_builder::TOOL_ARG1144                                      : common_chat_peg_minimax_m3_mapper::TOOL_ARG_ITEM;1145 1146    std::vector<common_peg_ast_id> entries;1147    minimax_m3_collect(arena, node, tag, entries);1148 1149    std::string result = is_object ? "{" : "[";1150 1151    bool add_comma = false;1152    for (auto entry_id : entries) {1153        const auto & entry = arena.get(entry_id);1154 1155        std::string text;1156        if (is_object) {1157            text = minimax_m3_member_to_json(arena, entry);1158        } else {1159            text = minimax_m3_value_to_json(arena, minimax_m3_value_of(arena, entry), !entry.is_partial);1160        }1161 1162        if (text.empty()) {1163            continue;1164        }1165 1166        if (add_comma) {1167            result += ",";1168        }1169        add_comma = true;1170        result += text;1171    }1172 1173    if (closed) {1174        result += is_object ? "}" : "]";1175    }1176    return result;1177}1178 1179static std::string minimax_m3_value_to_json(const common_peg_ast_arena & arena, common_peg_ast_id id, bool closed) {1180    if (id == COMMON_PEG_INVALID_AST_ID) {1181        return "";1182    }1183 1184    const auto & node = arena.get(id);1185 1186    if (node.tag == common_chat_peg_minimax_m3_mapper::TOOL_ARG_OBJECT) {1187        return minimax_m3_container_to_json(arena, node, /* is_object = */ true, closed);1188    }1189 1190    if (node.tag == common_chat_peg_minimax_m3_mapper::TOOL_ARG_ARRAY) {1191        return minimax_m3_container_to_json(arena, node, /* is_object = */ false, closed);1192    }1193 1194    if (node.tag == common_chat_peg_builder::TOOL_ARG_STRING_VALUE) {1195        return "\"" + escape_json_string_inner(std::string(node.text)) + (closed ? "\"" : "");1196    }1197 1198    // Numbers and booleans are written verbatim by the template1199    return std::string(node.text);1200}

Showing the first 1,200 of 1237 lines. Download the file for the rest.

Brunobkr/llama.cpp_AlgMor24_github · Team Ai