CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 2d agoView on Hugging Face
0likes1.1kdownloads
server-chat.cpp693 linesDownload Raw Back to server
1#include "server-chat.h"2#include "server-common.h"3 4#include <sstream>5 6json server_chat_convert_responses_to_chatcmpl(const json & response_body) {7    if (!response_body.contains("input")) {8        throw std::invalid_argument("'input' is required");9    }10    if (!json_value(response_body, "previous_response_id", std::string{}).empty()) {11        throw std::invalid_argument("llama.cpp does not support 'previous_response_id'.");12    }13 14    const json input_value = response_body.at("input");15    json chatcmpl_body = response_body;16    chatcmpl_body.erase("input");17    std::vector<json> chatcmpl_messages;18 19    if (response_body.contains("instructions")) {20        chatcmpl_messages.push_back({21            {"role",    "system"},22            {"content", json_value(response_body, "instructions", std::string())},23        });24        chatcmpl_body.erase("instructions");25    }26 27    if (input_value.is_string()) {28        // #responses_create-input-text_input29        chatcmpl_messages.push_back({30            {"role",    "user"},31            {"content", input_value},32        });33    } else if (input_value.is_array()) {34        // #responses_create-input-input_item_list35 36        static auto exists_and_is_array = [](const json & j, const char * key) -> bool {37            return j.contains(key) && j.at(key).is_array();38        };39        static auto exists_and_is_string = [](const json & j, const char * key) -> bool {40            return j.contains(key) && j.at(key).is_string();41        };42 43        for (json item : input_value) {44            bool merge_prev = !chatcmpl_messages.empty() && chatcmpl_messages.back().value("role", "") == "assistant";45 46            if (exists_and_is_string(item, "content")) {47                // #responses_create-input-input_item_list-input_message-content-text_input48                // Only "Input message" contains item["content"]::string49                // After converting item["content"]::string to item["content"]::array,50                // we can treat "Input message" as sum of "Item-Input message" and "Item-Output message"51                item["content"] = json::array({52                    json {53                        {"text", item.at("content")},54                        {"type", "input_text"}55                    }56                });57            }58 59            if (exists_and_is_array(item, "content") &&60                exists_and_is_string(item, "role") &&61                (item.at("role") == "user" ||62                    item.at("role") == "system" ||63                    item.at("role") == "developer")64            ) {65                // #responses_create-input-input_item_list-item-input_message66                std::vector<json> chatcmpl_content;67 68                for (const json & input_item : item.at("content")) {69                    const std::string type = json_value(input_item, "type", std::string());70 71                    if (type == "input_text") {72                        if (!input_item.contains("text")) {73                            throw std::invalid_argument("'Input text' requires 'text'");74                        }75                        chatcmpl_content.push_back({76                            {"text", input_item.at("text")},77                            {"type", "text"},78                        });79                    } else if (type == "input_image") {80                        // While `detail` is marked as required,81                        // it has default value("auto") and can be omitted.82 83                        if (!input_item.contains("image_url")) {84                            throw std::invalid_argument("'image_url' is required");85                        }86                        chatcmpl_content.push_back({87                            {"image_url", json {88                                {"url", input_item.at("image_url")}89                            }},90                            {"type", "image_url"},91                        });92                    } else if (type == "input_file") {93                        throw std::invalid_argument("'input_file' is not supported by llamacpp at this moment");94                    } else {95                        throw std::invalid_argument("'type' must be one of 'input_text', 'input_image', or 'input_file'");96                    }97                }98 99                if (item.contains("type")) {100                    item.erase("type");101                }102                if (item.contains("status")) {103                    item.erase("status");104                }105                item["content"] = chatcmpl_content;106 107                chatcmpl_messages.push_back(item);108            } else if (exists_and_is_string(item, "role") &&109                item.at("role") == "assistant" &&110                exists_and_is_string(item, "type") &&111                item.at("type") == "message"112            ) {113                // #responses_create-input-input_item_list-item-output_message114                auto chatcmpl_content = json::array();115 116                // Handle both string content and array content117                if (item.contains("content") && item.at("content").is_string()) {118                    // String content - convert to text content part119                    chatcmpl_content.push_back({120                        {"text", item.at("content")},121                        {"type", "text"},122                    });123                } else if (exists_and_is_array(item, "content")) {124                    // Array content - process each item125                    for (const auto & output_text : item.at("content")) {126                        const std::string type = json_value(output_text, "type", std::string());127                        if (type == "output_text" || type == "input_text") {128                            // Accept both output_text and input_text (string content gets converted to input_text)129                            if (!exists_and_is_string(output_text, "text")) {130                                throw std::invalid_argument("'Output text' requires 'text'");131                            }132                            chatcmpl_content.push_back({133                                {"text", output_text.at("text")},134                                {"type", "text"},135                            });136                        } else if (type == "refusal") {137                            if (!exists_and_is_string(output_text, "refusal")) {138                                throw std::invalid_argument("'Refusal' requires 'refusal'");139                            }140                            chatcmpl_content.push_back({141                                {"refusal", output_text.at("refusal")},142                                {"type", "refusal"},143                            });144                        } else {145                            throw std::invalid_argument("'type' must be one of 'output_text' or 'refusal'");146                        }147                    }148                }149 150                if (merge_prev) {151                    auto & prev_msg = chatcmpl_messages.back();152                    if (!exists_and_is_array(prev_msg, "content")) {153                        prev_msg["content"] = json::array();154                    }155                    auto & prev_content = prev_msg["content"];156                    prev_content.insert(chatcmpl_content);157                } else {158                    item.erase("status");159                    item.erase("type");160                    item["content"] = chatcmpl_content;161                    chatcmpl_messages.push_back(item);162                }163            } else if (exists_and_is_string(item, "arguments") &&164                exists_and_is_string(item, "call_id") &&165                exists_and_is_string(item, "name") &&166                exists_and_is_string(item, "type") &&167                item.at("type") == "function_call"168            ) {169                // #responses_create-input-input_item_list-item-function_tool_call170                json tool_call = {171                    {"function", json {172                        {"arguments", item.at("arguments")},173                        {"name",      item.at("name")},174                    }},175                    {"id",   item.at("call_id")},176                    {"type", "function"},177                };178 179                if (merge_prev) {180                    auto & prev_msg = chatcmpl_messages.back();181                    if (!exists_and_is_array(prev_msg, "tool_calls")) {182                        prev_msg["tool_calls"] = json::array();183                    }184                    prev_msg["tool_calls"].push_back(tool_call);185                } else {186                    chatcmpl_messages.push_back(json {187                        {"role",       "assistant"},188                        {"tool_calls", json::array({tool_call})}189                    });190                }191            } else if (exists_and_is_string(item, "call_id") &&192                (exists_and_is_string(item, "output") || exists_and_is_array(item, "output")) &&193                exists_and_is_string(item, "type") &&194                item.at("type") == "function_call_output"195            ) {196                // #responses_create-input-input_item_list-item-function_tool_call_output197                if (item.at("output").is_string()) {198                    chatcmpl_messages.push_back(json {199                        {"content",      item.at("output")},200                        {"role",         "tool"},201                        {"tool_call_id", item.at("call_id")},202                    });203                } else {204                    json chatcmpl_outputs = item.at("output");205                    for (json & chatcmpl_output : chatcmpl_outputs) {206                        if (!chatcmpl_output.contains("type") || chatcmpl_output.at("type") != "input_text") {207                            throw std::invalid_argument("Output of tool call should be 'Input text'");208                        }209                        chatcmpl_output["type"] = "text";210                    }211                    chatcmpl_messages.push_back(json {212                        {"content",      chatcmpl_outputs},213                        {"role",         "tool"},214                        {"tool_call_id", item.at("call_id")},215                    });216                }217            } else if (exists_and_is_array(item, "summary") &&218                exists_and_is_string(item, "type") &&219                item.at("type") == "reasoning") {220                // #responses_create-input-input_item_list-item-reasoning221 222                if (!exists_and_is_array(item, "content")) {223                    throw std::invalid_argument("item['content'] is not an array");224                }225                if (item.at("content").empty()) {226                    throw std::invalid_argument("item['content'] is empty");227                }228                if (!exists_and_is_string(item.at("content")[0], "text")) {229                    throw std::invalid_argument("item['content']['text'] is not a string");230                }231 232                if (merge_prev) {233                    auto & prev_msg = chatcmpl_messages.back();234                    prev_msg["reasoning_content"] = item.at("content")[0].at("text");235                } else {236                    chatcmpl_messages.push_back(json {237                        {"role", "assistant"},238                        {"content", json::array()},239                        {"reasoning_content", item.at("content")[0].at("text")},240                    });241                }242            } else {243                throw std::invalid_argument("Cannot determine type of 'item'");244            }245        }246    } else {247        throw std::invalid_argument("'input' must be a string or array of objects");248    }249 250    chatcmpl_body["messages"] = chatcmpl_messages;251 252    if (response_body.contains("tools")) {253        if (!response_body.at("tools").is_array()) {254            throw std::invalid_argument("'tools' must be an array of objects");255        }256        std::vector<json> chatcmpl_tools;257        for (json resp_tool : response_body.at("tools")) {258            json chatcmpl_tool;259 260            const std::string type = json_value(resp_tool, "type", std::string());261            if (type != "function") {262                // Non-function Responses tools have no Chat Completions equivalent.263                SRV_WRN("unsupported Responses tool type '%s' skipped\n", type.c_str());264                continue;265            }266            resp_tool.erase("type");267            chatcmpl_tool["type"] = "function";268 269            if (!resp_tool.contains("strict")) {270                resp_tool["strict"] = true;271            }272            chatcmpl_tool["function"] = resp_tool;273            chatcmpl_tools.push_back(chatcmpl_tool);274        }275        chatcmpl_body.erase("tools");276        if (!chatcmpl_tools.empty()) {277            chatcmpl_body["tools"] = chatcmpl_tools;278        }279    }280 281    if (response_body.contains("max_output_tokens")) {282        chatcmpl_body.erase("max_output_tokens");283        chatcmpl_body["max_tokens"] = response_body["max_output_tokens"];284    }285 286    if (response_body.contains("reasoning")) {287        // Only "effort" is handled so far288        const json & reasoning = response_body.at("reasoning");289        if (reasoning.contains("effort")) {290            chatcmpl_body["reasoning_effort"] = reasoning.at("effort");291        }292        chatcmpl_body.erase("reasoning");293    }294 295    return chatcmpl_body;296}297 298// Edits the cch section of an "x-anthropic-billing-header" system prompt.299// Does nothing to any other prompt.300//301// This is a claude message with a "cch=ef01a" attribute that breaks prefix caching.302// The cch stamp is a whitebox end-to-end integrity hint. It's not meaningful as a303// system prompt data, particularly to llama.cpp, but its presence means the prefix304// cache will not get past it: It changes on each request.305//306// Reference: https://github.com/ggml-org/llama.cpp/pull/21793307// Example header:308// ```309// x-anthropic-billing-header: cc_version=2.1.101.e51; cc_entrypoint=cli; cch=a5145;You are Claude Code, Anthropic's official CLI for Claude.310//                                                                            ^^^^^311// ```312static void normalize_anthropic_billing_header(std::string & system_text) {313    if (system_text.rfind("x-anthropic-billing-header:", 0) != 0) {314        return;315    }316 317    const size_t header_prefix_length = strlen("x-anthropic-billing-header:");318    const size_t cch_length = 5;319    const size_t index_cch = system_text.find("cch=", header_prefix_length);320    if (index_cch == std::string::npos) {321        return;322    }323 324    const size_t index_replace = index_cch + 4;325    if (index_replace + cch_length < system_text.length() && system_text[index_replace + cch_length] == ';') {326        for (size_t i = 0; i < cch_length; ++i) {327            system_text[index_replace + i] = 'f';328        }329    } else {330        LOG_ERR("anthropic string not as expected: %s", system_text.c_str());331    }332}333 334json server_chat_convert_anthropic_to_oai(const json & body) {335    json oai_body;336 337    // Convert system prompt338    json oai_messages = json::array();339    auto system_param = json_value(body, "system", json());340    if (!system_param.is_null()) {341        std::string system_content;342 343        if (system_param.is_string()) {344            system_content = system_param.get<std::string>();345            normalize_anthropic_billing_header(system_content);346        } else if (system_param.is_array()) {347            for (const auto & block : system_param) {348                if (json_value(block, "type", std::string()) == "text") {349                    auto system_text = json_value(block, "text", std::string());350                    normalize_anthropic_billing_header(system_text);351                    system_content += system_text;352                }353            }354        }355 356        oai_messages.push_back({357            {"role", "system"},358            {"content", system_content}359        });360    }361 362    // Convert messages363    if (!body.contains("messages")) {364        throw std::runtime_error("'messages' is required");365    }366    const json & messages = body.at("messages");367    if (messages.is_array()) {368        for (const auto & msg : messages) {369            std::string role = json_value(msg, "role", std::string());370 371            if (!msg.contains("content")) {372                if (role == "assistant") {373                    continue;374                }375                oai_messages.push_back(msg);376                continue;377            }378 379            const json & content = msg.at("content");380 381            if (content.is_string()) {382                oai_messages.push_back(msg);383                continue;384            }385 386            if (!content.is_array()) {387                oai_messages.push_back(msg);388                continue;389            }390 391            json tool_calls = json::array();392            json converted_content = json::array();393            json tool_results = json::array();394            std::string reasoning_content;395            bool has_tool_calls = false;396 397            for (const auto & block : content) {398                std::string type = json_value(block, "type", std::string());399 400                if (type == "text") {401                    converted_content.push_back(block);402                } else if (type == "thinking") {403                    reasoning_content += json_value(block, "thinking", std::string());404                } else if (type == "image") {405                    json source = json_value(block, "source", json::object());406                    std::string source_type = json_value(source, "type", std::string());407 408                    if (source_type == "base64") {409                        std::string media_type = json_value(source, "media_type", std::string("image/jpeg"));410                        std::string data = json_value(source, "data", std::string());411                        std::ostringstream ss;412                        ss << "data:" << media_type << ";base64," << data;413 414                        converted_content.push_back({415                            {"type", "image_url"},416                            {"image_url", {417                                {"url", ss.str()}418                            }}419                        });420                    } else if (source_type == "url") {421                        std::string url = json_value(source, "url", std::string());422                        converted_content.push_back({423                            {"type", "image_url"},424                            {"image_url", {425                                {"url", url}426                            }}427                        });428                    }429                } else if (type == "tool_use") {430                    tool_calls.push_back({431                        {"id", json_value(block, "id", std::string())},432                        {"type", "function"},433                        {"function", {434                            {"name", json_value(block, "name", std::string())},435                            {"arguments", json_value(block, "input", json::object()).dump()}436                        }}437                    });438                    has_tool_calls = true;439                } else if (type == "tool_result") {440                    std::string tool_use_id = json_value(block, "tool_use_id", std::string());441 442                    auto result_content = json_value(block, "content", json());443                    if (result_content.is_string()) {444                        tool_results.push_back({445                            {"role", "tool"},446                            {"tool_call_id", tool_use_id},447                            {"content", result_content.get<std::string>()}448                        });449                    } else if (result_content.is_array()) {450                        // Single-pass: build both text and content_parts, decide format at the end451                        std::string result_text;452                        json content_parts = json::array();453                        bool has_images = false;454 455                        for (const auto & c : result_content) {456                            std::string c_type = json_value(c, "type", std::string());457                            if (c_type == "text") {458                                std::string text = json_value(c, "text", std::string());459                                result_text += text;460                                content_parts.push_back({461                                    {"type", "text"},462                                    {"text", text}463                                });464                            } else if (c_type == "image") {465                                has_images = true;466                                json source = json_value(c, "source", json::object());467                                std::string source_type = json_value(source, "type", std::string());468                                if (source_type == "base64") {469                                    std::string media_type = json_value(source, "media_type", std::string("image/jpeg"));470                                    std::string data = json_value(source, "data", std::string());471                                    std::string url = "data:" + media_type + ";base64," + data;472                                    content_parts.push_back({473                                        {"type", "image_url"},474                                        {"image_url", {{"url", url}}}475                                    });476                                } else if (source_type == "url") {477                                    content_parts.push_back({478                                        {"type", "image_url"},479                                        {"image_url", {{"url", json_value(source, "url", std::string())}}}480                                    });481                                }482                            }483                        }484 485                        if (!has_images) {486                            // Text-only: collapse to a plain string for maximum compatibility487                            tool_results.push_back({488                                {"role", "tool"},489                                {"tool_call_id", tool_use_id},490                                {"content", result_text}491                            });492                        } else {493                            // Mixed or image-only: use array content parts (OpenAI multimodal tool format)494                            tool_results.push_back({495                                {"role", "tool"},496                                {"tool_call_id", tool_use_id},497                                {"content", content_parts}498                            });499                        }500                    } else {501                        tool_results.push_back({502                            {"role", "tool"},503                            {"tool_call_id", tool_use_id},504                            {"content", ""}505                        });506                    }507                }508            }509 510            if (!converted_content.empty() || has_tool_calls || !reasoning_content.empty()) {511                json new_msg = {{"role", role}};512                if (!converted_content.empty()) {513                    new_msg["content"] = converted_content;514                } else if (has_tool_calls || !reasoning_content.empty()) {515                    new_msg["content"] = "";516                }517                if (!tool_calls.empty()) {518                    new_msg["tool_calls"] = tool_calls;519                }520                if (!reasoning_content.empty()) {521                    new_msg["reasoning_content"] = reasoning_content;522                }523                oai_messages.push_back(new_msg);524            }525 526            for (const auto & tool_msg : tool_results) {527                oai_messages.push_back(tool_msg);528            }529        }530    }531 532    oai_body["messages"] = oai_messages;533 534    // Convert tools535    if (body.contains("tools")) {536        const json & tools = body.at("tools");537        if (tools.is_array()) {538            json oai_tools = json::array();539            for (const auto & tool : tools) {540                oai_tools.push_back({541                    {"type", "function"},542                    {"function", {543                        {"name", json_value(tool, "name", std::string())},544                        {"description", json_value(tool, "description", std::string())},545                        {"parameters", tool.contains("input_schema") ? tool.at("input_schema") : json::object()}546                    }}547                });548            }549            oai_body["tools"] = oai_tools;550        }551    }552 553    // Convert tool_choice554    if (body.contains("tool_choice")) {555        const json & tc = body.at("tool_choice");556        if (tc.is_object()) {557            std::string type = json_value(tc, "type", std::string());558            if (type == "auto") {559                oai_body["tool_choice"] = "auto";560            } else if (type == "any" || type == "tool") {561                oai_body["tool_choice"] = "required";562            }563        }564    }565 566    // Convert stop_sequences to stop567    if (body.contains("stop_sequences")) {568        oai_body["stop"] = body.at("stop_sequences");569    }570 571    // Handle max_tokens (required in Anthropic, but we're permissive)572    if (body.contains("max_tokens")) {573        oai_body["max_tokens"] = body.at("max_tokens");574    } else {575        oai_body["max_tokens"] = 4096;576    }577 578    // Pass through common params579    for (const auto & key : {"temperature", "top_p", "top_k", "stream", "chat_template_kwargs"}) {580        if (body.contains(key)) {581            oai_body[key] = body.at(key);582        }583    }584 585    // Handle Anthropic-specific thinking param586    if (body.contains("thinking")) {587        json thinking = json_value(body, "thinking", json::object());588        std::string thinking_type = json_value(thinking, "type", std::string());589        if (thinking_type == "enabled") {590            int budget_tokens = json_value(thinking, "budget_tokens", 10000);591            oai_body["thinking_budget_tokens"] = budget_tokens;592        }593    }594 595    // Handle Anthropic-specific metadata param596    if (body.contains("metadata")) {597        json metadata = json_value(body, "metadata", json::object());598        std::string user_id = json_value(metadata, "user_id", std::string());599        if (!user_id.empty()) {600            oai_body["__metadata_user_id"] = user_id;601        }602    }603 604    return oai_body;605}606 607json server_chat_msg_diff_to_json_oaicompat(const common_chat_msg_diff & diff) {608    json delta = json::object();609    if (!diff.reasoning_content_delta.empty()) {610        delta["reasoning_content"] = diff.reasoning_content_delta;611    }612    if (!diff.content_delta.empty()) {613        delta["content"] = diff.content_delta;614    }615    if (diff.tool_call_index != std::string::npos) {616        json tool_call;617        tool_call["index"] = diff.tool_call_index;618        if (!diff.tool_call_delta.id.empty()) {619            tool_call["id"]   = diff.tool_call_delta.id;620            tool_call["type"] = "function";621        }622        if (!diff.tool_call_delta.name.empty() || !diff.tool_call_delta.arguments.empty()) {623            json function = json::object();624            if (!diff.tool_call_delta.name.empty()) {625                function["name"] = diff.tool_call_delta.name;626            }627            if (!diff.tool_call_delta.arguments.empty()) {628                function["arguments"] = diff.tool_call_delta.arguments;629            }630            tool_call["function"] = function;631        }632        delta["tool_calls"] = json::array({ tool_call });633    }634    return delta;635}636 637json convert_transcriptions_to_chatcmpl(638        const json & inp_body,639        const common_chat_templates * tmpls,640        const std::map<std::string, uploaded_file> & in_files,641        std::vector<raw_buffer> & out_files) {642    // TODO @ngxson : this function may need to be improved in the future643    // handle input files644    out_files.clear();645    auto it = in_files.find("file");646    if (it != in_files.end()) {647        out_files.push_back(it->second.data);648    } else {649        throw std::invalid_argument("No input file found for transcription");650    }651 652    // handle input data653    std::string prompt          = json_value(inp_body, "prompt", std::string());654    std::string language        = json_value(inp_body, "language", std::string());655    std::string response_format = json_value(inp_body, "response_format", std::string("json"));656    if (response_format != "json") {657        throw std::invalid_argument("Only 'json' response_format is supported for transcription");658    }659    const common_chat_prompt_preset preset = common_chat_get_asr_prompt(tmpls);660    if (prompt.empty()) {661        prompt = preset.user;662    }663    if (!language.empty()) {664        prompt += string_format(" (language: %s)", language.c_str());665    }666    prompt += get_media_marker();667 668    json messages = json::array();669    if (!preset.system.empty()) {670        messages.push_back({{"role", "system"}, {"content", preset.system}});671    }672    messages.push_back({{"role", "user"}, {"content", prompt}});673 674    json chatcmpl_body = inp_body; // copy all fields675    chatcmpl_body["messages"] = messages;676 677    // because input from form-data, everything is string, we need to correct the types here678    std::string stream = json_value(inp_body, "stream", std::string("false"));679    chatcmpl_body["stream"] = stream == "true";680 681    if (inp_body.contains("max_tokens")) {682        std::string inp = inp_body["max_tokens"].get<std::string>();683        chatcmpl_body["max_tokens"] = std::stoul(inp);684    }685 686    if (inp_body.contains("temperature")) {687        std::string inp = inp_body["temperature"].get<std::string>();688        chatcmpl_body["temperature"] = std::stof(inp);689    }690 691    return chatcmpl_body;692}693