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-diff-analyzer.cpp1628 linesDownload Raw Back to common
1#include "chat-auto-parser.h"2#include "chat-auto-parser-helpers.h"3#include "chat-peg-parser.h"4#include "chat.h"5#include "common.h"6#include "log.h"7#include "nlohmann/json.hpp"8#include "peg-parser.h"9 10#include <algorithm>11#include <cctype>12#include <ostream>13#include <sstream>14 15#define ANSI_RESET  "\033[0m"16#define ANSI_PURPLE "\033[1m\x1b[38;5;126m"17#define ANSI_ORANGE "\033[1m\x1b[38;5;214m"18#define ANSI_RED    "\033[1m\x1b[38;5;196m"19 20using json = nlohmann::ordered_json;21 22namespace autoparser {23 24static const std::string FUN_FIRST = "FFF_FIRST_FUN_F";25static const std::string FUN_SECOND = "SSS_SECOND_FUN_S";26static const std::string ARG_FIRST = "AA_ARG_FST_AA";27static const std::string ARG_SECOND = "BB_ARG_SND_BB";28static const std::string USER_MSG = "U_USER_MSG Hello END_U";29static const std::string USER_MSG_TWO = "V_USER_MSG Hello END_V";30static const std::string ASSISTANT_MSG = "A_ASST_MSG I can help END_A";31static const std::string THINKING_CONTENT = "REASON_PART I am thinking END_R";32static const std::string CALL_ID_001 = "call00001";33static const std::string CALL_ID_002 = "call00002";34static const std::string CALL_ID_999 = "call99999";35 36static std::vector<std::function<void(const common_chat_template & tmpl, autoparser &)>> workarounds(37    { // Old reasoning Qwen templates - they don't really display reasoning content, but we still want to38      // support reasoning on them39      [](const common_chat_template & tmpl, autoparser & analysis) -> void {40          if (tmpl.src.find("content.split('</think>')") != std::string::npos &&41              tmpl.src.find("reasoning_content") == std::string::npos &&42              tmpl.src.find("<SPECIAL_12>") == std::string::npos &&43              analysis.reasoning.mode == reasoning_mode::NONE) {44              analysis.reasoning.mode  = reasoning_mode::TAG_BASED;45              analysis.reasoning.start = "<think>";46              analysis.reasoning.end   = "</think>";47              analysis.preserved_tokens.push_back("<think>");48              analysis.preserved_tokens.push_back("</think>");49              LOG_DBG(ANSI_ORANGE "[Patch: old Qwen/Deepseek thinking template]\n" ANSI_RESET);50          }51      },52      // Granite 3.3, with separate reasoning and content markers53      [](const common_chat_template & tmpl, autoparser & analysis) -> void {54          if (tmpl.src.find("Write your thoughts between <think></think> and write your response between "55                            "<response></response>") != std::string::npos) {56              analysis.reasoning.mode  = reasoning_mode::TAG_BASED;57              analysis.reasoning.start = "<think>";58              analysis.reasoning.end   = "</think>";59              analysis.preserved_tokens.push_back("<think>");60              analysis.preserved_tokens.push_back("</think>");61              analysis.content.mode  = content_mode::WRAPPED_WITH_REASONING;62              analysis.content.start = "<response>";63              analysis.content.end   = "</response>";64              analysis.preserved_tokens.push_back("<response>");65              analysis.preserved_tokens.push_back("</response>");66              LOG_DBG(ANSI_ORANGE "[Patch: Granite 3.3]\n" ANSI_RESET);67          }68      },69      // Cohere Command R+ - content wrapped in <|CHATBOT_TOKEN|>...<|END_OF_TURN_TOKEN|>70      [](const common_chat_template & tmpl, autoparser & analysis) -> void {71          if (tmpl.src.find("<|CHATBOT_TOKEN|>") != std::string::npos &&72              tmpl.src.find("<|END_OF_TURN_TOKEN|>") != std::string::npos && analysis.content.start.empty()) {73              analysis.content.mode  = content_mode::ALWAYS_WRAPPED;74              analysis.content.start = "<|CHATBOT_TOKEN|>";75              analysis.content.end   = "<|END_OF_TURN_TOKEN|>";76              analysis.preserved_tokens.push_back("<|CHATBOT_TOKEN|>");77              analysis.preserved_tokens.push_back("<|END_OF_TURN_TOKEN|>");78              analysis.user_start = "<|START_OF_TURN_TOKEN|><|USER_TOKEN|>";79              LOG_DBG(ANSI_ORANGE "[Patch: Cohere Command R+]\n" ANSI_RESET);80          }81      },82      // Functionary - no tool call section delimiter83      [](const common_chat_template & tmpl, autoparser & analysis) -> void {84          if (tmpl.src.find("set has_code_interpreter = tools | selectattr(\"type\", \"equalto\", "85                            "\"code_interpreter\") | list | length > 0") != std::string::npos) {86              analysis.content.mode                = content_mode::PLAIN;87              analysis.content.end                 = "";88              analysis.tools.function.name_prefix  = "";89              analysis.tools.format.section_start  = "";90              analysis.tools.format.section_end    = "";91              analysis.tools.format.per_call_start = "<function=";92              analysis.tools.format.per_call_end   = "</function>";93              analysis.tools.function.close        = "";94              analysis.preserved_tokens.clear();95              analysis.preserved_tokens.push_back("<|eot_id|>");96              analysis.preserved_tokens.push_back("<|eom_id|>");97              analysis.preserved_tokens.push_back("<function=");98              analysis.preserved_tokens.push_back(">");99              analysis.preserved_tokens.push_back("</function>");100              LOG_DBG(ANSI_ORANGE "[Patch: Functionary 3.1]\n" ANSI_RESET);101          }102      },103      // DeepSeek-R1-Distill-Qwen104      [](const common_chat_template & tmpl, autoparser & analysis) -> void {105          if (tmpl.src.find(106                  "{{'<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>' + tool['type'] + '<|tool▁sep|>'") !=107              std::string::npos) {108              analysis.tools.format.section_start  = "<|tool▁calls▁begin|>";109              analysis.tools.format.section_end    = "<|tool▁calls▁end|>";110              analysis.tools.format.per_call_start = "<|tool▁call▁begin|>function";111              analysis.tools.function.name_prefix  = "<|tool▁sep|>";112              analysis.tools.format.per_call_end   = "<|tool▁call▁end|>";113              analysis.tools.function.close        = "```";114              LOG_DBG(ANSI_ORANGE "[Patch: DeepSeek-R1-Distill-Qwen]\n" ANSI_RESET);115          }116      },117      // Nemotron Nano v2118      [](const common_chat_template & tmpl, autoparser & analysis) -> void {119          if (tmpl.src.find("<SPECIAL_10>") != std::string::npos && tmpl.src.find("<SPECIAL_11>") != std::string::npos &&120              tmpl.src.find("<SPECIAL_12>") != std::string::npos && tmpl.src.find("<TOOL_RESPONSE>") != std::string::npos) {121 122              analysis.tools.format.mode           = tool_format::JSON_NATIVE;123              analysis.tools.format.section_start  = "";124              analysis.tools.format.section_end    = "";125              analysis.tools.format.per_call_start = "<TOOLCALL>";126              analysis.tools.format.per_call_end   = "</TOOLCALL>";127              analysis.tools.format.tools_array_wrapped = true;128              analysis.content.mode                = content_mode::PLAIN;129              analysis.content.start               = "";130              analysis.content.end                 = "";131              analysis.reasoning.mode              = reasoning_mode::TAG_BASED;132              analysis.reasoning.start             = "<think>\n";133              analysis.reasoning.end               = "</think>";134              analysis.assistant_start             = "<SPECIAL_11>Assistant";135              analysis.user_start                  = "<SPECIAL_11>User";136              analysis.preserved_tokens.clear();137              analysis.preserved_tokens.push_back("<SPECIAL_11>");138              analysis.preserved_tokens.push_back("</think>");139              analysis.preserved_tokens.push_back("<TOOLCALL>");140              analysis.preserved_tokens.push_back("</TOOLCALL>");141              LOG_DBG(ANSI_ORANGE "[Patch: Nemotron Nano v2]\n" ANSI_RESET);142          }143      },144      // Fireworks145      [](const common_chat_template & tmpl, autoparser & analysis) -> void {146          if (tmpl.src.find("{%- set system_prompt = '<|start_header_id|>' + 'system' + '<|end_header_id|>\\n\\n'"147            " + message['content'] | trim + '\\n' + system_prompt_suffix + '<|eot_id|>' -%}") != std::string::npos) {148              analysis.assistant_start             = "<|start_header_id|>assistant<|end_header_id|>";149              analysis.user_start                  = "<|start_header_id|>user<|end_header_id|>";150              LOG_DBG(ANSI_ORANGE "[Patch: Fireworks v2]\n" ANSI_RESET);151          }152      },153      // Solar Open154      [](const common_chat_template & tmpl, autoparser & analysis) -> void {155          if (tmpl.src.find("<|begin|>assistant<|think|><|end|>") != std::string::npos) {156              analysis.assistant_start             = "<|begin|>assistant";157              LOG_DBG(ANSI_ORANGE "[Patch: Solar Open]\n" ANSI_RESET);158          }159      },160      // Apriel 1.6161      [](const common_chat_template & tmpl, autoparser & analysis) -> void {162          if (tmpl.src.find("if not loop.last and '[BEGIN FINAL RESPONSE]' in asst_text") != std::string::npos) {163              analysis.user_start                  = "<|begin_user|>";164              analysis.assistant_start             = "<|begin_assistant|>";165              LOG_DBG(ANSI_ORANGE "[Patch: Apriel 1.6]\n" ANSI_RESET);166          }167      },168      // template uses the JSON {name, parameters} tool instruction, emits the OpenAI function wrapper169      [](const common_chat_template & tmpl, autoparser & analysis) -> void {170          if (tmpl.src.find("Respond in the format {\"name\": function name") != std::string::npos &&171              tmpl.src.find("Do not use variables.") != std::string::npos) {172              analysis.tools.format.openai_wrapper_trigger = true;173              LOG_DBG(ANSI_ORANGE "[Patch: JSON name/parameters tool instruction]\n" ANSI_RESET);174          }175      },176      // Laguna (poolside) - the v4 chat template renders reasoning and tool-arg177      // delimiters with formatting whitespace ("<think>\n", "</arg_value>\n") that178      // the model does not emit, so the inferred delimiters carry a spurious179      // newline and never match the model output. Trim to the bare tag. (v8180      // renders without the whitespace, so this is a no-op there.)181      [](const common_chat_template & tmpl, autoparser & analysis) -> void {182          if (tmpl.src.find("laguna_glm_thinking") != std::string::npos) {183              analysis.reasoning.start              = trim_whitespace(analysis.reasoning.start);184              analysis.reasoning.end                = trim_whitespace(analysis.reasoning.end);185              analysis.tools.arguments.value_prefix = trim_whitespace(analysis.tools.arguments.value_prefix);186              analysis.tools.arguments.value_suffix = trim_whitespace(analysis.tools.arguments.value_suffix);187              analysis.tools.arguments.separator    = trim_whitespace(analysis.tools.arguments.separator);188              analysis.tools.arguments.tolerate_intertag_whitespace = true;189              // The CONTROL/eot </assistant> token only halts generation when emitted as the190              // single token; after tool calls the model can spell it out as text tokens.191              // A literal stop string catches it either way.192              analysis.additional_stops.push_back("</assistant>");193              LOG_DBG(ANSI_ORANGE "[Patch: Laguna]\n" ANSI_RESET);194          }195      },196 197    });198 199// Common JSON structures200static json params_schema = {201    { "type",       "object"                                                           },202    { "properties",203     { { ARG_FIRST, { { "type", "string" }, { "description", "First argument" } } },204        { ARG_SECOND, { { "type", "string" }, { "description", "Second argument" } } } } },205    { "required",   json::array({})                                                    }206};207 208static json tools = json::array({209    { { "type", "function" },210     { "function",211        json{ { "name", FUN_FIRST }, { "description", "Test function foo" }, { "parameters", params_schema } } } },212    { { "type", "function" },213     { "function",214        json{ { "name", FUN_SECOND }, { "description", "Test function bar" }, { "parameters", params_schema } } } }215});216 217static json user_msg = json{218    { "role",    "user"  },219    { "content", USER_MSG }220};221 222static json build_tool_call(const std::string & name, const json & args, const std::string & id = CALL_ID_001) {223    return json{224        { "id",       id                                              },225        { "type",     "function"                                      },226        { "function", json{ { "name", name }, { "arguments", args } } }227    };228}229 230static json first_tool_call_zero_args         = build_tool_call(FUN_FIRST, json::object(), CALL_ID_001);231static json first_tool_call_one_arg           = build_tool_call(FUN_FIRST, {{ ARG_FIRST, "XXXX" }}, CALL_ID_001);232static json first_tool_call_one_arg_other_val = build_tool_call(FUN_FIRST, {{ ARG_FIRST, "YYYY" }}, CALL_ID_001);233static json first_tool_call_other_arg         = build_tool_call(FUN_FIRST, {{ ARG_SECOND, "YYYY" }}, CALL_ID_001);234 235static json first_tool_call =236    build_tool_call(FUN_FIRST, json{{ ARG_FIRST,  "XXXX" }, { ARG_SECOND, "YYYY" }}, CALL_ID_001);237static json second_tool_call =238    build_tool_call(FUN_SECOND, json{ { ARG_FIRST,  "XXXX" }, { ARG_SECOND, "YYYY" }}, CALL_ID_002);239static json first_tool_call_alt_id =240    build_tool_call(FUN_FIRST, json{{ ARG_FIRST,  "XXXX" }, { ARG_SECOND, "YYYY" }}, CALL_ID_999);241 242template <typename T>243static std::string mode_to_str(T mode) {244    std::ostringstream os;245    os << mode;246    return os.str();247}248 249void autoparser::analyze_template(const common_chat_template & tmpl) {250    jinja_caps = tmpl.original_caps();251    reasoning = analyze_reasoning(tmpl, jinja_caps.supports_tool_calls);252    content = analyze_content(tmpl, reasoning);253    tools = analyze_tools(jinja_caps.supports_tool_calls ? analyze_tools(tmpl, jinja_caps, reasoning) : analyze_tools());254    assistant_start = detect_assistant_start_marker(tmpl);255    user_start = detect_user_start_marker(tmpl);256    collect_preserved_tokens();257 258    for (auto & workaround : workarounds) {259        workaround(tmpl, *this);260    }261 262    LOG_DBG("\n--- Reasoning & Content Structure ---\n");263    LOG_DBG("user_msg_start: %s\n", user_start.c_str());264    LOG_DBG("assistant_msg_start: %s\n", assistant_start.c_str());265    LOG_DBG("reasoning_mode: %s\n", mode_to_str(reasoning.mode).c_str());266    LOG_DBG("reasoning_start: '%s'\n", reasoning.start.c_str());267    LOG_DBG("reasoning_end: '%s'\n", reasoning.end.c_str());268    LOG_DBG("content_mode: %s\n", mode_to_str(content.mode).c_str());269    LOG_DBG("content_start: '%s'\n", content.start.c_str());270    LOG_DBG("content_end: '%s'\n", content.end.c_str());271 272    LOG_DBG("\n--- Tool Call Structure ---\n");273    LOG_DBG("tool_mode: %s\n", mode_to_str(tools.format.mode).c_str());274    LOG_DBG("supports_tools: %s\n", jinja_caps.supports_tools ? "true" : "false");275    LOG_DBG("supports_parallel_calls: %s\n", jinja_caps.supports_parallel_tool_calls ? "true" : "false");276    LOG_DBG("tool_section_start: '%s'\n", tools.format.section_start.c_str());277    LOG_DBG("tool_section_end: '%s'\n", tools.format.section_end.c_str());278    LOG_DBG("per_call_start: '%s'\n", tools.format.per_call_start.c_str());279    LOG_DBG("per_call_end: '%s'\n", tools.format.per_call_end.c_str());280    LOG_DBG("func_name_prefix: '%s'\n", tools.function.name_prefix.c_str());281    LOG_DBG("func_name_suffix: '%s'\n", tools.function.name_suffix.c_str());282    LOG_DBG("func_args_separator: '%s'\n", tools.function.args_separator.c_str());283    LOG_DBG("func_close: '%s'\n", tools.function.close.c_str());284    LOG_DBG("call_id_prefix: '%s'\n", tools.call_id.prefix.c_str());285    LOG_DBG("call_id_suffix: '%s'\n", tools.call_id.suffix.c_str());286    LOG_DBG("call_id_pos: '%s'\n", mode_to_str(tools.call_id.pos).c_str());287    LOG_DBG("args_start: '%s'\n", tools.arguments.start.c_str());288    LOG_DBG("args_end: '%s'\n", tools.arguments.end.c_str());289    LOG_DBG("arg_name_prefix: '%s'\n", tools.arguments.name_prefix.c_str());290    LOG_DBG("arg_name_suffix: '%s'\n", tools.arguments.name_suffix.c_str());291    LOG_DBG("arg_value_prefix: '%s'\n", tools.arguments.value_prefix.c_str());292    LOG_DBG("arg_value_suffix: '%s'\n", tools.arguments.value_suffix.c_str());293    LOG_DBG("name_field: '%s'\n", tools.format.name_field.c_str());294    LOG_DBG("args_field: '%s'\n", tools.format.args_field.c_str());295    LOG_DBG("id_field: '%s'\n", tools.format.id_field.c_str());296    LOG_DBG("gen_id_field: '%s'\n", tools.format.gen_id_field.c_str());297    LOG_DBG("parameter_order: '%s'\n", std::accumulate(tools.format.parameter_order.begin(), tools.format.parameter_order.end(),298        std::string(""), [] (const std::string & a, const std::string & b) { return a.empty() ? b : a + ", " + b; }299        ).c_str());300 301    LOG_DBG(ANSI_PURPLE "=== Differential analysis complete ===\n" ANSI_RESET);302    analysis_complete = true;303}304 305void autoparser::collect_preserved_tokens() {306    auto add_token = [this](const std::string & org_token) {307        std::string token = trim_whitespace(org_token);308        if (!token.empty()) {309            // Avoid duplicates310            if (std::find(preserved_tokens.begin(), preserved_tokens.end(), token) == preserved_tokens.end()) {311                preserved_tokens.push_back(token);312            }313        }314    };315 316    add_token(reasoning.start);317    add_token(reasoning.end);318    add_token(content.start);319    add_token(content.end);320    add_token(tools.format.section_start);321    add_token(tools.format.section_end);322    add_token(tools.format.per_call_start);323    add_token(tools.format.per_call_end);324    add_token(tools.function.name_prefix);325    add_token(tools.function.name_suffix);326    add_token(tools.function.args_separator);327    add_token(tools.function.close);328    add_token(tools.arguments.start);329    add_token(tools.arguments.end);330    add_token(tools.arguments.name_prefix);331    add_token(tools.arguments.name_suffix);332    add_token(tools.arguments.separator);333    add_token(tools.arguments.value_prefix);334    add_token(tools.arguments.value_suffix);335    add_token(tools.call_id.prefix);336    add_token(tools.call_id.suffix);337}338 339std::string autoparser::detect_assistant_start_marker(const common_chat_template & tmpl) {340    json user_msg = json{341        { "role",    "user"   },342        { "content", USER_MSG }343    };344 345    json assistant_no_reasoning = json{346        { "role",    "assistant"   },347        { "content", ASSISTANT_MSG }348    };349 350    template_params params;351    params.messages              = json::array({ user_msg });352    params.add_generation_prompt = false;353    params.enable_thinking       = true;354 355    auto comparison = compare_variants(356        tmpl, params, [&](template_params & p) {357            p.messages = json::array({ user_msg, assistant_no_reasoning });358        }359    );360 361    if (!comparison) {362        LOG_DBG(ANSI_ORANGE "%s: Template application failed, skipping assistant start detection\n" ANSI_RESET, __func__);363        return "";364    }365 366    auto usermsg = comparison->diff.right;367    if (usermsg.find(ASSISTANT_MSG) == std::string::npos) {368        LOG_DBG(ANSI_ORANGE "%s: Did not find assistant message in assistant message block, skipping detection\n" ANSI_RESET, __func__);369    }370 371    auto ast_prefix = usermsg.substr(0, usermsg.find(ASSISTANT_MSG));372    if (!reasoning.start.empty() && ast_prefix.find(trim_whitespace(reasoning.start)) != std::string::npos) {373        ast_prefix = ast_prefix.substr(0, ast_prefix.find(trim_whitespace(reasoning.start)));374    }375    if (!reasoning.end.empty() && ast_prefix.find(trim_whitespace(reasoning.end)) != std::string::npos) {376        ast_prefix = ast_prefix.substr(0, ast_prefix.find(trim_whitespace(reasoning.end)));377    }378    return trim_whitespace(ast_prefix);379}380 381std::string autoparser::detect_user_start_marker(const common_chat_template & tmpl) {382    json user_msg = json{383        { "role",    "user"   },384        { "content", USER_MSG }385    };386 387    json assistant = json{388        { "role",    "assistant"   },389        { "content", ASSISTANT_MSG }390    };391 392    json user_msg_two = json{393        { "role",    "user"       },394        { "content", USER_MSG_TWO }395    };396 397    template_params params;398    params.messages              = json::array({});399    params.add_generation_prompt = false;400    params.enable_thinking       = true;401 402    auto comparison = compare_variants(403        tmpl, params, [&](template_params & p) {404            p.messages = json::array({ user_msg });405        }406    );407 408    if (!comparison) {409        LOG_DBG(ANSI_ORANGE "%s: Template application failed, unsupported empty messages? trying complex variant\n" ANSI_RESET, __func__);410        params.messages = json::array({ user_msg_two, assistant });411        comparison = compare_variants(412            tmpl, params, [&](template_params & p) {413                p.messages = json::array({ user_msg_two, assistant, user_msg });414            }415        );416        if (!comparison) {417            LOG_DBG(ANSI_ORANGE "%s: Template application failed for reserve variant, aborting\n" ANSI_RESET, __func__);418            return "";419        }420    }421 422    auto usermsg = comparison->diff.right;423    if (usermsg.find(USER_MSG) == std::string::npos) {424        LOG_DBG(ANSI_ORANGE "%s: Did not find user message in user message block, aborting detection\n" ANSI_RESET, __func__);425    }426 427    if (usermsg.find(ASSISTANT_MSG) != std::string::npos) {428        usermsg = usermsg.substr(usermsg.find(ASSISTANT_MSG) + ASSISTANT_MSG.size());429    }430 431    auto candidate = usermsg.substr(0, usermsg.find(USER_MSG));432    auto candidate_split = segmentize_markers(candidate);433    std::stringstream result;434    bool encountered_marker = false;435    for (const auto & mrk : candidate_split) {436        std::string lower_mrk = std::string(mrk.value);437        std::transform(lower_mrk.begin(), lower_mrk.end(), lower_mrk.begin(),438            [](unsigned char c) { return std::tolower(c); });439        // heuristic to weed out potential end markers, but only at the start440        if (mrk.type == segment_type::MARKER && !encountered_marker &&441            (lower_mrk.find("end") != std::string::npos || lower_mrk.find("close") != std::string::npos)) {442            continue;443        }444        if (mrk.type == segment_type::TEXT && !encountered_marker && trim_whitespace(mrk.value).empty()) {445            continue;446        }447        encountered_marker |= mrk.type == segment_type::MARKER;448        result << mrk.value;449    }450    return trim_whitespace(result.str());451}452 453analyze_reasoning::analyze_reasoning(const common_chat_template & tmpl, bool supports_tools)454    : analyze_base(tmpl) {455    LOG_DBG(ANSI_PURPLE "=== Starting differential analysis ===\n" ANSI_RESET);456    LOG_DBG(ANSI_ORANGE "Phase 1: Reasoning analysis\n" ANSI_RESET);457 458    compare_reasoning_presence();459    compare_thinking_enabled();460    if (supports_tools) {461        compare_reasoning_scope();462    }463}464 465void analyze_reasoning::compare_reasoning_presence() {466    json user_msg = json{467        { "role",    "user"  },468        { "content", USER_MSG }469    };470 471    json assistant_no_reasoning = json{472        { "role",    "assistant"   },473        { "content", ASSISTANT_MSG }474    };475 476    json assistant_with_reasoning = json{477        { "role",              "assistant"                },478        { "content",           ASSISTANT_MSG              },479        { "reasoning_content", THINKING_CONTENT           }480    };481 482    template_params params;483    params.messages              = json::array({ user_msg, assistant_no_reasoning });484    params.add_generation_prompt = false;485    params.enable_thinking       = true;486 487    auto comparison = compare_variants(488        *tmpl, params, [&](template_params & p) { p.messages = json::array({ user_msg, assistant_with_reasoning }); });489 490    if (!comparison) {491        LOG_DBG(ANSI_ORANGE "%s: Template application failed, skipping reasoning detection\n" ANSI_RESET, __func__);492        return;493    }494 495    const auto & diff = comparison->diff;496 497    const std::string reasoning_content = THINKING_CONTENT;498 499    if (!diff.right.empty() && diff.right.find(reasoning_content) != std::string::npos) {500        auto parser_delimiter = build_tagged_peg_parser([&](common_peg_parser_builder &p) {501            return p.literal(reasoning_content) + p.space() + p.optional(p.tag("post", (p.marker() + p.space())) + p.rest());502        });503        auto parser_wrapped = build_tagged_peg_parser([&](common_peg_parser_builder &p) {504            return p.tag("pre", p.marker() + p.space()) + p.literal(reasoning_content) + p.tag("post", (p.space() + p.marker() + p.space())) + p.rest();505        });506        // try the more aggressive parse first, if it fails, fall back to the delimiter one507        auto result = parser_wrapped.parse_anywhere_and_extract(comparison->output_B);508        if (!result.result.success()) {509            result = parser_delimiter.parse_anywhere_and_extract(comparison->output_B);510        }511        if (result.result.success()) {512            if (!result.tags["pre"].empty() && !result.tags["post"].empty()) {513                mode = reasoning_mode::TAG_BASED;514                start = result.tags["pre"];515                end   = result.tags["post"];516            } else if (!result.tags["post"].empty()) {517                mode = reasoning_mode::TAG_BASED;518                end = result.tags["post"];519            }520        }521    }522}523 524void analyze_reasoning::compare_thinking_enabled() {525    json user_msg = json{526        { "role",    "user"  },527        { "content", USER_MSG }528    };529 530    template_params params;531    params.messages              = json::array({ user_msg });532    params.add_generation_prompt = true;533    params.enable_thinking       = false;534 535    auto comparison = compare_variants(*tmpl, params, [&](template_params & p) { p.enable_thinking = true; });536 537    if (!comparison) {538        LOG_DBG(ANSI_ORANGE "%s: Template application failed\n" ANSI_RESET , __func__);539        return;540    }541 542    const auto & diff = comparison->diff;543 544    std::string left_trimmed = trim_whitespace(diff.left);545    std::string right_trimmed = trim_whitespace(diff.right);546 547    if (left_trimmed.empty() && !diff.right.empty()) {548        if (!right_trimmed.empty() && string_ends_with(comparison->output_B, right_trimmed)) {549            if (start.empty()) {550                start = diff.right;551                mode  = reasoning_mode::TAG_BASED;552            }553        }554    } else if (right_trimmed.empty() && !diff.left.empty()) {555        if (!left_trimmed.empty() && string_ends_with(comparison->output_A, left_trimmed)) {556            if (end.empty()) {557                auto seg = prune_whitespace_segments(segmentize_markers(comparison->output_A));558                if (seg.size() >= 2 && seg[seg.size() - 1].value == left_trimmed && seg[seg.size() - 2].type == segment_type::MARKER) {559                    start = seg[seg.size() - 2].value;560                }561                end = diff.left;562                mode = reasoning_mode::TAG_BASED;563            }564        }565    } else if (!left_trimmed.empty() && !right_trimmed.empty()) {566        // Full-output diff is noisy (e.g., SmolLM3 changes the system message when enable_thinking flips).567        // Try to find reasoning markers by tail-anchoring:568        // one output's generation prompt tail may appear in the other with extra reasoning markers appended.569        const auto & output_A = comparison->output_A;570        const auto & output_B = comparison->output_B;571        const size_t anchor_len = 64;572 573        for (int dir = 0; dir < 2; dir++) {574            const auto & base     = dir == 0 ? output_B : output_A;575            const auto & extended = dir == 0 ? output_A : output_B;576 577            size_t len = std::min(base.size(), anchor_len);578            std::string anchor = base.substr(base.size() - len);579            auto pos = extended.rfind(anchor);580            if (pos == std::string::npos || pos + len >= extended.size()) {581                continue;582            }583 584            std::string extra = trim_whitespace(extended.substr(pos + len));585            if (extra.empty()) {586                continue;587            }588 589            auto seg = prune_whitespace_segments(segmentize_markers(extra));590            if (seg.size() == 2 && seg[0].type == segment_type::MARKER && seg[1].type == segment_type::MARKER) {591                if (start.empty()) {592                    start = seg[0].value;593                }594                if (end.empty()) {595                    end   = seg[1].value;596                }597                mode = reasoning_mode::TAG_BASED;598                break;599            }600        }601    }602 603    if (mode == reasoning_mode::NONE && start.empty() && !end.empty()) {604        mode = reasoning_mode::TAG_BASED;605    }606}607 608void analyze_reasoning::compare_reasoning_scope() {609    json assistant_reasoning_content = json{610        { "role",              "assistant"      },611        { "content",           ASSISTANT_MSG    },612        { "reasoning_content", THINKING_CONTENT }613    };614 615    json assistant_reasoning_tools = json{616        { "role",              "assistant"                                                                  },617        { "content",           nullptr                                                                      },618        { "reasoning_content", THINKING_CONTENT                                                             },619        { "tool_calls",620         json::array({ build_tool_call(FUN_FIRST, json{ { ARG_FIRST, "VVVV" }, { ARG_SECOND, "XXXX" } }) }) }621    };622 623    template_params params;624    params.messages              = json::array({ user_msg, assistant_reasoning_content });625    params.tools                 = tools;626    params.add_generation_prompt = false;627    params.enable_thinking       = true;628 629    auto comparison = compare_variants(630        *tmpl, params, [&](template_params & p) { p.messages = json::array({ user_msg, assistant_reasoning_tools }); });631 632    if (!comparison) {633        LOG_DBG(ANSI_ORANGE "%s: Template application failed\n" ANSI_RESET, __func__);634        return;635    }636 637    std::string reasoning_content = THINKING_CONTENT;638 639    // Check if reasoning only appears in variant B (with tools)640    bool reasoning_in_A = comparison->output_A.find(reasoning_content) != std::string::npos;641    bool reasoning_in_B = comparison->output_B.find(reasoning_content) != std::string::npos;642 643    if (!reasoning_in_A && reasoning_in_B) {644        mode = reasoning_mode::TOOLS_ONLY;645        LOG_DBG(ANSI_ORANGE "%s: Detected TOOLS_ONLY reasoning mode\n" ANSI_RESET, __func__);646 647        auto parser_wrapped = build_tagged_peg_parser([&](common_peg_parser_builder &p) {648            return p.tag("pre", p.marker() + p.space()) + p.literal(reasoning_content) + p.space() + p.tag("post", (p.marker() + p.space()));649        });650        auto result = parser_wrapped.parse_anywhere_and_extract(comparison->output_B);651        if (result.result.success()) {652            start = result.tags["pre"];653            end = result.tags["post"];654        } else {655            auto parser_delimiter = build_tagged_peg_parser([&](common_peg_parser_builder &p) {656                return p.literal(reasoning_content) + p.space() + p.optional(p.tag("post", (p.marker() + p.space())));657            });658            result = parser_delimiter.parse_anywhere_and_extract(comparison->output_B);659            if (result.result.success()) {660                end = result.tags["post"];661            } else {662                LOG_DBG(ANSI_ORANGE "%s: Unable to extract reasoning markers, falling back to reasoning = NONE\n" ANSI_RESET, __func__);663                mode = reasoning_mode::NONE;664            }665        }666    }667}668 669analyze_content::analyze_content(const common_chat_template & tmpl, const analyze_reasoning & reasoning)670    : analyze_base(tmpl) {671    LOG_DBG(ANSI_ORANGE "Phase 2: Content analysis\n" ANSI_RESET);672 673    json assistant_content_only = json{674        { "role",    "assistant"     },675        { "content", ASSISTANT_MSG   }676    };677 678    json assistant_with_tools = json{679        { "role",       "assistant" },680        { "content",    ""          },681        { "tool_calls", json::array({ build_tool_call("test_func", json{ { "arg1", "value1" } }) }) }682    };683 684    json assistant_with_reasoning = json{685        { "role",              "assistant"      },686        { "content",           ""               },687        { "reasoning_content", THINKING_CONTENT }688    };689 690    template_params params_content_only;691    params_content_only.messages              = json::array({ user_msg, assistant_content_only });692    params_content_only.add_generation_prompt = false;693    params_content_only.enable_thinking       = true;694    params_content_only.tools                 = tools;695 696    auto comparison_with_tools = compare_variants(tmpl, params_content_only, [&](template_params & p) {697        p.messages = json::array({ user_msg, assistant_with_tools });698    });699 700    auto comparison_with_reasoning = compare_variants(tmpl, params_content_only, [&](template_params & p) {701        p.messages = json::array({ user_msg, assistant_with_reasoning });702    });703 704    if (!comparison_with_tools || !comparison_with_reasoning) {705        LOG_DBG(ANSI_ORANGE "%s: Template application failed\n" ANSI_RESET, __func__);706        return;707    }708 709    const auto & diff_tools     = comparison_with_tools->diff;710    const auto & diff_reasoning = comparison_with_reasoning->diff;711 712    std::string response = ASSISTANT_MSG;713 714    bool found_plain_content = false;715    if (trim_whitespace(diff_tools.left) == response) {716        auto parser = build_tagged_peg_parser([&](common_peg_parser_builder & p) {717            return p.space() + diff_reasoning.left + p.space() + p.optional(p.marker()) + p.space() + p.end();718        });719        if (parser.parse_and_extract(diff_reasoning.left).result.success()) {720            // We only have the content text in the diff (possibly with a stray EOG marker), so no markers721            mode = content_mode::PLAIN;722            found_plain_content = true;723        } else if (reasoning.mode != reasoning_mode::NONE && !reasoning.end.empty()) {724            auto post_reasoning_parser = build_tagged_peg_parser([&](common_peg_parser_builder & p) {725                return p.literal(reasoning.end) + p.space() + p.literal(response);726            });727            if (post_reasoning_parser.parse_anywhere_and_extract(diff_reasoning.left).result.success()) {728                mode = content_mode::PLAIN;729                found_plain_content = true;730            }731        }732    }733    if (!found_plain_content) {734        std::string rdiff = diff_reasoning.left;735        if (!reasoning.end.empty() && rdiff.find(reasoning.end) != std::string::npos) {736            rdiff = rdiff.substr(rdiff.find(reasoning.end) + reasoning.end.length());737        }738        // Take the more promising diff739        std::string pure_content = rdiff.length() > diff_tools.left.length() ? rdiff : diff_tools.left;740        auto parser_wrapped = build_tagged_peg_parser([&](common_peg_parser_builder &p) {741            return p.tag("pre", p.marker() + p.space()) + p.literal(response) + p.space() + p.tag("post", (p.marker() + p.space())) + p.rest();742        });743        auto result = parser_wrapped.parse_anywhere_and_extract(pure_content);744        start = result.tags["pre"];745        end = result.tags["post"];746        // TODO: WRAPPED_WITH_REASONING747    }748 749    // Determine content mode750    if (!start.empty() || !end.empty()) {751        mode = content_mode::ALWAYS_WRAPPED;752        // TODO: END_DELIMITED content mode - delimited at end but not at start?753    }754}755 756bool analyze_content::is_always_wrapped() const {757    return mode == content_mode::ALWAYS_WRAPPED && !start.empty() && !end.empty();758}759 760analyze_tools::analyze_tools(const common_chat_template & tmpl,761                             const jinja::caps &          caps,762                             const analyze_reasoning &    reasoning)763    : analyze_base(tmpl) {764    LOG_DBG(ANSI_ORANGE "Phase 3: Tool call analysis\n" ANSI_RESET);765 766    analyze_tool_calls(reasoning, caps.supports_parallel_tool_calls);767 768    if (format.mode != tool_format::NONE && format.mode != tool_format::JSON_NATIVE) {769        if (caps.supports_parallel_tool_calls) {770            check_per_call_markers();771        }772        LOG_DBG(ANSI_ORANGE "Phase 3a: Function call analysis\n" ANSI_RESET);773        extract_function_markers();774        LOG_DBG(ANSI_ORANGE "Phase 3b: Argument analysis\n" ANSI_RESET);775        if (format.mode == tool_format::TAG_WITH_TAGGED) {776            analyze_arguments();777        }778        extract_argument_separator();779        extract_args_markers();780        LOG_DBG(ANSI_ORANGE "Phase 3c: Call id analysis\n" ANSI_RESET);781        extract_call_id_markers();782    }783}784 785void analyze_tools::analyze_tool_calls(const analyze_reasoning & reasoning, bool supports_parallel_tool_calls) {786    json assistant_no_tools = json{787        { "role",    "assistant"   },788        { "content", ASSISTANT_MSG }789    };790 791    json assistant_with_tools = json{792        { "role",       "assistant"                      },793        { "content",    ""                               },794        { "tool_calls", json::array({ first_tool_call }) }795    };796 797    template_params params;798    params.messages              = json::array({ user_msg, assistant_no_tools });799    params.tools                 = tools;800    params.add_generation_prompt = false;801    params.enable_thinking       = true;802 803    auto comparison = compare_variants(804        *tmpl, params, [&](template_params & p) { p.messages = json::array({ user_msg, assistant_with_tools }); });805 806    if (!comparison) {807        LOG_DBG(ANSI_ORANGE "%s: Template application failed\n" ANSI_RESET, __func__);808        return;809    }810 811    const auto & diff = comparison->diff;812 813    std::string tool_section = diff.right;814 815    if (tool_section.empty()) {816        return;817    }818 819    analyze_tool_call_format(tool_section, FUN_FIRST, ARG_FIRST, reasoning, supports_parallel_tool_calls);820}821 822void analyze_tools::analyze_tool_call_format(const std::string &       haystack,823                                             const std::string &       fun_name_needle,824                                             const std::string &       arg_name_needle,825                                             const analyze_reasoning & reasoning,826                                             bool                      supports_parallel_tool_calls) {827    if (fun_name_needle.empty() || arg_name_needle.empty() || haystack.empty()) {828        return;829    }830 831    auto in_json_haystack = [&haystack](const std::string & needle) -> bool {832        auto parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {833            return p.choice({ p.literal("{"), p.literal(":") }) << p.choice({834                p.tag("dq", p.literal("\"") + p.literal(needle) + p.literal("\"")) });835        });836        auto result = parser.parse_anywhere_and_extract(haystack);837        return result.result.success();838    };839 840    auto fun_quote = in_json_haystack(fun_name_needle);841    auto arg_quote = in_json_haystack(arg_name_needle);842 843    if (fun_quote) {844        // no need to check further, we're in JSON land845        format.mode = tool_format::JSON_NATIVE;846    } else if (arg_quote) {847        format.mode = tool_format::TAG_WITH_JSON;848    } else {849        format.mode = tool_format::TAG_WITH_TAGGED;850    }851 852    // first, remove any reasoning markers853    std::string clean_haystack = haystack;854    if (!reasoning.start.empty()) {855        auto pos = haystack.find(reasoning.start);856        if (pos != std::string::npos) {857            clean_haystack = haystack.substr(0, pos) + haystack.substr(pos + reasoning.start.length());858        }859    }860    if (!reasoning.end.empty()) {861        auto pos = clean_haystack.find(reasoning.end);862        if (pos != std::string::npos) {863            clean_haystack = clean_haystack.substr(0, pos) + clean_haystack.substr(pos + reasoning.end.length());864        }865    }866 867    if (format.mode == tool_format::JSON_NATIVE) {868        analyze_tool_call_format_json_native(clean_haystack, fun_name_needle, arg_name_needle);869        if (supports_parallel_tool_calls) {870            analyze_json_native_parallel_calls();871        }872    } else {873        analyze_tool_call_format_non_json(clean_haystack, fun_name_needle);874    }875    // always relax whitespace requirements on ending markers since they don't influence content876    format.section_end  = trim_whitespace(format.section_end);877    format.per_call_end = trim_whitespace(format.per_call_end);878}879 880void analyze_tools::analyze_json_native_parallel_calls() {881    json assistant_one_tool = json{882        { "role",       "assistant" },883        { "content",    ""          },884        { "tool_calls", json::array({ first_tool_call }) }885    };886 887    json assistant_two_tools = json{888        { "role",       "assistant" },889        { "content",    ""          },890        { "tool_calls", json::array({ first_tool_call, second_tool_call }) }891    };892 893    template_params params;894    params.messages              = json::array({ user_msg, assistant_one_tool });895    params.tools                 = tools;896    params.add_generation_prompt = false;897    params.enable_thinking       = true;898 899    auto comparison = compare_variants(900        *tmpl, params, [&](template_params & p) { p.messages = json::array({ user_msg, assistant_two_tools }); });901 902    if (!comparison) {903        LOG_DBG(ANSI_ORANGE "%s: Template application failed\n" ANSI_RESET, __func__);904        return;905    }906 907    std::string & second_call = comparison->diff.right;908    if (!format.section_start.empty() && second_call.find(format.section_start) != std::string::npos) {909        format.per_call_start = format.section_start;910        format.per_call_end = format.section_end;911        format.section_start.clear();912        format.section_end.clear();913    }914}915 916void analyze_tools::analyze_tool_call_format_json_native(const std::string & clean_haystack,917                                                         const std::string & fun_name_needle,918                                                         const std::string & arg_name_needle) {919    // we might not have the typical OpenAI tool calling structure920    int  json_start     = clean_haystack.find_first_of('{');921    int  json_end       = clean_haystack.find_last_of('}');922    std::string cut     = clean_haystack.substr(json_start, json_end - json_start + 1);923    json call_struct    = json::parse(cut);924    auto register_field = [&](const std::string & prefix, const nlohmann::detail::iteration_proxy_value<json::iterator> & subel) {925        if (subel.value().is_string() && std::string(subel.value()).find("call0000") != std::string::npos) {926            format.id_field = !prefix.empty() ? prefix + "." + subel.key() : subel.key();927        } else if (subel.value().is_string() && std::string(subel.value()) == fun_name_needle) {928            format.name_field = !prefix.empty() ? prefix + "." + subel.key() : subel.key();929        } else if (subel.value().dump().find(arg_name_needle) !=930                   std::string::npos) {  // handle both string and JSON obj variants931            format.args_field = !prefix.empty() ? prefix + "." + subel.key() : subel.key();932        } else if (subel.key().find("id") != std::string::npos) {933            // heuristics for generated id field934            format.gen_id_field = !prefix.empty() ? prefix + "." + subel.key() : subel.key();935        }936    };937    for (const auto & el : call_struct.items()) {938        if (el.key() == fun_name_needle) {939            format.fun_name_is_key = true;940            // When function name is the key, there's no name field and args are direct941            format.name_field.clear();942            format.args_field.clear();943            // Don't register this element - the function name IS the key, not a field944        } else {945            if (el.value().is_object() &&946                el.value().dump().find(arg_name_needle) == std::string::npos) {  // not the args object947                format.function_field = el.key();948                for (const auto & subel : el.value().items()) {949                    register_field(el.key(), subel);950                }951            }952            // Register this element as a potential field953            register_field("", el);954        }955    }956    auto array_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {957        return p.tag("pre", p.literal("[") + p.space()) + p.literal(cut) + p.tag("post", p.space() + p.literal("]"));958    });959 960    auto ar_parse_res = array_parser.parse_anywhere_and_extract(clean_haystack);961    if (ar_parse_res.result.success()) {962        format.tools_array_wrapped = true;963        json_start -= ar_parse_res.tags["pre"].length();964        json_end += ar_parse_res.tags["post"].length();965    }966    json_end++; // we want to move past the closing char for end marker extraction967 968    std::vector<std::pair<size_t, std::string>> located_params;969    if (!format.name_field.empty()) {970        located_params.push_back({ clean_haystack.find(format.name_field), format.name_field });971    }972    if (!format.args_field.empty()) {973        located_params.push_back({ clean_haystack.find(format.args_field), format.args_field });974    }975    if (!format.id_field.empty()) {976        located_params.push_back({ clean_haystack.find(format.id_field), format.id_field });977    }978    if (!format.gen_id_field.empty()) {979        located_params.push_back({ clean_haystack.find(format.gen_id_field), format.gen_id_field });980    }981    std::sort(located_params.begin(), located_params.end());982    for (auto & pair : located_params) {983        format.parameter_order.push_back(pair.second);984    }985    // we can immediately extract tool calling markers too986    format.section_start = trim_leading_whitespace(clean_haystack.substr(0, json_start));987    format.section_end   = trim_whitespace(clean_haystack.substr(json_end));988    // When tools_array_wrapped is true, the closing bracket is part of the array structure,989    // not a separate section end marker. Clear tool_section_end to avoid duplicate brackets.990    if (format.tools_array_wrapped && format.section_end == "]") {991        format.section_end.clear();992    }993}994 995void analyze_tools::analyze_tool_call_format_non_json(const std::string & clean_haystack,996                                                      const std::string & fun_name_needle) {997    // first, let's find out if the function is inside a tag or standalone998    auto fun_marker_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {999            return p.tag("fun_marker", p.choice({1000            p.tag("fun_pre", p.literal("<") + p.until_one_of({ ">", fun_name_needle })) + p.literal(fun_name_needle) +1001                p.tag("fun_post", p.negate(p.space() + p.literal("<")) + p.until(">") + p.literal(">")) + p.space(),1002            p.tag("fun_pre", p.literal("[") + p.until_one_of({ "]", fun_name_needle })) + p.literal(fun_name_needle) +1003                p.tag("fun_post", p.negate(p.space() + p.literal("[") + p.until("]") + p.literal("]")) + p.space()) }));1004    });1005    auto fun_res = fun_marker_parser.parse_anywhere_and_extract(clean_haystack);1006    std::string fun_marker = fun_name_needle;1007    if (fun_res.result.success()) {1008        fun_marker = fun_res.tags["fun_marker"];1009    }1010    // now, consume up to two markers, then treat everything up to the function marker as function name prefix1011    auto per_tool_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1012        return p.tag("sec_start", p.marker() + p.space()) + p.tag("call_start", p.marker() + p.space()) +1013            p.tag("fun_pre", p.until(fun_marker)) + fun_marker + p.tag("rest", p.rest());1014    });1015    auto section_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1016        return p.tag("sec_start", p.marker() + p.space()) + fun_marker + p.tag("rest", p.rest());1017    });1018    auto result = per_tool_parser.parse_anywhere_and_extract(clean_haystack);1019    tagged_parse_result result_end;1020    if (result.result.success()) {1021        auto double_closer_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1022            return p.tag("call_end", p.marker() + p.space()) + p.tag("sec_end", p.marker() + p.space()) + p.end();1023        });1024        result_end = double_closer_parser.parse_anywhere_and_extract(result.tags["rest"]);1025        function.name_prefix = fun_res.tags["fun_pre"] + function.name_prefix;1026    } else {1027        result = section_parser.parse_anywhere_and_extract(clean_haystack);1028        auto single_closer_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1029            return p.tag("sec_end", p.marker() + p.space()) + p.end();1030        });1031        result_end = single_closer_parser.parse_anywhere_and_extract(result.tags["rest"]);1032    }1033    format.per_call_start = result.tags["call_start"];1034    format.per_call_end = result_end.tags["call_end"];1035    format.section_start = result.tags["sec_start"];1036    format.section_end = result_end.tags["sec_end"];1037}1038 1039void analyze_tools::check_per_call_markers() {1040    json assistant_one_tool = json{1041        { "role",       "assistant" },1042        { "content",    ""          },1043        { "tool_calls", json::array({ first_tool_call }) }1044    };1045 1046    json assistant_two_tools = json{1047        { "role",       "assistant" },1048        { "content",    ""          },1049        { "tool_calls", json::array({ first_tool_call, second_tool_call }) }1050    };1051 1052    template_params params;1053    params.messages              = json::array({ user_msg, assistant_one_tool });1054    params.tools                 = tools;1055    params.add_generation_prompt = false;1056    params.enable_thinking       = true;1057 1058    auto one_vs_two = compare_variants(1059        *tmpl, params, [&](template_params & p) { p.messages = json::array({ user_msg, assistant_two_tools }); });1060 1061    if (!one_vs_two) {1062        LOG_DBG(ANSI_ORANGE "%s: Generating double tool call comparison failed\n" ANSI_RESET, __func__);1063        return;1064    }1065 1066    diff_split filter_common_call_part = calculate_diff_split(one_vs_two->diff.suffix, one_vs_two->diff.right);1067 1068    std::string second_tool_content = trim_leading_whitespace(filter_common_call_part.right);1069    if (!format.section_start.empty() &&1070        second_tool_content.find(format.section_start) == 0) {1071        format.per_call_start = format.section_start;1072        format.per_call_end   = format.section_end;1073        format.section_start.clear();1074        format.section_end.clear();1075    }1076 1077    if (!format.per_call_end.empty()) {1078        auto count_occurrences = [](const std::string & haystack, const std::string & needle) {1079            size_t count = 0;1080            for (size_t pos = haystack.find(needle); pos != std::string::npos;1081                 pos = haystack.find(needle, pos + needle.size())) {1082                count++;1083            }1084            return count;1085        };1086        size_t calls_one = count_occurrences(one_vs_two->output_A, format.per_call_end);1087        size_t calls_two = count_occurrences(one_vs_two->output_B, format.per_call_end);1088        if (calls_one > 0 && calls_one == calls_two) {1089            format.section_end = format.per_call_end;1090            format.per_call_end.clear();1091        }1092    }1093}1094 1095void analyze_tools::extract_function_markers() {1096    json assistant_nocall = json{1097        { "role",    "assistant"   },1098        { "content", ASSISTANT_MSG },1099    };1100 1101    json assistant_foofoo = json{1102        { "role",       "assistant"                      },1103        { "content",    ""                               },1104        { "tool_calls", json::array({ first_tool_call }) }1105    };1106 1107    json assistant_barbar = json{1108        { "role",       "assistant"                       },1109        { "content",    ""                                },1110        { "tool_calls", json::array({ second_tool_call }) }1111    };1112 1113    template_params params;1114    params.messages              = json::array({ user_msg, assistant_foofoo });1115    params.tools                 = tools;1116    params.add_generation_prompt = false;1117    params.enable_thinking       = true;1118 1119    auto comparison = compare_variants(1120        *tmpl, params, [&](template_params & p) { p.messages = json::array({ user_msg, assistant_barbar }); });1121 1122    if (!comparison) {1123        LOG_DBG(ANSI_ORANGE "%s: Template application failed\n" ANSI_RESET, __func__);1124        return;1125    }1126 1127    const auto & diff = comparison->diff;1128 1129    if (diff.left.find(FUN_FIRST) != std::string::npos && diff.right.find(FUN_SECOND) != std::string::npos) {1130        std::string prefix_marker;1131        if (!format.per_call_start.empty()) {1132            prefix_marker = format.per_call_start;1133        } else {1134            prefix_marker = format.section_start;1135        }1136        if (!prefix_marker.empty() && diff.prefix.rfind(prefix_marker) != std::string::npos) {1137            function.name_prefix =1138                diff.prefix.substr(diff.prefix.rfind(prefix_marker) + prefix_marker.size());1139        }1140 1141        // Extract name prefix/suffix from diff.left (stop at the next marker boundary)1142        auto name_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1143            return p.tag("pre", p.until(FUN_FIRST)) + p.literal(FUN_FIRST) +1144                   p.tag("post", p.zero_or_more(p.negate(p.marker()) + p.any()));1145        });1146        auto name_result = name_parser.parse_and_extract(diff.left);1147        if (name_result.result.success()) {1148            function.name_prefix += name_result.tags["pre"];1149            function.name_suffix = name_result.tags["post"];1150        }1151 1152        // Extend name_suffix with content from diff.suffix before args begin1153        if (format.mode == tool_format::TAG_WITH_JSON) {1154            // For JSON: name_suffix extends to the first non-marker { or [, including any1155            // markers along the way. Only applies if there's at least one marker after1156            // the JSON content (matching the original "stop < seg_suf.size() - 1" guard).1157            auto suffix_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1158                auto non_json = p.marker() | (p.negate(p.literal("{")) + p.negate(p.literal("[")) + p.any());1159                auto after_json = p.zero_or_more(p.negate(p.marker()) + p.any()) + p.marker();1160                return p.tag("ext", p.zero_or_more(non_json)) + after_json;1161            });1162            auto suf_result = suffix_parser.parse_and_extract(diff.suffix);1163            if (suf_result.result.success()) {1164                function.name_suffix += suf_result.tags["ext"];1165            }1166        } else {1167            // For tagged: name_suffix extends to the first marker (arg marker)1168            auto suffix_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1169                return p.tag("ext", p.zero_or_more(p.negate(p.marker()) + p.any()));1170            });1171            auto suf_result = suffix_parser.parse_and_extract(diff.suffix);1172            if (suf_result.result.success()) {1173                function.name_suffix += suf_result.tags["ext"];1174 1175                auto arg_start = [&](common_peg_parser_builder &p) {1176                    return p.marker() + p.space() + p.choice({ p.literal(ARG_FIRST), p.literal(ARG_SECOND) });1177                };1178                auto sep_parser = build_tagged_peg_parser([&](common_peg_parser_builder &p) {1179                    return p.tag("sep", p.zero_or_more(p.negate(arg_start(p)) + p.any())) + arg_start(p);1180                });1181                auto sep_result = sep_parser.parse_and_extract(diff.suffix.substr(suf_result.tags["ext"].size()));1182                if (sep_result.result.success()) {1183                    function.args_separator = trim_whitespace(sep_result.tags["sep"]);1184                }1185            }1186        }1187 1188        // Extract the closer (between last arg and call/section end marker)1189        std::string suffix_marker;1190        if (!format.per_call_end.empty()) {1191            suffix_marker = format.per_call_end;1192        } else {1193            suffix_marker = format.section_end;1194        }1195        std::string closer_suffix;1196        if (suffix_marker.empty()) {1197            // we'll have to rely on an extra diff with no-calls version1198            auto notool_comp = compare_variants(1199                *tmpl, params, [&](template_params & p) { p.messages = json::array({ user_msg, assistant_nocall }); });1200            if (notool_comp) {

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

Brunobkr/llama.cpp_AlgMor24_github · Team Ai