Felipe97/llama-cpp-compiled
01.1k
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 