CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 3d agoView on Hugging Face
0likes1.1kdownloads
chat-auto-parser.h454 linesDownload Raw Back to common
1#pragma once2 3#include "chat.h"4#include "common.h"5#include "jinja/caps.h"6#include "peg-parser.h"7#include "json.h"8 9#include <chrono>10#include <optional>11#include <string>12#include <utility>13#include <vector>14 15using json = common_json;16 17class common_chat_peg_builder;18 19// ============================================================================20// Parameters for template application (low-level, used by diff analysis)21// ============================================================================22struct template_params {23    json                messages;24    json                tools;25    bool                add_generation_prompt = false;26    bool                enable_thinking       = true;27    std::optional<json> extra_context         = std::nullopt;28};29 30struct diff_split {31    std::string prefix;32    std::string suffix;33    std::string left;34    std::string right;35 36    bool operator==(struct diff_split & other) const {37        return prefix == other.prefix && suffix == other.suffix && left == other.left && right == other.right;38    }39};40 41// Result of compare_variants containing diff and original outputs42struct compare_variants_result {43    diff_split  diff;44    std::string output_A;45    std::string output_B;46};47 48namespace autoparser {49 50// ============================================================================51// High-level params for parser generation52// ============================================================================53 54struct generation_params {55    json                                  messages;56    json                                  tools;57    common_chat_tool_choice               tool_choice = COMMON_CHAT_TOOL_CHOICE_AUTO;58    json                                  json_schema;59    bool                                  parallel_tool_calls = true;60    common_reasoning_format               reasoning_format    = COMMON_REASONING_FORMAT_AUTO;61    bool                                  stream              = true;62    std::string                           grammar;63    bool                                  add_generation_prompt  = false;64    common_chat_continuation              continue_final_message = COMMON_CHAT_CONTINUATION_NONE;65    common_chat_msg                       continue_msg;66    bool                                  enable_thinking        = true;67    std::chrono::system_clock::time_point now                    = std::chrono::system_clock::now();68    json                                  extra_context;69    bool                                  add_bos       = false;70    bool                                  add_eos       = false;71    bool                                  is_inference  = true;72    bool                                  add_inference = false;73    bool                                  mark_input    = true;  // whether to mark input strings in the jinja context74 75    bool has_continuation() const {76        return continue_final_message != COMMON_CHAT_CONTINUATION_NONE && !continue_msg.empty();77    }78};79 80// ============================================================================81// Analysis Result Enums82// ============================================================================83 84// Reasoning handling mode (derived from R1-R3 comparisons)85enum class reasoning_mode {86    NONE,           // No reasoning markers detected87    TAG_BASED,      // Tag-based: <think>...</think> (start can be empty for delimiter-style)88    TOOLS_ONLY      // Only reason on tool calls, not on normal content89};90 91inline std::ostream & operator<<(std::ostream & os, const reasoning_mode & mode) {92    switch (mode) {93        case reasoning_mode::NONE:94            return os << "NONE";95        case reasoning_mode::TAG_BASED:96            return os << "TAG_BASED";97        case reasoning_mode::TOOLS_ONLY:98            return os << "TOOLS_ONLY";99        default:100            return os << "UNKNOWN";101    }102}103 104// Content wrapping mode (derived from C1 comparison)105enum class content_mode {106    PLAIN,                   // No content markers107    ALWAYS_WRAPPED,          // Content always wrapped with markers108    WRAPPED_WITH_REASONING,  // Content wrapped only when reasoning present109};110 111inline std::ostream & operator<<(std::ostream & os, const content_mode & mode) {112    switch (mode) {113        case content_mode::PLAIN:114            return os << "PLAIN";115        case content_mode::ALWAYS_WRAPPED:116            return os << "ALWAYS_WRAPPED";117        case content_mode::WRAPPED_WITH_REASONING:118            return os << "WRAPPED_WITH_REASONING";119        default:120            return os << "UNKNOWN";121    }122}123 124// Call ID position in tool calls (for non-JSON formats)125enum class call_id_position {126    NONE,                   // No call ID support detected127    PRE_FUNC_NAME,          // Call ID before function name: [CALL_ID]id[FUNC]name{args}128    BETWEEN_FUNC_AND_ARGS,  // Call ID between function and args: [FUNC]name[CALL_ID]id{args}129    POST_ARGS,              // Call ID after arguments: [FUNC]name{args}[CALL_ID]id130};131 132inline std::ostream & operator<<(std::ostream & os, const call_id_position & pos) {133    switch (pos) {134        case call_id_position::NONE:135            return os << "NONE";136        case call_id_position::PRE_FUNC_NAME:137            return os << "PRE_FUNC_NAME";138        case call_id_position::BETWEEN_FUNC_AND_ARGS:139            return os << "BETWEEN_FUNC_AND_ARGS";140        case call_id_position::POST_ARGS:141            return os << "POST_ARGS";142        default:143            return os << "UNKNOWN";144    }145}146 147// Tool call format classification (derived from T1-T5, A1-A3 comparisons)148enum class tool_format {149    NONE,             // No tool support detected150    JSON_NATIVE,      // Pure JSON: {"name": "X", "arguments": {...}}151    TAG_WITH_JSON,    // Tag-based with JSON args: <function=X>{...}</function>152    TAG_WITH_TAGGED,  // Tag-based with tagged args: <param=key>value</param>153};154 155inline std::ostream & operator<<(std::ostream & os, const tool_format & format) {156    switch (format) {157        case tool_format::NONE:158            return os << "NONE";159        case tool_format::JSON_NATIVE:160            return os << "JSON_NATIVE";161        case tool_format::TAG_WITH_JSON:162            return os << "TAG_WITH_JSON";163        case tool_format::TAG_WITH_TAGGED:164            return os << "TAG_WITH_TAGGED";165        default:166            return os << "UNKNOWN";167    }168}169 170// ============================================================================171// Sub-structs for tool analysis172// ============================================================================173 174struct tool_format_analysis {175    tool_format mode = tool_format::NONE;176 177    std::string section_start;   // e.g., "<tool_call>", "[TOOL_CALLS]", ""178    std::string section_end;     // e.g., "</tool_call>", ""179    std::string per_call_start;  // e.g., "<|tool_call_begin|>", "" (for multi-call templates)180    std::string per_call_end;    // e.g., "<|tool_call_end|>", ""181 182    bool fun_name_is_key = false;       // In JSON format function name is JSON key, i.e. { "<funname>": { ... arguments ... } }183    bool tools_array_wrapped = false;   // Tool calls wrapped in JSON array [...]184    bool openai_wrapper_trigger = false;  // model emits the OpenAI function wrapper, trigger on it185 186    std::string              function_field = "function";187    std::string              name_field     = "name";188    std::string              args_field     = "arguments";189    std::string              id_field;190    std::string              gen_id_field;191    std::vector<std::string> parameter_order;192};193 194struct tool_function_analysis {195    std::string name_prefix;     // e.g., "<function=", "\"name\": \"", "functions."196    std::string name_suffix;     // e.g., ">", "\"", ":0"197    std::string args_separator;  // e.g., "<tool_sep>" (marker between function name and arguments)198    std::string close;           // e.g., "</function>", "" (for tag-based)199};200 201struct tool_arguments_analysis {202    std::string start;          // e.g., "<|tool_call_argument_begin|>", "<args>"203    std::string end;            // e.g., "<|tool_call_argument_end|>", "</args>"204    std::string name_prefix;   // e.g., "<param=", "<arg_key>", "\""205    std::string name_suffix;   // e.g., ">", "</arg_key>", "\":"206    std::string value_prefix;  // e.g., "", "<arg_value>", ""207    std::string value_suffix;  // e.g., "</param>", "</arg_value>", ""208    std::string separator;     // e.g., "", "\n", ","209    bool tolerate_intertag_whitespace = false; // Laguna: accept optional whitespace between arg tags210};211 212struct tool_id_analysis {213    call_id_position pos = call_id_position::NONE;214 215    std::string prefix;  // e.g., "[CALL_ID]" (marker before call ID value)216    std::string suffix;  // e.g., "" (marker after call ID value, before next section)217};218 219// ============================================================================220// Parser build context (shared interface for build_parser methods)221// ============================================================================222 223struct analyze_content;224struct analyze_reasoning;225 226struct parser_build_context {227    common_chat_peg_builder & p;228    const generation_params &         inputs;229    common_peg_parser                 reasoning_parser;230    bool                              extracting_reasoning = false;231    const analyze_reasoning *         reasoning            = nullptr;232    const analyze_content *           content              = nullptr;233 234    parser_build_context(common_chat_peg_builder & p, const generation_params & inputs);235};236 237// ============================================================================238// Base class for analyzers with parser building239// ============================================================================240 241struct analyze_base {242    virtual ~analyze_base() = default;243    virtual common_peg_parser build_parser(parser_build_context & ctx) const = 0;244 245  protected:246    const common_chat_template * tmpl = nullptr;247 248    analyze_base() = default;249    explicit analyze_base(const common_chat_template & tmpl) : tmpl(&tmpl) {}250};251 252// ============================================================================253// Reasoning analyzer254// ============================================================================255 256struct analyze_reasoning : analyze_base {257    reasoning_mode mode = reasoning_mode::NONE;258 259    std::string start;  // e.g., "<think>", "[THINK]", "<|START_THINKING|>", ""260    std::string end;    // e.g., "</think>", "[BEGIN FINAL RESPONSE]", "<|END_THINKING|>"261 262    analyze_reasoning() = default;263    analyze_reasoning(const common_chat_template & tmpl, bool supports_tools);264    analyze_reasoning(std::string start_, std::string end_) : start(std::move(start_)), end(std::move(end_)) {}265 266    common_peg_parser build_parser(parser_build_context & ctx) const override;267 268  private:269    // Look for reasoning markers in rendered content270    void compare_reasoning_presence();271 272    // Compare generation prompt with enable_thinking=true vs false273    void compare_thinking_enabled();274 275    // Check if reasoning is always possible or only in tool calls276    void compare_reasoning_scope();277};278 279// ============================================================================280// Content analyzer281// ============================================================================282 283struct analyze_content : analyze_base {284    content_mode mode = content_mode::PLAIN;285 286    std::string start;  // e.g., "<response>", ">>>all\n", ""287    std::string end;    // e.g., "</response>", ""288 289    bool requires_nonnull_content = false;290 291    analyze_content() = default;292    analyze_content(const common_chat_template & tmpl, const analyze_reasoning & reasoning);293 294    common_peg_parser build_parser(parser_build_context & ctx) const override;295 296    bool is_always_wrapped() const;297    common_peg_parser build_optional_wrapped(parser_build_context & ctx) const;298};299 300// ============================================================================301// Tool analyzer302// ============================================================================303 304struct analyze_tools : analyze_base {305    tool_format_analysis    format;306    tool_function_analysis  function;307    tool_arguments_analysis arguments;308    tool_id_analysis        call_id;309 310    analyze_tools() = default;311    analyze_tools(const common_chat_template & tmpl,312                  const jinja::caps &          caps,313                  const analyze_reasoning &    reasoning);314 315    common_peg_parser build_parser(parser_build_context & ctx) const override;316 317  private:318    // Extract tool calling 'haystack' for further analysis and delegate further analysis based on format319    void analyze_tool_calls(const analyze_reasoning & reasoning, bool supports_parallel_tool_calls);320 321    // Analyze format based on position of function and argument name in needle322    void analyze_tool_call_format(const std::string &       haystack,323                                  const std::string &       fun_name_needle,324                                  const std::string &       arg_name_needle,325                                  const analyze_reasoning & reasoning,326                                  bool                      supports_parallel_tool_calls);327 328    // Analyze specifics of JSON native format (entire tool call is a JSON object)329    void analyze_tool_call_format_json_native(const std::string & clean_haystack,330                                              const std::string & fun_name_needle,331                                              const std::string & arg_name_needle);332 333    // Check if parallel calls in JSON native format array wrapped or tag wrapped334    void analyze_json_native_parallel_calls();335 336    // Analyze specifics of non-JSON native format (tags for function name or for function name and arguments)337    void analyze_tool_call_format_non_json(const std::string & clean_haystack,338                                           const std::string & fun_name_needle);339 340    // Check for and extract specific per-call markers for non-native-JSON templates with parallel call support341    void check_per_call_markers();342 343    // Extract function name markers344    void extract_function_markers();345 346    // Delegates to separate functions for: separator analysis, argument name analysis, argument value analysis347    void analyze_arguments();348 349    // Extract argument name markers350    void extract_argument_name_markers();351 352    // Extract argument value markers353    void extract_argument_value_markers();354 355    // Extract argument separator, if specified (eg. <arg=foo>...</arg><sep><arg=bar>...</arg>)356    void extract_argument_separator();357 358    // Extract argument wrapper markers, if present (eg. '<args><arg=foo>...</arg><arg=bar>...</arg></args>')359    void extract_args_markers();360 361    // Extract call ID markers, if present362    void extract_call_id_markers();363 364    // Per-format tool parser builders365    common_peg_parser build_tool_parser_json_native(parser_build_context & ctx) const;366    common_peg_parser build_tool_parser_tag_json(parser_build_context & ctx) const;367    common_peg_parser build_tool_parser_tag_tagged(parser_build_context & ctx) const;368 369    // Shared helper: builds func_parser from open+call_id+args, handling atomic wrapping and close.370    // atomic_peek: if present, used as the peek expression in the third atomicity branch.371    common_peg_parser build_func_parser(common_chat_peg_builder & p, const std::string & name,372                                        const common_peg_parser & call_id_section, bool have_call_id,373                                        const common_peg_parser & args,374                                        std::optional<common_peg_parser> atomic_peek) const;375};376 377// ============================================================================378// Main autoparser class379// ============================================================================380 381struct autoparser {382    jinja::caps          jinja_caps;383    std::string          user_start;384    std::string          assistant_start;385    analyze_reasoning    reasoning;386    analyze_content      content;387    analyze_tools        tools;388    bool                 analysis_complete = false;389 390    // Preserved tokens for tokenizer (union of all non-empty markers)391    std::vector<std::string> preserved_tokens;392    std::vector<std::string> additional_stops;  // literal stop strings (e.g. Laguna </assistant>) caught however tokenized393 394    autoparser() = default;395 396    // Find the starting marker for the user message and assistant message397    std::string detect_user_start_marker(const common_chat_template & tmpl);398    std::string detect_assistant_start_marker(const common_chat_template & tmpl);399 400    // Run full differential analysis on a template401    void analyze_template(const common_chat_template & tmpl);402 403    // Build the PEG parser for this template404    common_peg_arena build_parser(const generation_params & inputs, const std::string & generation_prompt) const;405 406  private:407    // Collect tokens from entire analysis to preserve408    void collect_preserved_tokens();409};410 411// ============================================================================412// Parser generator413// ============================================================================414 415class peg_generator {416  public:417    static common_chat_params generate_parser(const common_chat_template &    tmpl,418                                              const struct generation_params & inputs);419 420    static common_chat_params generate_parser(const common_chat_template &    tmpl,421                                              const struct generation_params & inputs,422                                              const autoparser &              autoparser);423};424 425}  // namespace autoparser426 427enum segment_type { TEXT, MARKER };428 429inline std::ostream & operator<<(std::ostream & os, const segment_type & type) {430    switch (type) {431        case segment_type::TEXT:432            return os << "TEXT";433        case segment_type::MARKER:434            return os << "MARKER";435        default:436            return os << "UNKNOWN";437    }438}439 440struct segment {441    segment_type type;442    std::string  value;443 444    segment(segment_type type, std::string value) : type(type), value(std::move(value)) {}445 446    bool operator==(const segment & other) const {447        return type == other.type && value == other.value;448    }449 450    bool operator!=(const segment & other) const {451        return !(*this == other);452    }453};454