CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 2d agoView on Hugging Face
0likes1.1kdownloads
chat-auto-parser-generator.cpp479 linesDownload Raw Back to common
1#include "chat-auto-parser-helpers.h"2#include "chat-auto-parser.h"3#include "chat-peg-parser.h"4#include "chat.h"5#include "common.h"6#include "json-schema-to-grammar.h"7#include "log.h"8#include "parsers/parsers.h"9#include "peg-parser.h"10 11#include <stdexcept>12#include <string>13 14using json = common_json;15 16namespace autoparser {17 18parser_build_context::parser_build_context(common_chat_peg_builder & p, const generation_params & inputs) :19    p(p),20    inputs(inputs),21    reasoning_parser(p.eps()) {}22 23common_chat_params peg_generator::generate_parser(const common_chat_template &    tmpl,24                                                  const struct generation_params & inputs) {25    // Run differential analysis to extract template structure26    struct autoparser autoparser;27    autoparser.analyze_template(tmpl);28    return generate_parser(tmpl, inputs, autoparser);29}30 31common_chat_params peg_generator::generate_parser(const common_chat_template &    tmpl,32                                                  const struct generation_params & inputs,33                                                  const autoparser &              autoparser) {34    // Create the result structure35    common_chat_params data;36    data.prompt            = common_chat_template_direct_apply(tmpl, inputs);37    data.generation_prompt = common_chat_template_generation_prompt(tmpl, inputs);38    data.format            = COMMON_CHAT_FORMAT_PEG_NATIVE;39    data.preserved_tokens  = autoparser.preserved_tokens;40    data.additional_stops.insert(data.additional_stops.end(),41        autoparser.additional_stops.begin(), autoparser.additional_stops.end());42 43    std::string parser_generation_prompt = data.generation_prompt;44 45    if (inputs.continue_final_message != COMMON_CHAT_CONTINUATION_NONE && !inputs.continue_msg.empty()) {46        // Build up generation prompt manually47        const auto & msg = inputs.continue_msg;48 49        if (!autoparser.reasoning.start.empty()) {50            data.generation_prompt = data.generation_prompt.substr(0, data.generation_prompt.find(autoparser.reasoning.start));51            data.generation_prompt += autoparser.reasoning.start + msg.reasoning_content;52            if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {53                data.generation_prompt += autoparser.reasoning.end;54            }55        }56 57        if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {58            data.generation_prompt += msg.render_content();59        }60 61        data.prompt += data.generation_prompt;62    }63 64    auto parser = autoparser.build_parser(inputs, parser_generation_prompt);65    data.parser = parser.save();66 67    // Build grammar if tools are present68    bool has_tools =69        autoparser.tools.format.mode != tool_format::NONE && inputs.tools.is_array() && !inputs.tools.empty();70    std::string trigger_marker = !autoparser.tools.format.section_start.empty() ? autoparser.tools.format.section_start :71                                                                                  autoparser.tools.format.per_call_start;72 73    bool has_response_format = !inputs.json_schema.empty() && inputs.json_schema.is_object();74    bool include_grammar = has_response_format || (has_tools &&75            ((inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO && !trigger_marker.empty()) ||76              inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));77 78    if (include_grammar) {79        data.grammar_lazy = !has_response_format && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;80        data.grammar      = build_grammar([&](const common_grammar_builder & builder) {81            parser.build_grammar(builder, data.grammar_lazy);82        });83 84        // Set grammar triggers based on tool section markers (fall back to per-call markers)85        if (data.grammar_lazy) {86            data.grammar_triggers = {87                { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, trigger_marker }88            };89            if (autoparser.tools.format.openai_wrapper_trigger) {90                // model emits the OpenAI function wrapper, trigger on it91                data.grammar_triggers.push_back({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "{\"type\": \"function\"," });92            }93        }94    }95 96    return data;97}98 99common_peg_arena autoparser::build_parser(const generation_params & inputs, const std::string & generation_prompt) const {100    if (!analysis_complete) {101        throw std::invalid_argument("Cannot call build_parser on autoparser without performing analysis first, call analyze_template(...)");102    }103    return build_chat_peg_parser([&](common_chat_peg_builder & p) {104        parser_build_context ctx(p, inputs);105        bool                 extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;106 107        ctx.extracting_reasoning = extract_reasoning && reasoning.mode != reasoning_mode::NONE;108        ctx.content              = &content;109        ctx.reasoning            = &reasoning;110 111        // Build reasoning parser112        ctx.reasoning_parser = reasoning.build_parser(ctx);113 114        auto parser = p.eps();115 116        bool has_tools           = inputs.tools.is_array() && !inputs.tools.empty();117        bool has_response_format = inputs.json_schema.is_object() && !inputs.json_schema.empty();118        bool pure_content        = reasoning.mode == reasoning_mode::NONE;119 120        if (has_response_format) {121            auto response_format = p.rule("response-format", p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)));122            parser = ctx.reasoning_parser + p.space() + p.choice({123                p.literal("```json") + p.space() + response_format + p.space() + p.literal("```"),124                p.space() + response_format  + p.space()125            }) + p.end();126            pure_content = false;127        } else if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE && jinja_caps.supports_tool_calls) {128            parser = tools.build_parser(ctx);129            pure_content = false;130        } else {131            parser = content.build_parser(ctx);132        }133        const std::string reasoning_start = trim_whitespace(reasoning.start);134        return pure_content ? p.prefix(generation_prompt, reasoning_start) + parser : p.prefix(generation_prompt, reasoning_start) << parser;135    });136}137 138common_peg_parser analyze_reasoning::build_parser(parser_build_context & ctx) const {139    auto & p = ctx.p;140 141    if (!ctx.extracting_reasoning) {142        return p.eps();143    }144 145    if (mode == reasoning_mode::TAG_BASED || mode == reasoning_mode::TOOLS_ONLY) {146        if (!end.empty()) {147            if (!start.empty()) {148                // Standard tag-based: optional(<think>reasoning</think>)149                return p.optional(p.optspace(start) + p.reasoning(p.until(trim_whitespace(end))) + p.optspace(end));150            }151            // Delimiter-style (empty start)152            return p.optional(p.reasoning(p.until(trim_whitespace(end))) + p.optspace(end));153        }154    }155 156    return p.eps();157}158 159common_peg_parser analyze_content::build_parser(parser_build_context & ctx) const {160    auto & p = ctx.p;161 162    if (is_always_wrapped()) {163        if (ctx.extracting_reasoning) {164            return ctx.reasoning_parser + start + p.content(p.until(end)) + end + p.end();165        }166        return p.content(p.until(start)) + start + p.content(p.until(end)) + end + p.end();167    }168    return ctx.reasoning_parser + p.content(p.rest()) + p.end();169}170 171common_peg_parser analyze_content::build_optional_wrapped(parser_build_context & ctx) const {172    auto & p = ctx.p;173 174    if (is_always_wrapped()) {175        return p.optional(start + p.content(p.until(end)) + end);176    }177    return p.eps();178}179 180common_peg_parser analyze_tools::build_parser(parser_build_context & ctx) const {181    switch (format.mode) {182        case tool_format::JSON_NATIVE:183            return build_tool_parser_json_native(ctx);184        case tool_format::TAG_WITH_JSON:185            return build_tool_parser_tag_json(ctx);186        case tool_format::TAG_WITH_TAGGED:187            return build_tool_parser_tag_tagged(ctx);188        default:189            LOG_ERR("[ERROR] Template seems to support tool calls, but failed to determine tool format. Tool calling will not work properly. "190                "Check for a fixed template for your model in the models/templates directory of your llama.cpp installation or "191                "report an issue at https://github.com/ggml-org/llama.cpp/issues\n");192            return ctx.p.eps();193    }194}195 196common_peg_parser analyze_tools::build_tool_parser_json_native(parser_build_context & ctx) const {197    auto &       p           = ctx.p;198    const auto & inputs      = ctx.inputs;199 200    // Build effective field names with dot notation if function_field is set201    std::string name_field = format.name_field;202    std::string args_field = format.args_field;203 204    if (!format.function_field.empty() && format.function_field != "function" &&205        name_field.find('.') == std::string::npos) {206        name_field = format.function_field + "." + name_field;207        args_field = format.function_field + "." + args_field;208    }209 210    auto tools_parser = p.eps();211    if (format.section_start.empty() && !format.per_call_start.empty()) {212        auto single_tool_parser = p.standard_json_tools(213            format.per_call_start, format.per_call_end, inputs.tools, inputs.parallel_tool_calls,214            inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED, name_field, args_field, format.tools_array_wrapped,215            format.fun_name_is_key, format.id_field, format.gen_id_field, format.parameter_order, format.openai_wrapper_trigger);216        tools_parser = p.trigger_rule("tool-calls", p.one_or_more(single_tool_parser + p.space()));217    } else {218        tools_parser = p.standard_json_tools(219            format.section_start, format.section_end, inputs.tools, inputs.parallel_tool_calls,220            inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED, name_field, args_field, format.tools_array_wrapped,221            format.fun_name_is_key, format.id_field, format.gen_id_field, format.parameter_order, format.openai_wrapper_trigger);222    }223 224    // Handle content wrappers if present225    if (ctx.content && ctx.content->is_always_wrapped()) {226        auto wrapped_content = ctx.content->build_optional_wrapped(ctx);227        return ctx.reasoning_parser + wrapped_content + tools_parser + p.end();228    }229 230    std::string tool_start = "{";231    if (!format.section_start.empty()) {232        tool_start = format.section_start;233    } else if (!format.per_call_start.empty()) {234        tool_start = format.per_call_start;235    }236 237    return ctx.reasoning_parser + p.optional(p.content(p.until(tool_start))) + tools_parser + p.end();238}239 240common_peg_parser analyze_tools::build_func_parser(common_chat_peg_builder & p, const std::string & name,241                                                    const common_peg_parser & call_id_section, bool have_call_id,242                                                    const common_peg_parser & args,243                                                    std::optional<common_peg_parser> atomic_peek) const {244    auto              open           = p.tool_open(function.name_prefix + p.tool_name(p.literal(name)) + function.name_suffix);245    bool              matched_atomic = false;246    common_peg_parser func_parser    = p.eps();247 248    if (!function.args_separator.empty()) {249        open = open + p.space() + p.literal(function.args_separator);250    }251 252    if (!function.name_suffix.empty()) {253        func_parser    = open + call_id_section + p.space() + args;254        matched_atomic = true;255    } else if (have_call_id) {256        func_parser    = p.atomic(open + call_id_section) + p.space() + args;257        matched_atomic = true;258    } else if (atomic_peek.has_value()) {259        func_parser    = p.atomic(open + call_id_section + p.space() + *atomic_peek) + args;260        matched_atomic = true;261    } else {262        func_parser = open + call_id_section + p.space() + args;263    }264 265    if (!function.close.empty()) {266        func_parser = func_parser + p.space() + p.tool_close(p.literal(function.close));267    } else if (!format.per_call_end.empty()) {268        // When there's no func_close but there is a per_call_end marker, use peek() to ensure269        // we only emit tool_close when we can actually see the closing marker. This prevents270        // premature closing during partial parsing when we've seen e.g. "</" which could be271        // either "</tool_call>" (end) or "<arg_key>" prefix that failed to match.272        // Laguna (v4): the model may emit whitespace between the last </arg_value> and273        // </tool_call> even though the template renders them tight. Tolerate optional274        // leading space in the close lookahead so the tool call still closes.275        auto close_peek = arguments.tolerate_intertag_whitespace276                              ? p.peek(p.space() + p.literal(format.per_call_end))277                              : p.peek(p.literal(format.per_call_end));278        func_parser = func_parser + p.tool_close(close_peek);279    } else {280        func_parser = func_parser + p.tool_close(p.space());  // force this to process tool closing callbacks in mapper281    }282    if (!matched_atomic) {283        func_parser = p.atomic(func_parser);284    }285    return func_parser;286}287 288common_peg_parser analyze_tools::build_tool_parser_tag_json(parser_build_context & ctx) const {289    auto &       p           = ctx.p;290    const auto & inputs      = ctx.inputs;291 292    common_peg_parser tool_choice = p.choice();293 294    foreach_function(inputs.tools, [&](const json & tool) {295        const auto & func   = tool.at("function");296        std::string  name   = func.at("name");297        const auto   schema = common_chat_tool_parameters(func);298 299        // Build call_id parser based on position (if supported)300        bool have_call_id = false;301        common_peg_parser call_id_section = p.eps();302        if (call_id.pos == call_id_position::BETWEEN_FUNC_AND_ARGS && !call_id.prefix.empty() &&303            (!call_id.suffix.empty() || !arguments.start.empty())) {304            if (!call_id.suffix.empty()) {305                call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix))) + call_id.suffix;306            } else {307                call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(arguments.start)));308            }309            have_call_id = true;310        }311        auto args_parser = p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema));312        if (!arguments.start.empty()) {313            args_parser = p.literal(arguments.start) + args_parser;314        }315        if (!arguments.end.empty()) {316            args_parser = args_parser + p.literal(arguments.end);317        }318 319        auto atomic_peek = !arguments.start.empty() ? std::optional(p.peek(p.literal(arguments.start))) : std::nullopt;320        auto func_parser = build_func_parser(p, name, call_id_section, have_call_id, args_parser, atomic_peek);321        tool_choice |= p.rule("tool-" + name, func_parser);322    });323 324    auto require_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED;325 326    common_peg_parser tool_calls = p.eps();327 328    if (!format.per_call_start.empty()) {329        auto wrapped_call = format.per_call_start + tool_choice + format.per_call_end;330        if (inputs.parallel_tool_calls) {331            tool_calls = p.trigger_rule("tool-call", wrapped_call + p.zero_or_more(p.space() + wrapped_call));332        } else {333            tool_calls = p.trigger_rule("tool-call", wrapped_call);334        }335        if (!format.section_start.empty()) {336            tool_calls = p.trigger_rule("tool-calls",337                                        p.literal(format.section_start) + p.space() + tool_calls + p.space() +338                                            (format.section_end.empty() ? p.end() : p.literal(format.section_end)));339        }340    } else {341        std::string separator = ", ";  // Default342        if (inputs.parallel_tool_calls) {343            tool_calls = p.trigger_rule("tool-call", format.section_start + tool_choice +344                                                         p.zero_or_more(separator + tool_choice) + format.section_end);345        } else {346            tool_calls = p.trigger_rule("tool-call", format.section_start + tool_choice + format.section_end);347        }348    }349 350    if (!require_calls) {351        tool_calls = p.optional(tool_calls);352    }353 354    std::string trigger_marker       = !format.section_start.empty() ? format.section_start : format.per_call_start;355    auto        content_before_tools = trigger_marker.empty() ? p.eps() : p.until(trigger_marker);356    return ctx.reasoning_parser + p.optional(p.content(content_before_tools)) + tool_calls + p.end();357}358 359common_peg_parser analyze_tools::build_tool_parser_tag_tagged(parser_build_context & ctx) const {360    auto &       p           = ctx.p;361    const auto & inputs      = ctx.inputs;362 363    auto until_suffix = p.rule("until-suffix", p.until(arguments.value_suffix));364 365    common_peg_parser tool_choice = p.choice();366 367    foreach_function(inputs.tools, [&](const json & tool) {368        const auto & func = tool.at("function");369        std::string  name = func.at("name");370 371        // Build parser for each argument, separating required and optional372        std::vector<common_peg_parser> required_parsers;373        std::vector<common_peg_parser> optional_parsers;374        foreach_parameter(func, [&](const common_chat_schema_property & param, const common_chat_schema_document_ptr & doc) {375            auto arg =376                p.tool_arg(p.tool_arg_open(arguments.name_prefix + p.tool_arg_name(p.literal(param.name)) +377                                           arguments.name_suffix) +378                           arguments.value_prefix +379                           (param.schema->may_be_string() ?380                                p.ac(p.tool_arg_string_value(until_suffix) +381                                    p.tool_arg_close(p.literal(arguments.value_suffix)), arguments.value_suffix) :382                                (p.tool_arg_json_value(p.schema(383                                    p.json(), "tool-" + name + "-arg-" + param.name + "-schema", doc, *param.schema)) +384                                    p.tool_arg_close(p.literal(arguments.value_suffix)))));385 386            auto named_arg = p.rule("tool-" + name + "-arg-" + param.name, arg);387            if (param.required) {388                required_parsers.push_back(named_arg);389            } else {390                optional_parsers.push_back(named_arg);391            }392        });393 394        // Build required arg sequence in definition order395        common_peg_parser args_seq = p.eps();396        for (size_t i = 0; i < required_parsers.size(); i++) {397            if (i > 0) {398                args_seq = args_seq + p.space();399            }400            args_seq = args_seq + required_parsers[i];401        }402 403        // Build optional args with flexible ordering404        if (!optional_parsers.empty()) {405            common_peg_parser any_opt = p.choice();406            for (const auto & opt : optional_parsers) {407                any_opt |= opt;408            }409            args_seq = args_seq + p.repeat(p.space() + any_opt, 0, -1);410        }411 412        if (!arguments.start.empty()) {413            args_seq = p.literal(arguments.start) + args_seq;414        }415        if (!arguments.end.empty()) {416            args_seq = args_seq + p.literal(arguments.end);417        }418 419        // Build call_id parser based on position (if supported)420        common_peg_parser call_id_section = p.eps();421        bool have_call_id = false;422        if (call_id.pos == call_id_position::BETWEEN_FUNC_AND_ARGS && !call_id.prefix.empty() &&423            (!call_id.suffix.empty() || !arguments.start.empty())) {424            have_call_id = true;425            if (!call_id.suffix.empty()) {426                call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(call_id.suffix)) + call_id.suffix);427            } else {428                call_id_section = p.optional(call_id.prefix + p.tool_id(p.until(arguments.start)));429            }430        }431 432        // Only peek for an arg tag when there are required args that must follow.433        // When all args are optional, the model may emit no arg tags at all (#20650).434        auto atomic_peek = (!arguments.name_prefix.empty() && !required_parsers.empty()) ?435            std::optional(p.peek(p.literal(arguments.name_prefix))) : std::nullopt;436        auto func_parser = build_func_parser(p, name, call_id_section, have_call_id, args_seq, atomic_peek);437        tool_choice |= p.rule("tool-" + name, func_parser);438    });439 440    auto require_tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED;441 442    common_peg_parser tool_calls = p.eps();443 444    if (!format.per_call_start.empty()) {445        auto wrapped_call = format.per_call_start + p.space() + tool_choice + p.space() + format.per_call_end;446        if (inputs.parallel_tool_calls) {447            tool_calls = p.trigger_rule("tool-call", wrapped_call + p.zero_or_more(p.space() + wrapped_call) + p.space());448        } else {449            tool_calls = p.trigger_rule("tool-call", wrapped_call + p.space());450        }451        if (!format.section_start.empty()) {452            tool_calls = p.trigger_rule("tool-calls",453                                        p.literal(format.section_start) + p.space() + tool_calls + p.space() +454                                            (format.section_end.empty() ? p.end() : p.literal(format.section_end) + p.space()));455        }456    } else {457        std::string separator = ", ";  // Default458 459        if (inputs.parallel_tool_calls) {460            tool_calls = p.trigger_rule("tool-call", format.section_start + p.space() + tool_choice +461                                                         p.zero_or_more(separator + tool_choice) + p.space() +462                                                         format.section_end);463        } else {464            tool_calls = p.trigger_rule(465                "tool-call", format.section_start + p.space() + tool_choice + p.space() + format.section_end);466        }467    }468 469    if (!require_tools) {470        tool_calls = p.optional(tool_calls);471    }472 473    std::string trigger_marker       = !format.section_start.empty() ? format.section_start : format.per_call_start;474    auto        content_before_tools = trigger_marker.empty() ? p.eps() : p.until(trigger_marker);475    return ctx.reasoning_parser + p.optional(p.content(content_before_tools)) + tool_calls + p.end();476}477 478}  // namespace autoparser479