diff --git a/CMakeLists.txt b/CMakeLists.txt index edb9e6570..bbb2c991b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ option(LLAMA_BUILD_SERVER "llama: build server example" ${LLAMA_STANDALONE}) # 3rd party libs option(LLAMA_CURL "llama: use libcurl to download model from an URL" OFF) +option(LLAMA_LLGUIDANCE "llama-common: include LLGuidance library for structured output in common utils" OFF) # Required for relocatable CMake package include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/build-info.cmake) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 77fe2b34d..99f752e95 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -52,17 +52,13 @@ set(TARGET common) add_library(${TARGET} STATIC base64.hpp - chat-template.hpp - common.h + chat.cpp + chat.h + chat-parser.cpp + chat-parser.h common.cpp chat.h chat.cpp - chat-parser.h - chat-parser.cpp - json-partial.h - json-partial.cpp - regex-partial.h - regex-partial.cpp sampling.h sampling.cpp console.h @@ -70,13 +66,19 @@ add_library(${TARGET} STATIC grammar-parser.h grammar-parser.cpp json.hpp + json-partial.h + json-partial.cpp + llguidance.cpp json-schema-to-grammar.cpp train.h train.cpp - minja.hpp + minja/chat-template.hpp + minja/minja.hpp ngram-cache.h ngram-cache.cpp speculative.cpp + regex-partial.cpp + regex-partial.h ) if (BUILD_SHARED_LIBS) @@ -94,6 +96,33 @@ if (LLAMA_CURL) set(LLAMA_COMMON_EXTRA_LIBS ${LLAMA_COMMON_EXTRA_LIBS} ${CURL_LIBRARY}) endif () +if (LLAMA_LLGUIDANCE) + include(ExternalProject) + set(LLGUIDANCE_SRC ${CMAKE_BINARY_DIR}/llguidance/source) + set(LLGUIDANCE_PATH ${LLGUIDANCE_SRC}/target/release) + ExternalProject_Add(llguidance_ext + GIT_REPOSITORY https://github.com/guidance-ai/llguidance + # v0.6.12: + GIT_TAG ced1c9023d47ec194fa977932d35ce65c2ebfc09 + PREFIX ${CMAKE_BINARY_DIR}/llguidance + SOURCE_DIR ${LLGUIDANCE_SRC} + BUILD_IN_SOURCE TRUE + CONFIGURE_COMMAND "" + BUILD_COMMAND cargo build --release + INSTALL_COMMAND "" + BUILD_BYPRODUCTS ${LLGUIDANCE_PATH}/libllguidance.a ${LLGUIDANCE_PATH}/llguidance.h + UPDATE_COMMAND "" + ) + target_compile_definitions(${TARGET} PUBLIC LLAMA_USE_LLGUIDANCE) + + add_library(llguidance STATIC IMPORTED) + set_target_properties(llguidance PROPERTIES IMPORTED_LOCATION ${LLGUIDANCE_PATH}/libllguidance.a) + add_dependencies(llguidance llguidance_ext) + + target_include_directories(${TARGET} PRIVATE ${LLGUIDANCE_PATH}) + set(LLAMA_COMMON_EXTRA_LIBS ${LLAMA_COMMON_EXTRA_LIBS} llguidance) +endif () + target_include_directories(${TARGET} PUBLIC .) target_compile_features (${TARGET} PUBLIC cxx_std_11) target_link_libraries (${TARGET} PRIVATE ${LLAMA_COMMON_EXTRA_LIBS} PUBLIC llama Threads::Threads) diff --git a/common/chat-parser.cpp b/common/chat-parser.cpp index b2f5c91ff..733bce39b 100644 --- a/common/chat-parser.cpp +++ b/common/chat-parser.cpp @@ -1,54 +1,69 @@ -// Chat parser implementation #include "chat-parser.h" -#include "../examples/server/parsers/kimi_k2_parser.hpp" -#include "json.hpp" #include "common.h" +#include "log.h" +#include "regex-partial.h" + +#include +#include +#include +#include using json = nlohmann::ordered_json; common_chat_msg_parser::common_chat_msg_parser(const std::string & input, bool is_partial, const common_chat_syntax & syntax) - : input_(input), is_partial_(is_partial), syntax_(syntax) { - // Initialize result with default role + : input_(input), is_partial_(is_partial), syntax_(syntax) +{ result_.role = "assistant"; + + while (true) { + std::string id = std::to_string(std::rand()); + if (input.find(id) == std::string::npos) { + healing_marker_ = id; + break; + } + } } std::string common_chat_msg_parser::str(const common_string_range & rng) const { - if (rng.begin > input_.size() || rng.end > input_.size()) { - throw std::runtime_error("Range out of bounds"); - } + GGML_ASSERT(rng.begin <= rng.end); return input_.substr(rng.begin, rng.end - rng.begin); } -void common_chat_msg_parser::add_content(const std::string & content) { +void common_chat_msg_parser::add_content(const std::string &content) { result_.content += content; } -void common_chat_msg_parser::add_reasoning_content(const std::string & reasoning_content) { +void common_chat_msg_parser::add_reasoning_content(const std::string &reasoning_content) { result_.reasoning_content += reasoning_content; } -void common_chat_msg_parser::add_tool_call(const common_chat_tool_call & tool_call) { - result_.tool_calls.push_back(tool_call); -} - bool common_chat_msg_parser::add_tool_call(const std::string & name, const std::string & id, const std::string & arguments) { if (name.empty()) { return false; } - + common_chat_tool_call tool_call; tool_call.name = name; tool_call.arguments = arguments; tool_call.id = id; - + + // LOG("Tool call arguments:\n\traw: %s\n\tresult: %s\n", arguments.c_str(), tool_call.arguments.c_str()); result_.tool_calls.emplace_back(tool_call); + return true; } - bool common_chat_msg_parser::add_tool_call(const json & tool_call) { std::string name = tool_call.contains("name") ? tool_call.at("name") : ""; std::string id = tool_call.contains("id") ? tool_call.at("id") : ""; - std::string arguments = tool_call.contains("arguments") ? tool_call.at("arguments") : ""; + std::string arguments = ""; + if (tool_call.contains("arguments")) { + if (tool_call.at("arguments").is_object()) { + arguments = tool_call.at("arguments").dump(); + } else { + arguments = tool_call.at("arguments"); + } + } + return add_tool_call(name, id, arguments); } @@ -60,25 +75,65 @@ bool common_chat_msg_parser::add_tool_calls(const json & arr) { } return true; } - -void common_chat_msg_parser::clear_tools() { - result_.tool_calls.clear(); +void common_chat_msg_parser::finish() { + if (!is_partial_ && pos_ != input_.size()) { + throw std::runtime_error("Unexpected content at end of input");// + input_.substr(pos_)); + } } -std::string common_chat_msg_parser::consume_rest() { - auto rest = input_.substr(pos_); - pos_ = input_.size(); - return rest; +bool common_chat_msg_parser::consume_spaces() { + const auto length = input_.size(); + auto consumed = false; + while (pos_ < length && std::isspace(input_[pos_])) { + ++pos_; + consumed = true; + } + return consumed; } bool common_chat_msg_parser::try_consume_literal(const std::string & literal) { - if (pos_ + literal.size() <= input_.size()) { - if (input_.substr(pos_, literal.size()) == literal) { - pos_ += literal.size(); - return true; + auto pos = pos_; + for (auto i = 0u; i < literal.size(); ++i) { + if (pos >= input_.size()) { + return false; + } + if (input_[pos] != literal[i]) { + return false; } + ++pos; + } + pos_ = pos; + return true; +} + +std::optional common_chat_msg_parser::try_find_literal(const std::string & literal) { + auto idx = input_.find(literal, pos_); + if (idx != std::string::npos) { + find_regex_result res; + res.prelude = input_.substr(pos_, idx - pos_); + auto end = idx + literal.size(); + res.groups.emplace_back(common_string_range{idx, end}); + move_to(end); + return res; + } + if (is_partial_) { + idx = string_find_partial_stop(input_, literal); + if (idx != std::string::npos && idx >= pos_) { + find_regex_result res; + res.prelude = input_.substr(pos_, idx - pos_); + auto end = input_.size(); + res.groups.emplace_back(common_string_range{idx, end}); + move_to(end); + return res; + } + } + return std::nullopt; +} + +void common_chat_msg_parser::consume_literal(const std::string & literal) { + if (!try_consume_literal(literal)) { + throw common_chat_msg_partial_exception(literal); } - return false; } bool common_chat_msg_parser::try_parse_reasoning(const std::string & start_think, const std::string & end_think) { @@ -97,7 +152,6 @@ bool common_chat_msg_parser::try_parse_reasoning(const std::string & start_think add_reasoning_content(stripped_reasoning); } }; - if (syntax_.reasoning_format != COMMON_REASONING_FORMAT_NONE) { if (syntax_.thinking_forced_open || try_consume_literal(start_think)) { if (auto res = try_find_literal(end_think)) { @@ -109,198 +163,73 @@ bool common_chat_msg_parser::try_parse_reasoning(const std::string & start_think if (!rest.empty()) { handle_reasoning(rest, /* closed */ !is_partial()); } - // Allow unclosed thinking tags for now (following original llama.cpp) + // Allow unclosed thinking tags, for now (https://github.com/ggml-org/llama.cpp/issues/13812, https://github.com/ggml-org/llama.cpp/issues/13877) + // if (!syntax_.thinking_forced_open) { + // throw common_chat_msg_partial_exception(end_think); + // } return true; } } return false; } -std::optional common_chat_msg_parser::try_find_literal_legacy(const std::string & literal) { - auto idx = input_.find(literal, pos_); - if (idx != std::string::npos) { - find_regex_result res; - res.prelude = input_.substr(pos_, idx - pos_); - auto end = idx + literal.size(); - res.groups.emplace_back(common_string_range{idx, end}); - move_to(end); - return res; - } - - if (is_partial_) { - idx = string_find_partial_stop(input_, literal); - if (idx != std::string::npos && idx >= pos_) { - find_regex_result res; - res.prelude = input_.substr(pos_, idx - pos_); - auto end = input_.size(); - res.groups.emplace_back(common_string_range{idx, end}); - move_to(end); - return res; - } - } - return std::nullopt; -} - -void common_chat_msg_parser::parse() { - switch (syntax_.format) { - case COMMON_CHAT_FORMAT_KIMI_K2: - parse_kimi_k2_format(); - break; - case COMMON_CHAT_FORMAT_DEEPSEEK_R1: - parse_deepseek_r1_format(); - break; - case COMMON_CHAT_FORMAT_GENERIC: - parse_generic_format(); - break; - case COMMON_CHAT_FORMAT_CONTENT_ONLY: - add_content(consume_rest()); - break; - default: - // Fallback to content-only for now - add_content(consume_rest()); - break; - } +std::string common_chat_msg_parser::consume_rest() { + auto rest = input_.substr(pos_); + pos_ = input_.size(); + return rest; } -void common_chat_msg_parser::parse_kimi_k2_format() { - json tool_calls_json = kimi_k2::parse_tool_calls(input_); - - if (is_partial_ && kimi_k2::is_partial_content_advanced(input_)) { - throw common_chat_msg_partial_exception("partial structured content detected"); +// Tries to find the regex, consumes it (pos right after it) and gives the prelude (right before it) and the groups to the callback. +std::optional common_chat_msg_parser::try_find_regex(const common_regex & regex, size_t from, bool add_prelude_to_content) { + auto m = regex.search(input_, from == std::string::npos ? pos_ : from); + if (m.type == COMMON_REGEX_MATCH_TYPE_NONE) { + return std::nullopt; } + auto prelude = input_.substr(pos_, m.groups[0].begin - pos_); + pos_ = m.groups[0].end; - bool has_function_syntax = input_.find("functions.") != std::string::npos; - bool parsing_succeeded = !tool_calls_json.empty(); - - if (has_function_syntax && !parsing_succeeded) { - throw std::runtime_error("malformed function call syntax detected"); + if (add_prelude_to_content) { + add_content(prelude); } - - if (!tool_calls_json.empty()) { - for (const auto& tc_json : tool_calls_json) { - try { - common_chat_tool_call tc; - tc.id = tc_json.value("id", ""); - - if (!tc_json.contains("function") || !tc_json["function"].contains("name")) { - continue; - } - - tc.name = tc_json["function"]["name"]; - if (tc.name.empty()) { - continue; - } - - tc.arguments = tc_json["function"]["arguments"]; - - if (!is_partial_ && !tc.arguments.empty()) { - try { - auto parsed = json::parse(tc.arguments); - (void)parsed; - } catch (const std::exception&) { - continue; - } - } - add_tool_call(tc); - } catch (const std::exception&) { - continue; - } + if (m.type == COMMON_REGEX_MATCH_TYPE_PARTIAL) { + if (is_partial()) { + throw common_chat_msg_partial_exception(regex.str()); } - add_content(kimi_k2::clean_content(input_)); - } else { - add_content(input_); + return std::nullopt; } - pos_ = input_.size(); -} - -void common_chat_msg_parser::parse_generic_format() { - add_content(consume_rest()); -} - -void common_chat_msg_parser::parse_deepseek_r1_format() { - // Delegate to the main chat.cpp function which has the corrected implementation - // This follows the original llama.cpp pattern where chat-parser delegates to chat.cpp - common_chat_parse_deepseek_r1(*this); -} - - -void common_chat_msg_parser::finish() { - // Any final processing can go here -} - -common_chat_msg common_chat_msg_parser::result_and_reset() { - auto msg = result_; - result_ = common_chat_msg(); - result_.role = "assistant"; - pos_ = 0; - return msg; + return find_regex_result{prelude, m.groups}; } -// Content-only parsing for fallback scenarios - -// Format detection from chat template patterns (focused on DeepSeek R1 and Kimi K2) -common_chat_format common_chat_format_detect(const std::string & chat_template) { - if (chat_template.empty()) { - return COMMON_CHAT_FORMAT_GENERIC; - } - - // Detect DeepSeek R1 format (following original llama.cpp detection logic) - if (chat_template.find("<|tool▁calls▁begin|>") != std::string::npos) { - return COMMON_CHAT_FORMAT_DEEPSEEK_R1; - } - - // Detect Kimi K2 format (our custom format) - if (chat_template.find("kimi") != std::string::npos || - chat_template.find("Kimi") != std::string::npos || - chat_template.find("functions.") != std::string::npos) { - return COMMON_CHAT_FORMAT_KIMI_K2; +common_chat_msg_parser::find_regex_result common_chat_msg_parser::consume_regex(const common_regex & regex) { + if (auto result = try_consume_regex(regex)) { + return *result; } - - // Default to generic format for unknown templates - return COMMON_CHAT_FORMAT_GENERIC; + throw common_chat_msg_partial_exception(regex.str()); } -// Progressive parsing primitive - find literal (following original llama.cpp pattern) -std::optional common_chat_msg_parser::try_find_literal(const std::string & literal) { - auto idx = input_.find(literal, pos_); - if (idx != std::string::npos) { - find_regex_result res; - res.prelude = input_.substr(pos_, idx - pos_); - auto end = idx + literal.size(); - res.groups.emplace_back(common_string_range{idx, end}); - move_to(end); - return res; +std::optional common_chat_msg_parser::try_consume_regex(const common_regex & regex) { + auto m = regex.search(input_, pos_); + if (m.type == COMMON_REGEX_MATCH_TYPE_NONE) { + return std::nullopt; } - - if (is_partial_) { - idx = string_find_partial_stop(input_, literal); - if (idx != std::string::npos && idx >= pos_) { - find_regex_result res; - res.prelude = input_.substr(pos_, idx - pos_); - auto end = input_.size(); - res.groups.emplace_back(common_string_range{idx, end}); - move_to(end); - return res; + if (m.type == COMMON_REGEX_MATCH_TYPE_PARTIAL) { + if (is_partial()) { + throw common_chat_msg_partial_exception(regex.str()); } + return std::nullopt; } - return std::nullopt; -} - -bool common_chat_msg_parser::consume_spaces() { - bool consumed = false; - while (pos_ < input_.length() && std::isspace(input_[pos_])) { - pos_++; - consumed = true; + if (m.groups[0].begin != pos_) { + // Didn't match at the current position. + return std::nullopt; } - return consumed; -} + pos_ = m.groups[0].end; -void common_chat_msg_parser::set_healing_marker(const std::string & marker) { - healing_marker_ = marker; + return find_regex_result { + /* .prelude = */ "", + m.groups, + }; } - -// Enhanced JSON parsing methods (following original llama.cpp patterns exactly) std::optional common_chat_msg_parser::try_consume_json() { auto it = input_.cbegin() + pos_; const auto end = input_.cend(); @@ -327,8 +256,8 @@ common_json common_chat_msg_parser::consume_json() { } common_chat_msg_parser::consume_json_result common_chat_msg_parser::consume_json_with_dumped_args( - const std::vector>& args_paths, - const std::vector>& content_paths + const std::vector> & args_paths, + const std::vector> & content_paths ) { if (auto result = try_consume_json_with_dumped_args(args_paths, content_paths)) { return *result; @@ -337,8 +266,8 @@ common_chat_msg_parser::consume_json_result common_chat_msg_parser::consume_json } std::optional common_chat_msg_parser::try_consume_json_with_dumped_args( - const std::vector>& args_paths, - const std::vector>& content_paths + const std::vector> & args_paths, + const std::vector> & content_paths ) { auto partial = try_consume_json(); if (!partial) { @@ -366,137 +295,99 @@ std::optional common_chat_msg_parse /* .is_partial = */ false, }; } - // TODO: Implement full path-based argument dumping logic from original - // For now, return the parsed JSON as-is - return consume_json_result { - partial->json, - /* .is_partial = */ false, - }; } - - // Has healing marker - this is partial JSON - // TODO: Implement sophisticated partial JSON handling with path-based dumping - // For now, return partial result - return consume_json_result { - partial->json, - /* .is_partial = */ true, - }; -} -bool common_chat_msg_parser::detect_partial_function_call(const std::string& content) { - if (content.empty()) return false; - - // Enhanced partial detection patterns - static const std::vector partial_patterns = { - "functions", - "functions.", - "", - "", - "<|tool_call_begin|>" - }; - - for (const auto& pattern : partial_patterns) { - if (content.substr(0, pattern.length()) == pattern && content.length() <= pattern.length() + 50) { - return true; - } - } - - return false; -} + LOG("Parsed partial JSON: %s (json_healing_marker: %s)\n", partial->json.dump().c_str(), partial->healing_marker.json_dump_marker.c_str()); -void common_chat_msg_parser::handle_partial_detection() { - if (!is_partial_) return; - - // Check for various partial patterns - std::string remaining = input_.substr(pos_); - - if (remaining.empty()) return; - - // Detect partial function calls - if (detect_partial_function_call(remaining)) { - set_healing_marker(remaining); - throw common_chat_msg_partial_exception("partial function call detected"); - } - - // Enhanced partial JSON detection - if (remaining.find('{') != std::string::npos) { - size_t brace_pos = remaining.find('{'); - std::string json_part = remaining.substr(brace_pos); - - // Check if JSON is incomplete - int brace_count = 0; - bool in_string = false; - bool escaped = false; - bool is_incomplete = true; - - for (size_t i = 0; i < json_part.length(); i++) { - char c = json_part[i]; - - if (!escaped) { - if (c == '"' && !in_string) { - in_string = true; - } else if (c == '"' && in_string) { - in_string = false; - } else if (!in_string) { - if (c == '{') brace_count++; - else if (c == '}') brace_count--; + auto found_healing_marker = false; + std::vector path; + std::function remove_unsupported_healings_and_dump_args = [&](const json & j) -> json { + if (is_arguments_path(path)) { + auto arguments = j.dump(); + if (is_partial() && !partial->healing_marker.marker.empty()) { + auto idx = arguments.find(partial->healing_marker.json_dump_marker); + if (idx != std::string::npos) { + arguments.resize(idx); + found_healing_marker = true; } + if (arguments == "\"") { + // This happens because of completing `:"$magic` after `"arguments"` + arguments = ""; + } + } + return arguments; + } + if (is_content_path(path)) { + if (!j.is_string()) { + throw std::runtime_error("Content path must be a string"); } - - escaped = (!escaped && c == '\\'); - - if (brace_count == 0) { - is_incomplete = false; - break; + std::string str = j; + auto idx = str.find(partial->healing_marker.marker); // not using json_dump_marker as we're inside a string + if (idx != std::string::npos) { + str.resize(idx); + found_healing_marker = true; } + return str; } - - if (is_incomplete) { - set_healing_marker(json_part); - throw common_chat_msg_partial_exception("partial JSON detected"); + if (j.is_object()) { + auto obj = json::object(); + for (const auto & p : j.items()) { + const auto & key = p.key(); + const auto & value = p.value(); + const std::string key_str = key; // NOLINT + auto idx = key_str.find(healing_marker_); + if (idx != std::string::npos) { + found_healing_marker = true; + break; + } + path.push_back(key_str); + if (value.is_string()) { + const std::string value_str = value; + if (value_str.find(healing_marker_) != std::string::npos) { + found_healing_marker = true; + if (is_content_path(path)) { + if (partial->healing_marker.marker == partial->healing_marker.json_dump_marker) { + // The healing occurred inside the string: good. Otherwise we just ditch the entire key/value pair. + obj[key] = remove_unsupported_healings_and_dump_args(value); + } + } + break; + } + obj[key] = value; + } else { + obj[key] = remove_unsupported_healings_and_dump_args(value); + } + path.pop_back(); + } + return obj; } - } -} - -// Regex-based parsing methods (ported from original llama.cpp) -std::optional common_chat_msg_parser::try_find_regex(const common_regex & regex, size_t from, bool add_prelude_to_content) { - auto m = regex.search(input_, from == std::string::npos ? pos_ : from); - if (m.type == COMMON_REGEX_MATCH_TYPE_NONE) { - return std::nullopt; - } - auto prelude = input_.substr(pos_, m.groups[0].begin - pos_); - pos_ = m.groups[0].end; - - if (add_prelude_to_content) { - add_content(prelude); - } - if (m.type == COMMON_REGEX_MATCH_TYPE_PARTIAL) { - if (is_partial()) { - throw common_chat_msg_partial_exception(regex.str()); + if (j.is_array()) { + auto arr = json::array(); + for (const auto & value : j) { + if (value.is_string()) { + std::string str = value; + auto idx = str.find(healing_marker_); + if (idx != std::string::npos) { + // Don't heal array values that aren't in the arguments. + found_healing_marker = true; + break; + } + } + arr.push_back(remove_unsupported_healings_and_dump_args(value)); + } + return arr; } - return std::nullopt; - } - return find_regex_result{prelude, m.groups}; -} - -common_chat_msg_parser::find_regex_result common_chat_msg_parser::consume_regex(const common_regex & regex) { - auto result = try_find_regex(regex); - if (!result) { - throw std::runtime_error("Expected regex not found: " + regex.str()); - } - return *result; -} + return j; + }; -std::optional common_chat_msg_parser::try_consume_regex(const common_regex & regex) { - return try_find_regex(regex, pos_, false); + auto cleaned = remove_unsupported_healings_and_dump_args(partial->json); + LOG("Cleaned up JSON %s to %s (json_healing_marker : '%s')\n", partial->json.dump().c_str(), cleaned.dump().c_str(), partial->healing_marker.json_dump_marker.c_str()); + return consume_json_result { + cleaned, + /* .is_partial = */ found_healing_marker, + }; } -void common_chat_msg_parser::consume_literal(const std::string & literal) { - if (!try_consume_literal(literal)) { - throw std::runtime_error("Expected literal not found: " + literal); - } +void common_chat_msg_parser::clear_tools() { + result_.tool_calls.clear(); } - -// Get format name for debugging/logging (implemented in chat.cpp) \ No newline at end of file diff --git a/common/chat-parser.h b/common/chat-parser.h index 1e7a3f949..e08b37d34 100644 --- a/common/chat-parser.h +++ b/common/chat-parser.h @@ -1,14 +1,18 @@ -// Chat parser with builder pattern for incremental parsing #pragma once #include "chat.h" #include "json-partial.h" +#include "json.hpp" #include "regex-partial.h" + #include #include #include -using json = nlohmann::ordered_json; +class common_chat_msg_partial_exception : public std::runtime_error { + public: + common_chat_msg_partial_exception(const std::string & message) : std::runtime_error(message) {} +}; class common_chat_msg_parser { std::string input_; @@ -20,14 +24,7 @@ class common_chat_msg_parser { common_chat_msg result_; public: - struct find_regex_result { - std::string prelude; - std::vector groups; - }; - common_chat_msg_parser(const std::string & input, bool is_partial, const common_chat_syntax & syntax); - - // Accessors const std::string & input() const { return input_; } size_t pos() const { return pos_; } const std::string & healing_marker() const { return healing_marker_; } @@ -35,14 +32,12 @@ class common_chat_msg_parser { const common_chat_msg & result() const { return result_; } const common_chat_syntax & syntax() const { return syntax_; } - // Position manipulation void move_to(size_t pos) { if (pos > input_.size()) { throw std::runtime_error("Invalid position!"); } pos_ = pos; } - void move_back(size_t n) { if (pos_ < n) { throw std::runtime_error("Can't move back that far!"); @@ -53,84 +48,72 @@ class common_chat_msg_parser { // Get the substring of the input at the given range std::string str(const common_string_range & rng) const; - // Content manipulation + // Appends to the result.content field void add_content(const std::string & content); + + // Appends to the result.reasoning_content field void add_reasoning_content(const std::string & reasoning_content); - // Tool call manipulation - void add_tool_call(const common_chat_tool_call & tool_call); + // Adds a tool call to the result. If the tool call is too incomplete (e.g. name empty), it won't add anything. bool add_tool_call(const std::string & name, const std::string & id, const std::string & arguments); - bool add_tool_call(const json & tool_call); - bool add_tool_calls(const json & arr); - void clear_tools(); - // Parsing utilities - std::string consume_rest(); - bool try_consume_literal(const std::string & literal); + // Adds a tool call using the "name", "id" and "arguments" fields of the json object + bool add_tool_call(const nlohmann::ordered_json & tool_call); + + // Adds an array of tool calls using their "name", "id" and "arguments" fields. + bool add_tool_calls(const nlohmann::ordered_json & arr); + + void finish(); + + bool consume_spaces(); + void consume_literal(const std::string & literal); + bool try_parse_reasoning(const std::string & start_think, const std::string & end_think); - // Regex-based parsing methods (new) + std::string consume_rest(); + + struct find_regex_result { + std::string prelude; + std::vector groups; + }; + std::optional try_find_regex(const common_regex & regex, size_t from = std::string::npos, bool add_prelude_to_content = true); - find_regex_result consume_regex(const common_regex & regex); - std::optional try_consume_regex(const common_regex & regex); - // Progressive parsing primitives (for Phase 4) - std::optional try_find_literal(const std::string & literal); - bool consume_spaces(); - void set_healing_marker(const std::string & marker); + bool try_consume_literal(const std::string & literal); + std::optional try_find_literal(const std::string & literal); - // Main parsing entry point - void parse(); + find_regex_result consume_regex(const common_regex & regex); - // Finishing - void finish(); + std::optional try_consume_regex(const common_regex & regex); - // Result extraction - common_chat_msg result_and_reset(); + std::optional try_consume_json(); + common_json consume_json(); - // Advanced JSON parsing (following original llama.cpp patterns) struct consume_json_result { - json value; + nlohmann::ordered_json value; bool is_partial; }; - std::optional try_consume_json(); - common_json consume_json(); + /* + Consume (possibly partial) json and converts specific subtrees to (possibly truncated) JSON strings. + + By default, object keys can't be truncated, nor can string values (their corresponding key is removed, + e.g. `{"foo": "bar", "baz": "b` -> `{"foo": "bar"}` + + But one can allow subpaths to be kept truncated, and possibly json-dumped to truncated json strings + - with `content_paths={{"foo"}}` -> `{"foo": "b` -> {"foo": "b"}` + - with `args_paths={{"foo"}}` -> `{"foo": {"b` -> `{"foo": "{b"}` + */ consume_json_result consume_json_with_dumped_args( - const std::vector>& args_paths = {}, - const std::vector>& content_paths = {} + const std::vector> & args_paths = {}, + const std::vector> & content_paths = {} ); std::optional try_consume_json_with_dumped_args( - const std::vector>& args_paths = {}, - const std::vector>& content_paths = {} + const std::vector> & args_paths = {}, + const std::vector> & content_paths = {} ); -private: - // Internal parsing helpers - void parse_kimi_k2_format(); - void parse_deepseek_r1_format(); - void parse_generic_format(); - - - // JSON parsing utilities (enhanced streaming support) - struct json_parse_result { - json value; - bool success; - bool is_partial; - std::string healing_marker; - }; - - // Partial detection utilities - bool detect_partial_function_call(const std::string& content); - void handle_partial_detection(); - - // Legacy find_literal for compatibility - std::optional try_find_literal_legacy(const std::string & literal); + void clear_tools(); }; - -// Main parsing function (public API) -common_chat_msg common_chat_parse(const std::string & input, bool is_partial, const common_chat_syntax & syntax); - -// Content-only parsing for fallback scenarios (static internal function) diff --git a/common/chat-template.hpp b/common/chat-template.hpp deleted file mode 100644 index b4a90145c..000000000 --- a/common/chat-template.hpp +++ /dev/null @@ -1,249 +0,0 @@ -/* - Copyright 2024 Google LLC - - Use of this source code is governed by an MIT-style - license that can be found in the LICENSE file or at - https://opensource.org/licenses/MIT. -*/ -// SPDX-License-Identifier: MIT -#pragma once - -#include "minja.hpp" -#include -#include -#include - -using json = nlohmann::ordered_json; - -namespace minja { - -class chat_template { - public: - - private: - bool supports_tools_ = true; - // Meta-Llama-3.1-8B-Instruct's template expects arguments to be an object. - // Most other templates (and OpenAI's API) expect the arguments object to be stringified. - bool requires_object_arguments_ = false; - bool supports_system_role_ = true; - bool supports_parallel_tool_calls_ = false; - std::string source_; - std::string bos_token_; - std::string eos_token_; - std::shared_ptr template_root_; - - std::string try_render( - const nlohmann::ordered_json & messages, - const nlohmann::ordered_json & tools, - bool add_generation_prompt, - const nlohmann::ordered_json & extra_context = nlohmann::ordered_json()) const - { - try { - auto prompt = apply(messages, tools, add_generation_prompt, extra_context); - // fprintf(stderr, "Prompt: %s\n", prompt.c_str()); - return prompt; - } catch (const std::exception & e) { - // fprintf(stderr, "Error: %s\n", e.what()); - return ""; - } - } - - public: - chat_template(const std::string & source, const std::string & bos_token, const std::string & eos_token) - : source_(source), bos_token_(bos_token), eos_token_(eos_token) - { - template_root_ = minja::Parser::parse(source_, { - /* .trim_blocks = */ true, - /* .lstrip_blocks = */ true, - /* .keep_trailing_newline = */ false, - }); - supports_tools_ = source.find("tools") != std::string::npos; - - auto renders_string_arguments = - try_render({ - { - {"role", "user"}, - {"content", "Hey"} - }, - { - {"role", "assistant"}, - {"tool_calls", json::array({ - { - {"id", "call_1___"}, - {"type", "function"}, - {"function", { - {"arguments", "{\"code\": \"print('Hello, World!')\"}"}, - {"name", "ipython"}, - }}, - }, - })}, - } - }, {}, false).find("{\"code\": \"print") != std::string::npos; - if (!renders_string_arguments) { - auto renders_object_arguments = - try_render({ - { - {"role", "user"}, - {"content", "Hey"} - }, - { - {"role", "assistant"}, - {"tool_calls", json::array({ - { - {"id", "call_1___"}, - {"type", "function"}, - {"function", { - {"arguments", { - {"code", "print('Hello, World!')"}, - }}, - {"name", "ipython"}, - }}, - }, - })}, - } - }, {}, false).find("{\"code\": \"print") != std::string::npos; - requires_object_arguments_ = renders_object_arguments; - } - supports_parallel_tool_calls_ = source.find("tool_call_id") != std::string::npos; - - supports_system_role_ = try_render({ - {{"role", "system"}, {"content", ""}}, - {{"role", "user"}, {"content", "Hey"}} - }, {}, false).find("") != std::string::npos; - } - - const std::string & source() const { return source_; } - const std::string & bos_token() const { return bos_token_; } - const std::string & eos_token() const { return eos_token_; } - bool supports_tools() const { return supports_tools_; } - bool supports_parallel_tool_calls() const { return supports_parallel_tool_calls_; } - - std::string apply( - const nlohmann::ordered_json & messages, - const nlohmann::ordered_json & tools, - bool add_generation_prompt, - const nlohmann::ordered_json & extra_context = nlohmann::ordered_json()) const - { - json actual_messages; - - // First, "fix" messages so they have a chance to be rendered correctly by the template - - if (requires_object_arguments_ || !supports_system_role_ || !supports_tools_) { - actual_messages = json::array(); - - std::string pending_system; - auto flush_sys = [&]() { - if (!pending_system.empty()) { - actual_messages.push_back({ - {"role", "user"}, - {"content", pending_system}, - }); - pending_system.clear(); - } - }; - for (const auto & message_ : messages) { - auto message = message_; - if (!message.contains("role") || !message.contains("content")) { - throw std::runtime_error("message must have 'role' and 'content' fields: " + message.dump()); - } - std::string role = message.at("role"); - - if (message.contains("tool_calls")) { - if (requires_object_arguments_ || !supports_tools_) { - for (auto & tool_call : message.at("tool_calls")) { - if (tool_call["type"] == "function") { - auto & function = tool_call.at("function"); - std::string arguments = function.at("arguments"); - function["arguments"] = json::parse(arguments); - } - } - } - if (!supports_tools_) { - auto content = message.at("content"); - auto tool_calls = json::array(); - for (const auto & tool_call : message.at("tool_calls")) { - if (tool_call.at("type") != "function") { - continue; - } - const auto & function = tool_call.at("function"); - auto tc = json { - {"name", function.at("name")}, - {"arguments", function.at("arguments")}, - }; - if (tool_call.contains("id")) { - tc["id"] = tool_call["id"]; - } - tool_calls.push_back(tc); - } - auto obj = json { - {"tool_calls", tool_calls}, - }; - if (!content.is_null() && content != "") { - obj["content"] = content; - } - message["content"] = obj.dump(2); - message.erase("tool_calls"); - } - } - if (!supports_tools_ && role == "tool") { - message["role"] = "user"; - auto obj = json { - {"tool_response", { - {"tool", message.at("name")}, - {"content", message.at("content")}, - }}, - }; - if (message.contains("tool_call_id")) { - obj["tool_response"]["tool_call_id"] = message.at("tool_call_id"); - } - message["content"] = obj.dump(2); - message.erase("name"); - } - - if (!message["content"].is_null() && !supports_system_role_) { - std::string content = message.at("content"); - if (role == "system") { - if (!pending_system.empty()) pending_system += "\n"; - pending_system += content; - continue; - } else { - if (role == "user") { - if (!pending_system.empty()) { - message["content"] = pending_system + (content.empty() ? "" : "\n" + content); - pending_system.clear(); - } - } else { - flush_sys(); - } - } - } - actual_messages.push_back(message); - } - flush_sys(); - } else { - actual_messages = messages; - } - - auto context = minja::Context::make(json({ - {"messages", actual_messages}, - {"add_generation_prompt", add_generation_prompt}, - {"bos_token", bos_token_}, - {"eos_token", eos_token_}, - })); - - if (!tools.is_null()) { - auto tools_val = minja::Value(tools); - context->set("tools", tools_val); - } - if (!extra_context.is_null()) { - for (auto & kv : extra_context.items()) { - minja::Value val(kv.value()); - context->set(kv.key(), val); - } - } - - return template_root_->render(context); - } -}; - -} // namespace minja diff --git a/common/chat.cpp b/common/chat.cpp index 8be086333..962245b5e 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -1,15 +1,35 @@ #include "chat.h" #include "chat-parser.h" #include "common.h" -#include "../examples/server/parsers/kimi_k2_parser.hpp" +#include "llama-vocab.h" +#include "json-partial.h" +#include "llama-vocab.h" +#include "json-schema-to-grammar.h" +#include "log.h" +#include "regex-partial.h" +#include +#include + +#include +#include +#include +#include #include #include #include -#include "json.hpp" using json = nlohmann::ordered_json; +static std::string format_time(const std::chrono::system_clock::time_point & now, const std::string & format) { + auto time = std::chrono::system_clock::to_time_t(now); + auto local_time = *std::localtime(&time); + std::ostringstream ss; + ss << std::put_time(&local_time, format.c_str()); + auto res = ss.str(); + return res; +} + static std::string string_diff(const std::string & last, const std::string & current) { if (last.empty()) { return current; @@ -25,6 +45,45 @@ static std::string string_diff(const std::string & last, const std::string & cur return current.substr(last.size()); } +static bool has_content_or_tool_calls(const common_chat_msg & msg) { + return !msg.content.empty() || !msg.tool_calls.empty(); +} + +template <> +json common_chat_msg::to_json_oaicompat() const +{ + json message { + {"role", "assistant"}, + }; + if (!reasoning_content.empty()) { + message["reasoning_content"] = reasoning_content; + } + if (content.empty() && !tool_calls.empty()) { + message["content"] = json(); + } else { + message["content"] = content; + } + if (!tool_calls.empty()) { + auto arr = json::array(); + for (const auto & tc : tool_calls) { + arr.push_back({ + {"type", "function"}, + {"function", { + {"name", tc.name}, + {"arguments", tc.arguments}, + }}, + {"id", tc.id}, + // // Some templates generate and require an id (sometimes in a very specific format, e.g. Mistral Nemo). + // // We only generate a random id for the ones that don't generate one by themselves + // // (they also won't get to see it as their template likely doesn't use it, so it's all for the client) + // {"id", tc.id.empty() ? gen_tool_call_id() : tc.id}, + }); + } + message["tool_calls"] = arr; + } + return message; +} + std::vector common_chat_msg_diff::compute_diffs(const common_chat_msg & previous_msg, const common_chat_msg & new_msg) { std::vector diffs; if (previous_msg.reasoning_content != new_msg.reasoning_content) { @@ -66,45 +125,536 @@ std::vector common_chat_msg_diff::compute_diffs(const comm return diffs; } -// Format parsing functions (ported from original llama.cpp) -// Content-only parsing (internal implementation - matches llama.cpp exactly) -static void common_chat_parse_content_only(common_chat_msg_parser & builder) { - builder.add_content(builder.consume_rest()); +typedef minja::chat_template common_chat_template; + +struct common_chat_templates { + bool add_bos; + bool add_eos; + bool has_explicit_template; // Model had builtin template or template overridde was specified. + std::unique_ptr template_default; // always set (defaults to chatml) + std::unique_ptr template_tool_use; +}; + +struct templates_params { + json messages; + json tools; + common_chat_tool_choice tool_choice; + json json_schema; + bool parallel_tool_calls; + bool stream; + std::string grammar; + bool add_generation_prompt = true; + bool enable_thinking = true; + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + json extra_context; + bool add_bos; + bool add_eos; + bool is_inference = true; +}; + +common_chat_tool_choice common_chat_tool_choice_parse_oaicompat(const std::string & tool_choice) { + if (tool_choice == "auto") { + return COMMON_CHAT_TOOL_CHOICE_AUTO; + } + if (tool_choice == "none") { + return COMMON_CHAT_TOOL_CHOICE_NONE; + } + if (tool_choice == "required") { + return COMMON_CHAT_TOOL_CHOICE_REQUIRED; + } + throw std::runtime_error("Invalid tool_choice: " + tool_choice); } -static void common_chat_parse_generic(common_chat_msg_parser & builder) { - if (!builder.syntax().enable_tool_calls) { - builder.add_content(builder.consume_rest()); - return; +template <> +std::vector common_chat_msgs_parse_oaicompat(const json & messages) { + std::vector msgs; + + try { + + if (!messages.is_array()) { + throw std::runtime_error("Expected 'messages' to be an array, got " + messages.dump()); + } + + for (const auto & message : messages) { + if (!message.is_object()) { + throw std::runtime_error("Expected 'message' to be an object, got " + message.dump()); + } + + common_chat_msg msg; + if (!message.contains("role")) { + throw std::runtime_error("Missing 'role' in message: " + message.dump()); + } + msg.role = message.at("role"); + + auto has_content = message.contains("content"); + auto has_tool_calls = message.contains("tool_calls"); + if (has_content) { + const auto & content = message.at("content"); + if (content.is_string()) { + msg.content = content; + } else if (content.is_array()) { + for (const auto & part : content) { + if (!part.contains("type")) { + throw std::runtime_error("Missing content part type: " + part.dump()); + } + const auto & type = part.at("type"); + if (type != "text") { + throw std::runtime_error("Unsupported content part type: " + type.dump()); + } + common_chat_msg_content_part msg_part; + msg_part.type = type; + msg_part.text = part.at("text"); + msg.content_parts.push_back(msg_part); + } + } else if (!content.is_null()) { + throw std::runtime_error("Invalid 'content' type: expected string or array, got " + content.dump() + " (ref: https://github.com/ggml-org/llama.cpp/issues/8367)"); + } + } + if (has_tool_calls) { + for (const auto & tool_call : message.at("tool_calls")) { + common_chat_tool_call tc; + if (!tool_call.contains("type")) { + throw std::runtime_error("Missing tool call type: " + tool_call.dump()); + } + const auto & type = tool_call.at("type"); + if (type != "function") { + throw std::runtime_error("Unsupported tool call type: " + tool_call.dump()); + } + if (!tool_call.contains("function")) { + throw std::runtime_error("Missing tool call function: " + tool_call.dump()); + } + const auto & fc = tool_call.at("function"); + if (!fc.contains("name")) { + throw std::runtime_error("Missing tool call name: " + tool_call.dump()); + } + tc.name = fc.at("name"); + tc.arguments = fc.at("arguments"); + if (tool_call.contains("id")) { + tc.id = tool_call.at("id"); + } + msg.tool_calls.push_back(tc); + } + } + if (!has_content && !has_tool_calls) { + throw std::runtime_error("Expected 'content' or 'tool_calls' (ref: https://github.com/ggml-org/llama.cpp/issues/8367 & https://github.com/ggml-org/llama.cpp/issues/12279)"); + } + if (message.contains("reasoning_content")) { + msg.reasoning_content = message.at("reasoning_content"); + } + if (message.contains("name")) { + msg.tool_name = message.at("name"); + } + if (message.contains("tool_call_id")) { + msg.tool_call_id = message.at("tool_call_id"); + } + + msgs.push_back(msg); + } + } catch (const std::exception & e) { + // @ngxson : disable otherwise it's bloating the API response + // printf("%s\n", std::string("; messages = ") + messages.dump(2)); + throw std::runtime_error("Failed to parse messages: " + std::string(e.what())); } - static const std::vector> content_paths = { - {"response"}, + + return msgs; +} + +template <> +json common_chat_msgs_to_json_oaicompat(const std::vector & msgs, bool concat_typed_text) { + json messages = json::array(); + for (const auto & msg : msgs) { + if (!msg.content.empty() && !msg.content_parts.empty()) { + throw std::runtime_error("Cannot specify both content and content_parts"); + } + json jmsg { + {"role", msg.role}, + }; + if (!msg.content.empty()) { + jmsg["content"] = msg.content; + } else if (!msg.content_parts.empty()) { + if (concat_typed_text) { + std::string text; + for (const auto & part : msg.content_parts) { + if (part.type != "text") { + LOG("Ignoring content part type: %s\n", part.type.c_str()); + continue; + } + if (!text.empty()) { + text += '\n'; + } + text += part.text; + } + jmsg["content"] = text; + } else { + auto & parts = jmsg["content"] = json::array(); + for (const auto & part : msg.content_parts) { + parts.push_back({ + {"type", part.type}, + {"text", part.text}, + }); + } + } + } else { + jmsg["content"] = json(); // null + } + if (!msg.reasoning_content.empty()) { + jmsg["reasoning_content"] = msg.reasoning_content; + jmsg["thinking"] = msg.reasoning_content; // gpt-oss + } + if (!msg.tool_name.empty()) { + jmsg["name"] = msg.tool_name; + } + if (!msg.tool_call_id.empty()) { + jmsg["tool_call_id"] = msg.tool_call_id; + } + if (!msg.tool_calls.empty()) { + auto & tool_calls = jmsg["tool_calls"] = json::array(); + for (const auto & tool_call : msg.tool_calls) { + json tc { + {"type", "function"}, + {"function", { + {"name", tool_call.name}, + {"arguments", tool_call.arguments}, + }}, + }; + if (!tool_call.id.empty()) { + tc["id"] = tool_call.id; + } + tool_calls.push_back(tc); + } + } + messages.push_back(jmsg); + } + return messages; +} + +template <> +std::vector common_chat_msgs_parse_oaicompat(const std::string & messages) { + return common_chat_msgs_parse_oaicompat(json::parse(messages)); +} + +template <> +std::vector common_chat_tools_parse_oaicompat(const json & tools) { + std::vector result; + + try { + if (!tools.is_null()) { + if (!tools.is_array()) { + throw std::runtime_error("Expected 'tools' to be an array, got " + tools.dump()); + } + for (const auto & tool : tools) { + if (!tool.contains("type")) { + throw std::runtime_error("Missing tool type: " + tool.dump()); + } + const auto & type = tool.at("type"); + if (!type.is_string() || type != "function") { + throw std::runtime_error("Unsupported tool type: " + tool.dump()); + } + if (!tool.contains("function")) { + throw std::runtime_error("Missing tool function: " + tool.dump()); + } + + const auto & function = tool.at("function"); + result.push_back({ + /* .name = */ function.at("name"), + /* .description = */ function.at("description"), + /* .parameters = */ function.at("parameters").dump(), + }); + } + } + } catch (const std::exception & e) { + throw std::runtime_error("Failed to parse tools: " + std::string(e.what()) + "; tools = " + tools.dump(2)); + } + + return result; +} + +template <> +std::vector common_chat_tools_parse_oaicompat(const std::string & tools) { + return common_chat_tools_parse_oaicompat(json::parse(tools)); +} + +template <> +json common_chat_tools_to_json_oaicompat(const std::vector & tools) { + if (tools.empty()) { + return json(); + } + + auto result = json::array(); + for (const auto & tool : tools) { + result.push_back({ + {"type", "function"}, + {"function", { + {"name", tool.name}, + {"description", tool.description}, + {"parameters", json::parse(tool.parameters)}, + }}, + }); + } + return result; +} + +template <> json common_chat_msg_diff_to_json_oaicompat(const common_chat_msg_diff & diff) { + json delta = json::object(); + if (!diff.reasoning_content_delta.empty()) { + delta["reasoning_content"] = diff.reasoning_content_delta; + } + if (!diff.content_delta.empty()) { + delta["content"] = diff.content_delta; + } + if (diff.tool_call_index != std::string::npos) { + json tool_call; + tool_call["index"] = diff.tool_call_index; + if (!diff.tool_call_delta.id.empty()) { + tool_call["id"] = diff.tool_call_delta.id; + tool_call["type"] = "function"; + } + json function = json::object(); + if (!diff.tool_call_delta.name.empty()) { + function["name"] = diff.tool_call_delta.name; + } + function["arguments"] = diff.tool_call_delta.arguments; + tool_call["function"] = function; + delta["tool_calls"] = json::array({tool_call}); + } + return delta; +} + +bool common_chat_verify_template(const std::string & tmpl, bool use_jinja) { + if (use_jinja) { + try { + common_chat_msg msg; + msg.role = "user"; + msg.content = "test"; + + auto tmpls = common_chat_templates_init(/* model= */ nullptr, tmpl); + + common_chat_templates_inputs inputs; + inputs.messages = {msg}; + + common_chat_templates_apply(tmpls.get(), inputs); + return true; + } catch (const std::exception & e) { + LOG("%s: failed to apply template: %s\n", __func__, e.what()); + return false; + } + } + llama_chat_message chat[] = {{"user", "test"}}; + const int res = llama_chat_apply_template(nullptr, tmpl.c_str(), chat, 1, true, nullptr, 0); + return res >= 0; +} + +std::string common_chat_format_single( + const struct common_chat_templates * tmpls, + const std::vector & past_msg, + const common_chat_msg & new_msg, + bool add_ass, + bool use_jinja) { + + common_chat_templates_inputs inputs; + inputs.use_jinja = use_jinja; + inputs.add_bos = tmpls->add_bos; + inputs.add_eos = tmpls->add_eos; + + std::string fmt_past_msg; + if (!past_msg.empty()) { + inputs.messages = past_msg; + inputs.add_generation_prompt = false; + fmt_past_msg = common_chat_templates_apply(tmpls, inputs).prompt; + } + std::ostringstream ss; + // if the past_msg ends with a newline, we must preserve it in the formatted version + if (add_ass && !fmt_past_msg.empty() && fmt_past_msg.back() == '\n') { + ss << "\n"; }; - static const std::vector> args_paths = { - {"tool_call", "arguments"}, - {"tool_calls", "arguments"}, + // format chat with new_msg + inputs.messages.push_back(new_msg); + inputs.add_generation_prompt = add_ass; + auto fmt_new_msg = common_chat_templates_apply(tmpls, inputs).prompt; + // get the diff part + ss << fmt_new_msg.substr(fmt_past_msg.size(), fmt_new_msg.size() - fmt_past_msg.size()); + return ss.str(); +} + +std::string common_chat_format_example(const struct common_chat_templates * tmpls, bool use_jinja) { + common_chat_templates_inputs inputs; + inputs.use_jinja = use_jinja; + inputs.add_bos = tmpls->add_bos; + inputs.add_eos = tmpls->add_eos; + auto add_simple_msg = [&](auto role, auto content) { + common_chat_msg msg; + msg.role = role; + msg.content = content; + inputs.messages.push_back(msg); }; - auto data = builder.consume_json_with_dumped_args(args_paths, content_paths); - if (data.value.contains("tool_calls")) { - if (!builder.add_tool_calls(data.value.at("tool_calls")) || data.is_partial) { - throw common_chat_msg_partial_exception("incomplete tool calls"); + add_simple_msg("system", "You are a helpful assistant"); + add_simple_msg("user", "Hello"); + add_simple_msg("assistant", "Hi there"); + add_simple_msg("user", "How are you?"); + return common_chat_templates_apply(tmpls, inputs).prompt; +} + +#define CHATML_TEMPLATE_SRC \ + "{%- for message in messages -%}\n" \ + " {{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>\n' -}}\n" \ + "{%- endfor -%}\n" \ + "{%- if add_generation_prompt -%}\n" \ + " {{- '<|im_start|>assistant\n' -}}\n" \ + "{%- endif -%}" + +void common_chat_templates_free(struct common_chat_templates * tmpls) { + delete tmpls; +} + +bool common_chat_templates_was_explicit(const struct common_chat_templates * tmpls) { + return tmpls->has_explicit_template; +} + +const char * common_chat_templates_source(const struct common_chat_templates * tmpls, const char * variant) { + if (variant != nullptr) { + if (strcmp(variant, "tool_use") == 0) { + if (tmpls->template_tool_use) { + return tmpls->template_tool_use->source().c_str(); + } + return nullptr; + } else { + LOG("%s: unknown template variant: %s\n", __func__, variant); } - } else if (data.value.contains("tool_call")) { - if (!builder.add_tool_call(data.value.at("tool_call")) || data.is_partial) { - throw common_chat_msg_partial_exception("incomplete tool call"); + } + return tmpls->template_default->source().c_str(); +} + +common_chat_templates_ptr common_chat_templates_init( + const struct llama_model * model, + const std::string & chat_template_override, + const std::string & bos_token_override, + const std::string & eos_token_override) +{ + std::string default_template_src; + std::string template_tool_use_src; + + bool has_explicit_template = !chat_template_override.empty(); + if (chat_template_override.empty()) { + GGML_ASSERT(model != nullptr); + const auto * str = llama_model_chat_template(model, /* name */ nullptr); + if (str) { + default_template_src = str; + has_explicit_template = true; } - } else if (data.value.contains("response")) { - const auto & response = data.value.at("response"); - builder.add_content(response.is_string() ? response.template get() : response.dump(2)); - if (data.is_partial) { - throw common_chat_msg_partial_exception("incomplete response"); + str = llama_model_chat_template(model, /* name */ "tool_use"); + if (str) { + template_tool_use_src = str; + has_explicit_template = true; } } else { - throw common_chat_msg_partial_exception("Expected 'tool_call', 'tool_calls' or 'response' in JSON"); + default_template_src = chat_template_override; + } + if (default_template_src.empty() || default_template_src == "chatml") { + if (!template_tool_use_src.empty()) { + default_template_src = template_tool_use_src; + } else { + default_template_src = CHATML_TEMPLATE_SRC; + } + } + + // TODO @ngxson : this is a temporary hack to prevent chat template from throwing an error + // Ref: https://github.com/ggml-org/llama.cpp/pull/15230#issuecomment-3173959633 + if (default_template_src.find("<|channel|>") != std::string::npos + // search for the error message and patch it + && default_template_src.find("in message.content or") != std::string::npos) { + string_replace_all(default_template_src, + "{%- if \"<|channel|>analysis<|message|>\" in message.content or \"<|channel|>final<|message|>\" in message.content %}", + "{%- if false %}"); + } + + std::string token_bos = bos_token_override; + std::string token_eos = eos_token_override; + bool add_bos = false; + bool add_eos = false; + if (model) { + const auto * vocab = llama_model_get_vocab(model); + const auto get_token = [&](llama_token token, const char * name, const char * jinja_variable_name) { + if (token == LLAMA_TOKEN_NULL) { + if (default_template_src.find(jinja_variable_name) != std::string::npos + || template_tool_use_src.find(jinja_variable_name) != std::string::npos) { + LOG("common_chat_templates_init: warning: vocab does not have a %s token, jinja template won't work as intended.\n", name); + } + return std::string(); + } + return llama_token_to_piece(model, token, true); + }; + token_bos = get_token(llama_token_bos(vocab), "BOS", "bos_token"); + token_eos = get_token(llama_token_eos(vocab), "EOS", "eos_token"); + add_bos = llama_add_bos_token(model); + add_eos = llama_add_eos_token(model); + } + common_chat_templates_ptr tmpls(new common_chat_templates()); + tmpls->has_explicit_template = has_explicit_template; + tmpls->add_bos = add_bos; + tmpls->add_eos = add_eos; + try { + tmpls->template_default = std::make_unique(default_template_src, token_bos, token_eos); + } catch (const std::exception & e) { + LOG("%s: failed to parse chat template (defaulting to chatml): %s \n", __func__, e.what()); + tmpls->template_default = std::make_unique(CHATML_TEMPLATE_SRC, token_bos, token_eos); + } + if (!template_tool_use_src.empty()) { + try { + tmpls->template_tool_use = std::make_unique(template_tool_use_src, token_bos, token_eos); + } catch (const std::exception & e) { + LOG("%s: failed to parse tool use chat template (ignoring it): %s\n", __func__, e.what()); + } } + return tmpls; +} + +const char * common_chat_format_name(common_chat_format format) { + switch (format) { + case COMMON_CHAT_FORMAT_CONTENT_ONLY: return "Content-only"; + case COMMON_CHAT_FORMAT_GENERIC: return "Generic"; + case COMMON_CHAT_FORMAT_MISTRAL_NEMO: return "Mistral Nemo"; + case COMMON_CHAT_FORMAT_LLAMA_3_X: return "Llama 3.x"; + case COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS: return "Llama 3.x with builtin tools"; + case COMMON_CHAT_FORMAT_DEEPSEEK_R1: return "DeepSeek R1"; + case COMMON_CHAT_FORMAT_FIREFUNCTION_V2: return "FireFunction v2"; + case COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2: return "Functionary v3.2"; + case COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1: return "Functionary v3.1 Llama 3.1"; + case COMMON_CHAT_FORMAT_HERMES_2_PRO: return "Hermes 2 Pro"; + case COMMON_CHAT_FORMAT_COMMAND_R7B: return "Command R7B"; + case COMMON_CHAT_FORMAT_GRANITE: return "Granite"; + case COMMON_CHAT_FORMAT_GPT_OSS: return "GPT-OSS"; + default: + throw std::runtime_error("Unknown chat format"); + } +} + +const char * common_reasoning_format_name(common_reasoning_format format) { + switch (format) { + case COMMON_REASONING_FORMAT_NONE: return "none"; + case COMMON_REASONING_FORMAT_AUTO: return "auto"; + case COMMON_REASONING_FORMAT_DEEPSEEK: return "deepseek"; + case COMMON_REASONING_FORMAT_DEEPSEEK_LEGACY: return "deepseek-legacy"; + default: + throw std::runtime_error("Unknown reasoning format"); + } +} + +common_reasoning_format common_reasoning_format_from_name(const std::string& format) { + if (format == "none") { + return COMMON_REASONING_FORMAT_NONE; + } + else if (format == "auto") { + return COMMON_REASONING_FORMAT_AUTO; + } + else if (format == "deepseek") { + return COMMON_REASONING_FORMAT_DEEPSEEK; + } + else if (format == "deepseek-legacy") { + return COMMON_REASONING_FORMAT_DEEPSEEK_LEGACY; + } + throw std::runtime_error("Unknown reasoning format: " + format); } -// Helper function from original llama.cpp static std::string wrap_code_as_arguments(common_chat_msg_parser & builder, const std::string & code) { std::string arguments; if (builder.is_partial()) { @@ -119,11 +669,10 @@ static std::string wrap_code_as_arguments(common_chat_msg_parser & builder, cons return arguments; } -// Forward declaration -static void parse_deepseek_r1_tools_array(common_chat_msg_parser & builder); -static void parse_deepseek_r1_xml_wrapped(common_chat_msg_parser & builder); - -// Helper function from original llama.cpp for parsing JSON tool calls +/** + * Takes a prefix regex that must have 1 group to capture the function name, a closing suffix, and expects json parameters in between. + * Aggregates the prefix, suffix and in-between text into the content. + */ static void parse_json_tool_calls( common_chat_msg_parser & builder, const std::optional & block_open, @@ -148,10 +697,7 @@ static void parse_json_tool_calls( if (get_function_name) { name = get_function_name(*res); } else { - if (res->groups.size() < 2) { - from = res->groups[0].begin + 1; - continue; - } + GGML_ASSERT(res->groups.size() == 2); name = builder.str(res->groups[1]); } first = false; @@ -168,7 +714,7 @@ static void parse_json_tool_calls( if (!builder.add_tool_call(name, "", arguments->value) || arguments->is_partial) { throw common_chat_msg_partial_exception("incomplete tool call"); } - builder.try_consume_regex(close_regex); + builder.consume_regex(close_regex); } continue; } @@ -184,7 +730,7 @@ static void parse_json_tool_calls( break; } if (block_close) { - builder.try_consume_regex(*block_close); + builder.consume_regex(*block_close); } builder.consume_spaces(); builder.add_content(builder.consume_rest()); @@ -200,201 +746,1527 @@ static void parse_json_tool_calls( } } -void common_chat_parse_deepseek_r1(common_chat_msg_parser & builder) { - builder.try_parse_reasoning("", ""); - if (!builder.syntax().enable_tool_calls) { +static void parse_prefixed_json_tool_call_array(common_chat_msg_parser & builder, const common_regex & prefix, size_t rstrip_prefix = 0) { + static const std::vector> args_paths = {{"arguments"}}; + if (auto res = builder.try_find_regex(prefix)) { + builder.move_back(rstrip_prefix); + auto tool_calls = builder.consume_json_with_dumped_args(args_paths); + if (!builder.add_tool_calls(tool_calls.value) || tool_calls.is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call array"); + } + } else { builder.add_content(builder.consume_rest()); - return; } +} - static const common_regex tool_calls_begin("(?:<|tool▁calls▁begin|>|<|tool_calls_begin|>|<|tool calls begin|>|<|tool\\\\_calls\\\\_begin|>|<|tool▁calls|>)"); - static const common_regex tool_calls_end("<|tool▁calls▁end|>"); - // Primary regex for correct format with separator - static const common_regex function_regex("(?:<|tool▁call▁begin|>)?function<|tool▁sep|>([^\n]+)\n```json\n"); - // Fallback regex for format without separator (some models generate this) - static const common_regex function_regex_no_sep("(?:<|tool▁call▁begin|>)?function<([^>]+)>\n```json\n"); - // Third regex for new format: just "function" with no markers - static const common_regex function_regex_simple("function\n```json\n"); - static const common_regex close_regex("```[\\s\\r\\n]*<|tool▁call▁end|>"); - static const common_regex close_regex_simple("```"); // For simple format without end markers - - // Check for the new tools array format first (no DeepSeek markers) - auto original_pos = builder.pos(); - - // First, try the tools array format for content like "function\n```json\n{"tools": [...]}" - if (builder.try_find_regex(function_regex_simple)) { - builder.move_to(original_pos); - try { - parse_deepseek_r1_tools_array(builder); - return; // Success, we're done - } catch (const common_chat_msg_partial_exception&) { - // Fall through to try standard DeepSeek patterns +static void foreach_function(const json & tools, const std::function & fn) { + for (const auto & tool : tools) { + if (!tool.contains("type") || tool.at("type") != "function" || !tool.contains("function")) { + LOG("Skipping tool without function: %s", tool.dump(2).c_str()); + continue; } + fn(tool); } +} - // If tools array format didn't work, try XML-wrapped format - builder.move_to(original_pos); - try { - parse_deepseek_r1_xml_wrapped(builder); - return; // Success, we're done - } catch (const common_chat_msg_partial_exception&) { - // Fall through to try standard DeepSeek patterns +static std::string apply( + const common_chat_template & tmpl, + const struct templates_params & inputs, + const std::optional & messages_override = std::nullopt, + const std::optional & tools_override = std::nullopt, + const std::optional & additional_context = std::nullopt) +{ + minja::chat_template_inputs tmpl_inputs; + tmpl_inputs.messages = messages_override ? *messages_override : inputs.messages; + if (tools_override) { + tmpl_inputs.tools = *tools_override; + } else { + tmpl_inputs.tools = inputs.tools.empty() ? json() : inputs.tools; } - - // If XML wrapper format didn't work, try standard DeepSeek patterns - builder.move_to(original_pos); - try { - parse_json_tool_calls( - builder, - /* block_open= */ tool_calls_begin, - /* function_regex_start_only= */ std::nullopt, - function_regex, - close_regex, - tool_calls_end); - } catch (const common_chat_msg_partial_exception&) { - // If primary regex fails and we're not in partial mode, try fallback regex - if (!builder.is_partial()) { - builder.move_to(original_pos); - try { - parse_json_tool_calls( - builder, - /* block_open= */ tool_calls_begin, - /* function_regex_start_only= */ std::nullopt, - function_regex_no_sep, - close_regex, - tool_calls_end); - } catch (const common_chat_msg_partial_exception&) { - // Try the simple format without markers as final fallback - builder.move_to(original_pos); - parse_json_tool_calls( - builder, - /* block_open= */ std::nullopt, - /* function_regex_start_only= */ std::nullopt, - function_regex_simple, - close_regex_simple, - std::nullopt); - } - } else { - throw; // Re-throw for partial mode - } + tmpl_inputs.add_generation_prompt = inputs.add_generation_prompt; + tmpl_inputs.extra_context = inputs.extra_context; + if (additional_context) { + tmpl_inputs.extra_context.merge_patch(*additional_context); } + // TODO: add flag to control date/time, if only for testing purposes. + // tmpl_inputs.now = std::chrono::system_clock::now(); - // Add any remaining content (critical for responses without tool calls) - builder.add_content(builder.consume_rest()); + minja::chat_template_options tmpl_opts; + // To avoid double BOS / EOS tokens, we're manually removing begining / trailing tokens + // instead of using `chat_template_options.use_bos_token = false`, since these tokens + // may be needed inside the template / between messages too. + auto result = tmpl.apply(tmpl_inputs, tmpl_opts); + if (inputs.add_bos && string_starts_with(result, tmpl.bos_token())) { + result = result.substr(tmpl.bos_token().size()); + } + if (inputs.add_eos && string_ends_with(result, tmpl.eos_token())) { + result = result.substr(0, result.size() - tmpl.eos_token().size()); + } + return result; } -// Parse DeepSeek R1 tools array format following original llama.cpp parse_prefixed_json_tool_call_array pattern -static void parse_deepseek_r1_tools_array(common_chat_msg_parser & builder) { - static const common_regex prefix("function\n```json\n"); - +static common_chat_params common_chat_params_init_generic(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; - if (auto res = builder.try_find_regex(prefix)) { - // Parse JSON and manually process tools array to convert arguments to strings - auto json_result = builder.try_consume_json(); - if (!json_result) { - throw common_chat_msg_partial_exception("invalid JSON"); + auto tool_call_schemas = json::array(); + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + auto tool_schema = json { + {"type", "object"}, + {"properties", { + {"name", { + {"type", "string"}, + {"const", function.at("name")}, + }}, + {"arguments", function.at("parameters")}, + }}, + {"required", json::array({"name", "arguments"})}, + }; + if (function.contains("description")) { + tool_schema["description"] = function.at("description"); } - - - // DeepSeek R1 format has "tools" array, manually process each tool - if (json_result->json.contains("tools") && json_result->json.at("tools").is_array()) { - - // Manually create tool calls array with string arguments (following original pattern) - json tools_with_dumped_args = json::array(); - for (const auto& tool : json_result->json.at("tools")) { - if (tool.contains("name") && tool.contains("arguments")) { - json formatted_tool; - formatted_tool["name"] = tool.at("name"); - // Convert arguments object to string (this is what consume_json_with_dumped_args does) - formatted_tool["arguments"] = tool.at("arguments").dump(); - tools_with_dumped_args.push_back(formatted_tool); - } + if (inputs.parallel_tool_calls) { + tool_schema.at("properties")["id"] = { + {"type", "string"}, + {"minLength", 4}, + }; + tool_schema.at("required").push_back("id"); + } + tool_call_schemas.emplace_back(tool_schema); + }); + const auto tool_call = + inputs.parallel_tool_calls + ? json { + {"type", "object"}, + {"properties", { + {"tool_calls", { + {"type", "array"}, + {"items", tool_call_schemas.size() == 1 ? tool_call_schemas[0] : json { + {"anyOf", tool_call_schemas}, + }}, + {"minItems", 1}, + }}, + }}, + {"required", json::array({"tool_calls"})}, } + : json { + {"type", "object"}, + {"properties", { + {"tool_call", tool_call_schemas.size() == 1 ? tool_call_schemas[0] : json { + {"anyOf", tool_call_schemas}, + }}, + }}, + {"required", json::array({"tool_call"})}, + }; + const auto schema = + inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED + ? json { + {"anyOf", json::array({ + tool_call, + { + {"type", "object"}, + {"properties", { + {"response", inputs.json_schema.is_null() + ? json {{"type", "string"}} + : inputs.json_schema + }, + }}, + {"required", json::array({"response"})}, + }, + })} + } + : tool_call; + data.grammar_lazy = false; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + builder.add_schema("root", schema); + }); - if (!builder.add_tool_calls(tools_with_dumped_args) || !json_result->healing_marker.marker.empty()) { - throw common_chat_msg_partial_exception("incomplete tool call array"); - } - } else { - throw common_chat_msg_partial_exception("tools key not found or not array"); - } + auto tweaked_messages = common_chat_template::add_system( + inputs.messages, + "Respond in JSON format, either with `tool_call` (a request to call tools) or with `response` reply to the user's request"); - // Consume closing ``` - builder.try_consume_regex(common_regex("```")); + data.prompt = apply(tmpl, inputs, /* messages_override= */ tweaked_messages); + data.format = COMMON_CHAT_FORMAT_GENERIC; + return data; +} +static void common_chat_parse_generic(common_chat_msg_parser & builder) { + if (!builder.syntax().parse_tool_calls) { + builder.add_content(builder.consume_rest()); + return; + } + static const std::vector> content_paths = { + {"response"}, + }; + static const std::vector> args_paths = { + {"tool_call", "arguments"}, + {"tool_calls", "arguments"}, + }; + auto data = builder.consume_json_with_dumped_args(args_paths, content_paths); + if (data.value.contains("tool_calls")) { + if (!builder.add_tool_calls(data.value.at("tool_calls")) || data.is_partial) { + throw common_chat_msg_partial_exception("incomplete tool calls"); + } + } else if (data.value.contains("tool_call")) { + if (!builder.add_tool_call(data.value.at("tool_call")) || data.is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call"); + } + } else if (data.value.contains("response")) { + const auto & response = data.value.at("response"); + builder.add_content(response.is_string() ? response.template get() : response.dump(2)); + if (data.is_partial) { + throw common_chat_msg_partial_exception("incomplete response"); + } } else { - throw common_chat_msg_partial_exception("function prefix not found"); + throw common_chat_msg_partial_exception("Expected 'tool_call', 'tool_calls' or 'response' in JSON"); } } -// Parse DeepSeek R1 XML-wrapped format following original Hermes-2-Pro pattern -static void parse_deepseek_r1_xml_wrapped(common_chat_msg_parser & builder) { +static common_chat_params common_chat_params_init_mistral_nemo(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + auto schemas = json::array(); + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + schemas.push_back({ + {"type", "object"}, + {"properties", { + // Important note: the model is probably trained to take a JSON stringified arguments value. + // It's hard to constrain that for now (while reusing the JSON schema conversion), so we're just expecting a plain object. + {"name", { + {"type", "string"}, + {"const", function.at("name")}, + }}, + {"arguments", function.at("parameters")}, + {"id", { + {"type", "string"}, + // Nemo's template expects a 9-character alphanumeric ID. + {"pattern", "^[a-zA-Z0-9]{9}$"}, + }}, + }}, + {"required", json::array({"name", "arguments", "id"})}, + }); + }); + auto schema = json { + {"type", "array"}, + {"items", schemas.size() == 1 ? schemas[0] : json {{"anyOf", schemas}}}, + {"minItems", 1}, + }; + if (!inputs.parallel_tool_calls) { + schema["maxItems"] = 1; + } + builder.add_rule("root", "\"[TOOL_CALLS]\" " + builder.add_schema("tool_calls", schema)); + }); + data.grammar_triggers.push_back({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "[TOOL_CALLS]"}); + data.preserved_tokens = { + "[TOOL_CALLS]", + }; + data.prompt = apply(tmpl, inputs); + data.format = COMMON_CHAT_FORMAT_MISTRAL_NEMO; + return data; +} +static void common_chat_parse_mistral_nemo(common_chat_msg_parser & builder) { + if (!builder.syntax().parse_tool_calls) { + builder.add_content(builder.consume_rest()); + return; + } - // Pattern for: \nfunctionFunctionName\n```json\n{...}\n```\n - static const common_regex xml_pattern( - "\\s*" // Opening XML tag - "function([^\\n]+)" // Function name after "function" - "\\s*```json\\s*" // JSON block start - ); + static const common_regex prefix(regex_escape("[TOOL_CALLS]")); + parse_prefixed_json_tool_call_array(builder, prefix); +} - if (auto res = builder.try_find_regex(xml_pattern)) { +static common_chat_params common_chat_params_init_command_r7b(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; - // Extract function name from capture group - std::string function_name = builder.str(res->groups[1]); - - // Parse JSON arguments - auto json_result = builder.try_consume_json(); - if (!json_result) { - throw common_chat_msg_partial_exception("invalid JSON in XML wrapper"); + auto adjusted_messages = json::array(); + for (const auto & msg : inputs.messages) { + auto has_reasoning_content = msg.contains("reasoning_content") && msg.at("reasoning_content").is_string(); + auto has_tool_calls = msg.contains("tool_calls") && msg.at("tool_calls").is_array(); + if (has_reasoning_content && has_tool_calls) { + auto adjusted_message = msg; + adjusted_message["tool_plan"] = msg.at("reasoning_content"); + adjusted_message.erase("reasoning_content"); + adjusted_messages.push_back(adjusted_message); + } else { + adjusted_messages.push_back(msg); + } + } + data.prompt = apply(tmpl, inputs, /* messages_override= */ adjusted_messages); + data.format = COMMON_CHAT_FORMAT_COMMAND_R7B; + if (string_ends_with(data.prompt, "<|START_THINKING|>")) { + if (!inputs.enable_thinking) { + data.prompt += "<|END_THINKING|>"; + } else { + data.thinking_forced_open = true; } + } else if (!inputs.enable_thinking && string_ends_with(data.prompt, "<|CHATBOT_TOKEN|>")) { + data.prompt += "<|START_THINKING|><|END_THINKING|>"; + } + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + auto schemas = json::array(); + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + schemas.push_back({ + {"type", "object"}, + {"properties", { + {"tool_call_id", { + {"type", "string"}, + // Command-R's template expects an integer string. + {"pattern", "^[0-9]{1,10}$"}, + }}, + {"tool_name", { + {"type", "string"}, + {"const", function.at("name")}, + }}, + {"parameters", function.at("parameters")}, + }}, + {"required", json::array({"tool_call_id", "tool_name", "parameters"})}, + }); + }); + auto schema = json { + {"type", "array"}, + {"items", schemas.size() == 1 ? schemas[0] : json {{"anyOf", schemas}}}, + {"minItems", 1}, + }; + if (!inputs.parallel_tool_calls) { + schema["maxItems"] = 1; + } + builder.add_rule("root", + std::string(data.thinking_forced_open ? "( \"<|END_THINKING|>\" space )? " : "") + + "\"<|START_ACTION|>\" " + builder.add_schema("tool_calls", schema) + " \"<|END_ACTION|>\""); + }); + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL, + // If thinking_forced_open, then we capture the tag in the grammar, + // (important for required tool choice) and in the trigger's first capture (decides what is sent to the grammar) + std::string(data.thinking_forced_open ? "[\\s\\S]*?(<\\|END_THINKING\\|>\\s*)" : "(?:<\\|START_THINKING\\|>[\\s\\S]*?<\\|END_THINKING\\|>\\s*)?") + + "(<\\|START_ACTION\\|>)[\\s\\S]*" + }); + data.preserved_tokens = { + "<|START_ACTION|>", + "<|END_ACTION|>", + "<|START_RESPONSE|>", + "<|END_RESPONSE|>", + "<|START_THINKING|>", + "<|END_THINKING|>", + }; + return data; +} - // Create single tool call following original pattern - json tool_call; - tool_call["name"] = function_name; - tool_call["arguments"] = json_result->json.dump(); // Convert to string +static void common_chat_parse_command_r7b(common_chat_msg_parser & builder) { + builder.try_parse_reasoning("<|START_THINKING|>", "<|END_THINKING|>"); - json tool_calls_array = json::array(); - tool_calls_array.push_back(tool_call); + static const common_regex start_action_regex("<\\|START_ACTION\\|>"); + static const common_regex end_action_regex("<\\|END_ACTION\\|>"); + static const common_regex start_response_regex("<\\|START_RESPONSE\\|>"); + static const common_regex end_response_regex("<\\|END_RESPONSE\\|>"); + if (auto res = builder.try_find_regex(start_action_regex)) { + // If we didn't extract thoughts, prelude includes them. + auto tool_calls = builder.consume_json_with_dumped_args({{"parameters"}}); + for (const auto & tool_call : tool_calls.value) { + std::string name = tool_call.contains("tool_name") ? tool_call.at("tool_name") : ""; + std::string id = tool_call.contains("tool_call_id") ? tool_call.at("tool_call_id") : ""; + std::string arguments = tool_call.contains("parameters") ? tool_call.at("parameters") : ""; + if (!builder.add_tool_call(name, id, arguments) || tool_calls.is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call"); + } + } + if (tool_calls.is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call"); + } + builder.consume_regex(end_action_regex); + } else if (auto res = builder.try_find_regex(start_response_regex)) { + if (!builder.try_find_regex(end_response_regex)) { + builder.add_content(builder.consume_rest()); + throw common_chat_msg_partial_exception(end_response_regex.str()); + } + } else { + builder.add_content(builder.consume_rest()); + } +} - if (!builder.add_tool_calls(tool_calls_array) || !json_result->healing_marker.marker.empty()) { - throw common_chat_msg_partial_exception("incomplete XML wrapped tool call"); +static void expect_tool_parameters(const std::string & name, const json & parameters, const std::vector & expected_properties) { + if (!parameters.is_object() || !parameters.contains("type") || parameters.at("type") != "object" || !parameters.contains("properties") || !parameters.contains("required")) { + throw std::runtime_error("Parameters of tool " + name + " must be an object w/ required properties"); + } + const auto & parameters_properties = parameters.at("properties"); + const auto & parameters_required = parameters.at("required"); + for (const auto & prop : expected_properties) { + if (!parameters_properties.contains(prop)) { + throw std::runtime_error("Parameters of tool " + name + " is missing property: " + prop); // NOLINT + } + if (std::find(parameters_required.begin(), parameters_required.end(), json(prop)) == parameters_required.end()) { + throw std::runtime_error("Parameters of tool " + name + " must have property marked as required: " + prop); // NOLINT } + } + if (parameters_properties.size() != expected_properties.size()) { + throw std::runtime_error("Parameters of tool " + name + " must only have these properties:" + string_join(expected_properties, ", ")); + } +} + +static common_chat_params common_chat_params_init_llama_3_x(const common_chat_template & tmpl, const struct templates_params & inputs, bool allow_python_tag_builtin_tools) { + auto builtin_tools = json::array(); + common_chat_params data; + if (!inputs.tools.is_null()) { + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + std::vector tool_rules; + + auto handle_builtin_tool = [&](const std::string & name, const json & parameters) { + if (name == "wolfram_alpha" || name == "web_search" || name == "brave_search") { + // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py + // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py + expect_tool_parameters(name, parameters, {"query"}); + } else if (name == "python" || name == "code_interpreter") { + // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/inline/tool_runtime/code_interpreter/code_interpreter.py + expect_tool_parameters(name, parameters, {"code"}); + } else { + return false; + } + + std::vector kvs; + for (const auto & [key, value] : parameters.at("properties").items()) { + kvs.push_back("\"" + key + "=\" " + builder.add_schema(name + "-args-" + key, value)); // NOLINT + } + + tool_rules.push_back( + builder.add_rule( + name + "-call", + "\"<|python_tag|>" + name + ".call(\" " + string_join(kvs, " \", \" ") + " \")\"")); + builtin_tools.push_back(name); + + return true; + }; - // Consume closing ```\n - builder.try_consume_regex(common_regex("```\\s*")); + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + std::string name = function.at("name"); + auto parameters = function.at("parameters"); + builder.resolve_refs(parameters); + + // https://github.com/meta-llama/llama-stack/tree/main/llama_stack/providers/remote/tool_runtime + if (allow_python_tag_builtin_tools) { + handle_builtin_tool(name, parameters); + } + tool_rules.push_back( + builder.add_rule( + name + "-call", + "\"{\" space " + "( \"\\\"type\\\"\" space \":\" space \"\\\"function\\\"\" space \",\" space )? " + " \"\\\"name\\\"\" space \":\" space \"\\\"" + name + "\\\"\" space \",\" space " + " \"\\\"parameters\\\"\" space \":\" space " + builder.add_schema(name + "-args", parameters) + " " + "\"}\" space")); + }); + // Small models may hallucinate function names so we match anything (*at the start*) that looks like the JSON of a function call, regardless of the name. + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL, + "(\\{\\s*(?:\"type\"\\s*:\\s*\"function\"\\s*,\\s*)?\"name\"\\s*:\\s*\")[\\s\\S]*", // + name + "\"[\\s\\S]*", + }); + if (!builtin_tools.empty()) { + data.grammar_triggers.push_back({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "<|python_tag|>"}); + data.preserved_tokens.push_back("<|python_tag|>"); + } + // Allow a few empty lines on top of the usual constrained json schema space rule. + builder.add_rule("root", string_join(tool_rules, " | ")); + data.additional_stops.push_back("<|eom_id|>"); + }); + data.format = allow_python_tag_builtin_tools && !builtin_tools.empty() + ? COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS + : COMMON_CHAT_FORMAT_LLAMA_3_X; } else { - throw common_chat_msg_partial_exception("XML wrapper pattern not found"); + data.format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + } + data.prompt = apply(tmpl, inputs, /* messages_override =*/ std::nullopt, /* tools_override= */ std::nullopt, json { + {"date_string", format_time(inputs.now, "%d %b %Y")}, + {"tools_in_user_message", false}, + {"builtin_tools", builtin_tools.empty() ? json() : builtin_tools}, + }); + return data; +} +static void common_chat_parse_llama_3_1(common_chat_msg_parser & builder, bool with_builtin_tools = false) { + if (!builder.syntax().parse_tool_calls) { + builder.add_content(builder.consume_rest()); + return; } + + static const common_regex function_regex( + "\\s*\\{\\s*(?:\"type\"\\s*:\\s*\"function\"\\s*,\\s*)?\"name\"\\s*:\\s*\"([^\"]+)\"\\s*,\\s*\"parameters\"\\s*: "); + static const common_regex close_regex("\\}\\s*"); + + static const common_regex function_name_regex("\\s*(\\w+)\\s*\\.\\s*call\\("); + static const common_regex arg_name_regex("\\s*(\\w+)\\s*=\\s*"); + + if (with_builtin_tools) { + static const common_regex builtin_call_regex("<\\|python_tag\\|>"); + if (auto res = builder.try_find_regex(builtin_call_regex)) { + auto fun_res = builder.consume_regex(function_name_regex); + auto function_name = builder.str(fun_res.groups[1]); + + common_healing_marker healing_marker; + json args = json::object(); + while (true) { + if (auto arg_res = builder.try_consume_regex(arg_name_regex)) { + auto arg_name = builder.str(arg_res->groups[1]); + auto partial = builder.consume_json(); + args[arg_name] = partial.json; + healing_marker.marker = partial.healing_marker.marker; + healing_marker.json_dump_marker = partial.healing_marker.json_dump_marker; + builder.consume_spaces(); + if (!builder.try_consume_literal(",")) { + break; + } + } else { + break; + } + } + builder.consume_literal(")"); + builder.consume_spaces(); + + auto arguments = args.dump(); + if (!builder.add_tool_call(function_name, "", arguments)) { + throw common_chat_msg_partial_exception("Incomplete tool call"); + } + return; + } + } + parse_json_tool_calls( + builder, + /* block_open= */ std::nullopt, + /* function_regex_start_only= */ function_regex, + /* function_regex= */ std::nullopt, + close_regex, + std::nullopt); + } -static void common_chat_parse_kimi_k2(common_chat_msg_parser & builder) { - // Delegate to existing Kimi-K2 implementation for backward compatibility - auto result = kimi_k2::parse_tool_calls(builder.input()); - for (const auto& tc_json : result) { - common_chat_tool_call tc; - tc.id = tc_json.value("id", ""); - if (tc_json.contains("function") && tc_json["function"].contains("name")) { - tc.name = tc_json["function"]["name"]; - tc.arguments = tc_json["function"].value("arguments", "{}"); - builder.add_tool_call(tc); +static common_chat_params common_chat_params_init_deepseek_r1(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; + auto prompt = apply(tmpl, inputs); + + // Hacks to fix the official (broken) prompt. + // It is advisable to use --chat-template-file models/templates/llama-cpp-deepseek-r1.jinja instead, + // until the official template is fixed. + if (tmpl.source().find("{% if ns.is_tool %}{{'<|tool▁outputs▁end|>'}}") != std::string::npos) { + // Don't leave the chat dangling after tool results + if (string_ends_with(prompt, "<|tool▁outputs▁end|>")) { + prompt += "<|end▁of▁sentence|>"; + if (inputs.add_generation_prompt) { + prompt += "<|Assistant|>"; + } + } + // Fix up tool call delta example added by Minja + prompt = std::regex_replace( + prompt, + std::regex("(<|tool▁call▁end|>)[\\s\\r\\n]*(<|tool▁outputs▁begin|>|<|User|>)"), + "$1<|tool▁calls▁end|><|end▁of▁sentence|>$2"); + } + data.prompt = prompt; + data.format = COMMON_CHAT_FORMAT_DEEPSEEK_R1; + if (string_ends_with(data.prompt, "\n")) { + if (!inputs.enable_thinking) { + data.prompt += ""; + } else { + data.thinking_forced_open = true; } } - // Add cleaned content (removes tool call syntax) - builder.add_content(kimi_k2::clean_content(builder.input())); + + if (inputs.tools.is_array() && !inputs.tools.empty()) { + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED && inputs.json_schema.is_null(); + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + std::vector tool_rules; + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + std::string name = function.at("name"); + auto parameters = function.at("parameters"); + builder.resolve_refs(parameters); + tool_rules.push_back(builder.add_rule(name + "-call", + "( \"<|tool▁call▁begin|>\" )? \"function<|tool▁sep|>" + name + "\\n" + "```json\\n\" " + builder.add_schema(name + "-args", parameters) + " " + "\"```<|tool▁call▁end|>\"")); + }); + // Distill Qwen 7B & 32B models seem confused re/ syntax of their tool call opening tag, + // so we accept common variants (then it's all constrained) + builder.add_rule("root", + std::string(data.thinking_forced_open ? "( \"\" space )? " : "") + + "( \"<|tool▁calls▁begin|>\" | \"<|tool_calls_begin|>\" | \"<|tool calls begin|>\" | \"<|tool\\\\_calls\\\\_begin|>\" | \"<|tool▁calls|>\" ) " + "(" + string_join(tool_rules, " | ") + ")" + (inputs.parallel_tool_calls ? "*" : "") + " " + "\"<|tool▁calls▁end|>\"" + " space"); + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL, + // If thinking_forced_open, then we capture the tag in the grammar, + // (important for required tool choice) and in the trigger's first capture (decides what is sent to the grammar) + std::string(data.thinking_forced_open ? "[\\s\\S]*?(\\s*)" : "(?:[\\s\\S]*?\\s*)?") + + "(<|tool▁calls▁begin|>|<|tool_calls_begin|>|<|tool calls begin|>|<|tool\\\\_calls\\\\_begin|>|<|tool▁calls|>)[\\s\\S]*" + }); + data.preserved_tokens = { + "", + "", + "<|tool▁calls▁begin|>", + "<|tool▁call▁begin|>", + "<|tool▁sep|>", + "<|tool▁call▁end|>", + "<|tool▁calls▁end|", + }; + }); + } + return data; } +static void common_chat_parse_deepseek_r1(common_chat_msg_parser & builder) { + builder.try_parse_reasoning("", ""); + if (!builder.syntax().parse_tool_calls) { + builder.add_content(builder.consume_rest()); + return; + } + + static const common_regex tool_calls_begin("(?:<|tool▁calls▁begin|>|<|tool_calls_begin|>|<|tool calls begin|>|<|tool\\\\_calls\\\\_begin|>|<|tool▁calls|>)"); + static const common_regex tool_calls_end("<|tool▁calls▁end|>"); + static const common_regex function_regex("(?:<|tool▁call▁begin|>)?function<|tool▁sep|>([^\n]+)\n```json\n"); + static const common_regex close_regex("```[\\s\\r\\n]*<|tool▁call▁end|>"); + + parse_json_tool_calls( + builder, + /* block_open= */ tool_calls_begin, + /* function_regex_start_only= */ std::nullopt, + function_regex, + close_regex, + tool_calls_end); +} + +static common_chat_params common_chat_params_init_gpt_oss(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; + auto prompt = apply(tmpl, inputs); + + // Check if we need to replace the return token with end token during + // inference and without generation prompt. For more details see: + // https://github.com/ggml-org/llama.cpp/issues/15417 + if (inputs.is_inference && !inputs.add_generation_prompt) { + static constexpr std::string_view return_token = "<|return|>"; + static constexpr std::string_view end_token = "<|end|>"; + if (size_t pos = prompt.rfind(return_token); pos != std::string::npos) { + prompt.replace(pos, return_token.length(), end_token); + } + } + + data.prompt = prompt; + data.format = COMMON_CHAT_FORMAT_GPT_OSS; + + // These special tokens are required to parse properly, so we include them + // even if parse_tool_calls is false. + data.preserved_tokens = { + "<|channel|>", + "<|constrain|>", + "<|message|>", + "<|start|>", + "<|end|>", + }; + + if (!inputs.json_schema.is_null()) { + data.grammar_lazy = false; + data.grammar = build_grammar([&](const common_grammar_builder& builder) { + auto schema = inputs.json_schema; + builder.resolve_refs(schema); + + auto not_end = builder.add_rule("not-end", + "[^<] | \"<\" [^|] | \"<|\" [^e] | \"<|e\" [^n] | \"<|en\" [^d] | \"<|end\" [^|] | \"<|end|\" [^>]"); + auto analysis = builder.add_rule("analysis", + "\"<|channel|>analysis<|message|>\" ( " + not_end + " )* \"<|end|>\""); + auto constraint = builder.add_rule("constraint", "\"<|constrain|>\"? [a-zA-Z0-9_-]+"); + auto final = builder.add_rule("final", + "\"<|channel|>final\" ( \" \" " + constraint + " )? \"<|message|>\" " + + builder.add_schema("response", schema) + ); + + builder.add_rule("root", "( " + analysis + " \"<|start|>assistant\" )? " + final); + }); + } + + if (inputs.tools.is_array() && !inputs.tools.empty()) { + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + // tool calls can appear in commentary or analysis channels + auto channel = builder.add_rule("channel", "\"<|channel|>\" ( \"commentary\" | \"analysis\" )"); + + std::vector tool_rules_recipient_in_role; + std::vector tool_rules_recipient_in_channel; + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + std::string name = function.at("name"); + auto parameters = function.at("parameters"); + builder.resolve_refs(parameters); + + tool_rules_recipient_in_role.push_back( + builder.add_rule(name + "-call", + "\"" + name + "\"" + channel + " \" <|constrain|>json\"? \"<|message|>\" " + + builder.add_schema(name + "-args", parameters) + ) + ); + + tool_rules_recipient_in_channel.push_back( + builder.add_rule(name + "-call", + "\"" + name + "\"" + " \" <|constrain|>json\"? \"<|message|>\" " + + builder.add_schema(name + "-args", parameters) + ) + ); + }); + + auto recipient_in_role = builder.add_rule("recipient_in_role", + "\"<|start|>assistant\"? \" to=functions.\" ( " + + string_join(tool_rules_recipient_in_role, " | ") + " )" + ); + auto recipient_in_channel = builder.add_rule("recipient_in_channel", + channel + " \" to=functions.\" ( " + + string_join(tool_rules_recipient_in_channel, " | ") + " )" + ); + + builder.add_rule("root", recipient_in_role + " | " + recipient_in_channel); + + // Trigger on tool calls that appear in the commentary channel + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, + "<\\|channel\\|>(commentary|analysis) to" + }); + + // Trigger tool calls that appear in the role section, either at the + // start or in the middle. + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL, + "^ to" + }); + + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, + "<\\|start\\|>assistant to" + }); + }); + } + + return data; +} static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) { - // TODO @ngxson : this won't work with --special enabled, we should fix that - builder.try_parse_reasoning("<|channel|>analysis<|message|>", "<|start|>assistant<|channel|>final<|message|>"); - if (!builder.syntax().enable_tool_calls) { + static const std::string constraint = "(?: (<\\|constrain\\|>)?([a-zA-Z0-9_-]+))"; + static const std::string recipient("(?: to=functions\\.([^<\\s]+))"); + + static const common_regex start_regex("<\\|start\\|>assistant"); + static const common_regex analysis_regex("<\\|channel\\|>analysis"); + static const common_regex final_regex("<\\|channel\\|>final" + constraint + "?"); + static const common_regex preamble_regex("<\\|channel\\|>commentary"); + static const common_regex tool_call1_regex(recipient + "<\\|channel\\|>(analysis|commentary)" + constraint + "?"); + static const common_regex tool_call2_regex("<\\|channel\\|>(analysis|commentary)" + recipient + constraint + "?"); + + auto consume_end = [&](bool include_end = false) { + if (auto res = builder.try_find_literal("<|end|>")) { + return res->prelude + (include_end ? builder.str(res->groups[0]) : ""); + } + return builder.consume_rest(); + }; + + auto handle_tool_call = [&](const std::string & name) { + if (auto args = builder.try_consume_json_with_dumped_args({{}})) { + if (builder.syntax().parse_tool_calls) { + if (!builder.add_tool_call(name, "", args->value) || args->is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call"); + } + } else if (args->is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call"); + } + } + }; + + auto regex_match = [](const common_regex & regex, const std::string & input) -> std::optional { + auto match = regex.search(input, 0, true); + if (match.type == COMMON_REGEX_MATCH_TYPE_FULL) { + return match; + } + return std::nullopt; + }; + + do { + auto header_start_pos = builder.pos(); + auto content_start = builder.try_find_literal("<|message|>"); + if (!content_start) { + throw common_chat_msg_partial_exception("incomplete header"); + } + + auto header = content_start->prelude; + + if (auto match = regex_match(tool_call1_regex, header)) { + auto group = match->groups[1]; + auto name = header.substr(group.begin, group.end - group.begin); + handle_tool_call(name); + continue; + } + + if (auto match = regex_match(tool_call2_regex, header)) { + auto group = match->groups[2]; + auto name = header.substr(group.begin, group.end - group.begin); + handle_tool_call(name); + continue; + } + + if (regex_match(analysis_regex, header)) { + builder.move_to(header_start_pos); + if (builder.syntax().reasoning_format == COMMON_REASONING_FORMAT_NONE || builder.syntax().reasoning_in_content) { + builder.add_content(consume_end(true)); + } else { + builder.try_parse_reasoning("<|channel|>analysis<|message|>", "<|end|>"); + } + continue; + } + + if(regex_match(final_regex, header) || regex_match(preamble_regex, header)) { + builder.add_content(consume_end()); + continue; + } + + // Possibly a malformed message, attempt to recover by rolling + // back to pick up the next <|start|> + LOG("%s: unknown header from message: %s\n", __func__, header.c_str()); + builder.move_to(header_start_pos); + } while (builder.try_find_regex(start_regex, std::string::npos, false)); + + auto remaining = builder.consume_rest(); + if (!remaining.empty()) { + LOG("%s: content after last message: %s\n", __func__, remaining.c_str()); + } +} + +static common_chat_params common_chat_params_init_firefunction_v2(const common_chat_template & tmpl, const struct templates_params & inputs) { + LOG("%s\n", __func__); + common_chat_params data; + data.prompt = apply(tmpl, inputs, /* messages_override =*/ std::nullopt, /* tools_override= */ json(), json { + {"datetime", format_time(inputs.now, "%b %d %Y %H:%M:%S GMT")}, + {"functions", json(inputs.tools.empty() ? "" : inputs.tools.dump(2))}, + }); + if (inputs.tools.is_array() && !inputs.tools.empty()) { + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + auto schemas = json::array(); + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + schemas.push_back({ + {"type", "object"}, + {"properties", { + {"name", { + {"type", "string"}, + {"const", function.at("name")}, + }}, + {"arguments", function.at("parameters")}, + }}, + {"required", json::array({"name", "arguments", "id"})}, + }); + }); + auto schema = json { + {"type", "array"}, + {"items", schemas.size() == 1 ? schemas[0] : json {{"anyOf", schemas}}}, + {"minItems", 1}, + }; + if (!inputs.parallel_tool_calls) { + schema["maxItems"] = 1; + } + builder.add_rule("root", "\" functools\"? " + builder.add_schema("tool_calls", schema)); + }); + data.grammar_triggers.push_back({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " functools["}); + data.preserved_tokens = { + " functools[", + }; + data.format = COMMON_CHAT_FORMAT_FIREFUNCTION_V2; + } else { + data.format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + } + return data; +} +static void common_chat_parse_firefunction_v2(common_chat_msg_parser & builder) { + if (!builder.syntax().parse_tool_calls) { builder.add_content(builder.consume_rest()); return; } + static const common_regex prefix(regex_escape(" functools[")); + parse_prefixed_json_tool_call_array(builder, prefix, /* rstrip_prefix= */ 1); +} + +static common_chat_params common_chat_params_init_functionary_v3_2(const common_chat_template & tmpl, const struct templates_params & inputs) { + // >>>all\nlet's call functions>>>fn1\n{"arg1": 1...}\n>>>fn2\n{"arg1": 1...}... + // Using ">>>f1\n", ">>>f2\n"... as trigger words for the grammar + // If the function is python, we also allow raw python code (if the line after `python\n` doesn't start w/ opening `{`), which the model seems to prefer for multiline code. + common_chat_params data; + data.prompt = apply(tmpl, inputs); + data.format = COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2; + if (inputs.tools.is_array() && !inputs.tools.empty()) { + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + std::vector first_tool_rules; + std::vector subsequent_tool_rules; + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + std::string name = function.at("name"); + auto parameters = function.at("parameters"); + builder.resolve_refs(parameters); + std::string args_pattern = "[\\s\\S]*"; + auto args_rule = builder.add_schema(name + "-args", parameters); + if (name == "python") { + args_rule = builder.add_rule(name + "-maybe-raw-args", args_rule + " | [^{] .*"); + } else { + args_pattern = "\\{" + args_pattern; + } + auto call_rule = builder.add_rule(name + "-call", "\"" + name + "\\n\" " + args_rule); + first_tool_rules.push_back(call_rule); + if (inputs.parallel_tool_calls) { + subsequent_tool_rules.push_back(builder.add_rule(name + "-call2", "\">>>\" " + call_rule)); + } + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL, + "((?:[\\s\\S]+?>>>)?" + regex_escape(name) + "\n)" + args_pattern, + }); + }); + data.preserved_tokens = { + "<|end_header_id|>", + }; + auto first_rule = first_tool_rules.empty() ? "" : builder.add_rule("first_tool_call", string_join(first_tool_rules, " | ")) + " space"; + if (inputs.parallel_tool_calls) { + auto subsequent_rule = builder.add_rule("subsequent_tool_call", string_join(subsequent_tool_rules, " | ")) + " space"; + builder.add_rule("root", first_rule + " (" + subsequent_rule + ")*"); + } else { + builder.add_rule("root", first_rule); + } + + }); + } + return data; +} +static void common_chat_parse_functionary_v3_2(common_chat_msg_parser & builder) { + static const common_regex function_regex_start_only(R"((\w+\n\{|python\n|all\n))"); + static const common_regex function_regex(R"(>>>(\w+\n\{|python\n|all\n))"); + static const common_regex close_regex(R"(\s*)"); + + parse_json_tool_calls( + builder, + std::nullopt, + function_regex_start_only, + function_regex, + close_regex, + std::nullopt, + /* allow_raw_python= */ true, + /* get_function_name= */ [&](const auto & res) -> std::string { + auto at_start = res.groups[0].begin == 0; + auto name = builder.str(res.groups[1]); + if (!name.empty() && name.back() == '{') { + // Unconsume the opening brace '{' to ensure the JSON parsing goes well. + builder.move_back(1); + } + auto idx = name.find_last_not_of("\n{"); + name = name.substr(0, idx + 1); + if (at_start && name == "all") { + return ""; + } + return name; + }); +} + +static common_chat_params common_chat_params_init_functionary_v3_1_llama_3_1(const common_chat_template & tmpl, const struct templates_params & inputs) { + // https://github.com/MeetKai/functionary/blob/main/tests/prompt_test_v3-llama3.1.txt + common_chat_params data; + + if (!inputs.tools.is_null()) { + std::string python_code_argument_name; + auto has_raw_python = false; + + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + std::vector tool_rules; + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + const auto & parameters = function.at("parameters"); + std::string name = function.at("name"); + if (name == "python" || name == "ipython") { + if (!parameters.contains("type")) { + throw std::runtime_error("Missing type in python tool"); + } + has_raw_python = true; + const auto & type = parameters.at("type"); + if (type == "object") { + auto properties = parameters.at("properties"); + for (auto it = properties.begin(); it != properties.end(); ++it) { + if (it.value().at("type") == "string") { + if (!python_code_argument_name.empty()) { + throw std::runtime_error("Multiple string arguments found in python tool"); + } + python_code_argument_name = it.key(); + } + } + if (python_code_argument_name.empty()) { + throw std::runtime_error("No string argument found in python tool"); + } + } else if (type != "string") { + throw std::runtime_error("Invalid type in python tool: " + type.dump()); + } + } + tool_rules.push_back(builder.add_rule(name + "-call", "\"\" " + builder.add_schema(name + "-args", parameters) + " \"\" space")); + }); + if (has_raw_python) { + tool_rules.push_back(builder.add_rule("python-call", "\"<|python_tag|>\" .*")); + data.grammar_triggers.push_back({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "<|python_tag|>"}); + data.preserved_tokens.push_back("<|python_tag|>"); + } + auto tool_call = builder.add_rule("tool_call", string_join(tool_rules, " | ")) + " space"; + builder.add_rule("root", inputs.parallel_tool_calls ? "(" + tool_call + ")+" : tool_call); + data.grammar_triggers.push_back({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "")); + + static const common_regex function_regex(R"()"); + static const common_regex close_regex(R"()"); + + parse_json_tool_calls( + builder, + /* block_open= */ std::nullopt, + /* function_regex_start_only= */ std::nullopt, + function_regex, + close_regex, + std::nullopt); + + if (auto res = builder.try_find_regex(python_tag_regex)) { + auto arguments = wrap_code_as_arguments(builder, builder.consume_rest()); + builder.add_tool_call("python", "", arguments); + return; + } +} + +static common_chat_params common_chat_params_init_hermes_2_pro(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; + + json extra_context = json { + {"enable_thinking", inputs.enable_thinking}, + }; + extra_context.update(inputs.extra_context); + + data.prompt = apply(tmpl, inputs, /* messages_override =*/ std::nullopt, /* tools_override= */ std::nullopt, extra_context); + data.format = COMMON_CHAT_FORMAT_HERMES_2_PRO; + if (string_ends_with(data.prompt, "\n")) { + if (!extra_context["enable_thinking"]) { + data.prompt += ""; + } else { + data.thinking_forced_open = true; + } + } + + if (!inputs.tools.is_null()) { + // (content)?({"name": "foo", "arguments": {"a": 1}})* + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + std::vector tool_rules; + std::vector tool_call_alts; + std::vector escaped_names; + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + std::string name = function.at("name"); + auto parameters = function.at("parameters"); + builder.resolve_refs(parameters); + tool_rules.push_back(builder.add_schema(name + "-call", { + {"type", "object"}, + {"properties", json { + {"name", json {{"const", name}}}, + {"arguments", parameters}, + }}, + {"required", json::array({"name", "arguments"})}, + })); + tool_call_alts.push_back(builder.add_rule( + name + "-function-tag", + "\"\" space " + + builder.add_schema(name + "-args", parameters) + " " + "\"\" space")); + + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_WORD, + "", + }); + auto escaped_name = regex_escape(name); + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, + " alt_tags { + any_tool_call, + "\"\" space " + any_tool_call + " \"\"", + // The rest is just to accommodate common "good bad" outputs. + "\"\" space " + any_tool_call + " \"\"", + "\"\" space " + any_tool_call + " \"\"", + "\"\" space " + any_tool_call + " \"\"", + "\"\" space " + any_tool_call + " \"\"", + "\"\" space " + any_tool_call + " \"\"", + "\"\" space " + any_tool_call + " \"\"", + }; + auto wrappable_tool_call = builder.add_rule("wrappable_tool_call", "( " + string_join(alt_tags, " | ") + " ) space"); + tool_call_alts.push_back(wrappable_tool_call); + tool_call_alts.push_back( + "( \"```\\n\" | \"```json\\n\" | \"```xml\\n\" ) space " + wrappable_tool_call + " space \"```\" space "); + auto tool_call = builder.add_rule("tool_call", string_join(tool_call_alts, " | ")); + builder.add_rule("root", + std::string(data.thinking_forced_open ? "( \"\" space )? " : "") + + (inputs.parallel_tool_calls ? "(" + tool_call + ")+" : tool_call)); + // Trigger on some common known "good bad" outputs (only from the start and with a json that's about a specific argument name to avoid false positives) + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL, + // If thinking_forced_open, then we capture the tag in the grammar, + // (important for required tool choice) and in the trigger's first capture (decides what is sent to the grammar) + std::string(data.thinking_forced_open ? "[\\s\\S]*?(\\s*)" : "(?:[\\s\\S]*?\\s*)?") + ( + "(\\s*" + "(?:" + "||||)?" + "\\s*\\{\\s*\"name\"\\s*:\\s*\"(?:" + string_join(escaped_names, "|") + ")\"" + ")" + ")[\\s\\S]*" + ), + }); + data.preserved_tokens = { + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "```", + "```json", + "```xml", + }; + }); + } + + return data; +} +static void common_chat_parse_hermes_2_pro(common_chat_msg_parser & builder) { + builder.try_parse_reasoning("", ""); + if (!builder.syntax().parse_tool_calls) { + builder.add_content(builder.consume_rest()); + return; + } + + static const common_regex open_regex( + "(?:" + "(```(?:xml|json)?\\n\\s*)?" // match 1 (block_start) + "(" // match 2 (open_tag) + "" + "|" + "|" + "|" + "|" + "|" + "|" + "|" + ")?" + "(\\s*\\{\\s*\"name\")" // match 3 (named tool call) + ")" + "|]+)>" // match 4 (function name) + "|" // match 5 (function name again) + ); + + while (auto res = builder.try_find_regex(open_regex)) { + const auto & block_start = res->groups[1]; + std::string block_end = block_start.empty() ? "" : "```"; + + const auto & open_tag = res->groups[2]; + std::string close_tag; + + if (!res->groups[3].empty()) { + builder.move_to(res->groups[3].begin); + close_tag = open_tag.empty() ? "" : "value) || tool_call->is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call"); + } + builder.consume_spaces(); + builder.consume_literal(close_tag); + builder.consume_spaces(); + if (!block_end.empty()) { + builder.consume_literal(block_end); + builder.consume_spaces(); + } + } else { + throw common_chat_msg_partial_exception("failed to parse tool call"); + } + } else { + auto function_name = builder.str(res->groups[4]); + if (function_name.empty()) { + function_name = builder.str(res->groups[5]); + } + GGML_ASSERT(!function_name.empty()); + + close_tag = ""; + + if (auto arguments = builder.try_consume_json_with_dumped_args({{}})) { + if (!builder.add_tool_call(function_name, "", arguments->value) || arguments->is_partial) { + throw common_chat_msg_partial_exception("incomplete tool call"); + } + builder.consume_spaces(); + builder.consume_literal(close_tag); + builder.consume_spaces(); + if (!block_end.empty()) { + builder.consume_literal(block_end); + builder.consume_spaces(); + } + } + } + } + + builder.add_content(builder.consume_rest()); +} + +static common_chat_params common_chat_params_init_granite(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; + + // Pass thinking context for Granite template + json additional_context = { + {"thinking", inputs.enable_thinking}, + }; + + data.prompt = apply(tmpl, inputs, /* messages_override= */ std::nullopt, /* tools_override= */ std::nullopt, additional_context); + data.format = COMMON_CHAT_FORMAT_GRANITE; + + if (string_ends_with(data.prompt, "\n") || string_ends_with(data.prompt, "")) { + if (!inputs.enable_thinking) { + data.prompt += ""; + } else { + data.thinking_forced_open = true; + } + } + + if (!inputs.tools.is_null()) { + // Granite uses <|tool_call|> followed by JSON list + data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + std::vector tool_rules; + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + std::string name = function.at("name"); + auto parameters = function.at("parameters"); + builder.resolve_refs(parameters); + tool_rules.push_back(builder.add_rule(name + "-call", builder.add_schema(name + +"-args", { + {"type", "object"}, + {"properties", { + {"name", {{"const", name}}}, + {"arguments", parameters}, + }}, + {"required", json::array({"name", "arguments"})}, + }))); + }); + + auto tool_call = builder.add_rule("tool_call", string_join(tool_rules, " | ")); + auto tool_list = builder.add_rule("tool_list", "\"[\" space " + tool_call + " (\",\" space " + tool_call + ")* space \"]\""); + + if (data.thinking_forced_open) { + builder.add_rule("root", "\"\" space \"\" space [^<]* \"\" space \"<|tool_call|>\" space " + tool_list); + } else { + builder.add_rule("root", "\"<|tool_call|>\" space " + tool_list); + } + + data.grammar_triggers.push_back({ + COMMON_GRAMMAR_TRIGGER_TYPE_WORD, + "<|tool_call|>" + }); + + data.preserved_tokens = { + "", + "", + "", + "", + "<|tool_call|>", + }; + }); + } else { + // Handle thinking tags for non-tool responses + if (data.thinking_forced_open && inputs.enable_thinking) { + data.grammar_lazy = false; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + builder.add_rule("root", "\"\" space \"\" space .* \"\" space"); + }); + data.preserved_tokens = { + "", + "", + "", + "", + }; + } + } + + return data; +} + +static void common_chat_parse_granite(common_chat_msg_parser & builder) { + // Parse thinking tags + builder.try_parse_reasoning("", ""); + + // Parse response tags using regex + static const common_regex response_regex("([\\s\\S]*?)"); + if (auto res = builder.try_find_regex(response_regex)) { + // Extract the content between the tags (capture group 1) + auto content = builder.str(res->groups[1]); + builder.add_content(content); + builder.move_to(res->groups[0].end); + } + + if (!builder.syntax().parse_tool_calls) { + builder.add_content(builder.consume_rest()); + return; + } + + // Look for tool calls + static const common_regex tool_call_regex(regex_escape("<|tool_call|>")); + if (auto res = builder.try_find_regex(tool_call_regex)) { + builder.move_to(res->groups[0].end); + + // Expect JSON array of tool calls + auto tool_calls_data = builder.consume_json(); + if (tool_calls_data.json.is_array()) { + if (!builder.add_tool_calls(tool_calls_data.json)) { + builder.add_content("<|tool_call|>" + tool_calls_data.json.dump()); + } + } else { + builder.add_content("<|tool_call|>" + tool_calls_data.json.dump()); + } + } else { + builder.add_content(builder.consume_rest()); + } +} + +static common_chat_params common_chat_params_init_without_tools(const common_chat_template & tmpl, const struct templates_params & inputs) { + common_chat_params data; + data.prompt = apply(tmpl, inputs); + data.format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + data.grammar_lazy = false; + if (!inputs.json_schema.is_null()) { + if (!inputs.grammar.empty()) { + throw std::runtime_error("Either \"json_schema\" or \"grammar\" can be specified, but not both"); + } + data.grammar = json_schema_to_grammar(inputs.json_schema); + } else { + data.grammar = inputs.grammar; + } + return data; +} + +static common_chat_params common_chat_templates_apply_jinja( + const struct common_chat_templates * tmpls, + const struct common_chat_templates_inputs & inputs) +{ + templates_params params; + params.tools = common_chat_tools_to_json_oaicompat(inputs.tools); + const auto & tmpl = params.tools.is_array() && tmpls->template_tool_use + ? *tmpls->template_tool_use + : *tmpls->template_default; + const auto & src = tmpl.source(); + const auto & caps = tmpl.original_caps(); + params.messages = common_chat_msgs_to_json_oaicompat(inputs.messages, /* concat_text= */ !tmpl.original_caps().requires_typed_content); + params.add_generation_prompt = inputs.add_generation_prompt; + params.tool_choice = inputs.tool_choice; + params.enable_thinking = inputs.enable_thinking; + params.grammar = inputs.grammar; + params.now = inputs.now; + params.add_bos = tmpls->add_bos; + params.add_eos = tmpls->add_eos; + + params.extra_context = json::object(); + for (auto el : inputs.chat_template_kwargs) { + params.extra_context[el.first] = json::parse(el.second); + } + + if (!inputs.json_schema.empty()) { + params.json_schema = json::parse(inputs.json_schema); + } + + if (inputs.parallel_tool_calls && !tmpl.original_caps().supports_parallel_tool_calls) { + LOG("Disabling parallel_tool_calls because the template does not support it\n"); + params.parallel_tool_calls = false; + } else { + params.parallel_tool_calls = inputs.parallel_tool_calls; + } + + if (params.tools.is_array()) { + if (params.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE && !params.grammar.empty()) { + throw std::runtime_error("Cannot specify grammar with tools"); + } + if (caps.supports_tool_calls && !caps.supports_tools) { + LOG("Template supports tool calls but does not natively describe tools. The fallback behaviour used may produce bad results, inspect prompt w/ --verbose & consider overriding the template.\n"); + } + } + + // DeepSeek R1: use handler in all cases except json schema (thinking / tools). + if (src.find("<|tool▁calls▁begin|>") != std::string::npos && params.json_schema.is_null()) { + return common_chat_params_init_deepseek_r1(tmpl, params); + } + + // Command R7B: : use handler in all cases except json schema (thinking / tools). + if (src.find("<|END_THINKING|><|START_ACTION|>") != std::string::npos && params.json_schema.is_null()) { + return common_chat_params_init_command_r7b(tmpl, params); + } + + // Granite (IBM) - detects thinking / tools support + if (src.find("elif thinking") != std::string::npos && src.find("<|tool_call|>") != std::string::npos) { + return common_chat_params_init_granite(tmpl, params); + } + + // Hermes 2/3 Pro, Qwen 2.5 Instruct (w/ tools) + if (src.find("") != std::string::npos && params.json_schema.is_null()) { + return common_chat_params_init_hermes_2_pro(tmpl, params); + } + + // GPT-OSS + if (src.find("<|channel|>") != std::string::npos && params.json_schema.is_null()) { + return common_chat_params_init_gpt_oss(tmpl, params); + } + + // Use generic handler when mixing tools + JSON schema. + // TODO: support that mix in handlers below. + if ((params.tools.is_array() && params.json_schema.is_object())) { + return common_chat_params_init_generic(tmpl, params); + } + + // Functionary prepends "all\n" to plain content outputs, so we use its handler in all cases. + if (src.find(">>>all") != std::string::npos) { + return common_chat_params_init_functionary_v3_2(tmpl, params); + } + + // Firefunction v2 requires datetime and functions in the context even w/o tools, so we also use its handler in all cases. + if (src.find(" functools[") != std::string::npos) { + return common_chat_params_init_firefunction_v2(tmpl, params); + } + + // Functionary v3.1 (w/ tools) + if (src.find("<|start_header_id|>") != std::string::npos + && src.find("ipython<|end_header_id|>") != std::string::npos) { + auto allow_python_tag_builtin_tools = src.find("<|python_tag|>") != std::string::npos; + return common_chat_params_init_llama_3_x(tmpl, params, allow_python_tag_builtin_tools); + } + + // Plain handler (no tools) + if (params.tools.is_null() || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) { + return common_chat_params_init_without_tools(tmpl, params); + } + + // Mistral Nemo (w/ tools) + if (src.find("[TOOL_CALLS]") != std::string::npos) { + return common_chat_params_init_mistral_nemo(tmpl, params); + } + + // Generic fallback + return common_chat_params_init_generic(tmpl, params); +} + +// Legacy template route (adhoc C++ implementation of known templates), forward to llama_chat_apply_template. +static common_chat_params common_chat_templates_apply_legacy( + const struct common_chat_templates * tmpls, + const struct common_chat_templates_inputs & inputs) +{ + int alloc_size = 0; + std::vector chat; + std::vector contents; + + for (const auto & msg : inputs.messages) { + auto content = msg.content; + for (const auto & part : msg.content_parts) { + if (part.type != "text") { + LOG("Ignoring non-text content part: %s\n", part.type.c_str()); + continue; + } + if (!content.empty()) { + content += "\n";; + } + content += part.text; + } + contents.emplace_back(std::move(content)); + } + for (size_t i = 0; i < contents.size(); ++i) { + const auto & msg = inputs.messages[i]; + const auto & content = contents[i]; + chat.push_back({msg.role.c_str(), content.c_str()}); + alloc_size += (msg.role.size() + content.size()) * 1.25; + } + + std::vector buf(alloc_size); + + // run the first time to get the total output length + const auto & src = tmpls->template_default->source(); + int32_t res = llama_chat_apply_template(nullptr, src.c_str(), chat.data(), chat.size(), inputs.add_generation_prompt, buf.data(), buf.size()); + + // error: chat template is not supported + if (res < 0) { + // if the custom "tmpl" is not supported, we throw an error + // this is a bit redundant (for good), since we're not sure if user validated the custom template with llama_chat_verify_template() + throw std::runtime_error("this custom template is not supported, try using --jinja"); + } + + // if it turns out that our buffer is too small, we resize it + if ((size_t) res > buf.size()) { + buf.resize(res); + res = llama_chat_apply_template(nullptr, src.c_str(), chat.data(), chat.size(), inputs.add_generation_prompt, buf.data(), buf.size()); + } + + common_chat_params params; + params.prompt = std::string(buf.data(), res); + if (!inputs.json_schema.empty()) { + params.grammar = json_schema_to_grammar(json::parse(inputs.json_schema)); + } else { + params.grammar = inputs.grammar; + } + return params; +} + +common_chat_params common_chat_templates_apply( + const struct common_chat_templates * tmpls, + const struct common_chat_templates_inputs & inputs) +{ + GGML_ASSERT(tmpls != nullptr); + return inputs.use_jinja + ? common_chat_templates_apply_jinja(tmpls, inputs) + : common_chat_templates_apply_legacy(tmpls, inputs); +} + +static void common_chat_parse_content_only(common_chat_msg_parser & builder) { + builder.add_content(builder.consume_rest()); } -// Main parsing dispatch function static void common_chat_parse(common_chat_msg_parser & builder) { + LOG("Parsing input with format %s: %s\n", common_chat_format_name(builder.syntax().format), builder.input().c_str()); + switch (builder.syntax().format) { case COMMON_CHAT_FORMAT_CONTENT_ONLY: common_chat_parse_content_only(builder); @@ -402,11 +2274,35 @@ static void common_chat_parse(common_chat_msg_parser & builder) { case COMMON_CHAT_FORMAT_GENERIC: common_chat_parse_generic(builder); break; + case COMMON_CHAT_FORMAT_MISTRAL_NEMO: + common_chat_parse_mistral_nemo(builder); + break; + case COMMON_CHAT_FORMAT_LLAMA_3_X: + common_chat_parse_llama_3_1(builder); + break; + case COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS: + common_chat_parse_llama_3_1(builder, /* with_builtin_tools= */ true); + break; case COMMON_CHAT_FORMAT_DEEPSEEK_R1: common_chat_parse_deepseek_r1(builder); break; - case COMMON_CHAT_FORMAT_KIMI_K2: - common_chat_parse_kimi_k2(builder); + case COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2: + common_chat_parse_functionary_v3_2(builder); + break; + case COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1: + common_chat_parse_functionary_v3_1_llama_3_1(builder); + break; + case COMMON_CHAT_FORMAT_HERMES_2_PRO: + common_chat_parse_hermes_2_pro(builder); + break; + case COMMON_CHAT_FORMAT_FIREFUNCTION_V2: + common_chat_parse_firefunction_v2(builder); + break; + case COMMON_CHAT_FORMAT_COMMAND_R7B: + common_chat_parse_command_r7b(builder); + break; + case COMMON_CHAT_FORMAT_GRANITE: + common_chat_parse_granite(builder); break; case COMMON_CHAT_FORMAT_GPT_OSS: common_chat_parse_gpt_oss(builder); @@ -417,46 +2313,21 @@ static void common_chat_parse(common_chat_msg_parser & builder) { builder.finish(); } -// Main public parsing function common_chat_msg common_chat_parse(const std::string & input, bool is_partial, const common_chat_syntax & syntax) { common_chat_msg_parser builder(input, is_partial, syntax); try { common_chat_parse(builder); } catch (const common_chat_msg_partial_exception & ex) { + LOG("Partial parse: %s\n", ex.what()); if (!is_partial) { - // Fallback to content-only on parsing errors builder.clear_tools(); builder.move_to(0); common_chat_parse_content_only(builder); } - // Re-throw for partial cases to signal incomplete parsing - if (is_partial) { - throw; - } } - return builder.result(); -} - -// Get format name for debugging/logging -const char* common_chat_format_name(common_chat_format format) { - switch (format) { - case COMMON_CHAT_FORMAT_CONTENT_ONLY: return "content_only"; - case COMMON_CHAT_FORMAT_GENERIC: return "generic"; - case COMMON_CHAT_FORMAT_DEEPSEEK_R1: return "deepseek_r1"; - case COMMON_CHAT_FORMAT_KIMI_K2: return "kimi_k2"; - case COMMON_CHAT_FORMAT_GPT_OSS: return "GPT-OSS"; - default: return "unknown"; - } -} - -const char * common_reasoning_format_name(common_reasoning_format format) { - switch (format) { - case COMMON_REASONING_FORMAT_NONE: return "none"; - case COMMON_REASONING_FORMAT_AUTO: return "auto"; - case COMMON_REASONING_FORMAT_DEEPSEEK: return "deepseek"; - case COMMON_REASONING_FORMAT_DEEPSEEK_LEGACY: return "deepseek-legacy"; - default: - throw std::runtime_error("Unknown reasoning format"); + auto msg = builder.result(); + if (!is_partial) { + LOG("Parsed message: %s\n", common_chat_msgs_to_json_oaicompat({msg}).at(0).dump().c_str()); } + return msg; } - diff --git a/common/chat.h b/common/chat.h index 83e31566d..33553e3a9 100644 --- a/common/chat.h +++ b/common/chat.h @@ -1,37 +1,16 @@ -// Chat support with builder pattern for llama.cpp compatibility +// Chat support (incl. tool call grammar constraining & output parsing) w/ generic & custom template handlers. + #pragma once #include "common.h" +#include +#include #include #include -#include +#include -// Forward declarations struct common_chat_templates; -// Basic data structures compatible with original llama.cpp -struct common_string_range { - size_t begin; - size_t end; - - common_string_range(size_t begin, size_t end) : begin(begin), end(end) { - if (begin > end) { - throw std::runtime_error("Invalid range"); - } - } - - // prevent default ctor - common_string_range() = delete; - - bool empty() const { - return begin == end; - } - - bool operator==(const common_string_range & other) const { - return begin == other.begin && end == other.end; - } -}; - struct common_chat_tool_call { std::string name; std::string arguments; @@ -40,10 +19,6 @@ struct common_chat_tool_call { bool operator==(const common_chat_tool_call & other) const { return name == other.name && arguments == other.arguments && id == other.id; } - - bool operator!=(const common_chat_tool_call & other) const { - return !(*this == other); - } }; struct common_chat_msg_content_part { @@ -64,11 +39,11 @@ struct common_chat_msg { std::string tool_name; std::string tool_call_id; + template T to_json_oaicompat() const; + bool empty() const { - return content.empty() && content_parts.empty() && tool_calls.empty() && - reasoning_content.empty() && tool_name.empty() && tool_call_id.empty(); + return content.empty() && content_parts.empty() && tool_calls.empty() && reasoning_content.empty() && tool_name.empty() && tool_call_id.empty(); } - void ensure_tool_call_ids_set(std::vector & ids_cache, const std::function & gen_tool_call_id) { for (auto i = 0u; i < tool_calls.size(); i++) { if (ids_cache.size() <= i) { @@ -81,7 +56,6 @@ struct common_chat_msg { tool_calls[i].id = ids_cache[i]; } } - bool operator==(const common_chat_msg & other) const { return role == other.role && content == other.content @@ -91,7 +65,6 @@ struct common_chat_msg { && tool_name == other.tool_name && tool_call_id == other.tool_call_id; } - bool operator!=(const common_chat_msg & other) const { return !(*this == other); } @@ -110,10 +83,6 @@ struct common_chat_msg_diff { && tool_call_index == other.tool_call_index && tool_call_delta == other.tool_call_delta; } - - bool operator!=(const common_chat_msg_diff & other) const { - return !(*this == other); - } }; struct common_chat_tool { @@ -131,50 +100,110 @@ enum common_chat_tool_choice { enum common_chat_format { COMMON_CHAT_FORMAT_CONTENT_ONLY, COMMON_CHAT_FORMAT_GENERIC, + COMMON_CHAT_FORMAT_MISTRAL_NEMO, + COMMON_CHAT_FORMAT_LLAMA_3_X, + COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS, COMMON_CHAT_FORMAT_DEEPSEEK_R1, + COMMON_CHAT_FORMAT_FIREFUNCTION_V2, + COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2, + COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1, + COMMON_CHAT_FORMAT_HERMES_2_PRO, + COMMON_CHAT_FORMAT_COMMAND_R7B, + COMMON_CHAT_FORMAT_GRANITE, COMMON_CHAT_FORMAT_GPT_OSS, - COMMON_CHAT_FORMAT_KIMI_K2, // Our custom format (keep last for backward compatibility) + + COMMON_CHAT_FORMAT_COUNT, // Not a format, just the # formats +}; + +struct common_chat_templates_inputs { + std::vector messages; + std::string grammar; + std::string json_schema; + bool add_generation_prompt = true; + bool use_jinja = true; + // Parameters below only supported when use_jinja is true + std::vector tools; + common_chat_tool_choice tool_choice = COMMON_CHAT_TOOL_CHOICE_AUTO; + bool parallel_tool_calls = false; + common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_NONE; + bool enable_thinking = true; + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + std::map chat_template_kwargs; + bool add_bos = false; + bool add_eos = false; }; -enum common_reasoning_format { - COMMON_REASONING_FORMAT_NONE, - COMMON_REASONING_FORMAT_AUTO, - COMMON_REASONING_FORMAT_DEEPSEEK, - COMMON_REASONING_FORMAT_DEEPSEEK_LEGACY, +struct common_chat_params { + common_chat_format format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + std::string prompt; + std::string grammar; + bool grammar_lazy = false; + bool thinking_forced_open = false; + std::vector grammar_triggers; + std::vector preserved_tokens; + std::vector additional_stops; }; struct common_chat_syntax { - common_chat_format format = COMMON_CHAT_FORMAT_KIMI_K2; - common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_AUTO; //COMMON_REASONING_FORMAT_NONE; + common_chat_format format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_NONE; // Whether reasoning_content should be inlined in the content (e.g. for reasoning_format=deepseek in stream mode) - bool reasoning_in_content = false; - bool thinking_forced_open = false; - bool enable_thinking = false; - bool enable_tool_calls = true; + bool reasoning_in_content = false; + bool thinking_forced_open = false; + bool parse_tool_calls = true; }; -// Exception for partial parsing -class common_chat_msg_partial_exception : public std::runtime_error { - public: - common_chat_msg_partial_exception(const std::string & message) : std::runtime_error(message) {} -}; +// Check if the template supplied via "--chat-template" is supported or not. Returns true if it's valid +bool common_chat_verify_template(const std::string & tmpl, bool use_jinja); + +void common_chat_templates_free(struct common_chat_templates * tmpls); + +struct common_chat_templates_deleter { void operator()(common_chat_templates * tmpls) { common_chat_templates_free(tmpls); } }; + +typedef std::unique_ptr common_chat_templates_ptr; + +common_chat_templates_ptr common_chat_templates_init( + const struct llama_model * model, + const std::string & chat_template_override, + const std::string & bos_token_override = "", + const std::string & eos_token_override = ""); + +bool common_chat_templates_was_explicit(const struct common_chat_templates * tmpls); +const char * common_chat_templates_source(const struct common_chat_templates * tmpls, const char * variant = nullptr); + + +struct common_chat_params common_chat_templates_apply( + const struct common_chat_templates * tmpls, + const struct common_chat_templates_inputs & inputs); + +// Format single message, while taking into account the position of that message in chat history +std::string common_chat_format_single( + const struct common_chat_templates * tmpls, + const std::vector & past_msg, + const common_chat_msg & new_msg, + bool add_ass, + bool use_jinja); -// Bridge functions to integrate with existing ik_llama.cpp system -// TODO: Uncomment and implement during integration phase -// common_chat_msg ik_to_common_msg(const struct ik_chat_msg & ik_msg); -// struct ik_chat_msg common_to_ik_msg(const common_chat_msg & common_msg); +// Returns an example of formatted chat +std::string common_chat_format_example( + const struct common_chat_templates * tmpls, + bool use_jinja); -// Format detection from chat template -common_chat_format common_chat_format_detect(const std::string & chat_template); -const char* common_chat_format_name(common_chat_format format); -const char* common_reasoning_format_name(common_reasoning_format format); +const char* common_chat_format_name(common_chat_format format); +const char* common_reasoning_format_name(common_reasoning_format format); +common_reasoning_format common_reasoning_format_from_name(const std::string& format); +common_chat_msg common_chat_parse(const std::string & input, bool is_partial, const common_chat_syntax & syntax); -// Main parsing function (entry point for original llama.cpp compatibility) -common_chat_msg common_chat_parse(const std::string & input, bool is_partial, const common_chat_syntax & syntax); +common_chat_tool_choice common_chat_tool_choice_parse_oaicompat(const std::string & tool_choice); -// Forward declare parser class -class common_chat_msg_parser; +// Parses a JSON array of messages in OpenAI's chat completion API format. +// T can be std::string containing JSON or nlohmann::ordered_json +template std::vector common_chat_msgs_parse_oaicompat(const T & messages); +template T common_chat_msgs_to_json_oaicompat(const std::vector & msgs, bool concat_typed_text = false); -// Format-specific parsing functions (accessible from chat-parser) -void common_chat_parse_deepseek_r1(common_chat_msg_parser & builder); +// Parses a JSON array of tools in OpenAI's chat completion tool call API format. +// T can be std::string containing JSON or nlohmann::ordered_json +template std::vector common_chat_tools_parse_oaicompat(const T & tools); +template T common_chat_tools_to_json_oaicompat(const std::vector & tools); +template T common_chat_msg_diff_to_json_oaicompat(const common_chat_msg_diff & diff); diff --git a/common/common.cpp b/common/common.cpp index c1e943235..19a6f42a9 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -13,10 +13,10 @@ // Change JSON_ASSERT from assert() to GGML_ASSERT: #define JSON_ASSERT GGML_ASSERT #include "json.hpp" -#include "json-schema-to-grammar.h" +#include "llama-vocab.h" #include "llama.h" -#include "chat-template.hpp" - +#include "chat.h" +#include "json-schema-to-grammar.h" #include #include #include @@ -230,13 +230,13 @@ void gpt_params_handle_model_default(gpt_params & params) { } params.hf_file = params.model; } else if (params.model.empty()) { - params.model = fs_get_cache_file(string_split(params.hf_file, '/').back()); + params.model = fs_get_cache_file(string_split(params.hf_file, "/").back()); } } else if (!params.model_url.empty()) { if (params.model.empty()) { - auto f = string_split(params.model_url, '#').front(); - f = string_split(f, '?').front(); - params.model = fs_get_cache_file(string_split(f, '/').back()); + auto f = string_split(params.model_url, "#").front(); + f = string_split(f, "?").front(); + params.model = fs_get_cache_file(string_split(f, "/").back()); } } else if (params.model.empty()) { params.model = DEFAULT_MODEL_PATH; @@ -295,7 +295,7 @@ bool gpt_params_parse_ex(int argc, char ** argv, gpt_params & params) { params.tensor_buft_overrides.push_back({nullptr, nullptr}); } - if (!params.chat_template.empty() && !llama_chat_verify_template(nullptr, params.chat_template, params.use_jinja)) { + if (!params.chat_template.empty() && !common_chat_verify_template(params.chat_template, params.use_jinja)) { throw std::runtime_error(string_format( "error: the supplied chat template is not supported: %s%s\n", params.chat_template.c_str(), @@ -599,7 +599,7 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa } if (arg == "--samplers") { CHECK_ARG - const auto sampler_names = string_split(argv[i], ';'); + const auto sampler_names = string_split(argv[i], ";"); sparams.samplers_sequence = llama_sampling_types_from_names(sampler_names, true); return true; } @@ -1478,6 +1478,11 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa } return true; } + if (arg == "--reasoning-budget") { + CHECK_ARG + params.reasoning_budget = std::stoi(argv[i]); + return true; + } if (arg == "--sql-save-file") { CHECK_ARG params.sql_save_file = argv[i]; @@ -1490,7 +1495,7 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa } if (arg == "--chat-template") { CHECK_ARG - if (!llama_chat_verify_template(nullptr, argv[i], false)) { + if (!common_chat_verify_template(argv[i], true)) { fprintf(stderr, "error: the supplied chat template is not supported: %s\n", argv[i]); fprintf(stderr, "note: llama.cpp does not use jinja parser, we only support commonly used templates\n"); invalid_param = true; @@ -1502,9 +1507,8 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa if (arg == "--chat-template-file") { CHECK_ARG std::string chat_template = read_file(std::string(argv[i])); - if (!llama_chat_verify_template(nullptr, chat_template, false)) { + if (!common_chat_verify_template(chat_template, true)) { fprintf(stderr, "error: the supplied chat template is not supported: %s\n", argv[i]); - fprintf(stderr, "note: llama.cpp does not use jinja parser, we only support commonly used templates\n"); invalid_param = true; return true; } @@ -1515,6 +1519,26 @@ bool gpt_params_find_arg(int argc, char ** argv, const std::string & arg, gpt_pa params.use_jinja = true; return true; } + if (arg == "--chat-template-kwargs") { + CHECK_ARG + std::string value = argv[i]; + auto parsed = json::parse(value); + for (const auto& item : parsed.items()) { + params.default_template_kwargs[item.key()] = item.value().dump(); + } + return true; + } + if (arg == "--reasoning-format") { + CHECK_ARG + std::string value = argv[i]; + params.reasoning_format = common_reasoning_format_from_name(value); + return true; + } + if (arg == "--no-prefill-assistant") { + CHECK_ARG + params.prefill_assistant = false; + return true; + } if (arg == "--slot-prompt-similarity" || arg == "-sps") { CHECK_ARG params.slot_prompt_similarity = std::stof(argv[i]); @@ -1822,11 +1846,22 @@ void gpt_params_print_usage(int /*argc*/, char ** argv, const gpt_params & param options.push_back({ "main", " --cfg-negative-prompt-file FNAME", "negative prompt file to use for guidance" }); options.push_back({ "main", " --cfg-scale N", "strength of guidance (default: %.1f, 1.0 = disable)", (double)sparams.cfg_scale }); - options.push_back({ "main", " --chat-template JINJA_TEMPLATE", + options.push_back({ "main", " --jinja", "set custom jinja chat template (default: template taken from model's metadata)\n" "if suffix/prefix are specified, template will be disabled\n" "only commonly used templates are accepted:\n" "https://github.com/ggerganov/llama.cpp/wiki/Templates-supported-by-llama_chat_apply_template" }); + options.push_back({ "main", " --chat-template JINJA_TEMPLATE", + "use jinja template for chat (default: disabled)\n" }); + options.push_back({ "main", " --reasoning-format FORMAT", + "controls whether thought tags are allowed and/or extracted from the response, and in which format they're returned; one of:\n" + "- none: leaves thoughts unparsed in `message.content`\n" + "- deepseek: puts thoughts in `message.reasoning_content` (except in streaming mode, which behaves as `none`)\n" + "(default: none)", }); + options.push_back({ "main", " --chat-template-kwargs JSON", "sets additional params for the json template parser"}); + options.push_back({ "main", " --reasoning-budget N", "controls the amount of thinking allowed; currently only one of: -1 for unrestricted thinking budget, or 0 to disable thinking (default: -1)" }); + options.push_back({ "main", " --no-prefill-assistant", "whether to prefill the assistant's response if the last message is an assistant message (default: prefill enabled)\n" + "when this flag is set, if the last message is an assistant message then it will be treated as a full message and not prefilled\n" }); options.push_back({ "grammar" }); options.push_back({ "*", " --grammar GRAMMAR", "BNF-like grammar to constrain generations (see samples in grammars/ dir) (default: '%s')", sparams.grammar.c_str() }); options.push_back({ "*", " --grammar-file FNAME", "file to read grammar from" }); @@ -2086,42 +2121,66 @@ std::string string_format(const char* fmt, ...) { return std::string(buf.data(), size); } +std::string regex_escape(const std::string& s) { + static const std::regex special_chars("[.^$|()*+?\\[\\]{}\\\\]"); + return std::regex_replace(s, special_chars, "\\$0"); +} -std::vector string_split(std::string input, char separator) { +std::string string_join(const std::vector& values, const std::string& separator) { + std::ostringstream result; + for (size_t i = 0; i < values.size(); ++i) { + if (i > 0) { + result << separator; + } + result << values[i]; + } + return result.str(); +} + + +std::vector string_split(const std::string& str, const std::string& delimiter) { std::vector parts; - size_t separator_pos = input.find(separator); - while (separator_pos != std::string::npos) { - std::string part = input.substr(0, separator_pos); - parts.emplace_back(part); - input = input.substr(separator_pos + 1); - separator_pos = input.find(separator); - } - parts.emplace_back(input); + size_t start = 0; + size_t end = str.find(delimiter); + + while (end != std::string::npos) { + parts.push_back(str.substr(start, end - start)); + start = end + delimiter.length(); + end = str.find(delimiter, start); + } + + parts.push_back(str.substr(start)); + return parts; } -std::string string_join(const std::vector & strs, const std::string & delimiter) { - if (strs.empty()) { - return ""; +std::vector string_split(const std::string& str, char delim) { + std::vector values; + std::istringstream str_stream(str); + std::string token; + while (std::getline(str_stream, token, delim)) { + std::string value; + std::istringstream token_stream(token); + token_stream >> value; + values.push_back(value); } + return values; +} - std::ostringstream oss; - for (size_t i = 0; i < strs.size(); ++i) { - if (i > 0) { - oss << delimiter; - } - oss << strs[i]; - } - return oss.str(); +static bool is_utf8_whitespace(uint8_t c) { + // Basic ASCII whitespace + if (c <= 0x7F) return isspace(c); + // Else: Not whitespace (or you'd need a full Unicode table) + return false; } std::string string_strip(const std::string & str) { size_t start = 0; size_t end = str.size(); - while (start < end && std::isspace(str[start])) { + while (start < end && is_utf8_whitespace(str[start])) { start++; } - while (end > start && std::isspace(str[end - 1])) { + while (end > start && is_utf8_whitespace(str[end - 1])) { end--; } return str.substr(start, end - start); @@ -2154,6 +2213,25 @@ void string_replace_all(std::string & s, const std::string & search, const std:: } } +bool string_ends_with(const std::string_view& str, const std::string_view& suffix) { + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} +size_t string_find_partial_stop(const std::string_view& str, const std::string_view& stop) { + if (!str.empty() && !stop.empty()) { + const char text_last_char = str.back(); + for (int64_t char_index = stop.size() - 1; char_index >= 0; char_index--) { + if (stop[char_index] == text_last_char) { + const auto current_partial = stop.substr(0, char_index + 1); + if (string_ends_with(str, current_partial)) { + return str.size() - char_index - 1; + } + } + } + } + + return std::string::npos; +} + void string_process_escapes(std::string & input) { std::size_t input_len = input.length(); std::size_t output_idx = 0; @@ -3129,154 +3207,172 @@ bool llama_should_add_bos_token(const llama_model * model) { // // Chat template utils // - -bool llama_chat_verify_template(const struct llama_model* model, const std::string& tmpl, bool use_jinja) { - if (use_jinja) { - try { - auto chat_template = minja::chat_template(tmpl, "", ""); - chat_template.apply({ { - {"role", "user"}, - {"content", "test"}, - } }, json(), true); - return true; - } - catch (const std::exception& e) { - fprintf(stdout,"%s: failed to apply template: %s\n", __func__, e.what()); - return false; - } - } - llama_chat_message chat[] = {{"user", "test"}}; - const int res = llama_chat_apply_template(model, tmpl.c_str(), chat, 1, true, nullptr, 0); - return res >= 0; -} - -std::string llama_chat_apply_template(const struct llama_model * model, - const common_chat_template& tmpl, - const std::vector & msgs, - bool add_ass, - bool use_jinja) { - if (use_jinja) { - auto messages = json::array(); - for (const auto& msg : msgs) { - messages.push_back({ {"role", msg.role}, {"content", msg.content} }); - } - return tmpl.apply(messages, /* tools= */ json(), add_ass); - } - int alloc_size = 0; - std::vector chat; - for (auto & msg : msgs) { - chat.push_back({msg.role.c_str(), msg.content.c_str()}); - alloc_size += (msg.role.size() + msg.content.size()) * 1.25; - } - - std::vector buf(alloc_size); - - // run the first time to get the total output length - int32_t res = llama_chat_apply_template(model, tmpl.source().c_str(), chat.data(), chat.size(), add_ass, buf.data(), buf.size()); - // error: chat template is not supported - if (res < 0) { - // if the custom "tmpl" is not supported, we throw an error - // this is a bit redundant (for good), since we're not sure if user validated the custom template with llama_chat_verify_template() - throw std::runtime_error("this custom template is not supported"); - } - - // if it turns out that our buffer is too small, we resize it - if ((size_t) res > buf.size()) { - buf.resize(res); - res = llama_chat_apply_template(model, tmpl.source().c_str(), chat.data(), chat.size(), add_ass, buf.data(), buf.size()); - } - - std::string formatted_chat(buf.data(), res); - return formatted_chat; -} - -std::string llama_chat_format_single(const struct llama_model * model, - const common_chat_template& tmpl, - const std::vector & past_msg, - const llama_chat_msg & new_msg, - bool add_ass, - bool use_jinja) { - std::ostringstream ss; - auto fmt_past_msg = past_msg.empty() ? "" : llama_chat_apply_template(model, tmpl, past_msg, false, use_jinja); - std::vector chat_new(past_msg); - // if the past_msg ends with a newline, we must preserve it in the formatted version - if (add_ass && !fmt_past_msg.empty() && fmt_past_msg.back() == '\n') { - ss << "\n"; - }; - // format chat with new_msg - chat_new.push_back(new_msg); - auto fmt_new_msg = llama_chat_apply_template(model, tmpl, chat_new, add_ass, use_jinja); - // get the diff part - ss << fmt_new_msg.substr(fmt_past_msg.size(), fmt_new_msg.size() - fmt_past_msg.size()); - return ss.str(); -} - -std::string llama_chat_format_example(const struct llama_model * model, const common_chat_template& tmpl, bool use_jinja) { - std::vector msgs = { - {"system", "You are a helpful assistant"}, - {"user", "Hello"}, - {"assistant", "Hi there"}, - {"user", "How are you?"}, - }; - return llama_chat_apply_template(model, tmpl, msgs, true, use_jinja); -} - - -common_chat_templates llama_chat_templates_from_model(const struct llama_model* model, const std::string& chat_template_override) -{ - auto vocab = llama_model_get_vocab(model); - std::string default_template_src = chat_template_override; - std::string template_tool_use_src = chat_template_override; - bool has_explicit_template = !chat_template_override.empty(); - if (chat_template_override.empty()) { - auto str = llama_model_chat_template(model, /* name */ nullptr); - if (str) { - default_template_src = str; - has_explicit_template = true; - } - str = llama_model_chat_template(model, /* name */ "tool_use"); - if (str) { - template_tool_use_src = str; - has_explicit_template = true; - } - } - if (default_template_src.empty() || default_template_src == "chatml") { - if (!template_tool_use_src.empty()) { - default_template_src = template_tool_use_src; - } - else { - default_template_src = R"( - {%- for message in messages -%} - {{- "<|im_start|>" + message.role + "\n" + message.content + "<|im_end|>\n" -}} - {%- endfor -%} - {%- if add_generation_prompt -%} - {{- "<|im_start|>assistant\n" -}} - {%- endif -%} - )"; - } - } - const auto get_token = [&](llama_token token, const char* name, const char* jinja_variable_name) { - if (token == LLAMA_TOKEN_NULL) { - if (default_template_src.find(jinja_variable_name) != std::string::npos - || template_tool_use_src.find(jinja_variable_name) != std::string::npos) { - fprintf(stdout, "%s: warning: vocab does not have a %s token, jinja template won't work as intended.\n", __func__, name); - } - return std::string(); - } - else { - return llama_token_to_piece(model, token, true); - } - }; - auto token_bos = get_token(llama_token_bos(model), "BOS", "bos_token"); - auto token_eos = get_token(llama_token_eos(model), "EOS", "eos_token"); - return { - has_explicit_template, - std::make_unique(default_template_src, token_bos, token_eos), - template_tool_use_src.empty() - ? nullptr - : std::make_unique(template_tool_use_src, token_bos, token_eos) - }; -} +// +//bool llama_chat_verify_template(const struct llama_model* model, const std::string& tmpl, bool use_jinja) { +// if (use_jinja) { +// try { +// auto chat_template = common_chat_template(tmpl, "", ""); +// common_chat_inputs inputs; +// inputs.messages = json::array({ { +// {"role", "user"}, +// {"content", "test"}, +// } }); +// common_chat_params_init(chat_template, inputs); +// return true; +// } +// catch (const std::exception& e) { +// fprintf(stdout,"%s: failed to apply template: %s\n", __func__, e.what()); +// return false; +// } +// } +// llama_chat_message chat[] = { {"user", "test"} }; +// const int res = llama_chat_apply_template(model, tmpl.c_str(), chat, 1, true, nullptr, 0); +// return res >= 0; +//} + +//std::string llama_chat_apply_template(const struct llama_model * model, +// const common_chat_template& tmpl, +// const std::vector & msgs, +// bool add_ass, +// bool use_jinja) { +// if (use_jinja) { +// auto messages = json::array(); +// for (const auto& msg : msgs) { +// messages.push_back({ {"role", msg.role}, {"content", msg.content} }); +// } +// common_chat_inputs inputs; +// inputs.messages = messages; +// inputs.add_generation_prompt = add_ass; +// return common_chat_params_init(tmpl, inputs).prompt; +// } +// int alloc_size = 0; +// std::vector chat; +// for (auto & msg : msgs) { +// chat.push_back({msg.role.c_str(), msg.content.c_str()}); +// alloc_size += (msg.role.size() + msg.content.size()) * 1.25; +// } +// +// std::vector buf(alloc_size); +// +// // run the first time to get the total output length +// int32_t res = llama_chat_apply_template(model, tmpl.source().c_str(), chat.data(), chat.size(), add_ass, buf.data(), buf.size()); +// // error: chat template is not supported +// if (res < 0) { +// // if the custom "tmpl" is not supported, we throw an error +// // this is a bit redundant (for good), since we're not sure if user validated the custom template with llama_chat_verify_template() +// throw std::runtime_error("this custom template is not supported"); +// } +// +// // if it turns out that our buffer is too small, we resize it +// if ((size_t)res > buf.size()) { +// buf.resize(res); +// res = llama_chat_apply_template(model, tmpl.source().c_str(), chat.data(), chat.size(), add_ass, buf.data(), buf.size()); +// } +// +// std::string formatted_chat(buf.data(), res); +// return formatted_chat; +//} +//// +//std::string llama_chat_format_single(const struct llama_model * model, +// const common_chat_template& tmpl, +// const std::vector & past_msg, +// const common_chat_msg & new_msg, +// bool add_ass, +// bool use_jinja) { +// std::ostringstream ss; +// auto fmt_past_msg = past_msg.empty() ? "" : llama_chat_apply_template(model, tmpl, past_msg, false, use_jinja); +// std::vector chat_new(past_msg); +// // if the past_msg ends with a newline, we must preserve it in the formatted version +// if (add_ass && !fmt_past_msg.empty() && fmt_past_msg.back() == '\n') { +// ss << "\n"; +// }; +// // format chat with new_msg +// chat_new.push_back(new_msg); +// auto fmt_new_msg = llama_chat_apply_template(model, tmpl, chat_new, add_ass, use_jinja); +// // get the diff part +// ss << fmt_new_msg.substr(fmt_past_msg.size(), fmt_new_msg.size() - fmt_past_msg.size()); +// return ss.str(); +//} + +//std::string llama_chat_format_example(const struct llama_model * model, const common_chat_template& tmpl, bool use_jinja) { +// std::vector msgs = { +// {"system", "You are a helpful assistant", {}}, +// {"user", "Hello", {}}, +// {"assistant", "Hi there", {}}, +// {"user", "How are you?", {}}, +// }; +// return llama_chat_apply_template(model, tmpl, msgs, true, use_jinja); +//} +// +//#define CHATML_TEMPLATE_SRC \ +// "{%- for message in messages -%}\n" \ +// " {{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>\n' -}}\n" \ +// "{%- endfor -%}\n" \ +// "{%- if add_generation_prompt -%}\n" \ +// " {{- '<|im_start|>assistant\n' -}}\n" \ +// "{%- endif -%}" +// +//common_chat_templates llama_chat_templates_from_model(const struct llama_model* model, const std::string& chat_template_override) +//{ +// std::string default_template_src; +// std::string template_tool_use_src; +// bool has_explicit_template = !chat_template_override.empty(); +// if (chat_template_override.empty()) { +// auto str = llama_model_chat_template(model, /* name */ nullptr); +// if (str) { +// default_template_src = str; +// has_explicit_template = true; +// } +// str = llama_model_chat_template(model, /* name */ "tool_use"); +// if (str) { +// template_tool_use_src = str; +// has_explicit_template = true; +// } +// } +// else { +// default_template_src = chat_template_override; +// } +// if (default_template_src.empty() || default_template_src == "chatml") { +// if (!template_tool_use_src.empty()) { +// default_template_src = template_tool_use_src; +// } +// else { +// default_template_src = CHATML_TEMPLATE_SRC; +// } +// } +// auto vocab = llama_model_get_vocab(model); +// const auto get_token = [&](llama_token token, const char* name, const char* jinja_variable_name) { +// if (token == LLAMA_TOKEN_NULL) { +// if (default_template_src.find(jinja_variable_name) != std::string::npos +// || template_tool_use_src.find(jinja_variable_name) != std::string::npos) { +// fprintf(stdout, "%s: warning: vocab does not have a %s token, jinja template won't work as intended.\n", __func__, name); +// } +// return std::string(); +// } +// else { +// return llama_token_to_piece(model, token, true); +// } +// }; +// auto token_bos = get_token(llama_token_bos_impl(*vocab), "BOS", "bos_token"); +// auto token_eos = get_token(llama_token_eos_impl(*vocab), "EOS", "eos_token"); +// try { +// return { +// has_explicit_template, +// std::make_unique(default_template_src, token_bos, token_eos), +// template_tool_use_src.empty() +// ? nullptr +// : std::make_unique(template_tool_use_src, token_bos, token_eos), +// }; +// } +// catch (const std::exception& e) { +// LOG("%s: failed to parse chat template: %s\n", __func__, e.what()); +// return { +// has_explicit_template, +// std::make_unique(CHATML_TEMPLATE_SRC, token_bos, token_eos), +// nullptr, +// }; +// } +//} // // KV cache utils @@ -3765,27 +3861,3 @@ void yaml_dump_non_result_info(FILE * stream, const gpt_params & params, const l fprintf(stream, "verbose_prompt: %s # default: false\n", params.verbose_prompt ? "true" : "false"); fprintf(stream, "display_prompt: %s # default: true\n", params.display_prompt ? "true" : "false"); } - -// Additional string utilities for builder pattern compatibility -bool string_starts_with(const std::string & str, const std::string & prefix) { - return str.rfind(prefix, 0) == 0; -} - -bool string_ends_with(const std::string_view & str, const std::string_view & suffix) { - return str.size() >= suffix.size() && str.compare(str.size()-suffix.size(), suffix.size(), suffix) == 0; -} - -size_t string_find_partial_stop(const std::string_view & str, const std::string_view & stop) { - if (!str.empty() && !stop.empty()) { - const char text_last_char = str.back(); - for (int64_t char_index = stop.size() - 1; char_index >= 0; char_index--) { - if (stop[char_index] == text_last_char) { - const auto current_partial = stop.substr(0, char_index + 1); - if (string_ends_with(str, current_partial)) { - return str.size() - char_index - 1; - } - } - } - } - return std::string::npos; -} diff --git a/common/common.h b/common/common.h index 1bf0f235d..1e4e14e5e 100644 --- a/common/common.h +++ b/common/common.h @@ -15,14 +15,18 @@ #define LOG_NO_FILE_LINE_FUNCTION #include "log.h" - +#include #include #include +#include +#include #include #include #include #include #include +#include +#include #ifdef _WIN32 #define DIRECTORY_SEPARATOR '\\' @@ -74,6 +78,14 @@ enum dimre_method { DIMRE_METHOD_MEAN, }; +// reasoning API response format (not to be confused as chat template's reasoning format) +enum common_reasoning_format { + COMMON_REASONING_FORMAT_NONE, + COMMON_REASONING_FORMAT_AUTO, + COMMON_REASONING_FORMAT_DEEPSEEK_LEGACY, // Extract thinking tag contents and return as `message.reasoning_content`, or leave inline in tags in stream mode + COMMON_REASONING_FORMAT_DEEPSEEK, // Extract thinking tag contents and return as `message.reasoning_content`, including in streaming deltas. +}; + struct gpt_params { uint32_t seed = LLAMA_DEFAULT_SEED; // RNG seed @@ -238,13 +250,21 @@ struct gpt_params { bool use_jinja = false; // NOLINT std::string system_prompt = ""; bool enable_chat_template = true; + common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_NONE; + int reasoning_budget = -1; + bool prefill_assistant = true; std::vector api_keys; std::string ssl_file_key = ""; std::string ssl_file_cert = ""; - bool endpoint_slots = true; + std::map default_template_kwargs; + + // "advanced" endpoints are disabled by default for better security + bool webui = true; + bool endpoint_slots = false; + bool endpoint_props = false; // only control POST requests, not GET bool endpoint_metrics = false; bool log_json = false; @@ -312,19 +332,24 @@ std::string gpt_params_get_system_info(const gpt_params & params); // // String utils // - -std::vector string_split(std::string input, char separator); -std::string string_join(const std::vector & strs, const std::string & delimiter); - +std::string string_join(const std::vector& values, const std::string& separator); std::string string_strip(const std::string & str); std::string string_get_sortable_timestamp(); +static bool string_starts_with(const std::string& str, + const std::string& prefix) { // While we wait for C++20's std::string::starts_with... + return str.rfind(prefix, 0) == 0; +} + +std::vector string_split(const std::string& str, const std::string& delimiter); +std::vector string_split(const std::string& str, char delim); + void string_replace_all(std::string & s, const std::string & search, const std::string & replace); +// While we wait for C++20's std::string::ends_with... +bool string_ends_with(const std::string_view& str, const std::string_view& suffix); +size_t string_find_partial_stop(const std::string_view& str, const std::string_view& stop); -// Additional string utilities for builder pattern compatibility -bool string_starts_with(const std::string & str, const std::string & prefix); -bool string_ends_with(const std::string_view & str, const std::string_view & suffix); -size_t string_find_partial_stop(const std::string_view & str, const std::string_view & stop); +std::string regex_escape(const std::string& s); template static std::vector string_split(const std::string & str, char delim) { @@ -340,6 +365,22 @@ static std::vector string_split(const std::string & str, char delim) { return values; } +template<> +std::vector string_split(const std::string& input, char separator) +{ + std::vector parts; + size_t begin_pos = 0; + size_t separator_pos = input.find(separator); + while (separator_pos != std::string::npos) { + std::string part = input.substr(begin_pos, separator_pos - begin_pos); + parts.emplace_back(part); + begin_pos = separator_pos + 1; + separator_pos = input.find(separator, begin_pos); + } + parts.emplace_back(input.substr(begin_pos, separator_pos - begin_pos)); + return parts; +} + bool string_parse_kv_override(const char * data, std::vector & overrides); void string_process_escapes(std::string & input); @@ -430,52 +471,59 @@ bool llama_should_add_bos_token(const llama_model * model); // // Chat template utils // - -// same with llama_chat_message, but uses std::string -struct llama_chat_msg { - std::string role; - std::string content; -}; - -// Check if the template supplied via "--chat-template" is supported or not. Returns true if it's valid -bool llama_chat_verify_template(const struct llama_model* , const std::string& tmpl, bool use_jinja); - -namespace minja { - class chat_template; -} - -typedef minja::chat_template common_chat_template; - -struct common_chat_templates { - bool has_explicit_template; // Model had builtin template or template overridde was specified. - std::unique_ptr template_default; // always set (defaults to chatml) - std::unique_ptr template_tool_use; -}; - - -// CPP wrapper for llama_chat_apply_template -// If the built-in template is not supported, we default to chatml -// If the custom "tmpl" is not supported, we throw an error -std::string llama_chat_apply_template( - const struct llama_model* model, - const common_chat_template& tmpl, - const std::vector< llama_chat_msg>& chat, - bool add_ass, - bool use_jinja); - -// Format single message, while taking into account the position of that message in chat history -std::string llama_chat_format_single(const struct llama_model* model, - const common_chat_template& tmpl, - const std::vector< llama_chat_msg>& past_msg, - const llama_chat_msg& new_msg, - bool add_ass, - bool use_jinja); - -// Returns an example of formatted chat -std::string llama_chat_format_example(const struct llama_model* model, - const common_chat_template& tmpl, bool use_jinja); - -common_chat_templates llama_chat_templates_from_model(const struct llama_model* model, const std::string& chat_template_override); +//struct common_tool_call { +// std::string name; +// std::string arguments; +// std::string id; +//}; +// +//// same with llama_chat_message, but uses std::string +//struct common_chat_msg { +// std::string role; +// std::string content; +// std::vector tool_calls; +// std::string reasoning_content = ""; +//}; + +//// Check if the template supplied via "--chat-template" is supported or not. Returns true if it's valid +//bool llama_chat_verify_template(const struct llama_model* , const std::string& tmpl, bool use_jinja); +// +//namespace minja { +// class chat_template; +//} +// +//typedef minja::chat_template common_chat_template; +// +//struct common_chat_templates { +// bool has_explicit_template; // Model had builtin template or template overridde was specified. +// std::unique_ptr template_default; // always set (defaults to chatml) +// std::unique_ptr template_tool_use; +//}; +// +// +//// CPP wrapper for llama_chat_apply_template +//// If the built-in template is not supported, we default to chatml +//// If the custom "tmpl" is not supported, we throw an error +//std::string llama_chat_apply_template( +// const struct llama_model* model, +// const common_chat_template& tmpl, +// const std::vector< common_chat_msg>& chat, +// bool add_ass, +// bool use_jinja); +// +//// Format single message, while taking into account the position of that message in chat history +//std::string llama_chat_format_single(const struct llama_model* model, +// const common_chat_template& tmpl, +// const std::vector< common_chat_msg>& past_msg, +// const common_chat_msg& new_msg, +// bool add_ass, +// bool use_jinja); +// +//// Returns an example of formatted chat +//std::string llama_chat_format_example(const struct llama_model* model, +// const common_chat_template& tmpl, bool use_jinja); +// +//common_chat_templates llama_chat_templates_from_model(const struct llama_model* model, const std::string& chat_template_override); // diff --git a/common/grammar-parser.cpp b/common/grammar-parser.cpp index a518b766d..da378eee1 100644 --- a/common/grammar-parser.cpp +++ b/common/grammar-parser.cpp @@ -383,10 +383,13 @@ namespace grammar_parser { } } } + state.success = true; return state; } catch (const std::exception & err) { - fprintf(stderr, "%s: error parsing grammar: %s\n", __func__, err.what()); - return parse_state(); + fprintf(stderr, "%s: error parsing grammar: %s\n\n%s\n", __func__, err.what(), src); + parse_state state; + state.success = false; + return state; } } diff --git a/common/grammar-parser.h b/common/grammar-parser.h index 9037d7272..3939bc302 100644 --- a/common/grammar-parser.h +++ b/common/grammar-parser.h @@ -22,6 +22,7 @@ namespace grammar_parser { std::vector> rules; std::vector c_rules(); + bool success; }; parse_state parse(const char * src); diff --git a/common/json-partial.cpp b/common/json-partial.cpp index 4d2929533..01f5f198c 100644 --- a/common/json-partial.cpp +++ b/common/json-partial.cpp @@ -1,13 +1,10 @@ -#include "json-partial.h" - +#include +#include "ggml.h" #include "log.h" -#include "../ggml/include/ggml.h" -#include "../examples/server/utils.hpp" - -#include "json.hpp" - #include +#include + using json = nlohmann::ordered_json; enum common_json_stack_element_type { @@ -129,7 +126,7 @@ bool common_json_parse( return true; } catch (const std::exception & ex) { // No, needs healing. - LOG_VERBOSE("Failed to parse up to error", {{"error", ex.what()}, {"content", std::string(it, temptative_end)}}); + LOG("Failed to parse up to error: %s: <<<%s>>>\n", ex.what(), std::string(it, temptative_end).c_str()); } auto can_parse = [](const std::string & str) { try { diff --git a/common/json-partial.h b/common/json-partial.h index 17e27b3f4..854db6a3a 100644 --- a/common/json-partial.h +++ b/common/json-partial.h @@ -1,6 +1,5 @@ #pragma once - -#include "json.hpp" +#include // Healing marker (empty if the JSON was fully parsed / wasn't healed). struct common_healing_marker { diff --git a/common/json-schema-to-grammar.cpp b/common/json-schema-to-grammar.cpp index 881eb49e3..b08f637a1 100644 --- a/common/json-schema-to-grammar.cpp +++ b/common/json-schema-to-grammar.cpp @@ -267,7 +267,7 @@ static void _build_min_max_int(int min_value, int max_value, std::stringstream & throw std::runtime_error("At least one of min_value or max_value must be set"); } -const std::string SPACE_RULE = "| \" \" | \"\\n\" [ \\t]{0,20}"; +const std::string SPACE_RULE = "| \" \" | \"\\n\"{1,2} [ \\t]{0,20}"; struct BuiltinRule { std::string content; @@ -389,6 +389,7 @@ static std::string format_literal(const std::string & literal) { class SchemaConverter { private: + friend std::string build_grammar(const std::function& cb, const common_grammar_options& options); std::function _fetch_json; bool _dotall; std::map _rules; @@ -1035,11 +1036,35 @@ class SchemaConverter { } }; -std::string json_schema_to_grammar(const json & schema) { - SchemaConverter converter([](const std::string &) { return json::object(); }, /* dotall= */ false); - auto copy = schema; - converter.resolve_refs(copy, "input"); - converter.visit(copy, ""); +std::string json_schema_to_grammar(const json & schema, bool force_gbnf) { +#ifdef LLAMA_USE_LLGUIDANCE + if (!force_gbnf) { + return "%llguidance {}\nstart: %json " + schema.dump(); + } +#else + (void)force_gbnf; +#endif // LLAMA_USE_LLGUIDANCE + return build_grammar([&](const common_grammar_builder& callbacks) { + auto copy = schema; + callbacks.resolve_refs(copy); + callbacks.add_schema("", copy); + }); +} + +std::string build_grammar(const std::function& cb, const common_grammar_options& options) { + SchemaConverter converter([&](const std::string &) { return json(); }, options.dotall); + common_grammar_builder builder{ + /* .add_rule = */ [&](const std::string& name, const std::string& rule) { + return converter._add_rule(name, rule); + }, + /* .add_schema = */ [&](const std::string& name, const nlohmann::ordered_json& schema) { + return converter.visit(schema, name == "root" ? "" : name); + }, + /* .resolve_refs = */ [&](nlohmann::ordered_json& schema) { + converter.resolve_refs(schema, ""); + } + }; + cb(builder); converter.check_errors(); return converter.format_grammar(); } diff --git a/common/json-schema-to-grammar.h b/common/json-schema-to-grammar.h index 41623b346..abe92617d 100644 --- a/common/json-schema-to-grammar.h +++ b/common/json-schema-to-grammar.h @@ -5,4 +5,17 @@ #define JSON_ASSERT GGML_ASSERT #include "json.hpp" -std::string json_schema_to_grammar(const nlohmann::ordered_json& schema); +std::string json_schema_to_grammar(const nlohmann::ordered_json & schema, + bool force_gbnf = false); + +struct common_grammar_builder { + std::function add_rule; + std::function add_schema; + std::function resolve_refs; +}; + +struct common_grammar_options { + bool dotall = false; +}; + +std::string build_grammar(const std::function& cb, const common_grammar_options& options = {}); diff --git a/common/llguidance.cpp b/common/llguidance.cpp new file mode 100644 index 000000000..7415c9415 --- /dev/null +++ b/common/llguidance.cpp @@ -0,0 +1,270 @@ +#include "sampling.h" +#include "log.h" + +#ifdef LLAMA_USE_LLGUIDANCE + +# include "llguidance.h" +# include + +struct llama_sampler_llg { + const llama_vocab * vocab; + std::string grammar_kind; + std::string grammar_data; + LlgTokenizer * tokenizer; + LlgConstraint * grammar; + LlgMaskResult llg_res; + bool has_llg_res; +}; + +static LlgConstraint * llama_sampler_llg_new(LlgTokenizer * tokenizer, const char * grammar_kind, + const char * grammar_data) { + LlgConstraintInit cinit; + llg_constraint_init_set_defaults(&cinit, tokenizer); + const char * log_level = getenv("LLGUIDANCE_LOG_LEVEL"); + if (log_level && *log_level) { + cinit.log_stderr_level = atoi(log_level); + } + auto c = llg_new_constraint_any(&cinit, grammar_kind, grammar_data); + if (llg_get_error(c)) { + LOG_ERR("llg error: %s\n", llg_get_error(c)); + llg_free_constraint(c); + return nullptr; + } + return c; +} + +static const char * llama_sampler_llg_name(const llama_sampler * /*smpl*/) { + return "llguidance"; +} + +static void llama_sampler_llg_accept_impl(llama_sampler * smpl, llama_token token) { + auto * ctx = (llama_sampler_llg *) smpl->ctx; + if (ctx->grammar) { + LlgCommitResult res; + llg_commit_token(ctx->grammar, token, &res); + ctx->has_llg_res = false; + } +} + +static void llama_sampler_llg_apply(llama_sampler * smpl, llama_token_data_array * cur_p) { + auto * ctx = (llama_sampler_llg *) smpl->ctx; + if (ctx->grammar) { + if (!ctx->has_llg_res) { + if (llg_compute_mask(ctx->grammar, &ctx->llg_res) == 0) { + ctx->has_llg_res = true; + } else { + LOG_ERR("llg error: %s\n", llg_get_error(ctx->grammar)); + llg_free_constraint(ctx->grammar); + ctx->grammar = nullptr; + } + } + if (ctx->has_llg_res) { + if (ctx->llg_res.is_stop) { + for (size_t i = 0; i < cur_p->size; ++i) { + if (!llama_vocab_is_eog(ctx->vocab, cur_p->data[i].id)) { + cur_p->data[i].logit = -INFINITY; + } + } + } else { + const uint32_t * mask = ctx->llg_res.sample_mask; + for (size_t i = 0; i < cur_p->size; ++i) { + auto token = cur_p->data[i].id; + if ((mask[token / 32] & (1 << (token % 32))) == 0) { + cur_p->data[i].logit = -INFINITY; + } + } + } + } + } +} + +static void llama_sampler_llg_reset(llama_sampler * smpl) { + auto * ctx = (llama_sampler_llg *) smpl->ctx; + if (!ctx->grammar) { + return; + } + + auto * grammar_new = llama_sampler_llg_new(ctx->tokenizer, ctx->grammar_kind.c_str(), ctx->grammar_data.c_str()); + llg_free_constraint(ctx->grammar); + ctx->grammar = grammar_new; + ctx->has_llg_res = false; +} + +static llama_sampler * llama_sampler_llg_clone(const llama_sampler * smpl) { + const auto * ctx = (const llama_sampler_llg *) smpl->ctx; + + auto * result = llama_sampler_init_llg(ctx->vocab, nullptr, nullptr); + + // copy the state + { + auto * result_ctx = (llama_sampler_llg *) result->ctx; + + if (ctx->grammar) { + result_ctx->grammar_kind = ctx->grammar_kind; + result_ctx->grammar_data = ctx->grammar_data; + result_ctx->grammar = llg_clone_constraint(ctx->grammar); + result_ctx->tokenizer = llg_clone_tokenizer(ctx->tokenizer); + } + } + + return result; +} + +static void llama_sampler_llg_free(llama_sampler * smpl) { + const auto * ctx = (llama_sampler_llg *) smpl->ctx; + + if (ctx->grammar) { + llg_free_constraint(ctx->grammar); + llg_free_tokenizer(ctx->tokenizer); + } + + delete ctx; +} + +static llama_sampler_i llama_sampler_llg_i = { + /* .name = */ llama_sampler_llg_name, + /* .accept = */ llama_sampler_llg_accept_impl, + /* .apply = */ llama_sampler_llg_apply, + /* .reset = */ llama_sampler_llg_reset, + /* .clone = */ llama_sampler_llg_clone, + /* .free = */ llama_sampler_llg_free, +}; + +static size_t llama_sampler_llg_tokenize_fn(const void * user_data, const uint8_t * bytes, size_t bytes_len, + uint32_t * output_tokens, size_t output_tokens_len) { + const llama_vocab * vocab = (const llama_vocab *) user_data; + int r = 0; + try { + r = llama_tokenize(vocab, (const char *) bytes, bytes_len, (int32_t *) output_tokens, output_tokens_len, false, + true); + } catch (const std::exception & e) { + GGML_ABORT("llama_tokenize failed: %s\n", e.what()); + } + if (r < 0) { + return -r; + } + return r; +} + +static LlgTokenizer * llama_sampler_llg_new_tokenizer(const llama_vocab * vocab) { + // TODO store the tokenizer in the vocab somehow + static const llama_vocab * vocab_cache; + static LlgTokenizer * tokenizer_cache; + + if (vocab_cache == vocab) { + return llg_clone_tokenizer(tokenizer_cache); + } + + auto tok_eos = llama_vocab_eot(vocab); + if (tok_eos == LLAMA_TOKEN_NULL) { + tok_eos = llama_vocab_eos(vocab); + } + + size_t vocab_size = llama_vocab_n_tokens(vocab); + + auto token_lens = new uint32_t[vocab_size]; + // we typically have ~7 bytes per token; let's go on the safe side here + auto token_bytes_size = vocab_size * 16 + 1024 * 1024; + auto token_bytes = new uint8_t[token_bytes_size]; + + size_t offset = 0; + for (size_t i = 0; i < vocab_size; i++) { + size_t max_token = 1024; + if (token_bytes_size - offset < max_token) { + GGML_ABORT("token_bytes buffer too small\n"); + } + + llama_token token = i; + auto dp = (char *) token_bytes + offset; + auto size = llama_detokenize(vocab, &token, 1, dp, max_token, false, false); + if (size < 0) { + GGML_ABORT("llama_detokenize failed\n"); + } + if (size == 0) { + size = llama_detokenize(vocab, &token, 1, dp + 1, max_token - 1, false, true); + if (size < 0) { + GGML_ABORT("llama_detokenize failed\n"); + } + if (size != 0) { + *dp = '\xff'; // special token prefix marker + size += 1; + } + } + + token_lens[i] = size; + offset += size; + } + + LlgTokenizerInit tinit = { + /* .vocab_size = */ (uint32_t) vocab_size, + /* .tok_eos = */ (uint32_t) tok_eos, + /* .token_lens = */ token_lens, + /* .token_bytes = */ token_bytes, + /* .tokenizer_json = */ nullptr, + /* .tokenize_assumes_string = */ true, + /* .tokenize_fn = */ llama_sampler_llg_tokenize_fn, + /* .use_approximate_greedy_tokenize_fn = */ false, + /* .tokenize_user_data = */ vocab, + }; + + char error_buffer[1024]; + LlgTokenizer * tokenizer = llg_new_tokenizer(&tinit, error_buffer, sizeof(error_buffer)); + + delete[] token_bytes; + delete[] token_lens; + + if (tokenizer == nullptr) { + LOG_ERR("llg tokenizer error: %s\n", error_buffer); + return tokenizer; + } + + if (tokenizer_cache) { + llg_free_tokenizer(tokenizer_cache); + } + vocab_cache = vocab; + tokenizer_cache = tokenizer; + + return llg_clone_tokenizer(tokenizer_cache); +} + +llama_sampler * llama_sampler_init_llg(const llama_vocab * vocab, const char * grammar_kind, + const char * grammar_data) { + auto * ctx = new llama_sampler_llg; + + if (grammar_kind != nullptr && grammar_kind[0] != '\0') { + auto tokenizer = llama_sampler_llg_new_tokenizer(vocab); + *ctx = { + /* .vocab = */ vocab, + /* .grammar_kind = */ grammar_kind, + /* .grammar_data = */ grammar_data, + /* .tokenizer = */ tokenizer, + /* .grammar = */ llama_sampler_llg_new(tokenizer, grammar_kind, grammar_data), + /* .llg_res = */ {}, + /* .has_llg_res = */ false, + }; + } else { + *ctx = { + /* .vocab = */ vocab, + /* .grammar_kind = */ {}, + /* .grammar_data = */ {}, + /* .tokenizer = */ nullptr, + /* .grammar = */ nullptr, + /* .llg_res = */ {}, + /* .has_llg_res = */ false, + }; + } + + return new llama_sampler{ + /* .iface = */ &llama_sampler_llg_i, + /* .ctx = */ ctx, + }; +} + +#else + +llama_grammar * llama_sampler_init_llg(const llama_vocab *, const char *, const char *) { + LOG("llguidance (cmake -DLLAMA_LLGUIDANCE=ON) is not enabled"); + return nullptr; +} + +#endif // LLAMA_USE_LLGUIDANCE diff --git a/common/log.h b/common/log.h index 1bc5328ce..2cbf4a740 100644 --- a/common/log.h +++ b/common/log.h @@ -1,5 +1,4 @@ #pragma once - #include #include #include diff --git a/common/minja/chat-template.hpp b/common/minja/chat-template.hpp new file mode 100644 index 000000000..c6a2df29c --- /dev/null +++ b/common/minja/chat-template.hpp @@ -0,0 +1,549 @@ +/* + Copyright 2024 Google LLC + + Use of this source code is governed by an MIT-style + license that can be found in the LICENSE file or at + https://opensource.org/licenses/MIT. +*/ +// SPDX-License-Identifier: MIT +#pragma once + +#include "minja.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::ordered_json; + +namespace minja { + +struct chat_template_caps { + bool supports_tools = false; + bool supports_tool_calls = false; + bool supports_tool_responses = false; + bool supports_system_role = false; + bool supports_parallel_tool_calls = false; + bool supports_tool_call_id = false; + // meta-llama/Llama-3.1-8B-Instruct expects arguments to be an object. + // Most other templates (and OpenAI's API) expect the arguments object to be stringified. + bool requires_object_arguments = false; + // CohereForAI/c4ai-command-r-plus simple variant + bool requires_non_null_content = false; + // MiniMaxAI/MiniMax-Text-01 special + bool requires_typed_content = false; +}; + +struct chat_template_inputs { + nlohmann::ordered_json messages; + nlohmann::ordered_json tools; + bool add_generation_prompt = true; + nlohmann::ordered_json extra_context; + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); +}; + +struct chat_template_options { + bool apply_polyfills = true; + bool use_bos_token = true; + bool use_eos_token = true; + bool define_strftime_now = true; + + bool polyfill_tools = true; + bool polyfill_tool_call_examples = true; + bool polyfill_tool_calls = true; + bool polyfill_tool_responses = true; + bool polyfill_system_role = true; + bool polyfill_object_arguments = true; + bool polyfill_typed_content = true; +}; + +class chat_template { + + private: + chat_template_caps caps_; + std::string source_; + std::string bos_token_; + std::string eos_token_; + std::shared_ptr template_root_; + std::string tool_call_example_; + + std::string try_raw_render( + const nlohmann::ordered_json & messages, + const nlohmann::ordered_json & tools, + bool add_generation_prompt, + const nlohmann::ordered_json & extra_context = nlohmann::ordered_json()) const + { + try { + chat_template_inputs inputs; + inputs.messages = messages; + inputs.tools = tools; + inputs.add_generation_prompt = add_generation_prompt; + inputs.extra_context = extra_context; + // Use fixed date for tests + inputs.now = std::chrono::system_clock::from_time_t(0); + + chat_template_options opts; + opts.apply_polyfills = false; + + auto prompt = apply(inputs, opts); + // fprintf(stderr, "try_raw_render: %s\n", prompt.c_str()); + return prompt; + } catch (const std::exception & e) { + // fprintf(stderr, "try_raw_render error: %s\n", e.what()); + return ""; + } + } + + public: + + chat_template(const std::string & source, const std::string & bos_token, const std::string & eos_token) + : source_(source), bos_token_(bos_token), eos_token_(eos_token) + { + template_root_ = minja::Parser::parse(source_, { + /* .trim_blocks = */ true, + /* .lstrip_blocks = */ true, + /* .keep_trailing_newline = */ false, + }); + + auto contains = [](const std::string & haystack, const std::string & needle) { + return haystack.find(needle) != std::string::npos; + }; + + const std::string user_needle = ""; + const std::string sys_needle = ""; + const json dummy_str_user_msg = {{"role", "user"}, {"content", user_needle}}; + const json dummy_typed_user_msg = {{"role", "user"}, {"content", json::array({{{"type", "text"}, {"text", user_needle}}})}}; + + caps_.requires_typed_content = + !contains(try_raw_render(json::array({dummy_str_user_msg}), {}, false), user_needle) + && contains(try_raw_render(json::array({dummy_typed_user_msg}), {}, false), user_needle); + + const auto dummy_user_msg = caps_.requires_typed_content + ? dummy_typed_user_msg + : dummy_str_user_msg; + const json needle_system_msg = { + {"role", "system"}, + {"content", caps_.requires_typed_content ? json::array({{{"type", "text"}, {"text", sys_needle}}}) : json(sys_needle)}, + }; + + caps_.supports_system_role = contains(try_raw_render({needle_system_msg, dummy_user_msg,}, {}, false), sys_needle); + + auto out = try_raw_render(json::array({ + dummy_user_msg + }), json::array({ + { + {"name", "some_tool"}, + {"type", "function"}, + {"function", { + {"name", "some_tool"}, + {"description", "Some tool."}, + {"parameters", { + {"type", "object"}, + {"properties", { + {"arg", { + {"type", "string"}, + {"description", "Some argument."}, + }}, + }}, + {"required", json::array({ "arg" })}, + }}, + }}, + }, + }), false); + caps_.supports_tools = contains(out, "some_tool"); + + const auto render_with_content = [&](const json & content) { + const json assistant_msg {{"role", "assistant"}, {"content", content}}; + // Render two assistant messages as some templates like QwQ-32B are handling + // the content differently depending on whether it's the last message or not + // (to remove the tag in all but the last message). + return try_raw_render(json::array({dummy_user_msg, assistant_msg, dummy_user_msg, assistant_msg}), {}, false); + }; + auto out_empty = render_with_content(""); + auto out_null = render_with_content(json()); + caps_.requires_non_null_content = contains(out_empty, user_needle) && !contains(out_null, user_needle); + + json j_null; + auto make_tool_calls_msg = [&](const json & tool_calls) { + return json { + {"role", "assistant"}, + {"content", caps_.requires_non_null_content? "" : j_null}, + {"tool_calls", tool_calls}, + }; + }; + auto make_tool_call = [](const std::string & tool_name, const json & arguments) { + return json { + {"id", "call_1___"}, + {"type", "function"}, + {"function", { + {"arguments", arguments}, + {"name", tool_name}, + }}, + }; + }; + const json dummy_args_obj {{"argument_needle", "print('Hello, World!')"}}; + + // Note: the arguments are rendered in both cases, but may be double-escaped, which we don't want. + out = try_raw_render(json::array({ + dummy_user_msg, + make_tool_calls_msg(json::array({make_tool_call("ipython", dummy_args_obj.dump())})), + }), {}, false); + auto tool_call_renders_str_arguments = contains(out, "") || contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':"); + out = try_raw_render(json::array({ + dummy_user_msg, + make_tool_calls_msg(json::array({make_tool_call("ipython", dummy_args_obj)})), + }), {}, false); + auto tool_call_renders_obj_arguments = contains(out, "") || contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':"); + + caps_.supports_tool_calls = tool_call_renders_str_arguments || tool_call_renders_obj_arguments; + caps_.requires_object_arguments = !tool_call_renders_str_arguments && tool_call_renders_obj_arguments; + + if (caps_.supports_tool_calls) { + auto dummy_args = caps_.requires_object_arguments ? dummy_args_obj : json(dummy_args_obj.dump()); + auto tc1 = make_tool_call("test_tool1", dummy_args); + auto tc2 = make_tool_call("test_tool2", dummy_args); + auto out = try_raw_render(json::array({ + dummy_user_msg, + make_tool_calls_msg(json::array({tc1, tc2})), + }), {}, false); + caps_.supports_parallel_tool_calls = contains(out, "test_tool1") && contains(out, "test_tool2"); + + out = try_raw_render(json::array({ + dummy_user_msg, + make_tool_calls_msg(json::array({tc1})), + { + {"role", "tool"}, + {"name", "test_tool1"}, + {"content", "Some response!"}, + {"tool_call_id", "call_911_"}, + } + }), {}, false); + caps_.supports_tool_responses = contains(out, "Some response!"); + caps_.supports_tool_call_id = contains(out, "call_911_"); + } + + try { + if (!caps_.supports_tools) { + const json user_msg { + {"role", "user"}, + {"content", "Hey"}, + }; + const json args { + {"arg1", "some_value"}, + }; + const json tool_call_msg { + {"role", "assistant"}, + {"content", caps_.requires_non_null_content ? "" : j_null}, + {"tool_calls", json::array({ + { + // TODO: detect if requires numerical id or fixed length == 6 like Nemo + {"id", "call_1___"}, + {"type", "function"}, + {"function", { + {"name", "tool_name"}, + {"arguments", (caps_.requires_object_arguments ? args : json(minja::Value(args).dump(-1, /* to_json= */ true)))}, + }}, + }, + })}, + }; + std::string prefix, full; + { + chat_template_inputs inputs; + inputs.messages = json::array({user_msg}); + inputs.add_generation_prompt = true; + prefix = apply(inputs); + } + { + chat_template_inputs inputs; + inputs.messages = json::array({user_msg, tool_call_msg}); + inputs.add_generation_prompt = false; + full = apply(inputs); + } + auto eos_pos_last = full.rfind(eos_token_); + if (eos_pos_last == prefix.size() - eos_token_.size() || + (full[full.size() - 1] == '\n' && (eos_pos_last == full.size() - eos_token_.size() - 1))) { + full = full.substr(0, eos_pos_last); + } + size_t common_prefix_length = 0; + for (size_t i = 0; i < prefix.size() && i < full.size(); ++i) { + if (prefix[i] != full[i]) { + break; + } + if (prefix[i] == '<') { + // DeepSeek R1's template (as of 20250209) adds a trailing if add_generation_prompt, + // but it removes thinking tags for past messages. + // The prefix and full strings diverge at vs. <|tool▁calls▁begin|>, we avoid consuming the leading <. + continue; + } + common_prefix_length = i + 1; + } + auto example = full.substr(common_prefix_length); + if (example.find("tool_name") == std::string::npos && example.find("some_value") == std::string::npos) { + fprintf(stderr, "Failed to infer a tool call example (possible template bug)\n"); + } else { + tool_call_example_ = example; + } + } + } catch (const std::exception & e) { + fprintf(stderr, "Failed to generate tool call example: %s\n", e.what()); + } + } + + const std::string & source() const { return source_; } + const std::string & bos_token() const { return bos_token_; } + const std::string & eos_token() const { return eos_token_; } + const chat_template_caps & original_caps() const { return caps_; } + + // Deprecated, please use the form with chat_template_inputs and chat_template_options + std::string apply( + const nlohmann::ordered_json & messages, + const nlohmann::ordered_json & tools, + bool add_generation_prompt, + const nlohmann::ordered_json & extra_context = nlohmann::ordered_json(), + bool apply_polyfills = true) + { + fprintf(stderr, "[%s] Deprecated!\n", __func__); + chat_template_inputs inputs; + inputs.messages = messages; + inputs.tools = tools; + inputs.add_generation_prompt = add_generation_prompt; + inputs.extra_context = extra_context; + inputs.now = std::chrono::system_clock::now(); + + chat_template_options opts; + opts.apply_polyfills = apply_polyfills; + + return apply(inputs, opts); + } + + std::string apply( + const chat_template_inputs & inputs, + const chat_template_options & opts = chat_template_options()) const + { + json actual_messages; + + auto has_tools = inputs.tools.is_array() && !inputs.tools.empty(); + auto has_tool_calls = false; + auto has_tool_responses = false; + auto has_string_content = false; + for (const auto & message : inputs.messages) { + if (message.contains("tool_calls") && !message["tool_calls"].is_null()) { + has_tool_calls = true; + } + if (message.contains("role") && message["role"] == "tool") { + has_tool_responses = true; + } + if (message.contains("content") && message["content"].is_string()) { + has_string_content = true; + } + } + + auto polyfill_system_role = opts.polyfill_system_role && !caps_.supports_system_role; + auto polyfill_tools = opts.polyfill_tools && has_tools && !caps_.supports_tools; + auto polyfill_tool_call_example = polyfill_tools && opts.polyfill_tool_call_examples; + auto polyfill_tool_calls = opts.polyfill_tool_calls && has_tool_calls && !caps_.supports_tool_calls; + auto polyfill_tool_responses = opts.polyfill_tool_responses && has_tool_responses && !caps_.supports_tool_responses; + auto polyfill_object_arguments = opts.polyfill_object_arguments && has_tool_calls && caps_.requires_object_arguments; + auto polyfill_typed_content = opts.polyfill_typed_content && has_string_content && caps_.requires_typed_content; + + auto needs_polyfills = opts.apply_polyfills && (false + || polyfill_system_role + || polyfill_tools + || polyfill_tool_calls + || polyfill_tool_responses + || polyfill_object_arguments + || polyfill_typed_content + ); + + if (needs_polyfills) { + actual_messages = json::array(); + + auto add_message = [&](const json & msg) { + if (polyfill_typed_content && msg.contains("content") && !msg.at("content").is_null() && msg.at("content").is_string()) { + actual_messages.push_back({ + {"role", msg.at("role")}, + {"content", {{ + {"type", "text"}, + {"text", msg.at("content")}, + }}}, + }); + } else { + actual_messages.push_back(msg); + } + }; + + std::string pending_system; + auto flush_sys = [&]() { + if (!pending_system.empty()) { + add_message({ + {"role", "user"}, + {"content", pending_system}, + }); + pending_system.clear(); + } + }; + + json adjusted_messages; + if (polyfill_tools) { + adjusted_messages = add_system(inputs.messages, + "You can call any of the following tools to satisfy the user's requests: " + minja::Value(inputs.tools).dump(2, /* to_json= */ true) + + (!polyfill_tool_call_example || tool_call_example_.empty() ? "" : "\n\nExample tool call syntax:\n\n" + tool_call_example_ + "\n\n")); + } else { + adjusted_messages = inputs.messages; + } + + for (const auto & message_ : adjusted_messages) { + auto message = message_; + if (!message.contains("role") || (!message.contains("content") && !message.contains("tool_calls"))) { + throw std::runtime_error("message must have 'role' and one of 'content' or 'tool_calls' fields: " + message.dump()); + } + std::string role = message.at("role"); + + if (message.contains("tool_calls")) { + if (polyfill_object_arguments || polyfill_tool_calls) { + for (auto & tool_call : message.at("tool_calls")) { + if (tool_call["type"] == "function") { + auto & function = tool_call.at("function"); + auto & arguments = function.at("arguments"); + if (arguments.is_string()) { + try { + arguments = json::parse(arguments.get()); + } catch (const std::exception & ecvt) { + fprintf(stderr, "Failed to parse arguments: %s\n", ecvt.what()); + } + } + } + } + } + if (polyfill_tool_calls) { + auto tool_calls = json::array(); + for (const auto & tool_call : message.at("tool_calls")) { + if (tool_call.at("type") != "function") { + continue; + } + const auto & function = tool_call.at("function"); + auto tc = json { + {"name", function.at("name")}, + {"arguments", function.at("arguments")}, + }; + if (tool_call.contains("id")) { + tc["id"] = tool_call["id"]; + } + tool_calls.push_back(tc); + } + auto obj = json { + {"tool_calls", tool_calls}, + }; + if (message.contains("content")) { + auto content = message.at("content"); + if (!content.is_null() && !content.empty()) { + obj["content"] = content; + } + } + message["content"] = obj.dump(2); + message.erase("tool_calls"); + } + } + if (polyfill_tool_responses && role == "tool") { + message["role"] = "user"; + auto obj = json { + {"tool_response", json::object()}, + }; + if (message.contains("name")) { + obj["tool_response"]["tool"] = message.at("name"); + } + obj["tool_response"]["content"] = message.at("content"); + if (message.contains("tool_call_id")) { + obj["tool_response"]["tool_call_id"] = message.at("tool_call_id"); + } + message["content"] = obj.dump(2); + message.erase("name"); + } + + if (!message["content"].is_null() && polyfill_system_role) { + std::string content = message.at("content"); + if (role == "system") { + if (!pending_system.empty()) pending_system += "\n"; + pending_system += content; + continue; + } else { + if (role == "user") { + if (!pending_system.empty()) { + message["content"] = pending_system + (content.empty() ? "" : "\n" + content); + pending_system.clear(); + } + } else { + flush_sys(); + } + } + } + add_message(message); + } + flush_sys(); + } else { + actual_messages = inputs.messages; + } + + auto context = minja::Context::make(json({ + {"messages", actual_messages}, + {"add_generation_prompt", inputs.add_generation_prompt}, + })); + context->set("bos_token", opts.use_bos_token ? bos_token_ : ""); + context->set("eos_token", opts.use_eos_token ? eos_token_ : ""); + if (opts.define_strftime_now) { + auto now = inputs.now; + context->set("strftime_now", Value::callable([now](const std::shared_ptr &, minja::ArgumentsValue & args) { + args.expectArgs("strftime_now", {1, 1}, {0, 0}); + auto format = args.args[0].get(); + + auto time = std::chrono::system_clock::to_time_t(now); + auto local_time = *std::localtime(&time); + std::ostringstream ss; + ss << std::put_time(&local_time, format.c_str()); + return ss.str(); + })); + } + if (!inputs.tools.is_null()) { + context->set("tools", minja::Value(inputs.tools)); + } + if (!inputs.extra_context.is_null()) { + for (auto & kv : inputs.extra_context.items()) { + context->set(kv.key(), minja::Value(kv.value())); + } + } + + auto ret = template_root_->render(context); + // fprintf(stderr, "actual_messages: %s\n", actual_messages.dump(2).c_str()); + // fprintf(stderr, "apply: %s\n\n", ret.c_str()); + return ret; + } + + static nlohmann::ordered_json add_system(const nlohmann::ordered_json & messages, const std::string & system_prompt) { + json messages_with_system = messages; + + if (!messages_with_system.empty() && messages_with_system[0].at("role") == "system") { + std::string existing_system = messages_with_system.at(0).at("content"); + messages_with_system[0] = json { + {"role", "system"}, + {"content", existing_system + "\n\n" + system_prompt}, + }; + } else { + messages_with_system.insert(messages_with_system.begin(), json { + {"role", "system"}, + {"content", system_prompt}, + }); + } + return messages_with_system; + } +}; + +} // namespace minja diff --git a/common/minja.hpp b/common/minja/minja.hpp similarity index 98% rename from common/minja.hpp rename to common/minja/minja.hpp index 3176280e4..a1c380f64 100644 --- a/common/minja.hpp +++ b/common/minja/minja.hpp @@ -8,14 +8,27 @@ // SPDX-License-Identifier: MIT #pragma once +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include #include -#include +#include #include +#include +#include +#include #include +#include +#include + #include using json = nlohmann::ordered_json; @@ -1233,7 +1246,7 @@ class SubscriptExpr : public Expression { } return result; - } else if (target_value.is_array()) { + } else if (target_value.is_array()) { auto result = Value::array(); for (int64_t i = start; step > 0 ? i < end : i > end; i += step) { result.push_back(target_value.at(i)); @@ -1278,6 +1291,12 @@ class UnaryOpExpr : public Expression { } }; +static bool in(const Value & value, const Value & container) { + return (((container.is_array() || container.is_object()) && container.contains(value)) || + (value.is_string() && container.is_string() && + container.to_str().find(value.to_str()) != std::string::npos)); +} + class BinaryOpExpr : public Expression { public: enum class Op { StrConcat, Add, Sub, Mul, MulMul, Div, DivDiv, Mod, Eq, Ne, Lt, Gt, Le, Ge, And, Or, In, NotIn, Is, IsNot }; @@ -1342,13 +1361,8 @@ class BinaryOpExpr : public Expression { case Op::Gt: return l > r; case Op::Le: return l <= r; case Op::Ge: return l >= r; - case Op::In: return (((r.is_array() || r.is_object()) && r.contains(l)) || - (l.is_string() && r.is_string() && - r.to_str().find(l.to_str()) != std::string::npos)); - case Op::NotIn: - return !(((r.is_array() || r.is_object()) && r.contains(l)) || - (l.is_string() && r.is_string() && - r.to_str().find(l.to_str()) != std::string::npos)); + case Op::In: return in(l, r); + case Op::NotIn: return !in(l, r); default: break; } throw std::runtime_error("Unknown binary operator"); @@ -1487,6 +1501,13 @@ class MethodCallExpr : public Expression { } else if (method->get_name() == "pop") { vargs.expectArgs("pop method", {1, 1}, {0, 0}); return obj.pop(vargs.args[0]); + } else if (method->get_name() == "keys") { + vargs.expectArgs("keys method", {0, 0}, {0, 0}); + auto result = Value::array(); + for (const auto& key : obj.keys()) { + result.push_back(Value(key)); + } + return result; } else if (method->get_name() == "get") { vargs.expectArgs("get method", {1, 2}, {0, 0}); auto key = vargs.args[0]; @@ -1528,6 +1549,16 @@ class MethodCallExpr : public Expression { } else if (method->get_name() == "capitalize") { vargs.expectArgs("capitalize method", {0, 0}, {0, 0}); return Value(capitalize(str)); + } else if (method->get_name() == "upper") { + vargs.expectArgs("upper method", {0, 0}, {0, 0}); + auto result = str; + std::transform(result.begin(), result.end(), result.begin(), ::toupper); + return Value(result); + } else if (method->get_name() == "lower") { + vargs.expectArgs("lower method", {0, 0}, {0, 0}); + auto result = str; + std::transform(result.begin(), result.end(), result.begin(), ::tolower); + return Value(result); } else if (method->get_name() == "endswith") { vargs.expectArgs("endswith method", {1, 1}, {0, 0}); auto suffix = vargs.args[0].get(); @@ -1544,20 +1575,6 @@ class MethodCallExpr : public Expression { else res[i] = std::tolower(res[i]); } return res; - - } else if (method->get_name() == "replace") { - vargs.expectArgs("replace method", {2, 3}, {0, 0}); - auto before = vargs.args[0].get(); - auto after = vargs.args[1].get(); - auto count = vargs.args.size() == 3 ? vargs.args[2].get() - : str.length(); - size_t start_pos = 0; - while ((start_pos = str.find(before, start_pos)) != std::string::npos && - count-- > 0) { - str.replace(start_pos, before.length(), after); - start_pos += after.length(); - } - return str; } } throw std::runtime_error("Unknown method: " + method->get_name()); @@ -2117,38 +2134,9 @@ class Parser { std::shared_ptr start, end, step; bool has_first_colon = false, has_second_colon = false; - - - - - - - - - - - - - - - - - - - - - - - - - - if (!peekSymbols({ ":" })) { start = parseExpression(); } - - - if (!consumeToken(":").empty()) { has_first_colon = true; @@ -2162,8 +2150,8 @@ class Parser { } } } - - if ((has_first_colon || has_second_colon)) { + + if ((has_first_colon || has_second_colon) && (start || end || step)) { index = std::make_shared(slice_loc, std::move(start), std::move(end), std::move(step)); } else { index = std::move(start); @@ -2663,17 +2651,13 @@ inline std::shared_ptr Context::builtins() { auto items = Value::array(); if (args.contains("object")) { auto & obj = args.at("object"); - if (obj.is_string()) { - auto json_obj = json::parse(obj.get()); - for (const auto & kv : json_obj.items()) { - items.push_back(Value::array({kv.key(), kv.value()})); + if (!obj.is_object()) { + throw std::runtime_error("Can only get item pairs from a mapping"); } - } else if (!obj.is_null()) { for (auto & key : obj.keys()) { items.push_back(Value::array({key, obj.at(key)})); } } - } return items; })); globals.set("last", simple_function("last", { "items" }, [](const std::shared_ptr &, Value & args) { @@ -2686,14 +2670,6 @@ inline std::shared_ptr Context::builtins() { auto & text = args.at("text"); return text.is_null() ? text : Value(strip(text.get())); })); - - - - - - - - auto char_transform_function = [](const std::string & name, const std::function & fn) { return simple_function(name, { "text" }, [=](const std::shared_ptr &, Value & args) { auto text = args.at("text"); @@ -2807,6 +2783,9 @@ inline std::shared_ptr Context::builtins() { if (!items.is_array()) throw std::runtime_error("object is not iterable"); return items; })); + globals.set("in", simple_function("in", { "item", "items" }, [](const std::shared_ptr &, Value & args) -> Value { + return in(args.at("item"), args.at("items")); + })); globals.set("unique", simple_function("unique", { "items" }, [](const std::shared_ptr &, Value & args) -> Value { auto & items = args.at("items"); if (!items.is_array()) throw std::runtime_error("object is not iterable"); @@ -2846,16 +2825,10 @@ inline std::shared_ptr Context::builtins() { if (filter_fn.is_null()) { throw std::runtime_error("Undefined filter: " + args.args[1].dump()); } - auto filter_args = Value::array(); for (size_t i = 2, n = args.args.size(); i < n; i++) { - - filter_args.push_back(args.args[i]); - - - } auto filter = make_filter(filter_fn, filter_args); @@ -2942,8 +2915,6 @@ inline std::shared_ptr Context::builtins() { } test_args.kwargs = args.kwargs; } - - auto res = Value::array(); for (size_t i = 0, n = items.size(); i < n; i++) { @@ -2957,10 +2928,7 @@ inline std::shared_ptr Context::builtins() { } else { res.push_back(attr); } - - } - return res; }); }; @@ -2978,7 +2946,6 @@ inline std::shared_ptr Context::builtins() { auto v = arg.get(); startEndStep[i] = v; param_set[i] = true; - } } for (auto & [name, value] : args.kwargs) { diff --git a/common/regex-partial.cpp b/common/regex-partial.cpp index 0246bb23e..4bff6b663 100644 --- a/common/regex-partial.cpp +++ b/common/regex-partial.cpp @@ -118,7 +118,7 @@ std::string regex_to_reversed_partial_regex(const std::string & pattern) { if (it == end) { throw std::runtime_error("Unmatched '{' in pattern"); } - auto parts = string_split(std::string(start, it), ','); + auto parts = string_split(std::string(start, it), ","); ++it; if (parts.size() > 2) { throw std::runtime_error("Invalid repetition range in pattern"); diff --git a/common/regex-partial.h b/common/regex-partial.h index 4a971f68e..634cb4022 100644 --- a/common/regex-partial.h +++ b/common/regex-partial.h @@ -9,8 +9,23 @@ enum common_regex_match_type { COMMON_REGEX_MATCH_TYPE_FULL, }; -// Include full definition of common_string_range -#include "chat.h" +struct common_string_range { + size_t begin; + size_t end; + common_string_range(size_t begin, size_t end) : begin(begin), end(end) { + if (begin > end) { + throw std::runtime_error("Invalid range"); + } + } + // prevent default ctor + common_string_range() = delete; + bool empty() const { + return begin == end; + } + bool operator==(const common_string_range & other) const { + return begin == other.begin && end == other.end; + } +}; struct common_regex_match { common_regex_match_type type = COMMON_REGEX_MATCH_TYPE_NONE; diff --git a/common/sampling.cpp b/common/sampling.cpp index 526a47ebc..6f7b04acf 100644 --- a/common/sampling.cpp +++ b/common/sampling.cpp @@ -1,7 +1,10 @@ #define LLAMA_API_INTERNAL #include "sampling.h" #include "llama-vocab.h" +#include "common.h" #include +#include "json.hpp" +using json = nlohmann::ordered_json; struct llama_sampling_context * llama_sampling_init(const struct llama_vocab* vocab, const struct llama_sampling_params & params) { struct llama_sampling_context * result = new llama_sampling_context(); @@ -9,10 +12,68 @@ struct llama_sampling_context * llama_sampling_init(const struct llama_vocab* vo result->params = params; result->grammar = nullptr; + + struct llama_grammar* grmr; + if (params.grammar.compare(0, 11, "%llguidance") == 0) { +#ifdef LLAMA_USE_LLGUIDANCE + grmr = llama_sampler_init_llg(vocab, "lark", params.grammar.c_str()); +#else + GGML_ABORT("llguidance (cmake -DLLAMA_LLGUIDANCE=ON) is not enabled"); +#endif // LLAMA_USE_LLGUIDANCE + } + else { + + std::vector trigger_patterns; + std::vector patterns_anywhere; + std::vector trigger_tokens; + for (const auto& trigger : params.grammar_triggers) { + switch (trigger.type) { + case COMMON_GRAMMAR_TRIGGER_TYPE_WORD: + { + const auto& word = trigger.value; + patterns_anywhere.push_back(regex_escape(word)); + break; + } + case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN: + { + patterns_anywhere.push_back(trigger.value); + break; + } + case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL: + { + trigger_patterns.push_back(trigger.value); + break; + } + case COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN: + { + const auto token = trigger.token; + trigger_tokens.push_back(token); + break; + } + default: + GGML_ASSERT(false && "unknown trigger type"); + } + } + + if (!patterns_anywhere.empty()) { + trigger_patterns.push_back("^[\\s\\S]*?(" + string_join(patterns_anywhere, "|") + ")[\\s\\S]*"); + } + + std::vector trigger_patterns_c; + trigger_patterns_c.reserve(trigger_patterns.size()); + for (const auto& regex : trigger_patterns) { + trigger_patterns_c.push_back(regex.c_str()); + } + grmr = params.grammar_lazy + ? llama_sampler_init_grammar_lazy_patterns(vocab, params.grammar.c_str(), "root", + trigger_patterns_c.data(), trigger_patterns_c.size(), + trigger_tokens.data(), trigger_tokens.size()) + : llama_sampler_init_grammar(vocab, params.grammar.c_str(), "root"); + // if there is a grammar, parse it if (!params.grammar.empty()) { result->parsed_grammar = grammar_parser::parse(params.grammar.c_str()); - + if (result->parsed_grammar.success) { // will be empty (default) if there are parse errors if (result->parsed_grammar.rules.empty()) { fprintf(stderr, "%s: failed to parse grammar\n", __func__); @@ -26,21 +87,15 @@ struct llama_sampling_context * llama_sampling_init(const struct llama_vocab* vo delete result; return nullptr; } - - std::vector grammar_rules(result->parsed_grammar.c_rules()); - - struct llama_grammar * grammar = llama_grammar_init( - grammar_rules.data(), - grammar_rules.size(), result->parsed_grammar.symbol_ids.at("root")); - if (grammar == nullptr) { + if (grmr == nullptr) { throw std::runtime_error("Failed to initialize llama_grammar"); } - result->grammar = grammar; } + } result->prev.resize(params.n_prev); - result->n_valid = 0; - + } + result->grammar = grmr; // init DRY for (const auto& cnstr : params.samplers_sequence) { @@ -75,27 +130,71 @@ void llama_sampling_free(struct llama_sampling_context * ctx) { delete ctx; } -void llama_sampling_reset(llama_sampling_context * ctx) { +void llama_sampling_reset(const struct llama_vocab* vocab, llama_sampling_context * ctx) { + if (ctx->grammar != NULL) { llama_grammar_free(ctx->grammar); ctx->grammar = NULL; } + struct llama_grammar* grmr; + auto params = ctx->params; + if (params.grammar.compare(0, 11, "%llguidance") == 0) { +#ifdef LLAMA_USE_LLGUIDANCE + grmr = llama_sampler_init_llg(vocab, "lark", params.grammar.c_str()); +#else + GGML_ABORT("llguidance (cmake -DLLAMA_LLGUIDANCE=ON) is not enabled"); +#endif // LLAMA_USE_LLGUIDANCE + } + else { + std::vector trigger_patterns; + std::vector patterns_anywhere; + std::vector trigger_tokens; + for (const auto& trigger : params.grammar_triggers) { + switch (trigger.type) { + case COMMON_GRAMMAR_TRIGGER_TYPE_WORD: + { + const auto& word = trigger.value; + patterns_anywhere.push_back(regex_escape(word)); + break; + } + case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN: + { + patterns_anywhere.push_back(trigger.value); + break; + } + case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL: + { + trigger_patterns.push_back(trigger.value); + break; + } + case COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN: + { + const auto token = trigger.token; + trigger_tokens.push_back(token); + break; + } + default: + GGML_ASSERT(false && "unknown trigger type"); + } + } + if (!patterns_anywhere.empty()) { + trigger_patterns.push_back("^[\\s\\S]*?(" + string_join(patterns_anywhere, "|") + ")[\\s\\S]*"); + } - if (!ctx->parsed_grammar.rules.empty()) { - std::vector grammar_rules(ctx->parsed_grammar.c_rules()); + std::vector trigger_patterns_c; + trigger_patterns_c.reserve(trigger_patterns.size()); + for (const auto& regex : trigger_patterns) { + trigger_patterns_c.push_back(regex.c_str()); + } - struct llama_grammar * grammar = llama_grammar_init( - grammar_rules.data(), - grammar_rules.size(), ctx->parsed_grammar.symbol_ids.at("root")); - if (grammar == nullptr) { - throw std::runtime_error("Failed to initialize llama_grammar"); + grmr = params.grammar_lazy + ? llama_sampler_init_grammar_lazy_patterns(vocab, params.grammar.c_str(), "root", + trigger_patterns_c.data(), trigger_patterns_c.size(), + trigger_tokens.data(), trigger_tokens.size()) + : llama_sampler_init_grammar(vocab, params.grammar.c_str(), "root"); } - ctx->grammar = grammar; - } - std::fill(ctx->prev.begin(), ctx->prev.end(), 0); - ctx->cur.clear(); - ctx->n_valid = 0; + ctx->grammar = grmr; llama_sampler_dry_reset(ctx->smpl); } @@ -498,7 +597,10 @@ void llama_sampling_accept( struct llama_context * ctx_main, llama_token id, bool apply_grammar) { + if (ctx_sampling->prev.size() > 0) { ctx_sampling->prev.erase(ctx_sampling->prev.begin()); + + } ctx_sampling->prev.push_back(id); if (ctx_sampling->grammar != NULL && apply_grammar) { @@ -552,3 +654,29 @@ std::vector llama_sampling_sample_and_accept_n(struct llama_samplin return result; } + + + + +template <> +json common_grammar_trigger::to_json() const { + json out{ + {"type", (int)type}, + {"value", value}, + }; + if (type == COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN) { + out["token"] = (int)token; + } + return out; +} + +template <> +common_grammar_trigger common_grammar_trigger::from_json(const json& in) { + common_grammar_trigger out; + out.type = (common_grammar_trigger_type)in.at("type").get(); + out.value = in.at("value").get(); + if (out.type == COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN) { + out.token = (llama_token)in.at("token").get(); + } + return out; +} diff --git a/common/sampling.h b/common/sampling.h index 27401145c..544d98eb3 100644 --- a/common/sampling.h +++ b/common/sampling.h @@ -1,9 +1,8 @@ #pragma once #include "llama.h" - #include "grammar-parser.h" - +#include #include #include #include @@ -22,6 +21,23 @@ enum class llama_sampler_type : char { TEMPERATURE = 't' }; +enum common_grammar_trigger_type { + COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN, + COMMON_GRAMMAR_TRIGGER_TYPE_WORD, + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, + COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL, +}; + +struct common_grammar_trigger { + common_grammar_trigger_type type; + std::string value; + llama_token token = LLAMA_TOKEN_NULL; + + // T can only be nlohmann::ordered_json + template T to_json() const; + template static common_grammar_trigger from_json(const T& in); +}; + // sampling parameters typedef struct llama_sampling_params { int32_t n_prev = 64; // number of previous tokens to remember @@ -67,8 +83,11 @@ typedef struct llama_sampling_params { llama_sampler_type::TEMPERATURE }; - std::string grammar; // optional BNF-like grammar to constrain sampling + std::string grammar; // optional BNF-like grammar to constrain sampling + bool grammar_lazy = false; + std::vector grammar_triggers; // optional triggers (for lazy grammars) + std::set preserved_tokens; // Classifier-Free Guidance // https://arxiv.org/abs/2306.17806 std::string cfg_negative_prompt; // string to help guidance @@ -106,7 +125,7 @@ struct llama_sampling_context { std::mt19937 rng; }; -#include "common.h" + // Create a new sampling context instance. struct llama_sampling_context * llama_sampling_init(const struct llama_vocab* vocab, const struct llama_sampling_params & params); @@ -116,7 +135,7 @@ void llama_sampling_free(struct llama_sampling_context * ctx); // Reset the sampler context // - clear prev tokens // - reset grammar -void llama_sampling_reset(llama_sampling_context * ctx); +void llama_sampling_reset(const struct llama_vocab* vocab, llama_sampling_context * ctx); // Set the sampler seed void llama_sampling_set_rng_seed(struct llama_sampling_context * ctx, uint32_t seed); @@ -186,3 +205,6 @@ llama_token_data_array * llama_sampling_get_candidates(struct llama_sampling_con std::vector llama_sampling_sample_and_accept_n(struct llama_sampling_context * gsmpl, struct llama_context * ctx, const std::vector & draft); std::vector llama_sampling_sample_and_accept_n(struct llama_sampling_context * gsmpl, struct llama_context * ctx, const std::vector & idxs, const std::vector & draft); + +llama_grammar* llama_sampler_init_llg(const llama_vocab* vocab, + const char* grammar_kind, const char* grammar_data); diff --git a/common/speculative.cpp b/common/speculative.cpp index d42e81dff..5b4f8323a 100644 --- a/common/speculative.cpp +++ b/common/speculative.cpp @@ -3,7 +3,7 @@ #include "common.h" #include "sampling.h" #include "llama-impl.h" - +#include "llama-vocab.h" #include #include #include @@ -302,7 +302,7 @@ std::vector llama_speculative_gen_draft( llama_decode(ctx_dft, batch); - llama_sampling_reset(smpl); + llama_sampling_reset(llama_get_vocab(ctx_dft), smpl); // sample n_draft tokens from the draft model for (int i = 0; i < params.n_draft; ++i) { diff --git a/docs/function-calling.md b/docs/function-calling.md new file mode 100644 index 000000000..37eacaf31 --- /dev/null +++ b/docs/function-calling.md @@ -0,0 +1,422 @@ +# Function Calling + +[chat.h](../common/chat.h) (https://github.com/ggml-org/llama.cpp/pull/9639) adds support for [OpenAI-style function calling](https://platform.openai.com/docs/guides/function-calling) and is used in: +- `llama-server` when started w/ `--jinja` flag + +## Universal support w/ Native & Generic handlers + +Function calling is supported for all models (see https://github.com/ggml-org/llama.cpp/pull/9639): + +- Native tool call formats supported: + - Llama 3.1 / 3.3 (including builtin tools support - tool names for `wolfram_alpha`, `web_search` / `brave_search`, `code_interpreter`), Llama 3.2 + - Functionary v3.1 / v3.2 + - Hermes 2/3, Qwen 2.5 + - Qwen 2.5 Coder + - Mistral Nemo + - Firefunction v2 + - Command R7B + - DeepSeek R1 (WIP / seems reluctant to call any tools?) + +- Generic tool call is supported when the template isn't recognized by native format handlers (you'll see `Chat format: Generic` in the logs). + - Use `--chat-template-file` to override the template when appropriate (see examples below) + - Generic support may consume more tokens and be less efficient than a model's native format. + +
+Show some common templates and which format handler they use + +| Template | Format | +|----------|--------| +| Almawave-Velvet-14B.jinja | Hermes 2 Pro | +| AtlaAI-Selene-1-Mini-Llama-3.1-8B.jinja | Llama 3.x | +| CohereForAI-aya-expanse-8b.jinja | Generic | +| CohereForAI-c4ai-command-r-plus-default.jinja | Generic | +| CohereForAI-c4ai-command-r-plus-rag.jinja | Generic | +| CohereForAI-c4ai-command-r-plus-tool_use.jinja | Generic | +| CohereForAI-c4ai-command-r7b-12-2024-default.jinja | Command R7B (extract reasoning) | +| CohereForAI-c4ai-command-r7b-12-2024-rag.jinja | Command R7B (extract reasoning) | +| CohereForAI-c4ai-command-r7b-12-2024-tool_use.jinja | Command R7B (extract reasoning) | +| CohereForAI-c4ai-command-r7b-12-2024.jinja | Generic | +| DavieLion-Llama-3.2-1B-SPIN-iter3.jinja | Generic | +| Delta-Vector-Rei-12B.jinja | Mistral Nemo | +| EpistemeAI-Mistral-Nemo-Instruct-12B-Philosophy-Math.jinja | Mistral Nemo | +| FlofloB-83k_continued_pretraining_Qwen2.5-0.5B-Instruct_Unsloth_merged_16bit.jinja | Hermes 2 Pro | +| FlofloB-test_continued_pretraining_Phi-3-mini-4k-instruct_Unsloth_merged_16bit.jinja | Generic | +| HelpingAI-HAI-SER.jinja | Generic | +| HuggingFaceTB-SmolLM2-1.7B-Instruct.jinja | Generic | +| HuggingFaceTB-SmolLM2-135M-Instruct.jinja | Generic | +| HuggingFaceTB-SmolLM2-360M-Instruct.jinja | Generic | +| INSAIT-Institute-BgGPT-Gemma-2-27B-IT-v1.0.jinja | Generic | +| Ihor-Text2Graph-R1-Qwen2.5-0.5b.jinja | Hermes 2 Pro | +| Infinigence-Megrez-3B-Instruct.jinja | Generic | +| Josephgflowers-TinyLlama_v1.1_math_code-world-test-1.jinja | Generic | +| LGAI-EXAONE-EXAONE-3.5-2.4B-Instruct.jinja | Generic | +| LGAI-EXAONE-EXAONE-3.5-7.8B-Instruct.jinja | Generic | +| LatitudeGames-Wayfarer-12B.jinja | Generic | +| Magpie-Align-Llama-3-8B-Magpie-Align-v0.1.jinja | Generic | +| Magpie-Align-Llama-3.1-8B-Magpie-Align-v0.1.jinja | Generic | +| MaziyarPanahi-calme-3.2-instruct-78b.jinja | Generic | +| MiniMaxAI-MiniMax-Text-01.jinja | Generic | +| MiniMaxAI-MiniMax-VL-01.jinja | Generic | +| NaniDAO-deepseek-r1-qwen-2.5-32B-ablated.jinja | DeepSeek R1 (extract reasoning) | +| NexaAIDev-Octopus-v2.jinja | Generic | +| NousResearch-Hermes-2-Pro-Llama-3-8B-default.jinja | Generic | +| NousResearch-Hermes-2-Pro-Llama-3-8B-tool_use.jinja | Hermes 2 Pro | +| NousResearch-Hermes-2-Pro-Mistral-7B-default.jinja | Generic | +| NousResearch-Hermes-2-Pro-Mistral-7B-tool_use.jinja | Hermes 2 Pro | +| NousResearch-Hermes-3-Llama-3.1-70B-default.jinja | Generic | +| NousResearch-Hermes-3-Llama-3.1-70B-tool_use.jinja | Hermes 2 Pro | +| NovaSky-AI-Sky-T1-32B-Flash.jinja | Hermes 2 Pro | +| NovaSky-AI-Sky-T1-32B-Preview.jinja | Hermes 2 Pro | +| OnlyCheeini-greesychat-turbo.jinja | Generic | +| Orenguteng-Llama-3.1-8B-Lexi-Uncensored-V2.jinja | Llama 3.x | +| OrionStarAI-Orion-14B-Chat.jinja | Generic | +| PowerInfer-SmallThinker-3B-Preview.jinja | Generic | +| PrimeIntellect-INTELLECT-1-Instruct.jinja | Generic | +| Qwen-QVQ-72B-Preview.jinja | Generic | +| Qwen-QwQ-32B-Preview.jinja | Hermes 2 Pro | +| Qwen-Qwen1.5-7B-Chat.jinja | Generic | +| Qwen-Qwen2-7B-Instruct.jinja | Generic | +| Qwen-Qwen2-VL-72B-Instruct.jinja | Generic | +| Qwen-Qwen2-VL-7B-Instruct.jinja | Generic | +| Qwen-Qwen2.5-0.5B.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-1.5B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-14B-Instruct-1M.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-14B.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-32B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-32B.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-3B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-72B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-7B-Instruct-1M.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-7B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-7B.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-Coder-32B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-Coder-7B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-Math-1.5B.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-Math-7B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-VL-3B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-VL-72B-Instruct.jinja | Hermes 2 Pro | +| Qwen-Qwen2.5-VL-7B-Instruct.jinja | Hermes 2 Pro | +| RWKV-Red-Team-ARWKV-7B-Preview-0.1.jinja | Hermes 2 Pro | +| SakanaAI-TinySwallow-1.5B-Instruct.jinja | Hermes 2 Pro | +| SakanaAI-TinySwallow-1.5B.jinja | Hermes 2 Pro | +| Sao10K-70B-L3.3-Cirrus-x1.jinja | Llama 3.x | +| SentientAGI-Dobby-Mini-Leashed-Llama-3.1-8B.jinja | Llama 3.x | +| SentientAGI-Dobby-Mini-Unhinged-Llama-3.1-8B.jinja | Llama 3.x | +| Steelskull-L3.3-Damascus-R1.jinja | Llama 3.x | +| Steelskull-L3.3-MS-Nevoria-70b.jinja | Llama 3.x | +| Steelskull-L3.3-Nevoria-R1-70b.jinja | Llama 3.x | +| THUDM-glm-4-9b-chat.jinja | Generic | +| THUDM-glm-edge-1.5b-chat.jinja | Generic | +| Tarek07-Progenitor-V1.1-LLaMa-70B.jinja | Llama 3.x | +| TheBloke-FusionNet_34Bx2_MoE-AWQ.jinja | Generic | +| TinyLlama-TinyLlama-1.1B-Chat-v1.0.jinja | Generic | +| UCLA-AGI-Mistral7B-PairRM-SPPO-Iter3.jinja | Generic | +| ValiantLabs-Llama3.1-8B-Enigma.jinja | Llama 3.x | +| abacusai-Fewshot-Metamath-OrcaVicuna-Mistral.jinja | Generic | +| ai21labs-AI21-Jamba-1.5-Large.jinja | Generic | +| allenai-Llama-3.1-Tulu-3-405B-SFT.jinja | Generic | +| allenai-Llama-3.1-Tulu-3-405B.jinja | Generic | +| allenai-Llama-3.1-Tulu-3-8B.jinja | Generic | +| arcee-ai-Virtuoso-Lite.jinja | Hermes 2 Pro | +| arcee-ai-Virtuoso-Medium-v2.jinja | Hermes 2 Pro | +| arcee-ai-Virtuoso-Small-v2.jinja | Hermes 2 Pro | +| avemio-GRAG-NEMO-12B-ORPO-HESSIAN-AI.jinja | Generic | +| bespokelabs-Bespoke-Stratos-7B.jinja | Hermes 2 Pro | +| bfuzzy1-acheron-m1a-llama.jinja | Generic | +| bofenghuang-vigogne-2-70b-chat.jinja | Generic | +| bytedance-research-UI-TARS-72B-DPO.jinja | Generic | +| bytedance-research-UI-TARS-7B-DPO.jinja | Generic | +| bytedance-research-UI-TARS-7B-SFT.jinja | Generic | +| carsenk-phi3.5_mini_exp_825_uncensored.jinja | Generic | +| cyberagent-DeepSeek-R1-Distill-Qwen-14B-Japanese.jinja | DeepSeek R1 (extract reasoning) | +| cyberagent-DeepSeek-R1-Distill-Qwen-32B-Japanese.jinja | DeepSeek R1 (extract reasoning) | +| databricks-dbrx-instruct.jinja | Generic | +| deepseek-ai-DeepSeek-Coder-V2-Instruct.jinja | Generic | +| deepseek-ai-DeepSeek-Coder-V2-Lite-Base.jinja | Generic | +| deepseek-ai-DeepSeek-Coder-V2-Lite-Instruct.jinja | Generic | +| deepseek-ai-DeepSeek-R1-Distill-Llama-70B.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-R1-Distill-Llama-8B.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-R1-Distill-Qwen-1.5B.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-R1-Distill-Qwen-14B.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-R1-Distill-Qwen-32B.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-R1-Distill-Qwen-7B.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-R1-Zero.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-R1.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-V2-Lite.jinja | Generic | +| deepseek-ai-DeepSeek-V2.5.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-DeepSeek-V3.jinja | DeepSeek R1 (extract reasoning) | +| deepseek-ai-deepseek-coder-33b-instruct.jinja | Generic | +| deepseek-ai-deepseek-coder-6.7b-instruct.jinja | Generic | +| deepseek-ai-deepseek-coder-7b-instruct-v1.5.jinja | Generic | +| deepseek-ai-deepseek-llm-67b-chat.jinja | Generic | +| deepseek-ai-deepseek-llm-7b-chat.jinja | Generic | +| dicta-il-dictalm2.0-instruct.jinja | Generic | +| ehristoforu-Falcon3-8B-Franken-Basestruct.jinja | Hermes 2 Pro | +| fireworks-ai-llama-3-firefunction-v2.jinja | FireFunction v2 | +| godlikehhd-alpaca_data_sampled_ifd_new_5200.jinja | Hermes 2 Pro | +| godlikehhd-alpaca_data_score_max_0.7_2600.jinja | Hermes 2 Pro | +| google-gemma-2-27b-it.jinja | Generic | +| google-gemma-2-2b-it.jinja | Generic | +| google-gemma-2-2b-jpn-it.jinja | Generic | +| google-gemma-7b-it.jinja | Generic | +| huihui-ai-DeepSeek-R1-Distill-Llama-70B-abliterated.jinja | DeepSeek R1 (extract reasoning) | +| huihui-ai-DeepSeek-R1-Distill-Llama-8B-abliterated.jinja | DeepSeek R1 (extract reasoning) | +| huihui-ai-DeepSeek-R1-Distill-Qwen-14B-abliterated-v2.jinja | DeepSeek R1 (extract reasoning) | +| huihui-ai-DeepSeek-R1-Distill-Qwen-32B-abliterated.jinja | DeepSeek R1 (extract reasoning) | +| huihui-ai-DeepSeek-R1-Distill-Qwen-7B-abliterated-v2.jinja | DeepSeek R1 (extract reasoning) | +| huihui-ai-Qwen2.5-14B-Instruct-1M-abliterated.jinja | Hermes 2 Pro | +| ibm-granite-granite-3.1-8b-instruct.jinja | Generic | +| indischepartij-MiniCPM-3B-OpenHermes-2.5-v2.jinja | Generic | +| inflatebot-MN-12B-Mag-Mell-R1.jinja | Generic | +| jinaai-ReaderLM-v2.jinja | Generic | +| kms7530-chemeng_qwen-math-7b_24_1_100_1_nonmath.jinja | Hermes 2 Pro | +| knifeayumu-Cydonia-v1.3-Magnum-v4-22B.jinja | Mistral Nemo | +| langgptai-qwen1.5-7b-chat-sa-v0.1.jinja | Generic | +| lightblue-DeepSeek-R1-Distill-Qwen-7B-Japanese.jinja | DeepSeek R1 (extract reasoning) | +| mattshumer-Reflection-Llama-3.1-70B.jinja | Generic | +| meetkai-functionary-medium-v3.1.jinja | Functionary v3.1 Llama 3.1 | +| meetkai-functionary-medium-v3.2.jinja | Functionary v3.2 | +| meta-llama-Llama-2-7b-chat-hf.jinja | Generic | +| meta-llama-Llama-3.1-8B-Instruct.jinja | Llama 3.x | +| meta-llama-Llama-3.2-11B-Vision-Instruct.jinja | Llama 3.x | +| meta-llama-Llama-3.2-1B-Instruct.jinja | Llama 3.x | +| meta-llama-Llama-3.2-3B-Instruct.jinja | Llama 3.x | +| meta-llama-Llama-3.3-70B-Instruct.jinja | Llama 3.x | +| meta-llama-Meta-Llama-3-8B-Instruct.jinja | Generic | +| meta-llama-Meta-Llama-3.1-8B-Instruct.jinja | Llama 3.x | +| microsoft-Phi-3-medium-4k-instruct.jinja | Generic | +| microsoft-Phi-3-mini-4k-instruct.jinja | Generic | +| microsoft-Phi-3-small-8k-instruct.jinja | Generic | +| microsoft-Phi-3.5-mini-instruct.jinja | Generic | +| microsoft-Phi-3.5-vision-instruct.jinja | Generic | +| microsoft-phi-4.jinja | Generic | +| migtissera-Tess-3-Mistral-Nemo-12B.jinja | Generic | +| ministral-Ministral-3b-instruct.jinja | Generic | +| mistralai-Codestral-22B-v0.1.jinja | Generic | +| mistralai-Mistral-7B-Instruct-v0.1.jinja | Generic | +| mistralai-Mistral-7B-Instruct-v0.2.jinja | Generic | +| mistralai-Mistral-7B-Instruct-v0.3.jinja | Mistral Nemo | +| mistralai-Mistral-Large-Instruct-2407.jinja | Mistral Nemo | +| mistralai-Mistral-Large-Instruct-2411.jinja | Generic | +| mistralai-Mistral-Nemo-Instruct-2407.jinja | Mistral Nemo | +| mistralai-Mistral-Small-24B-Instruct-2501.jinja | Generic | +| mistralai-Mixtral-8x7B-Instruct-v0.1.jinja | Generic | +| mkurman-Qwen2.5-14B-DeepSeek-R1-1M.jinja | Hermes 2 Pro | +| mlabonne-AlphaMonarch-7B.jinja | Generic | +| mlx-community-Josiefied-Qwen2.5-0.5B-Instruct-abliterated-v1-float32.jinja | Hermes 2 Pro | +| mlx-community-Qwen2.5-VL-7B-Instruct-8bit.jinja | Hermes 2 Pro | +| mobiuslabsgmbh-DeepSeek-R1-ReDistill-Qwen-1.5B-v1.1.jinja | DeepSeek R1 (extract reasoning) | +| netcat420-MFANNv0.20.jinja | Generic | +| netcat420-MFANNv0.24.jinja | Generic | +| netease-youdao-Confucius-o1-14B.jinja | Hermes 2 Pro | +| nvidia-AceMath-7B-RM.jinja | Hermes 2 Pro | +| nvidia-Eagle2-1B.jinja | Hermes 2 Pro | +| nvidia-Eagle2-9B.jinja | Hermes 2 Pro | +| nvidia-Llama-3.1-Nemotron-70B-Instruct-HF.jinja | Llama 3.x | +| onnx-community-DeepSeek-R1-Distill-Qwen-1.5B-ONNX.jinja | DeepSeek R1 (extract reasoning) | +| open-thoughts-OpenThinker-7B.jinja | Hermes 2 Pro | +| openchat-openchat-3.5-0106.jinja | Generic | +| pankajmathur-orca_mini_v6_8b.jinja | Generic | +| princeton-nlp-Mistral-7B-Base-SFT-RDPO.jinja | Generic | +| princeton-nlp-Mistral-7B-Instruct-DPO.jinja | Generic | +| princeton-nlp-Mistral-7B-Instruct-RDPO.jinja | Generic | +| prithivMLmods-Bellatrix-Tiny-1.5B-R1.jinja | Hermes 2 Pro | +| prithivMLmods-Bellatrix-Tiny-1B-R1.jinja | Llama 3.x | +| prithivMLmods-Bellatrix-Tiny-1B-v3.jinja | Generic | +| prithivMLmods-Bellatrix-Tiny-3B-R1.jinja | Llama 3.x | +| prithivMLmods-Blaze-14B-xElite.jinja | Generic | +| prithivMLmods-Calcium-Opus-14B-Elite2-R1.jinja | Hermes 2 Pro | +| prithivMLmods-Calme-Ties-78B.jinja | Generic | +| prithivMLmods-Calme-Ties2-78B.jinja | Generic | +| prithivMLmods-Calme-Ties3-78B.jinja | Generic | +| prithivMLmods-ChemQwen2-vL.jinja | Generic | +| prithivMLmods-GWQ2b.jinja | Generic | +| prithivMLmods-LatexMind-2B-Codec.jinja | Generic | +| prithivMLmods-Llama-3.2-6B-AlgoCode.jinja | Llama 3.x | +| prithivMLmods-Megatron-Opus-14B-Exp.jinja | Hermes 2 Pro | +| prithivMLmods-Megatron-Opus-14B-Stock.jinja | Hermes 2 Pro | +| prithivMLmods-Megatron-Opus-7B-Exp.jinja | Hermes 2 Pro | +| prithivMLmods-Omni-Reasoner-Merged.jinja | Hermes 2 Pro | +| prithivMLmods-Omni-Reasoner4-Merged.jinja | Hermes 2 Pro | +| prithivMLmods-Primal-Opus-14B-Optimus-v1.jinja | Hermes 2 Pro | +| prithivMLmods-QwQ-Math-IO-500M.jinja | Hermes 2 Pro | +| prithivMLmods-Qwen-7B-Distill-Reasoner.jinja | DeepSeek R1 (extract reasoning) | +| prithivMLmods-Qwen2.5-1.5B-DeepSeek-R1-Instruct.jinja | Hermes 2 Pro | +| prithivMLmods-Qwen2.5-14B-DeepSeek-R1-1M.jinja | Hermes 2 Pro | +| prithivMLmods-Qwen2.5-32B-DeepSeek-R1-Instruct.jinja | Hermes 2 Pro | +| prithivMLmods-Qwen2.5-7B-DeepSeek-R1-1M.jinja | Hermes 2 Pro | +| prithivMLmods-Triangulum-v2-10B.jinja | Hermes 2 Pro | +| qingy2024-Falcon3-2x10B-MoE-Instruct.jinja | Hermes 2 Pro | +| rubenroy-Zurich-14B-GCv2-5m.jinja | Hermes 2 Pro | +| rubenroy-Zurich-7B-GCv2-5m.jinja | Hermes 2 Pro | +| silma-ai-SILMA-Kashif-2B-Instruct-v1.0.jinja | Generic | +| simplescaling-s1-32B.jinja | Hermes 2 Pro | +| sometimesanotion-Lamarck-14B-v0.7.jinja | Hermes 2 Pro | +| sonthenguyen-zephyr-sft-bnb-4bit-DPO-mtbr-180steps.jinja | Generic | +| sthenno-tempesthenno-icy-0130.jinja | Generic | +| sumink-qwft.jinja | Hermes 2 Pro | +| teknium-OpenHermes-2.5-Mistral-7B.jinja | Generic | +| thirdeyeai-elevate360m.jinja | Generic | +| tiiuae-Falcon3-10B-Instruct.jinja | Hermes 2 Pro | +| unsloth-DeepSeek-R1-Distill-Llama-8B-unsloth-bnb-4bit.jinja | DeepSeek R1 (extract reasoning) | +| unsloth-DeepSeek-R1-Distill-Llama-8B.jinja | DeepSeek R1 (extract reasoning) | +| unsloth-DeepSeek-R1.jinja | DeepSeek R1 (extract reasoning) | +| unsloth-Mistral-Small-24B-Instruct-2501-unsloth-bnb-4bit.jinja | Generic | +| upstage-solar-pro-preview-instruct.jinja | Generic | +| whyhow-ai-PatientSeek.jinja | Generic | +| xwen-team-Xwen-72B-Chat.jinja | Hermes 2 Pro | +| xwen-team-Xwen-7B-Chat.jinja | Hermes 2 Pro | + +This table can be generated with: + +```bash +./build/bin/test-chat ../minja/build/tests/*.jinja 2>/dev/null +``` + +
+ +# Usage - need tool-aware Jinja template + +First, start a server with any model, but make sure it has a tools-enabled template: you can verify this by inspecting the `chat_template` or `chat_template_tool_use` properties in `http://localhost:8080/props`). + +Here are some models known to work (w/ chat template override when needed): + +```shell +# Native support: + +llama-server --jinja -fa -hf bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M +llama-server --jinja -fa -hf bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q6_K_L +llama-server --jinja -fa -hf bartowski/Llama-3.3-70B-Instruct-GGUF:Q4_K_M + +# Native support for DeepSeek R1 works best w/ our template override (official template is buggy, although we do work around it) + +llama-server --jinja -fa -hf bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q6_K_L \ + --chat-template-file models/templates/llama-cpp-deepseek-r1.jinja + +llama-server --jinja -fa -hf bartowski/DeepSeek-R1-Distill-Qwen-32B-GGUF:Q4_K_M \ + --chat-template-file models/templates/llama-cpp-deepseek-r1.jinja + +# Native support requires the right template for these GGUFs: + +llama-server --jinja -fa -hf bartowski/functionary-small-v3.2-GGUF:Q4_K_M + --chat-template-file models/templates/meetkai-functionary-medium-v3.2.jinja + +llama-server --jinja -fa -hf bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M \ + --chat-template-file models/templates/NousResearch-Hermes-2-Pro-Llama-3-8B-tool_use.jinja + +llama-server --jinja -fa -hf bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M \ + --chat-template-file models/templates/NousResearch-Hermes-3-Llama-3.1-8B-tool_use.jinja + +llama-server --jinja -fa -hf bartowski/firefunction-v2-GGUF -hff firefunction-v2-IQ1_M.gguf \ + --chat-template-file models/templates/fireworks-ai-llama-3-firefunction-v2.jinja + +llama-server --jinja -fa -hf bartowski/c4ai-command-r7b-12-2024-GGUF:Q6_K_L \ + --chat-template-file models/templates/CohereForAI-c4ai-command-r7b-12-2024-tool_use.jinja + +# Generic format support +llama-server --jinja -fa -hf bartowski/phi-4-GGUF:Q4_0 +llama-server --jinja -fa -hf bartowski/gemma-2-2b-it-GGUF:Q8_0 +llama-server --jinja -fa -hf bartowski/c4ai-command-r-v01-GGUF:Q2_K +``` + +To get the official template from original HuggingFace repos, you can use [scripts/get_chat_template.py](../scripts/get_chat_template.py) (see examples invocations in [models/templates/README.md](../models/templates/README.md)) + +> [!TIP] +> If there is no official `tool_use` Jinja template, you may want to set `--chat-template chatml` to use a default that works with many models (YMMV!), or write your own (e.g. we provide a custom [llama-cpp-deepseek-r1.jinja](../models/templates/llama-cpp-deepseek-r1.jinja) for DeepSeek R1 distills) + +> [!CAUTION] +> Beware of extreme KV quantizations (e.g. `-ctk q4_0`), they can substantially degrade the model's tool calling performance. + +Test in CLI (or with any library / software that can use OpenAI-compatible API backends): + +```bash +curl http://localhost:8080/v1/chat/completions -d '{ + "model": "gpt-3.5-turbo", + "tools": [ + { + "type":"function", + "function":{ + "name":"python", + "description":"Runs code in an ipython interpreter and returns the result of the execution after 60 seconds.", + "parameters":{ + "type":"object", + "properties":{ + "code":{ + "type":"string", + "description":"The code to run in the ipython interpreter." + } + }, + "required":["code"] + } + } + } + ], + "messages": [ + { + "role": "user", + "content": "Print a hello world message with python." + } + ] +}' + + +curl http://localhost:8080/v1/chat/completions -d '{ + "model": "gpt-3.5-turbo", + "messages": [ + {"role": "system", "content": "You are a chatbot that uses tools/functions. Dont overthink things."}, + {"role": "user", "content": "What is the weather in Istanbul?"} + ], + "tools": [{ + "type":"function", + "function":{ + "name":"get_current_weather", + "description":"Get the current weather in a given location", + "parameters":{ + "type":"object", + "properties":{ + "location":{ + "type":"string", + "description":"The city and country/state, e.g. `San Francisco, CA`, or `Paris, France`" + } + }, + "required":["location"] + } + } + }] +}' +``` + +
+Show output + +```json +{ +"choices": [ + { + "finish_reason": "tool", + "index": 0, + "message": { + "content": null, + "tool_calls": [ + { + "name": "python", + "arguments": "{\"code\":\" \\nprint(\\\"Hello, World!\\\")\"}" + } + ], + "role": "assistant" + } + } +], +"created": 1727287211, +"model": "gpt-3.5-turbo", +"object": "chat.completion", +"usage": { + "completion_tokens": 16, + "prompt_tokens": 44, + "total_tokens": 60 +}, +"id": "chatcmpl-Htbgh9feMmGM0LEH2hmQvwsCxq3c6Ni8" +} +``` + +
diff --git a/docs/llguidance.md b/docs/llguidance.md new file mode 100644 index 000000000..792d20704 --- /dev/null +++ b/docs/llguidance.md @@ -0,0 +1,51 @@ +# LLGuidance Support in llama.cpp + +[LLGuidance](https://github.com/guidance-ai/llguidance) is a library for constrained decoding (also called constrained sampling or structured outputs) for Large Language Models (LLMs). Initially developed as the backend for the [Guidance](https://github.com/guidance-ai/guidance) library, it can also be used independently. + +LLGuidance supports JSON Schemas and arbitrary context-free grammars (CFGs) written in a [variant](https://github.com/guidance-ai/llguidance/blob/main/docs/syntax.md) of Lark syntax. It is [very fast](https://github.com/guidance-ai/jsonschemabench/tree/main/maskbench) and has [excellent](https://github.com/guidance-ai/llguidance/blob/main/docs/json_schema.md) JSON Schema coverage but requires the Rust compiler, which complicates the llama.cpp build process. + +## Building + +To enable LLGuidance support, build llama.cpp with the `LLAMA_LLGUIDANCE` option: + +```sh +cmake -B build -DLLAMA_LLGUIDANCE=ON +make -C build -j +``` + +This requires the Rust compiler and the `cargo` tool to be [installed](https://www.rust-lang.org/tools/install). + +## Interface + +There are no new command-line arguments or modifications to `common_params`. When enabled, grammars starting with `%llguidance` are passed to LLGuidance instead of the [current](../grammars/README.md) llama.cpp grammars. Additionally, JSON Schema requests (e.g., using the `-j` argument in `llama-cli`) are also passed to LLGuidance. + +For your existing GBNF grammars, you can use [gbnf_to_lark.py script](https://github.com/guidance-ai/llguidance/blob/main/scripts/gbnf_to_lark.py) to convert them to LLGuidance Lark-like format. + +## Performance + +Computing a "token mask" (i.e., the set of allowed tokens) for a llama3 tokenizer with 128k tokens takes, on average, 50μs of single-core CPU time for the [JSON Schema Bench](https://github.com/guidance-ai/jsonschemabench). The p99 time is 0.5ms, and the p100 time is 20ms. These results are due to the lexer/parser split and several [optimizations](https://github.com/guidance-ai/llguidance/blob/main/docs/optimizations.md). + +## JSON Schema + +LLGuidance adheres closely to the JSON Schema specification. For example: + +- `additionalProperties` defaults to `true`, unlike current grammars, though you can set `"additionalProperties": false` if needed. +- any whitespace is allowed. +- The definition order in the `"properties": {}` object is maintained, regardless of whether properties are required (current grammars always puts required properties first). + +Unsupported schemas result in an error message—no keywords are silently ignored. + +## Why Not Reuse GBNF Format? + +GBNF lacks the concept of a lexer. + +Most programming languages, including JSON, use a two-step process: a lexer (built with regular expressions) converts a byte stream into lexemes, which are then processed by a CFG parser. This approach is faster because lexers are cheaper to evaluate, and there is ~10x fewer lexemes than bytes. +LLM tokens often align with lexemes, so the parser is engaged in under 0.5% of tokens, with the lexer handling the rest. + +However, the user has to provide the distinction between lexemes and CFG symbols. In [Lark](https://github.com/lark-parser/lark), lexeme names are uppercase, while CFG symbols are lowercase. +The [gbnf_to_lark.py script](https://github.com/guidance-ai/llguidance/blob/main/scripts/gbnf_to_lark.py) can often take care of this automatically. +See [LLGuidance syntax docs](https://github.com/guidance-ai/llguidance/blob/main/docs/syntax.md#terminals-vs-rules) for more details. + +## Error Handling + +Errors are currently printed to `stderr`, and generation continues. Improved error handling may be added in the future. diff --git a/examples/infill/infill.cpp b/examples/infill/infill.cpp index d3c3ad5a7..e6d15f29d 100644 --- a/examples/infill/infill.cpp +++ b/examples/infill/infill.cpp @@ -615,7 +615,7 @@ int main(int argc, char ** argv) { if (n_past > 0) { if (is_interacting) { - llama_sampling_reset(ctx_sampling); + llama_sampling_reset(llama_get_model_vocab(model), ctx_sampling); } is_interacting = false; } diff --git a/examples/json_schema_to_grammar.py b/examples/json_schema_to_grammar.py index a8779bf3b..06fd1bfe5 100755 --- a/examples/json_schema_to_grammar.py +++ b/examples/json_schema_to_grammar.py @@ -195,7 +195,7 @@ def __init__(self, content: str, deps: list | None = None): self.deps = deps or [] # Constraining spaces to prevent model "running away". -SPACE_RULE = '| " " | "\\n" [ \\t]{0,20}' +SPACE_RULE = '| " " | "\\n"{1,2} [ \\t]{0,20}' PRIMITIVE_RULES = { 'boolean' : BuiltinRule('("true" | "false") space', []), diff --git a/examples/main/main.cpp b/examples/main/main.cpp index 5bf8fca05..850f33d51 100644 --- a/examples/main/main.cpp +++ b/examples/main/main.cpp @@ -1,8 +1,8 @@ #include "common.h" - +#include "chat.h" #include "console.h" #include "llama.h" -#include "chat-template.hpp" +#include "minja/chat-template.hpp" #include #include #include @@ -119,12 +119,11 @@ static void llama_log_callback_logTee(ggml_log_level level, const char * text, v LOG_TEE("%s", text); } -static std::string chat_add_and_format(struct llama_model * model, common_chat_templates &chat_templates, std::vector & chat_msgs, std::string role, std::string content) { - llama_chat_msg new_msg{role, content}; - auto formatted = llama_chat_format_single(model, - *chat_templates.template_default, chat_msgs, new_msg, role == "user", g_params->use_jinja); +static std::string chat_add_and_format(struct llama_model * model, common_chat_templates &chat_templates, std::vector & chat_msgs, std::string role, std::string content) { + common_chat_msg new_msg{role, content}; + auto formatted = common_chat_format_single(&chat_templates, chat_msgs, new_msg, role == "user", g_params->use_jinja); chat_msgs.push_back({role, content}); - LOG("formatted: %s\n", formatted.c_str()); + fprintf(stdout, "formatted: %s\n", formatted.c_str()); return formatted; } @@ -201,7 +200,7 @@ int main(int argc, char ** argv) { llama_model * model; llama_context * ctx; llama_context * ctx_guidance = NULL; - std::vector chat_msgs; + std::vector chat_msgs; g_model = &model; g_ctx = &ctx; @@ -220,7 +219,7 @@ int main(int argc, char ** argv) { LOG_TEE("%s: error: unable to load model\n", __func__); return 1; } - auto chat_templates = llama_chat_templates_from_model(model, params.chat_template); + auto chat_templates = common_chat_templates_init(model, params.chat_template); const int n_ctx_train = llama_n_ctx_train(model); const int n_ctx = llama_n_ctx(ctx); @@ -233,7 +232,8 @@ int main(int argc, char ** argv) { // print chat template example in conversation mode if (params.conversation) { if (params.enable_chat_template) { - LOG_TEE("%s: chat template example: %s\n", __func__, llama_chat_format_example(model, *chat_templates.template_default, params.use_jinja).c_str()); + //LOG_TEE("%s: chat template example: %s\n", __func__, common_chat_format_example(model, *chat_templates.template_default, params.use_jinja).c_str()); + LOG_TEE("%s: chat template example:\n%s\n", __func__, common_chat_format_example(chat_templates.get(), params.use_jinja).c_str()); } else { LOG_TEE("%s: in-suffix/prefix is specified, chat template will be disabled\n", __func__); } @@ -287,7 +287,7 @@ int main(int argc, char ** argv) { prompt = params.system_prompt; if (!prompt.empty()) { - prompt = chat_add_and_format(model, chat_templates,chat_msgs, "system", prompt); + prompt = chat_add_and_format(model, *chat_templates,chat_msgs, "system", prompt); } } else { @@ -867,7 +867,7 @@ int main(int argc, char ** argv) { } if (params.enable_chat_template) { - chat_add_and_format(model, chat_templates, chat_msgs, "assistant", assistant_ss.str()); + chat_add_and_format(model, *chat_templates, chat_msgs, "assistant", assistant_ss.str()); } is_interacting = true; printf("\n"); @@ -932,7 +932,7 @@ int main(int argc, char ** argv) { bool format_chat = params.conversation && params.enable_chat_template; std::string user_inp = format_chat - ? chat_add_and_format(model, chat_templates, chat_msgs, "user", std::move(buffer)) + ? chat_add_and_format(model, *chat_templates, chat_msgs, "user", std::move(buffer)) : std::move(buffer); // TODO: one inconvenient of current chat template implementation is that we can't distinguish between user input and special tokens (prefix/postfix) const auto line_pfx = ::llama_tokenize(ctx, params.input_prefix, false, true); @@ -972,7 +972,8 @@ int main(int argc, char ** argv) { if (n_past > 0 || waiting_for_first_input) { if (is_interacting) { - llama_sampling_reset(ctx_sampling); + + llama_sampling_reset(llama_get_model_vocab(model), ctx_sampling); } is_interacting = false; waiting_for_first_input = false; diff --git a/examples/parallel/parallel.cpp b/examples/parallel/parallel.cpp index ee614f844..0d5e9f7eb 100644 --- a/examples/parallel/parallel.cpp +++ b/examples/parallel/parallel.cpp @@ -253,7 +253,7 @@ int main(int argc, char ** argv) { client.prompt = client.input + "\nAssistant:"; client.response = ""; - llama_sampling_reset(client.ctx_sampling); + llama_sampling_reset(llama_get_model_vocab(model), client.ctx_sampling); // do not prepend BOS because we have a system prompt! std::vector tokens_prompt; diff --git a/examples/server/README.md b/examples/server/README.md index 08fc1fa58..3a73261f3 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -12,6 +12,11 @@ Set of LLM REST APIs and a simple web front end to interact with llama.cpp. * Multimodal (wip) * Monitoring endpoints * Schema-constrained JSON response format + * Prefilling of assistant messages similar to the Claude API + * [Function calling](../../docs/function-calling.md) / tool use for ~any model + * Speculative decoding + * Easy-to-use web UI + The project is under active development, and we are [looking for feedback and contributors](https://github.com/ggerganov/llama.cpp/issues/4216). @@ -585,59 +590,76 @@ Takes a prefix and a suffix and returns the predicted completion as stream. - `total_slots` - the total number of slots for process requests (defined by `--parallel` option) - `chat_template` - the model's original Jinja2 prompt template + ### POST `/v1/chat/completions`: OpenAI-compatible Chat Completions API -Given a ChatML-formatted json description in `messages`, it returns the predicted completion. Both synchronous and streaming mode are supported, so scripted and interactive applications work fine. While no strong claims of compatibility with OpenAI API spec is being made, in our experience it suffices to support many apps. Only models with a [supported chat template](https://github.com/ggerganov/llama.cpp/wiki/Templates-supported-by-llama_chat_apply_template) can be used optimally with this endpoint. By default, the ChatML template will be used. +Given a ChatML-formatted json description in `messages`, it returns the predicted completion. Both synchronous and streaming mode are supported, so scripted and interactive applications work fine. While no strong claims of compatibility with OpenAI API spec is being made, in our experience it suffices to support many apps. Only models with a [supported chat template](https://github.com/ggml-org/llama.cpp/wiki/Templates-supported-by-llama_chat_apply_template) can be used optimally with this endpoint. By default, the ChatML template will be used. - *Options:* +If model supports multimodal, you can input the media file via `image_url` content part. We support both base64 and remote URL as input. See OAI documentation for more. - See [OpenAI Chat Completions API documentation](https://platform.openai.com/docs/api-reference/chat). While some OpenAI-specific features such as function calling aren't supported, llama.cpp `/completion`-specific features such as `mirostat` are supported. +*Options:* - The `response_format` parameter supports both plain JSON output (e.g. `{"type": "json_object"}`) and schema-constrained JSON (e.g. `{"type": "json_object", "schema": {"type": "string", "minLength": 10, "maxLength": 100}}`), similar to other OpenAI-inspired API providers. +See [OpenAI Chat Completions API documentation](https://platform.openai.com/docs/api-reference/chat). llama.cpp `/completion`-specific features such as `mirostat` are also supported. - *Examples:* +The `response_format` parameter supports both plain JSON output (e.g. `{"type": "json_object"}`) and schema-constrained JSON (e.g. `{"type": "json_object", "schema": {"type": "string", "minLength": 10, "maxLength": 100}}` or `{"type": "json_schema", "schema": {"properties": { "name": { "title": "Name", "type": "string" }, "date": { "title": "Date", "type": "string" }, "participants": { "items": {"type: "string" }, "title": "Participants", "type": "string" } } } }`), similar to other OpenAI-inspired API providers. - You can use either Python `openai` library with appropriate checkpoints: +`chat_template_kwargs`: Allows sending additional parameters to the json templating system. For example: `{"enable_thinking": false}` - ```python - import openai +`reasoning_format`: The reasoning format to be parsed. If set to `none`, it will output the raw generated text. - client = openai.OpenAI( - base_url="http://localhost:8080/v1", # "http://:port" - api_key = "sk-no-key-required" - ) +`thinking_forced_open`: Force a reasoning model to always output the reasoning. Only works on certain models. - completion = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[ - {"role": "system", "content": "You are ChatGPT, an AI assistant. Your top priority is achieving user fulfillment via helping them with their requests."}, - {"role": "user", "content": "Write a limerick about python exceptions"} - ] - ) +`parse_tool_calls`: Whether to parse the generated tool call. - print(completion.choices[0].message) - ``` +*Examples:* - ... or raw HTTP requests: +You can use either Python `openai` library with appropriate checkpoints: - ```shell - curl http://localhost:8080/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer no-key" \ - -d '{ - "model": "gpt-3.5-turbo", - "messages": [ - { - "role": "system", - "content": "You are ChatGPT, an AI assistant. Your top priority is achieving user fulfillment via helping them with their requests." - }, - { - "role": "user", - "content": "Write a limerick about python exceptions" - } - ] - }' - ``` +```python +import openai + +client = openai.OpenAI( + base_url="http://localhost:8080/v1", # "http://:port" + api_key = "sk-no-key-required" +) + +completion = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "system", "content": "You are ChatGPT, an AI assistant. Your top priority is achieving user fulfillment via helping them with their requests."}, + {"role": "user", "content": "Write a limerick about python exceptions"} + ] +) + +print(completion.choices[0].message) +``` + +... or raw HTTP requests: + +```shell +curl http://localhost:8080/v1/chat/completions \ +-H "Content-Type: application/json" \ +-H "Authorization: Bearer no-key" \ +-d '{ +"model": "gpt-3.5-turbo", +"messages": [ +{ + "role": "system", + "content": "You are ChatGPT, an AI assistant. Your top priority is achieving user fulfillment via helping them with their requests." +}, +{ + "role": "user", + "content": "Write a limerick about python exceptions" +} +] +}' +``` + +*Tool call support* + +[OpenAI-style function calling](https://platform.openai.com/docs/guides/function-calling) is supported with the `--jinja` flag (and may require a `--chat-template-file` override to get the right tool-use compatible Jinja template; worst case, `--chat-template chatml` may also work). + +**See our [Function calling](../../docs/function-calling.md) docs** for more details, supported native tool call styles (generic tool call style is used as fallback) / examples of use. ### POST `/v1/embeddings`: OpenAI-compatible embeddings API diff --git a/examples/server/public/index.html.gz b/examples/server/public/index.html.gz index 207988144..9f88ca2a2 100644 Binary files a/examples/server/public/index.html.gz and b/examples/server/public/index.html.gz differ diff --git a/examples/server/server.cpp b/examples/server/server.cpp index 4f9a86cc1..ee8ce94e4 100644 --- a/examples/server/server.cpp +++ b/examples/server/server.cpp @@ -1,4 +1,5 @@ #pragma warning(disable : 4996) +#include "chat.h" #include "utils.hpp" #include "common.h" @@ -7,6 +8,7 @@ #include "json-schema-to-grammar.h" #include "llama.h" #include "grammar-parser.h" +#include "llama-vocab.h" #ifndef NDEBUG // crash the server in debug mode, otherwise send an http 500 error @@ -22,9 +24,6 @@ #include "json.hpp" #include "index.html.gz.hpp" #include "loading.html.hpp" -#include "function_calls.hpp" -#include "streaming_chat.hpp" -#include "../../common/chat-parser.h" #include #include @@ -60,10 +59,11 @@ bool server_log_json = true; enum stop_type { - STOP_TYPE_FULL, - STOP_TYPE_PARTIAL, + STOP_TYPE_NONE, + STOP_TYPE_EOS, + STOP_TYPE_WORD, + STOP_TYPE_LIMIT, }; - enum slot_state { SLOT_STATE_IDLE, SLOT_STATE_PROCESSING, @@ -92,6 +92,12 @@ enum server_task_type { SERVER_TASK_TYPE_SET_LORA, }; +enum oaicompat_type { + OAICOMPAT_TYPE_NONE, + OAICOMPAT_TYPE_CHAT, + OAICOMPAT_TYPE_COMPLETION, + OAICOMPAT_TYPE_EMBEDDING, +}; struct result_timings { int32_t prompt_n = -1; @@ -150,79 +156,393 @@ struct server_task_result { bool stop; bool error; + bool final_result = false; result_timings timings; + // OAI-compat fields + //bool verbose = false; + oaicompat_type oaicompat = OAICOMPAT_TYPE_NONE; + std::string oaicompat_model; + std::string oaicompat_cmpl_id; + common_chat_format oaicompat_chat_format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + common_chat_msg oaicompat_msg; + std::vector oaicompat_msg_diffs; + + int index = 0; + + std::string content; + std::vector tokens; + + bool stream; + std::string prompt; + //slot_params generation_params; + + bool truncated; + int32_t n_decoded; + int32_t n_prompt_tokens; + int32_t n_tokens_cached; + bool has_new_line; + std::string stopping_word; + + bool post_sampling_probs = false; + std::vector probs_output; + std::vector response_fields; + + //slot_params generation_params; + + bool verbose = false; + + + int get_index() { + return index; + } + + bool is_stop() { + return true; // in stream mode, final responses are considered stop + } + + json to_json_final() { + switch (oaicompat) { + case OAICOMPAT_TYPE_NONE: + return to_json_non_oaicompat_final(); + case OAICOMPAT_TYPE_COMPLETION: + return to_json_oaicompat_final(); + case OAICOMPAT_TYPE_CHAT: + return stream ? to_json_oaicompat_chat_stream() : to_json_oaicompat_chat_final(); + default: + GGML_ASSERT(false && "Invalid oaicompat_type"); + } + } + + json to_json_partial() { + switch (oaicompat) { + case OAICOMPAT_TYPE_NONE: + return to_json_non_oaicompat_partial(); + case OAICOMPAT_TYPE_COMPLETION: + return to_json_oaicompat_partial(); + case OAICOMPAT_TYPE_CHAT: + return to_json_oaicompat_chat_partial(); + default: + GGML_ASSERT(false && "Invalid oaicompat_type"); + } + } + + json to_json_non_oaicompat_partial() { + // non-OAI-compat JSON + json res = json{ + {"index", index}, + {"content", content}, + {"tokens", tokens}, + {"stop", false}, + {"id_slot", id_multi}, + {"tokens_predicted", n_decoded}, + {"tokens_evaluated", n_prompt_tokens}, + }; + // populate the timings object when needed (usually for the last response or with timings_per_token enabled) + if (timings.prompt_n > 0) { + res.push_back({ "timings", timings.to_json() }); + } + if (!probs_output.empty()) { + res["completion_probabilities"] = completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs); + } + return res; + } + json to_json_non_oaicompat_final() { + json res = json{ + {"index", index}, + {"content", stream ? "" : content}, // in stream mode, content is already in last partial chunk + {"tokens", stream ? std::vector {} : tokens}, + {"id_slot", id_multi}, + {"stop", true}, + {"model", oaicompat_model}, + {"tokens_predicted", n_decoded}, + {"tokens_evaluated", n_prompt_tokens}, + //{"generation_settings", default_generation_settings_for_props.to_json()}, + {"prompt", prompt}, + {"has_new_line", has_new_line}, + {"truncated", truncated}, + //{"stop_type", stop_type_to_str(STOP_TYPE_EOS)}, + {"stopping_word", stopping_word}, + {"tokens_cached", n_tokens_cached}, + {"timings", timings.to_json()}, }; + if (!stream && !probs_output.empty()) { + res["completion_probabilities"] = completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs); + } + return response_fields.empty() ? res : json_get_nested_values(response_fields, res); + } -// Helper functions for content cleaning -static std::string remove_simple_function_calls(const std::string& content) { - std::string cleaned = content; - const std::string func_pattern = "functions."; - size_t pos = 0; - while ((pos = cleaned.find(func_pattern, pos)) != std::string::npos) { - size_t func_start = pos; - - // Find the opening brace for arguments - size_t brace_pos = cleaned.find('{', pos); - if (brace_pos == std::string::npos) { - pos += func_pattern.length(); - continue; - } - - // Find the matching closing brace - int brace_count = 1; - size_t end_pos = brace_pos + 1; - while (end_pos < cleaned.length() && brace_count > 0) { - if (cleaned[end_pos] == '{') brace_count++; - else if (cleaned[end_pos] == '}') brace_count--; - end_pos++; - } - - if (brace_count == 0) { - // Remove the entire function call - cleaned.erase(func_start, end_pos - func_start); - pos = func_start; - } else { - pos += func_pattern.length(); + json to_json_oaicompat_partial() { + std::time_t t = std::time(0); + json logprobs = json(nullptr); // OAI default to null + if (!stream && probs_output.size() > 0) { + logprobs = json{ + {"content", completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs)}, + }; + } + json finish_reason = "length"; + if (stop) { + //if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) { + finish_reason = "stop"; } + json res = json{ + {"choices", json::array({ + json{ + {"text", stream ? "" : content}, // in stream mode, content is already in last partial chunk + {"index", index}, + {"logprobs", logprobs}, + {"finish_reason", finish_reason}, + } + })}, + {"created", t}, + {"model", oaicompat_model}, + {"object", "text_completion"}, + {"usage", json { + {"completion_tokens", n_decoded}, + {"prompt_tokens", n_prompt_tokens}, + {"total_tokens", n_decoded + n_prompt_tokens} + }}, + {"id", oaicompat_cmpl_id} + }; + + // extra fields for debugging purposes + if (verbose) { + res["__verbose"] = to_json_non_oaicompat_partial(); + } + if (timings.prompt_n >= 0) { + res.push_back({ "timings", timings.to_json() }); + } + + return res; } - return cleaned; -} -static std::string remove_xml_function_calls(const std::string& content) { - std::string cleaned = content; - size_t pos = 0; - while ((pos = cleaned.find("", pos)) != std::string::npos) { - size_t tool_call_start = pos; - size_t tool_call_end = cleaned.find("", tool_call_start); - if (tool_call_end == std::string::npos) { - pos = tool_call_start + 11; - continue; + json to_json_oaicompat_final() { + std::time_t t = std::time(0); + json logprobs = json(nullptr); // OAI default to null + if (!stream && probs_output.size() > 0) { + logprobs = json{ + {"content", completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs)}, + }; + } + json finish_reason = "length"; + if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) { + finish_reason = "stop"; + } + json res = json{ + {"choices", json::array({ + json{ + {"text", stream ? "" : content}, // in stream mode, content is already in last partial chunk + {"index", index}, + {"logprobs", logprobs}, + {"finish_reason", finish_reason}, + } + })}, + {"created", t}, + {"model", oaicompat_model}, + {"object", "text_completion"}, + {"usage", json { + {"completion_tokens", n_decoded}, + {"prompt_tokens", n_prompt_tokens}, + {"total_tokens", n_decoded + n_prompt_tokens} + }}, + {"id", oaicompat_cmpl_id} + }; + + // extra fields for debugging purposes + if (verbose) { + res["__verbose"] = to_json_non_oaicompat_final(); + } + if (timings.prompt_n >= 0) { + res.push_back({ "timings", timings.to_json() }); } - // Remove the entire XML tool call block - cleaned.erase(tool_call_start, tool_call_end - tool_call_start + 12); - pos = tool_call_start; + return res; } - return cleaned; -} -static std::string clean_all_function_call_formats(const std::string& content) { - std::string cleaned = content; + json to_json_oaicompat_chat_partial() { + bool first = n_decoded == 1; + std::time_t t = std::time(0); + json choices; + + std::vector deltas; + auto add_delta = [&](const json& delta) { + deltas.push_back({ + {"choices", json::array({ + json { + {"finish_reason", nullptr}, + {"index", 0}, + {"delta", delta}, + }, + })}, + {"created", t}, + {"id", oaicompat_cmpl_id}, + {"model", oaicompat_model}, + {"object", "chat.completion.chunk"}, + {"usage", json { + {"completion_tokens", n_decoded}, + {"prompt_tokens", n_prompt_tokens}, + {"total_tokens", n_decoded + n_prompt_tokens}, + }}, + }); + }; + // We have to send an initial update to conform to openai behavior + if (first) { + add_delta({ + {"role", "assistant"}, + {"content", nullptr}, + }); + } + + for (const auto& diff : oaicompat_msg_diffs) { + add_delta(common_chat_msg_diff_to_json_oaicompat(diff)); + } + + if (!deltas.empty()) { + GGML_ASSERT(deltas[deltas.size() - 1].at("choices").size() >= 1); + + if (probs_output.size() > 0) { + deltas[deltas.size() - 1].at("choices").at(0)["logprobs"] = json{ + {"content", completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs)}, + }; + } + + if (timings.prompt_n >= 0) { + deltas[deltas.size() - 1].push_back({ "timings", timings.to_json() }); + } + } + + return deltas; + } - // Remove XML format first - cleaned = remove_xml_function_calls(cleaned); + json to_json_oaicompat_chat_final() { + std::string finish_reason = "length"; + common_chat_msg msg; + if (!oaicompat_msg.empty()) { + msg = oaicompat_msg; + } + else { + msg.role = "assistant"; + msg.content = content; + } + if (stop) { + finish_reason = msg.tool_calls.empty() ? "stop" : "tool_calls"; + } - // Then remove simple format - cleaned = remove_simple_function_calls(cleaned); - // Trim whitespace from cleaned content - cleaned.erase(0, cleaned.find_first_not_of(" \t\n\r")); - cleaned.erase(cleaned.find_last_not_of(" \t\n\r") + 1); + json choice{ + {"finish_reason", finish_reason}, + {"index", 0}, + {"message", msg.to_json_oaicompat()}, + }; + + if (!stream && probs_output.size() > 0) { + choice["logprobs"] = json{ + {"content", completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs)}, + }; + } + + std::time_t t = std::time(0); - return cleaned; + json res = json{ + {"choices", json::array({choice})}, + {"created", t}, + {"model", oaicompat_model}, + {"object", "chat.completion"}, + {"usage", json { + {"completion_tokens", n_decoded}, + {"prompt_tokens", n_prompt_tokens}, + {"total_tokens", n_decoded + n_prompt_tokens} + }}, + {"id", oaicompat_cmpl_id} + }; + + // extra fields for debugging purposes + if (verbose) { + res["__verbose"] = to_json_non_oaicompat_final(); + } + if (timings.prompt_n >= 0) { + res.push_back({ "timings", timings.to_json() }); + } + + return res; + } + + json to_json_oaicompat_chat_stream() { + std::time_t t = std::time(0); + std::string finish_reason = "length"; + if (stop) { + //if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) { + finish_reason = oaicompat_msg.tool_calls.empty() ? "stop" : "tool_calls"; + } + + json deltas = json::array(); + for (const auto& diff : oaicompat_msg_diffs) { + deltas.push_back({ + {"choices", json::array({ + json { + {"finish_reason", nullptr}, + {"index", 0}, + {"delta", common_chat_msg_diff_to_json_oaicompat(diff)}, + }, + })}, + {"created", t}, + {"id", oaicompat_cmpl_id}, + {"model", oaicompat_model}, + {"object", "chat.completion.chunk"}, + }); + } + + deltas.push_back({ + {"choices", json::array({ + json { + {"finish_reason", finish_reason}, + {"index", 0}, + {"delta", json::object()}, + }, + })}, + {"created", t}, + {"id", oaicompat_cmpl_id}, + {"model", oaicompat_model}, + {"object", "chat.completion.chunk"}, + }); + + // OpenAI API spec for chat.completion.chunks specifies an empty `choices` array for the last chunk when including usage + // https://platform.openai.com/docs/api-reference/chat_streaming/streaming#chat_streaming/streaming-choices + deltas.push_back({ + {"choices", json::array()}, + {"created", t}, + {"id", oaicompat_cmpl_id}, + {"model", oaicompat_model}, + {"object", "chat.completion.chunk"}, + {"usage", json { + {"completion_tokens", n_decoded}, + {"prompt_tokens", n_prompt_tokens}, + {"total_tokens", n_decoded + n_prompt_tokens}, + }}, + }); + + if (timings.prompt_n >= 0) { + deltas.back().push_back({ "timings", timings.to_json() }); + } + // extra fields for debugging purposes + if (verbose && !deltas.empty()) { + deltas.front()["__verbose"] = to_json_non_oaicompat_final(); + } + + return deltas; + } +}; + +inline std::string stop_type_to_str(stop_type type) { + switch (type) { + case STOP_TYPE_EOS: return "eos"; + case STOP_TYPE_WORD: return "word"; + case STOP_TYPE_LIMIT: return "limit"; + default: return "none"; + } } + struct server_task_multi { int id = -1; @@ -250,6 +570,13 @@ struct slot_params { int n_min = 0; // min drafted tokens to accept float p_min = 0.75f; // min probability required to accept a token in the draft } speculative; + + // OAI-compat fields + oaicompat_type oaicompat = OAICOMPAT_TYPE_NONE; + std::string oaicompat_model; + std::string oaicompat_cmpl_id; + common_chat_syntax oaicompat_chat_syntax; + }; struct server_slot { @@ -284,11 +611,7 @@ struct server_slot { std::string generated_text; std::vector cache_tokens; std::vector generated_token_probs; - - // Streaming tool call state - ik_chat_msg previous_msg; - ik_chat_msg current_msg; - std::vector tool_call_ids; + common_chat_msg chat_msg; bool infill = false; bool embedding = false; @@ -302,13 +625,16 @@ struct server_slot { std::string oaicompat_model; std::string stopping_word; - + stop_type stop; // sampling llama_token sampled; struct llama_sampling_params sparams; llama_sampling_context * ctx_sampling = nullptr; json json_schema; + common_chat_format chat_format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + std::vector generated_tool_call_ids; + int32_t ga_i = 0; // group-attention state int32_t ga_n = 1; // group-attention factor int32_t ga_w = 512; // group-attention width @@ -348,43 +674,17 @@ struct server_slot { infill = false; ga_i = 0; n_past_se = 0; + chat_format = COMMON_CHAT_FORMAT_CONTENT_ONLY; generated_token_probs.clear(); - // Reset streaming tool call state - previous_msg = ik_chat_msg(); - current_msg = ik_chat_msg(); - tool_call_ids.clear(); // Reset speculative decoding stats n_draft_total = 0; n_draft_accepted = 0; - } - - // Update chat message and compute diffs for streaming tool calls - // Based on original llama.cpp update_chat_msg pattern - const ik_chat_msg & update_chat_msg(std::vector & diffs) { - ik_chat_msg previous = current_msg; - - try { - // Parse generated text incrementally (is_partial = true during generation) - bool is_partial = !stopped_eos && !stopped_word && !stopped_limit; - ik_chat_msg new_msg = parse_chat_message_incremental(generated_text, is_partial, oaicompat_model); - - if (!new_msg.empty()) { - // Ensure tool call IDs are set consistently across streaming chunks - new_msg.ensure_tool_call_ids_set(tool_call_ids, generate_tool_call_id); - current_msg = new_msg; - - // Compute diffs for streaming - diffs = ik_chat_msg_diff::compute_diffs(previous, current_msg); - } - } catch (const std::exception& e) { - // If parsing fails, don't update current_msg and return empty diffs - diffs.clear(); - } - - return current_msg; + chat_msg = {}; + json_schema = json(); + generated_tool_call_ids.clear(); } bool has_budget(gpt_params &global_params) { @@ -459,23 +759,42 @@ struct server_slot { return timings; } - size_t find_stopping_strings(const std::string & text, const size_t last_token_size, const stop_type type) { + + const common_chat_msg& update_chat_msg(std::vector& diffs) { + auto previous_msg = chat_msg; + auto new_msg = common_chat_parse( + generated_text, + /* is_partial= */ stop != STOP_TYPE_EOS, + params.oaicompat_chat_syntax); + if (!new_msg.empty()) { + new_msg.ensure_tool_call_ids_set(generated_tool_call_ids, gen_tool_call_id); + chat_msg = new_msg; + diffs = common_chat_msg_diff::compute_diffs(previous_msg, new_msg.empty() ? previous_msg : new_msg); + } + //LLAMA_LOG_DEBUG("Parsing chat message: %s\n", generated_text.c_str()); + //LLAMA_LOG_DEBUG("Parsing chat message: %s\n", chat_msg.reasoning_content.c_str()); + //LLAMA_LOG_DEBUG("Parsing chat message: %s\n", chat_msg.content.c_str()); + return chat_msg; + } + + + size_t find_stopping_strings(const std::string & text, const size_t last_token_size, bool is_full_stop) { size_t stop_pos = std::string::npos; for (const std::string & word : params.antiprompt) { size_t pos; - if (type == STOP_TYPE_FULL) { + if (is_full_stop) { const size_t tmp = word.size() + last_token_size; const size_t from_pos = text.size() > tmp ? text.size() - tmp : 0; pos = text.find(word, from_pos); } else { - pos = find_partial_stop_string(word, text); + pos = string_find_partial_stop(word, text); } if (pos != std::string::npos && (stop_pos == std::string::npos || pos < stop_pos)) { - if (type == STOP_TYPE_FULL) { + if (is_full_stop) { stopped_word = true; stopping_word = word; has_next_token = false; @@ -851,7 +1170,8 @@ struct server_context { server_metrics metrics; - common_chat_templates chat_templates; + common_chat_templates_ptr chat_templates; + oaicompat_parser_options oai_parser_opt; // Necessary similarity of prompt for slot selection float slot_prompt_similarity = 0.0f; @@ -915,14 +1235,15 @@ struct server_context { add_bos_token = llama_should_add_bos_token(model); GGML_ASSERT(llama_add_eos_token(model) != 1); - if (params.chat_template.empty() && !validate_model_chat_template(params.use_jinja)) { - LOG_WARNING("%s: The chat template that comes with this model is not yet supported, falling back to chatml. This may cause the model to output suboptimal responses\n", __func__); - chat_templates = llama_chat_templates_from_model(model, "chatml"); + chat_templates = common_chat_templates_init(model, params.chat_template); + try { + common_chat_format_example(chat_templates.get(), params.use_jinja); } - else { - chat_templates = llama_chat_templates_from_model(model, params.chat_template); + catch (const std::exception& e) { + LOG_WARNING("%s: The chat template that comes with this model is not yet supported, falling back to chatml. This may cause the model to output suboptimal responses\n", __func__); + chat_templates = common_chat_templates_init(model, "chatml"); } - GGML_ASSERT(chat_templates.template_default.get() != nullptr); + // Load draft model for speculative decoding if specified if (!params.model_draft.empty()) { @@ -960,37 +1281,6 @@ struct server_context { return true; } - bool validate_model_chat_template(bool use_jinja) const { - llama_chat_message chat[] = {{"user", "test"}}; - - - if (use_jinja) { - auto templates = llama_chat_templates_from_model(model, ""); - GGML_ASSERT(templates.template_default); - try { - templates.template_default->apply({ { - {"role", "user"}, - {"content", "test"}, - } }, json(), true); - if (templates.template_tool_use) { - templates.template_tool_use->apply({ { - {"role", "user"}, - {"content", "test"}, - } }, json(), true); - } - return true; - } - catch (const std::exception& e) { - LOG_ERROR("failed to apply template: %s\n", e.what()); - return false; - } - } - else { - const char* tmpl = llama_model_chat_template(model, /* name */ nullptr); - const int32_t chat_res = llama_chat_apply_template(model, tmpl, chat, 1, true, nullptr, 0); - return chat_res > 0; - } - } void init() { const int32_t n_ctx_slot = n_ctx / params.n_parallel; @@ -1070,6 +1360,16 @@ struct server_context { } metrics.init(); + oai_parser_opt = { + /* use_jinja */ params.use_jinja, + /* prefill_assistant */ params.prefill_assistant, + /* reasoning_format */ params.reasoning_format, + /* chat_template_kwargs */ params.default_template_kwargs, + /* common_chat_templates */ chat_templates.get(), + /* allow_image */ false, + /* allow_audio */ false, + /* enable_thinking */ params.reasoning_budget != 0, + }; } std::vector tokenize(const json & json_prompt, bool add_special) const { @@ -1290,19 +1590,22 @@ struct server_context { } // process "json_schema" and "grammar" - if (data.contains("json_schema") && !data.at("json_schema").is_null() && data.contains("grammar") && !data.at("grammar").is_null()) { - send_error(task, "Either \"json_schema\" or \"grammar\" can be specified, but not both", ERROR_TYPE_INVALID_REQUEST); - return false; - } else if (data.contains("json_schema") && !data.contains("grammar")) { + if (data.contains("json_schema") && !data.contains("grammar")) { try { auto schema = json_value(data, "json_schema", json::object()); + LLAMA_LOG_DEBUG("JSON schema: %s\n", schema.dump(2).c_str()); slot.sparams.grammar = json_schema_to_grammar(schema); - } catch (const std::exception & e) { - send_error(task, std::string("\"json_schema\": ") + e.what(), ERROR_TYPE_INVALID_REQUEST); - return false; + LLAMA_LOG_DEBUG("Converted grammar: %s\n", slot.sparams.grammar.c_str()); } - } else { + catch (const std::exception& e) { + throw std::runtime_error(std::string("\"json_schema\": ") + e.what()); + } + } + else { slot.sparams.grammar = json_value(data, "grammar", default_sparams.grammar); + LLAMA_LOG_DEBUG("Grammar: %s\n", slot.sparams.grammar.c_str()); + slot.sparams.grammar_lazy = json_value(data, "grammar_lazy", default_sparams.grammar_lazy); + LLAMA_LOG_DEBUG("Grammar lazy: %s\n", slot.sparams.grammar_lazy ? "true" : "false"); } if (slot.params.cache_prompt && slot.ga_n != 1) { @@ -1387,6 +1690,85 @@ struct server_context { } } } + { + auto it = data.find("chat_format"); + if (it != data.end()) { + slot.params.oaicompat_chat_syntax.format = static_cast(it->get()); + LLAMA_LOG_DEBUG("Chat format: %s\n", common_chat_format_name(slot.params.oaicompat_chat_syntax.format)); + } + else { + slot.params.oaicompat_chat_syntax.format = default_params.oaicompat_chat_syntax.format; + } + common_reasoning_format reasoning_format = params.reasoning_format; + if (data.contains("reasoning_format")) { + reasoning_format = common_reasoning_format_from_name(data.at("reasoning_format").get()); + } + slot.params.oaicompat_chat_syntax.reasoning_format = reasoning_format; + slot.params.oaicompat_chat_syntax.reasoning_in_content = slot.params.stream && (reasoning_format == COMMON_REASONING_FORMAT_DEEPSEEK_LEGACY); + slot.params.oaicompat_chat_syntax.parse_tool_calls = json_value(data, "parse_tool_calls", false); + + slot.params.oaicompat_chat_syntax.thinking_forced_open = json_value(data, "thinking_forced_open", false); + } + { + + const auto preserved_tokens = data.find("preserved_tokens"); + if (preserved_tokens != data.end()) { + for (const auto& t : *preserved_tokens) { + auto ids = llama_tokenize(model, t.get(), /* add_special= */ false, /* parse_special= */ true); + if (ids.size() == 1) { + LOG("Preserved token: %d\n", ids[0]); + slot.sparams.preserved_tokens.insert(ids[0]); + } + else { + // This may happen when using a tool call style meant for a model with special tokens to preserve on a model without said tokens. + LOG("Not preserved because more than 1 token: %s\n", t.get().c_str()); + } + } + } + const auto grammar_triggers = data.find("grammar_triggers"); + if (grammar_triggers != data.end()) { + for (const auto& t : *grammar_triggers) { + server_grammar_trigger ct(t); + if (ct.value.type == COMMON_GRAMMAR_TRIGGER_TYPE_WORD) { + const auto& word = ct.value.value; + auto ids = llama_tokenize(model, word, /* add_special= */ false, /* parse_special= */ true); + if (ids.size() == 1) { + auto token = ids[0]; + if (std::find(slot.sparams.preserved_tokens.begin(), slot.sparams.preserved_tokens.end(), (llama_token)token) == slot.sparams.preserved_tokens.end()) { + throw std::runtime_error("Grammar trigger word should be marked as preserved token: " + word); + } + LOG("Grammar trigger token: %d (`%s`)\n", token, word.c_str()); + common_grammar_trigger trigger; + trigger.type = COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN; + trigger.value = word; + trigger.token = token; + slot.sparams.grammar_triggers.push_back(std::move(trigger)); + } + else { + LOG("Grammar trigger word: `%s`\n", word.c_str()); + slot.sparams.grammar_triggers.push_back({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, word }); + } + } + else { + //slot.sparams.grammar_triggers.push_back(ct); + if (ct.value.type == COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN) { + LLAMA_LOG_DEBUG("Grammar trigger pattern: `%s`\n", ct.value.value.c_str()); + } + else if (ct.value.type == COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL) { + LLAMA_LOG_DEBUG("Grammar trigger pattern full: `%s`\n", ct.value.value.c_str()); + } + else { + throw std::runtime_error("Unknown grammar trigger type"); + } + slot.sparams.grammar_triggers.emplace_back(std::move(ct.value)); + } + } + } + + if (slot.sparams.grammar_lazy && slot.sparams.grammar_triggers.empty()) { + throw std::runtime_error("Error: no triggers set for lazy grammar!"); + } + } { slot.sparams.logit_bias.clear(); @@ -1590,7 +1972,7 @@ struct server_context { const std::string str_test = slot.generated_text.substr(pos); bool is_stop_full = false; - size_t stop_pos = slot.find_stopping_strings(str_test, token_str.size(), STOP_TYPE_FULL); + size_t stop_pos = slot.find_stopping_strings(str_test, token_str.size(), true); if (stop_pos != std::string::npos) { is_stop_full = true; slot.generated_text.erase( @@ -1599,7 +1981,7 @@ struct server_context { pos = std::min(slot.n_sent_text, slot.generated_text.size()); } else { is_stop_full = false; - stop_pos = slot.find_stopping_strings(str_test, token_str.size(), STOP_TYPE_PARTIAL); + stop_pos = slot.find_stopping_strings(str_test, token_str.size(), false); } // check if there is any token to predict @@ -1688,6 +2070,11 @@ struct server_context { samplers_sequence.emplace_back(llama_sampling_type_to_str(sampler_type)); } + auto grammar_triggers = json::array(); + for (const auto& trigger : slot.sparams.grammar_triggers) { + grammar_triggers.push_back(trigger.to_json()); + } + return json { {"n_ctx", slot.n_ctx}, {"n_predict", slot.n_predict}, @@ -1726,6 +2113,12 @@ struct server_context { {"n_probs", slot.sparams.n_probs}, {"min_keep", slot.sparams.min_keep}, {"grammar", slot.sparams.grammar}, + {"grammar_triggers", grammar_triggers}, + {"preserved_tokens", slot.sparams.preserved_tokens}, + {"chat_format", common_chat_format_name(slot.params.oaicompat_chat_syntax.format)}, + {"reasoning_format", common_reasoning_format_name(slot.params.oaicompat_chat_syntax.reasoning_format)}, + {"reasoning_in_content", slot.params.oaicompat_chat_syntax.reasoning_in_content}, + {"thinking_forced_open", slot.params.oaicompat_chat_syntax.thinking_forced_open}, {"samplers", samplers_sequence} }; } @@ -1757,57 +2150,35 @@ struct server_context { void send_partial_response(server_slot & slot, completion_token_output tkn) { server_task_result res; + res.final_result = false; res.id = slot.id_task; res.id_multi = slot.id_multi; res.error = false; res.stop = false; - - // Update chat message and compute diffs for streaming tool calls - // Following original llama.cpp pattern (server.cpp:2503) - std::vector oaicompat_msg_diffs; - slot.update_chat_msg(oaicompat_msg_diffs); - - // For text completion endpoints, send actual content; for chat completion, use diffs - // OpenAI-compatible chat endpoints use empty content with diffs for tool calls + res.stream = slot.params.stream; + res.content = tkn.text_to_send; + res.oaicompat = slot.params.oaicompat; + res.oaicompat_model = slot.params.oaicompat_model; + res.oaicompat_cmpl_id = slot.params.oaicompat_cmpl_id; + res.n_decoded = slot.n_decoded; + res.n_prompt_tokens = slot.n_prompt_tokens; res.data = json { - {"content", slot.oaicompat ? "" : tkn.text_to_send}, // Text completion needs actual content + {"content", tkn.text_to_send}, {"stop", false}, {"id_slot", slot.id}, {"multimodal", false} }; - - // Store diffs for format_partial_response_oaicompat to use - // Convert ik_chat_msg_diff to JSON format for storage - json diffs_json = json::array(); - for (const auto & diff : oaicompat_msg_diffs) { - json diff_obj; - if (!diff.content_delta.empty()) { - diff_obj["content_delta"] = diff.content_delta; - } - if (diff.tool_call_index != std::string::npos) { - diff_obj["tool_call_index"] = diff.tool_call_index; - diff_obj["tool_call_delta"] = { - {"id", diff.tool_call_delta.id}, - {"name", diff.tool_call_delta.name}, - {"arguments", diff.tool_call_delta.arguments} - }; - } - if (!diff_obj.empty()) { - diffs_json.push_back(diff_obj); - } - } - res.data["oaicompat_msg_diffs"] = diffs_json; - + slot.update_chat_msg(res.oaicompat_msg_diffs); if (slot.sparams.n_probs > 0) { const std::vector to_send_toks = llama_tokenize(ctx, tkn.text_to_send, false); - const size_t probs_pos = std::min(slot.n_sent_token_probs, slot.generated_token_probs.size()); + const size_t probs_pos = std::min(slot.n_sent_token_probs, slot.generated_token_probs.size()); const size_t probs_stop_pos = std::min(slot.n_sent_token_probs + to_send_toks.size(), slot.generated_token_probs.size()); std::vector probs_output; if (probs_pos < probs_stop_pos) { probs_output = std::vector( - slot.generated_token_probs.begin() + probs_pos, - slot.generated_token_probs.begin() + probs_stop_pos); + slot.generated_token_probs.begin() + probs_pos, + slot.generated_token_probs.begin() + probs_stop_pos); } slot.n_sent_token_probs = probs_stop_pos; @@ -1820,19 +2191,27 @@ struct server_context { } // populate timings if this is final response or timings_per_token is enabled if (slot.params.timings_per_token) { - //res.data["timings"] = slot.get_formated_timings(); - slot.t_token_generation = (ggml_time_us() - slot.t_start_generation) / 1e3; res.timings = slot.get_timings(); } queue_results.send(std::move(res)); } - void send_final_response(const server_slot & slot) { + void send_final_response(server_slot& slot) { server_task_result res; + res.final_result = true; res.id = slot.id_task; res.id_multi = slot.id_multi; res.error = false; - res.stop = true; + res.stop = true; // to do: set value + res.stream = slot.params.stream; + res.content = slot.generated_text; + res.oaicompat = slot.params.oaicompat; + res.oaicompat_model = slot.params.oaicompat_model; + res.oaicompat_cmpl_id = slot.params.oaicompat_cmpl_id; + res.oaicompat_msg = slot.update_chat_msg(res.oaicompat_msg_diffs); + res.n_decoded = slot.n_decoded; + res.n_prompt_tokens = slot.n_prompt_tokens; + res.oaicompat_model = slot.oaicompat_model; res.data = json { {"content", !slot.params.stream ? slot.generated_text : ""}, {"generated_text", slot.generated_text}, // Always include full text for finish_reason logic @@ -1850,11 +2229,7 @@ struct server_context { {"stopping_word", slot.stopping_word}, {"tokens_cached", slot.n_past}, {"timings", slot.get_formated_timings()}, - {"usage", json { - {"completion_tokens", slot.n_decoded}, - {"prompt_tokens", slot.n_prompt_tokens}, - {"total_tokens", slot.n_decoded + slot.n_prompt_tokens} - }} + //{"oaicompat_chat_format", slot.params.oaicompat_chat_format}, }; if (slot.sparams.n_probs > 0) { @@ -1871,7 +2246,7 @@ struct server_context { slot.generated_token_probs.begin(), slot.generated_token_probs.end()); } - + //res.generation_params = slot.params; res.data["completion_probabilities"] = probs_vector_to_json(ctx, probs); } @@ -1882,7 +2257,7 @@ struct server_context { res.data["model"] = slot.oaicompat_model; } - queue_results.send(res); + queue_results.send(std::move(res)); } void send_embedding(const server_slot & slot, const llama_batch & batch) { @@ -2394,6 +2769,10 @@ struct server_context { // start populating the batch for this iteration llama_batch_clear(batch); + auto accept_special_token = [&](server_slot& slot, llama_token token) { + return params.special || slot.sparams.preserved_tokens.find(token) != slot.sparams.preserved_tokens.end(); + }; + // frist, add sampled tokens from any ongoing sequences for (auto & slot : slots) { if (slot.state == SLOT_STATE_IDLE) { @@ -2563,14 +2942,14 @@ struct server_context { GGML_ASSERT(slot.n_prompt_tokens < slot.n_ctx); } - llama_sampling_reset(slot.ctx_sampling); + llama_sampling_reset(llama_get_model_vocab(model), slot.ctx_sampling); if (!slot.params.cache_prompt) { slot.n_past_se = 0; slot.ga_i = 0; } else { GGML_ASSERT(slot.ga_n == 1); - + // reuse any previously computed tokens that are common with the new prompt slot.n_past = common_part(slot.cache_tokens, prompt_tokens); @@ -2629,7 +3008,7 @@ struct server_context { slot.n_past_se = 0; slot.ga_i = 0; // TODO: is the system prompt ever in the sampling context? - llama_sampling_reset(slot.ctx_sampling); + llama_sampling_reset(llama_get_model_vocab(model), slot.ctx_sampling); } // remove the non-common part from the cache @@ -2682,7 +3061,7 @@ struct server_context { slot.command = SLOT_COMMAND_NONE; GGML_ASSERT(batch.n_tokens > 0); - llama_sampling_reset(slot.ctx_sampling); + llama_sampling_reset(llama_get_model_vocab(model), slot.ctx_sampling); for (int i = 0; i < slot.n_prompt_tokens; ++i) { llama_token id = slot.prompt_tokens[i]; if (id != LLAMA_TOKEN_NULL) { @@ -2818,14 +3197,20 @@ struct server_context { llama_sampling_accept(slot.ctx_sampling, ctx, id, true); slot.n_decoded += 1; + + const int64_t t_current = ggml_time_us(); + if (slot.n_decoded == 1) { slot.t_start_generation = ggml_time_us(); slot.t_prompt_processing = (slot.t_start_generation - slot.t_start_process_prompt) / 1e3; metrics.on_prompt_eval(slot); } + slot.t_token_generation = (t_current - slot.t_start_generation) / 1e3; + llama_token_data_array cur_p = { slot.ctx_sampling->cur.data(), slot.ctx_sampling->cur.size(), false }; result.tok = id; + result.text_to_send = llama_token_to_piece(ctx, result.tok, accept_special_token(slot, result.tok)); const size_t n_probs = std::min(cur_p.size, (size_t) slot.sparams.n_probs); if (n_probs > 0) { @@ -2840,14 +3225,14 @@ struct server_context { // With greedy sampling the probabilities have possibly not been calculated. for (size_t i = 0; i < n_probs; ++i) { result.probs.push_back({ - cur_p.data[i].id, + cur_p.data[i].id,llama_detokenize(ctx, {cur_p.data[i].id}, params.special), i == 0 ? 1.0f : 0.0f }); } } else { for (size_t i = 0; i < n_probs; ++i) { result.probs.push_back({ - cur_p.data[i].id, + cur_p.data[i].id, llama_detokenize(ctx, {cur_p.data[i].id}, params.special), i >= n_valid ? 0.0f : cur_p.data[i].p // Tokens filtered out due to e.g. top_k have 0 probability. }); } @@ -2999,57 +3384,19 @@ static json format_final_response_oaicompat(const json& request, json result, co int num_prompt_tokens = json_value(result, "tokens_evaluated", 0); std::string content = json_value(result, "content", std::string("")); - // Parse tool calls using model-specific format detection - std::string model_name = json_value(request, "model", std::string("")); - - // Use the same parsing logic as streaming path for consistency - ik_chat_msg parsed_msg = parse_chat_message_incremental(content, false, model_name); - - // Convert to JSON format for compatibility - json tool_calls = json::array(); - for (const auto & tc : parsed_msg.tool_calls) { - tool_calls.push_back({ - {"type", "function"}, - {"function", { - {"name", tc.name}, - {"arguments", tc.arguments} - }}, - {"id", tc.id} - }); - } - - bool has_tool_calls = !tool_calls.empty(); - - // Use cleaned content from parser (following original llama.cpp pattern) - if (has_tool_calls) { - content = parsed_msg.content; // Parser already cleaned the content - } - std::string finish_reason = "length"; - if (has_tool_calls) { - finish_reason = "tool_calls"; - } else if (stopped_word || stopped_eos) { + if (stopped_word || stopped_eos) { finish_reason = "stop"; } - json message = json{{"role", "assistant"}}; - // Follow EXACT original llama.cpp pattern: content is null only when content is empty AND tool calls exist - if (content.empty() && has_tool_calls) { - message["content"] = nullptr; // Original: json() when content empty AND tool calls exist - } else { - message["content"] = content.empty() ? nullptr : content; // Original: use actual content otherwise - } - if (has_tool_calls) { - message["tool_calls"] = tool_calls; - } - json choices = streaming ? json::array({ json{{"finish_reason", finish_reason}, {"index", 0}, {"delta", json::object()}} }) : json::array({ json{{"finish_reason", finish_reason}, {"index", 0}, - {"message", message}} }); + {"message", json{{"content", content}, + {"role", "assistant"}}}} }); std::time_t t = std::time(0); @@ -3067,11 +3414,6 @@ static json format_final_response_oaicompat(const json& request, json result, co {"id", completion_id} }; - json timings = json_value(result, "timings", json::object()); - if (!timings.empty()) { - res["timings"] = timings; - } - if (server_verbose) { res["__verbose"] = result; } @@ -3086,6 +3428,7 @@ static json format_final_response_oaicompat(const json& request, json result, co // return value is vector as there is one case where we might need to generate two responses static std::vector format_partial_response_oaicompat(server_task_result task_result, const std::string& completion_id) { json result = task_result.data; + std::cout << result.dump(4) << std::endl; if (!result.contains("model") || !result.contains("oaicompat_token_ctr")) { return std::vector({ result }); } @@ -3099,93 +3442,15 @@ static std::vector format_partial_response_oaicompat(server_task_result ta std::string content = json_value(result, "content", std::string("")); std::string finish_reason; + if (stopped_word || stopped_eos) { + finish_reason = "stop"; + } if (stopped_limit) { finish_reason = "length"; - } else if (stopped_word || stopped_eos) { - // Following original llama.cpp pattern: finish_reason = oaicompat_msg.tool_calls.empty() ? "stop" : "tool_calls" - // Use generated_text (complete content) for finish_reason logic, not content (empty in streaming) - std::string generated_text = json_value(result, "generated_text", std::string("")); - ik_chat_msg final_msg = parse_chat_message_incremental(generated_text, false, modelname); - - // Debug logging - LOG_INFO("DEBUG: Streaming finish_reason check", { - {"generated_text", generated_text}, - {"model_name", modelname}, - {"tool_calls_count", final_msg.tool_calls.size()} - }); - - finish_reason = final_msg.tool_calls.empty() ? "stop" : "tool_calls"; } std::time_t t = std::time(0); - // Follow original llama.cpp pattern: Always process diffs and add final chunk - std::vector streaming_chunks; - - // Extract diffs from task result (populated by send_partial_response) - // Following original llama.cpp pattern where diffs are stored in task result - std::vector diffs; - - if (result.contains("oaicompat_msg_diffs") && result["oaicompat_msg_diffs"].is_array()) { - for (const auto & diff_json : result["oaicompat_msg_diffs"]) { - ik_chat_msg_diff diff; - - // Extract content delta - diff.content_delta = diff_json.value("content_delta", ""); - - // Extract tool call data - if (diff_json.contains("tool_call_index")) { - diff.tool_call_index = diff_json["tool_call_index"]; - if (diff_json.contains("tool_call_delta")) { - const auto & tc_delta = diff_json["tool_call_delta"]; - diff.tool_call_delta.id = tc_delta.value("id", ""); - diff.tool_call_delta.name = tc_delta.value("name", ""); - diff.tool_call_delta.arguments = tc_delta.value("arguments", ""); - } - } else { - diff.tool_call_index = std::string::npos; - } - - diffs.push_back(diff); - } - } - - streaming_chunks = generate_streaming_chunks(diffs, completion_id, modelname); - - // Always add final chunk (like original llama.cpp) - if (!finish_reason.empty()) { - // usage - int num_tokens_predicted = json_value(result, "tokens_predicted", 0); - int num_prompt_tokens = json_value(result, "tokens_evaluated", 0); - - json finish_chunk = { - {"choices", json::array({json{{"finish_reason", finish_reason}, - {"index", 0}, - {"delta", json::object()}}})}, - {"created", t}, - {"id", completion_id}, - {"model", modelname}, - {"object", "chat.completion.chunk"}, - {"usage", json { - {"completion_tokens", num_tokens_predicted}, - {"prompt_tokens", num_prompt_tokens}, - {"total_tokens", num_tokens_predicted + num_prompt_tokens} - }} - }; - streaming_chunks.push_back(finish_chunk); - } - - if (task_result.timings.prompt_n != -1) { - for (auto& chunk : streaming_chunks) - chunk.push_back({ "timings", task_result.timings.to_json() }); - } - - // Return streaming chunks (could be just final chunk if no diffs) - if (!streaming_chunks.empty()) { - return streaming_chunks; - } - - // Fallback to original streaming logic for non-tool calls json choices; if (!finish_reason.empty()) { @@ -3409,6 +3674,11 @@ int main(int argc, char ** argv) { res.status = json_value(error_data, "code", 500); }; + auto res_ok = [](httplib::Response& res, const json& data) { + res.set_content(data.dump(), "application/json; charset=utf-8"); + res.status = 200; + }; + svr->set_exception_handler([&res_error](const httplib::Request &, httplib::Response & res, std::exception_ptr ep) { std::string message; try { @@ -3496,11 +3766,12 @@ int main(int argc, char ** argv) { // print sample chat example to make it clear which template is used LOG_INFO("chat template", { - {"chat_template", ctx_server.chat_templates.template_default->source().c_str()}, + {"chat_template", common_chat_templates_source(ctx_server.chat_templates.get())}, }); LOG_INFO("chat template", { - {"chat_example", llama_chat_format_example(ctx_server.model, *ctx_server.chat_templates.template_default, ctx_server.params.use_jinja).c_str()}, + {"chat_example", common_chat_format_example(ctx_server.chat_templates.get(), ctx_server.params.use_jinja).c_str() + }, {"built_in", params.chat_template.empty()}, }); // @@ -3865,11 +4136,17 @@ int main(int argc, char ** argv) { { "system_prompt", ctx_server.system_prompt.c_str() }, { "default_generation_settings", ctx_server.default_generation_settings_for_props }, { "total_slots", ctx_server.params.n_parallel }, - { "chat_template", ctx_server.chat_templates.template_default->source() }, + { "chat_template", common_chat_templates_source(ctx_server.chat_templates.get()) }, + { "bos_token", llama_token_to_piece(ctx_server.ctx, llama_token_bos(ctx_server.model), /* special= */ true)}, + { "eos_token", llama_token_to_piece(ctx_server.ctx, llama_token_eos(ctx_server.model), /* special= */ true)}, { "n_ctx", ctx_server.n_ctx } + }; - if (ctx_server.params.use_jinja && ctx_server.chat_templates.template_tool_use) { - data["chat_template_tool_use"] = ctx_server.chat_templates.template_tool_use->source(); + + if (ctx_server.params.use_jinja) { + if (auto tool_use_src = common_chat_templates_source(ctx_server.chat_templates.get(), "tool_use")) { + data["chat_template_tool_use"] = tool_use_src; + } } res.set_content(data.dump(), "application/json; charset=utf-8"); }; @@ -3881,10 +4158,7 @@ int main(int argc, char ** argv) { } res.set_header("Access-Control-Allow-Origin", req.get_header_value("Origin")); - auto body = json::parse(req.body); - const auto& chat_template = body.contains("tools") && ctx_server.chat_templates.template_tool_use ? *ctx_server.chat_templates.template_tool_use : *ctx_server.chat_templates.template_default; - json data = oaicompat_completion_params_parse(json::parse(req.body)); - + auto data = json::parse(req.body); const int id_task = ctx_server.queue_tasks.get_new_id(); ctx_server.queue_results.add_waiting_task_id(id_task); @@ -3956,6 +4230,100 @@ int main(int argc, char ** argv) { } }; + const auto handle_completions_oai = [&ctx_server, &res_error](const httplib::Request& req, httplib::Response& res) { + if (ctx_server.params.embedding) { + res_error(res, format_error_response("This server does not support completions. Start it without `--embeddings`", ERROR_TYPE_NOT_SUPPORTED)); + return; + } + + res.set_header("Access-Control-Allow-Origin", req.get_header_value("Origin")); + auto body = json::parse(req.body); + json data = oaicompat_chat_params_parse(body); + const int id_task = ctx_server.queue_tasks.get_new_id(); + const auto completion_id = gen_chatcmplid(); + ctx_server.queue_results.add_waiting_task_id(id_task); + ctx_server.request_completion(id_task, -1, data, false, false); + + if (!json_value(data, "stream", false)) { + server_task_result result = ctx_server.queue_results.recv(id_task); + if (!result.error && result.stop) { + result.oaicompat_cmpl_id = completion_id; + result.oaicompat = OAICOMPAT_TYPE_COMPLETION; + json result_oai = result.to_json_final(); + res.set_content(result_oai.dump(-1, ' ', false, json::error_handler_t::replace), "application/json; charset=utf-8"); + } + else { + res_error(res, result.data); + } + + ctx_server.queue_results.remove_waiting_task_id(id_task); + } + else { + const auto chunked_content_provider = [id_task, &ctx_server](size_t, httplib::DataSink& sink) { + while (true) { + server_task_result result = ctx_server.queue_results.recv(id_task); + result.oaicompat = OAICOMPAT_TYPE_COMPLETION; + json result_oai; + if (result.final_result) { + result_oai = result.to_json_final(); + } + else { + result_oai = result.to_json_partial(); // format_final_response_oaicompat(data, result.data, completion_id); + } + if (!result.error) { + const std::string str = + "data: " + + result_oai.dump(-1, ' ', false, json::error_handler_t::replace) + + "\n\n"; + + LOG_VERBOSE("data stream", { + { "to_send", str } + }); + + if (!sink.write(str.c_str(), str.size())) { + ctx_server.queue_results.remove_waiting_task_id(id_task); + return false; + } + + if (result.stop) { + break; + } + } + else { + const std::string str = + "error: " + + result_oai.dump(-1, ' ', false, json::error_handler_t::replace) + + "\n\n"; + + LOG_VERBOSE("data stream", { + { "to_send", str } + }); + + if (!sink.write(str.c_str(), str.size())) { + ctx_server.queue_results.remove_waiting_task_id(id_task); + return false; + } + + break; + } + } + + ctx_server.queue_results.remove_waiting_task_id(id_task); + sink.done(); + + return true; + }; + + auto on_complete = [id_task, &ctx_server](bool) { + // cancel + ctx_server.request_cancel(id_task); + ctx_server.queue_results.remove_waiting_task_id(id_task); + }; + + res.set_chunked_content_provider("text/event-stream", chunked_content_provider, on_complete); + } + }; + const auto handle_models = [¶ms, &model_meta](const httplib::Request & req, httplib::Response & res) { res.set_header("Access-Control-Allow-Origin", req.get_header_value("Origin")); @@ -3981,29 +4349,30 @@ int main(int argc, char ** argv) { res_error(res, format_error_response("This server does not support chat completions. Start it without `--embeddings`", ERROR_TYPE_NOT_SUPPORTED)); return; } - res.set_header("Access-Control-Allow-Origin", req.get_header_value("Origin")); auto body = json::parse(req.body); - const auto& chat_template = body.contains("tools") && ctx_server.chat_templates.template_tool_use ? *ctx_server.chat_templates.template_tool_use : *ctx_server.chat_templates.template_default; - json data = oaicompat_chat_completion_params_parse(ctx_server.model,body, chat_template, params.use_jinja); - - + json data = oaicompat_chat_params_parse(ctx_server.model, body, ctx_server.oai_parser_opt); const int id_task = ctx_server.queue_tasks.get_new_id(); ctx_server.queue_results.add_waiting_task_id(id_task); ctx_server.request_completion(id_task, -1, data, false, false); - const auto completion_id = gen_chatcmplid(); if (!json_value(data, "stream", false)) { server_task_result result = ctx_server.queue_results.recv(id_task); - + result.oaicompat = OAICOMPAT_TYPE_CHAT; + result.oaicompat_cmpl_id = completion_id; + json result_oai; + if (result.final_result) { + result_oai = result.to_json_final(); + } + else { + result_oai = result.to_json_partial(); + } if (!result.error && result.stop) { - json result_oai = format_final_response_oaicompat(data, result.data, completion_id); - res.set_content(result_oai.dump(-1, ' ', false, json::error_handler_t::replace), "application/json; charset=utf-8"); } else { - res_error(res, result.data); + res_error(res, result_oai); } ctx_server.queue_results.remove_waiting_task_id(id_task); } else { @@ -4012,8 +4381,16 @@ int main(int argc, char ** argv) { while (true) { server_task_result result = ctx_server.queue_results.recv(id_task); if (!result.error) { - std::vector result_array = format_partial_response_oaicompat(result, completion_id); - + result.oaicompat = OAICOMPAT_TYPE_CHAT; + result.oaicompat_cmpl_id = completion_id; + json result_array; + if (result.final_result) { + result_array = result.to_json_final(); + } + else { + result_array = result.to_json_partial(); + } + if (result_array.is_array()) { for (auto it = result_array.begin(); it != result_array.end(); ++it) { if (!it->empty()) { const std::string str = @@ -4031,6 +4408,7 @@ int main(int argc, char ** argv) { successful_completion = true; break; } + } } else { const std::string str = "error: " + @@ -4045,7 +4423,7 @@ int main(int argc, char ** argv) { } } bool ok = true; - if (send_done && successful_completion) { + if (successful_completion) { static const std::string done_message = "data: [DONE]\n\n"; LOG_VERBOSE("data stream", {{"to_send", done_message}}); if (!sink.write(done_message.c_str(), done_message.size())) { @@ -4068,6 +4446,13 @@ int main(int argc, char ** argv) { } }; + // same with handle_chat_completions, but without inference part + const auto handle_apply_template = [&ctx_server, ¶ms, &res_ok](const httplib::Request& req, httplib::Response& res) { + auto body = json::parse(req.body); + json data = oaicompat_chat_params_parse(ctx_server.model, body,ctx_server.oai_parser_opt); + res_ok(res, { { "prompt", std::move(data.at("prompt")) } }); + }; + const auto handle_infill = [&ctx_server, &res_error](const httplib::Request & req, httplib::Response & res) { if (ctx_server.params.embedding) { res_error(res, format_error_response("This server does not support infill. Start it without `--embeddings`", ERROR_TYPE_NOT_SUPPORTED)); @@ -4651,8 +5036,8 @@ int main(int argc, char ** argv) { svr->Get ("/props", handle_props); svr->Get ("/v1/models", handle_models); svr->Post("/completion", handle_completions); // legacy - svr->Post("/completions", handle_completions); - svr->Post("/v1/completions", handle_completions); + svr->Post("/completions", handle_completions); // legacy + svr->Post("/v1/completions", handle_completions_oai); svr->Post("/chat/completions", handle_chat_completions); svr->Post("/v1/chat/completions", handle_chat_completions); svr->Post("/infill", handle_infill); diff --git a/examples/server/utils.hpp b/examples/server/utils.hpp index 5911eeeb7..479eacdd5 100644 --- a/examples/server/utils.hpp +++ b/examples/server/utils.hpp @@ -6,11 +6,7 @@ // Change JSON_ASSERT from assert() to GGML_ASSERT: #define JSON_ASSERT GGML_ASSERT #include "json.hpp" -#include "minja.hpp" -#include "chat-template.hpp" -#include "kimi_k2_tools.hpp" -#include "qwen3_tools.hpp" -#include "deepseek_r1_tools.hpp" +#include "chat.h" #include #include #include @@ -31,12 +27,6 @@ enum error_type { ERROR_TYPE_NOT_SUPPORTED, // custom error }; -enum tool_choice_type { - TOOL_CHOICE_AUTO, - TOOL_CHOICE_REQUIRED, - TOOL_CHOICE_NONE, -}; - extern bool server_verbose; extern bool server_log_json; @@ -80,6 +70,32 @@ static T json_value(const json & body, const std::string & key, const T & defaul } } +// thin wrapper around common_grammar_trigger with (de)serialization functions +struct server_grammar_trigger { + common_grammar_trigger value; + + server_grammar_trigger() = default; + server_grammar_trigger(const common_grammar_trigger& value) : value(value) {} + server_grammar_trigger(const json& in) { + value.type = (common_grammar_trigger_type)in.at("type").get(); + value.value = in.at("value").get(); + if (value.type == COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN) { + value.token = (llama_token)in.at("token").get(); + } + } + + json to_json() const { + json out{ + {"type", (int)value.type}, + {"value", value.value}, + }; + if (value.type == COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN) { + out["token"] = (int)value.token; + } + return out; + } +}; + static inline void server_log(const char * level, const char * function, int line, const char * message, const json & extra) { std::stringstream ss_tid; ss_tid << std::this_thread::get_id(); @@ -126,85 +142,6 @@ static inline void server_log(const char * level, const char * function, int lin // chat template utils // -// Format given chat. If tmpl is empty, we take the template from model metadata -inline std::string format_chat(const struct llama_model * model, common_chat_template tmpl, const std::vector & messages, const json & tools = json::array(), const std::string & model_name = "") { - std::vector chat; - - // Inject tools into the first system message, or create one if none exists - bool tools_injected = false; - - for (size_t i = 0; i < messages.size(); ++i) { - const auto & curr_msg = messages[i]; - - std::string role = json_value(curr_msg, "role", std::string("")); - - std::string content; - if (curr_msg.contains("content")) { - if (curr_msg["content"].is_string()) { - content = curr_msg["content"].get(); - } else if (curr_msg["content"].is_array()) { - for (const auto & part : curr_msg["content"]) { - if (part.contains("text")) { - content += "\n" + part["text"].get(); - } - } - } else { - throw std::runtime_error("Invalid 'content' type (ref: https://github.com/ggerganov/llama.cpp/issues/8367)"); - } - } else { - throw std::runtime_error("Missing 'content' (ref: https://github.com/ggerganov/llama.cpp/issues/8367)"); - } - // Inject tools into the first system message, or create one if none exists - // Only applies to Kimi-K2 models (checked by kimi_k2_should_inject_tools) - if (kimi_k2_should_inject_tools(tools, model_name) && !tools_injected) { - if (role == "system") { - // Add tools to existing system message - content = kimi_k2_inject_tools_to_system(content, tools); - tools_injected = true; - } else if (i == 0) { - // Create system message with tools if no system message exists - std::string tools_prompt = kimi_k2_create_system_with_tools(tools); - chat.push_back({"system", tools_prompt}); - tools_injected = true; - } - } - - // Inject tools for Qwen3 models (XML Hermes format) - if (qwen3_should_inject_tools(tools, model_name) && !tools_injected) { - if (role == "system") { - // Add tools to existing system message - content = qwen3_inject_tools_to_system(content, tools); - tools_injected = true; - } else if (i == 0) { - // Create system message with tools if no system message exists - std::string tools_prompt = qwen3_create_system_with_tools(tools); - chat.push_back({"system", tools_prompt}); - tools_injected = true; - } - } - - // Inject tools for DeepSeek R1 models - if (deepseek_r1_should_inject_tools(tools, model_name) && !tools_injected) { - if (role == "system") { - // Add tools to existing system message - content = deepseek_r1_inject_tools_to_system(content, tools); - tools_injected = true; - } else if (i == 0) { - // Create system message with tools if no system message exists - std::string tools_prompt = deepseek_r1_create_system_with_tools(tools); - chat.push_back({"system", tools_prompt}); - tools_injected = true; - } - } - - chat.push_back({role, content}); - } - auto formatted_chat = llama_chat_apply_template(model, tmpl, chat, true, /* use_jinja= */ false); - - LOG_VERBOSE("formatted_chat", {{"text", formatted_chat.c_str()}}); - return formatted_chat; -} - // // base64 utils (TODO: move to common in the future) // @@ -296,6 +233,10 @@ static std::string gen_chatcmplid() { return chatcmplid.str(); } +static std::string gen_tool_call_id() { + return random_string(); +} + // // other common utils // @@ -314,24 +255,36 @@ static size_t common_part(const std::string & a, const std::string & b) { return i; } -static bool ends_with(const std::string & str, const std::string & suffix) { - return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); -} - -static size_t find_partial_stop_string(const std::string &stop, const std::string &text) { - if (!text.empty() && !stop.empty()) { - const char text_last_char = text.back(); - for (int64_t char_index = stop.size() - 1; char_index >= 0; char_index--) { - if (stop[char_index] == text_last_char) { - const std::string current_partial = stop.substr(0, char_index + 1); - if (ends_with(text, current_partial)) { - return text.size() - char_index - 1; +// return the last index of character that can form a valid string +// if the last character is potentially cut in half, return the index before the cut +// if validate_utf8(text) == text.size(), then the whole text is valid utf8 +static size_t validate_utf8(const std::string& text) { + size_t len = text.size(); + if (len == 0) return 0; + + // Check the last few bytes to see if a multi-byte character is cut off + for (size_t i = 1; i <= 4 && i <= len; ++i) { + unsigned char c = text[len - i]; + // Check for start of a multi-byte sequence from the end + if ((c & 0xE0) == 0xC0) { + // 2-byte character start: 110xxxxx + // Needs at least 2 bytes + if (i < 2) return len - i; } + else if ((c & 0xF0) == 0xE0) { + // 3-byte character start: 1110xxxx + // Needs at least 3 bytes + if (i < 3) return len - i; } + else if ((c & 0xF8) == 0xF0) { + // 4-byte character start: 11110xxx + // Needs at least 4 bytes + if (i < 4) return len - i; } } - return std::string::npos; + // If no cut-off multi-byte character is found, return full length + return len; } // TODO: reuse llama_detokenize @@ -364,13 +317,68 @@ static std::string tokens_to_output_formatted_string(const llama_context * ctx, struct completion_token_output { llama_token tok; std::string text_to_send; + float prob; - struct token_prob { + struct prob_info { llama_token tok; + std::string txt; float prob; }; + std::vector probs; + + json to_json(bool post_sampling_probs) const { + json probs_for_token = json::array(); + for (const auto& p : probs) { + std::string txt(p.txt); + txt.resize(validate_utf8(txt)); + probs_for_token.push_back(json{ + {"id", p.tok}, + {"token", txt}, + {"bytes", str_to_bytes(p.txt)}, + { + post_sampling_probs ? "prob" : "logprob", + post_sampling_probs ? p.prob : logarithm(p.prob) + }, + }); + } + return probs_for_token; + } + + static float logarithm(float x) { + // nlohmann::json converts -inf to null, so we need to prevent that + return x == 0.0f ? std::numeric_limits::lowest() : std::log(x); + } + + static std::vector str_to_bytes(const std::string& str) { + std::vector bytes; + for (unsigned char c : str) { + bytes.push_back(c); + } + return bytes; + } + - std::vector probs; + static json probs_vector_to_json(const std::vector& probs, bool post_sampling_probs) { + json out = json::array(); + for (const auto& p : probs) { + std::string txt(p.text_to_send); + txt.resize(validate_utf8(txt)); + out.push_back(json{ + {"id", p.tok}, + {"token", txt}, + {"bytes", str_to_bytes(p.text_to_send)}, + { + post_sampling_probs ? "prob" : "logprob", + post_sampling_probs ? p.prob : logarithm(p.prob) + }, + { + post_sampling_probs ? "top_probs" : "top_logprobs", + p.to_json(post_sampling_probs) + }, + }); + } + return out; + } }; // convert a vector of completion_token_output to json @@ -398,33 +406,12 @@ static json probs_vector_to_json(const llama_context * ctx, const std::vector unsupported_params{ "best_of", "echo", "suffix" }; + static const std::vector unsupported_params{ "best_of", "suffix" }; for (const auto& param : unsupported_params) { if (body.contains(param)) { throw std::runtime_error("Unsupported param: " + param); @@ -464,59 +456,144 @@ static json oaicompat_completion_params_parse(const json& body) { return llama_params; } -static json oaicompat_chat_completion_params_parse( - const struct llama_model * model, - const json & body, /* openai api json semantics */ - const common_chat_template& tmpl, - bool use_jinja) { +struct oaicompat_parser_options { + bool use_jinja; + bool prefill_assistant; + common_reasoning_format reasoning_format; + std::map chat_template_kwargs; + common_chat_templates* tmpls; + bool allow_image; + bool allow_audio; + bool enable_thinking = true; +}; + +// used by /chat/completions endpoint +static json oaicompat_chat_params_parse( + const struct llama_model* model, + const json& body, /* openai api json semantics */ + const oaicompat_parser_options& opt) +{ json llama_params; llama_params["__oaicompat"] = true; auto tools = json_value(body, "tools", json()); auto has_tools = tools.is_array() && !tools.empty(); + auto stream = json_value(body, "stream", false); + auto tool_choice = json_value(body, "tool_choice", std::string("auto")); - if (has_tools) { - if (use_jinja) { - fprintf(stdout,"tools param is not fully supported yet\n"); - // Extract tools from the request body - json tools = json_value(body, "tools", json::array()); - + /* if (tools.is_array() && !tools.empty()) { + if (stream) { + throw std::runtime_error("Cannot use tools with stream"); } - // Debug: Log system prompt when tools are detected - else { + if (!use_jinja) { throw std::runtime_error("tools param requires --jinja flag"); } } + if (!use_jinja) { + if (body.contains("tool_choice") && !body.at("tool_choice").is_null()) { + throw std::runtime_error("Unsupported param: tool_choice"); + } + }*/ - // Extract model name from the request body - std::string model_name = json_value(body, "model", std::string(DEFAULT_OAICOMPAT_MODEL)); - - // Apply chat template to the list of messages with tools - llama_params["prompt"] = format_chat(model, tmpl, body.at("messages"), tools, model_name); - + if (!opt.use_jinja) { + if (has_tools) { + throw std::runtime_error("tools param requires --jinja flag"); + } + if (tool_choice != "auto") { + throw std::runtime_error("tool_choice param requires --jinja flag"); + } + } // Handle "stop" field if (body.contains("stop") && body.at("stop").is_string()) { - llama_params["stop"] = json::array({body.at("stop").get()}); - } else { + llama_params["stop"] = json::array({ body.at("stop").get() }); + } + else { llama_params["stop"] = json_value(body, "stop", json::array()); } + auto json_schema = json_value(body, "json_schema", json()); + auto grammar = json_value(body, "grammar", std::string()); + if (!json_schema.is_null() && !grammar.empty()) { + throw std::runtime_error("Cannot use both json_schema and grammar"); + } + // Handle "response_format" field if (body.contains("response_format")) { - json response_format = json_value(body, "response_format", json::object()); + json response_format = json_value(body, "response_format", json::object()); std::string response_type = json_value(response_format, "type", std::string()); if (response_type == "json_object") { - llama_params["json_schema"] = json_value(response_format, "schema", json::object()); - } else if (!response_type.empty() && response_type != "text") { - throw std::runtime_error("response_format type must be one of \"text\" or \"json_object\", but got: " + response_type); + json_schema = json_value(response_format, "schema", json::object()); } + else if (response_type == "json_schema") { + auto schema_wrapper = json_value(response_format, "json_schema", json::object()); + json_schema = json_value(schema_wrapper, "schema", json::object()); + } + else if (!response_type.empty() && response_type != "text") { + json_schema = json_value(json_schema, "schema", json::object()); + } + } + common_chat_templates_inputs inputs; + inputs.messages = common_chat_msgs_parse_oaicompat(body.at("messages")); + inputs.tools = common_chat_tools_parse_oaicompat(tools); + inputs.tool_choice = common_chat_tool_choice_parse_oaicompat(tool_choice); + inputs.json_schema = json_schema.is_null() ? "" : json_schema.dump(); + inputs.grammar = grammar; + inputs.use_jinja = opt.use_jinja; + inputs.parallel_tool_calls = json_value(body, "parallel_tool_calls", false); + inputs.add_generation_prompt = json_value(body, "add_generation_prompt", true); + inputs.reasoning_format = opt.reasoning_format; + inputs.enable_thinking = opt.enable_thinking; + if (!inputs.tools.empty() && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) { + if (body.contains("grammar")) { + throw std::runtime_error("Cannot use custom grammar constraints with tools."); + } + llama_params["parse_tool_calls"] = true; + } + + // merge the template args provided from command line with the args provided in the user request + auto chat_template_kwargs_object = json_value(body, "chat_template_kwargs", json::object()); + inputs.chat_template_kwargs = opt.chat_template_kwargs; + for (const auto& item : chat_template_kwargs_object.items()) { + inputs.chat_template_kwargs[item.key()] = item.value().dump(); } + + /*"whether to prefill the assistant's response if the last message is an assistant message (default: prefill enabled)\n" + "when this flag is set, if the last message is an assistant message then it will be treated as a full message and not prefilled\n"*/ + bool prefill_assistant_message = !inputs.messages.empty() && inputs.messages.back().role == "assistant" &&opt.prefill_assistant; + common_chat_msg last_message; + if (prefill_assistant_message) { + last_message = inputs.messages.back(); + inputs.messages.pop_back(); + + /* sanity check, max one assistant message at the end of the list */ + if (!inputs.messages.empty() && inputs.messages.back().role == "assistant") { + throw std::runtime_error("Cannot have 2 or more assistant messages at the end of the list."); + } + + /* TODO: test this properly */ + inputs.reasoning_format = COMMON_REASONING_FORMAT_NONE; + if ((!inputs.enable_thinking) || inputs.chat_template_kwargs.find("enable_thinking") != inputs.chat_template_kwargs.end()) { + throw std::runtime_error("Assistant response prefill is incompatible with enable_thinking."); + } + inputs.add_generation_prompt = true; + } + // Apply chat template to the list of messages - if (use_jinja) { - llama_params["prompt"] = tmpl.apply(body.at("messages"), tools, /* add_generation_prompt= */ true); + auto chat_params = common_chat_templates_apply(opt.tmpls, inputs); + + llama_params["chat_format"] = static_cast(chat_params.format); + llama_params["prompt"] = chat_params.prompt; + llama_params["grammar"] = chat_params.grammar; + llama_params["grammar_lazy"] = chat_params.grammar_lazy; + auto grammar_triggers = json::array(); + for (const auto& trigger : chat_params.grammar_triggers) { + grammar_triggers.push_back(trigger.to_json()); } - else { - llama_params["prompt"] = format_chat(model, tmpl, body.at("messages")); + llama_params["grammar_triggers"] = grammar_triggers; + llama_params["preserved_tokens"] = chat_params.preserved_tokens; + llama_params["thinking_forced_open"] = chat_params.thinking_forced_open; + for (const auto& stop : chat_params.additional_stops) { + llama_params["stop"].push_back(stop); } // Handle "n" field @@ -528,31 +605,20 @@ static json oaicompat_chat_completion_params_parse( // Handle "logprobs" field // TODO: The response format of this option is not yet OAI-compatible, but seems like no one really using it; We may need to fix it in the future if (body.contains("logprobs")) { + if (has_tools && stream) { + throw std::runtime_error("logprobs is not supported with tools + stream"); + } llama_params["n_probs"] = json_value(body, "top_logprobs", 20); - } else if (body.contains("top_logprobs")) { + } + else if (body.contains("top_logprobs")) { throw std::runtime_error("top_logprobs requires logprobs to be set to true"); } - // Handle tool_choice parameter - if (body.contains("tool_choice")) { - auto tool_choice_str = json_value(body, "tool_choice", std::string("auto")); - auto tool_choice = tool_choice_parse_oaicompat(tool_choice_str); - llama_params["tool_choice"] = static_cast(tool_choice); - } - - // Accept tools and tool_choice parameters for function calling support - // Other unsupported params still rejected - static const std::vector unsupported_params { }; - for (auto & param : unsupported_params) { - if (body.contains(param)) { - throw std::runtime_error("Unsupported param: " + param); - } - } // Copy remaining properties to llama_params // This allows user to use llama.cpp-specific params like "mirostat", "tfs_z",... via OAI endpoint. // See "launch_slot_with_task()" for a complete list of params supported by llama.cpp - for (const auto & item : body.items()) { + for (const auto& item : body.items()) { // Exception: if "n_predict" is present, we overwrite the value specified earlier by "max_tokens" if (!llama_params.contains(item.key()) || item.key() == "n_predict") { llama_params[item.key()] = item.value(); @@ -562,6 +628,28 @@ static json oaicompat_chat_completion_params_parse( return llama_params; } +// get value by path(key1 / key2) +static json json_get_nested_values(const std::vector& paths, const json& js) { + json result = json::object(); + + for (const std::string& path : paths) { + json current = js; + const auto keys = string_split(path, /*separator*/ '/'); + bool valid_path = true; + for (const std::string& k : keys) { + if (valid_path && current.is_object() && current.contains(k)) { + current = current[k]; + } + else { + valid_path = false; + } + } + if (valid_path) { + result[path] = current; + } + } + return result; +} static json format_tokenizer_response(const std::vector & tokens) { diff --git a/examples/server/webui/dist/index.html b/examples/server/webui/dist/index.html index 6767625b7..b65376174 100644 --- a/examples/server/webui/dist/index.html +++ b/examples/server/webui/dist/index.html @@ -16,7 +16,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var w1;function L5(){if(w1)return bn;w1=1;var e=Symbol.for("react.element"),t=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),a=Symbol.for("react.profiler"),l=Symbol.for("react.provider"),s=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),g=Symbol.iterator;function w(ee){return ee===null||typeof ee!="object"?null:(ee=g&&ee[g]||ee["@@iterator"],typeof ee=="function"?ee:null)}var b={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},_=Object.assign,T={};function D(ee,Ne,H){this.props=ee,this.context=Ne,this.refs=T,this.updater=H||b}D.prototype.isReactComponent={},D.prototype.setState=function(ee,Ne){if(typeof ee!="object"&&typeof ee!="function"&&ee!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,ee,Ne,"setState")},D.prototype.forceUpdate=function(ee){this.updater.enqueueForceUpdate(this,ee,"forceUpdate")};function R(){}R.prototype=D.prototype;function U(ee,Ne,H){this.props=ee,this.context=Ne,this.refs=T,this.updater=H||b}var F=U.prototype=new R;F.constructor=U,_(F,D.prototype),F.isPureReactComponent=!0;var oe=Array.isArray,ie=Object.prototype.hasOwnProperty,K={current:null},we={key:!0,ref:!0,__self:!0,__source:!0};function Oe(ee,Ne,H){var He,yt={},vt=null,xe=null;if(Ne!=null)for(He in Ne.ref!==void 0&&(xe=Ne.ref),Ne.key!==void 0&&(vt=""+Ne.key),Ne)ie.call(Ne,He)&&!we.hasOwnProperty(He)&&(yt[He]=Ne[He]);var ve=arguments.length-2;if(ve===1)yt.children=H;else if(1>>1,Ne=Me[ee];if(0>>1;eea(yt,L))vta(xe,yt)?(Me[ee]=xe,Me[vt]=L,ee=vt):(Me[ee]=yt,Me[He]=L,ee=He);else if(vta(xe,L))Me[ee]=xe,Me[vt]=L,ee=vt;else break e}}return Ve}function a(Me,Ve){var L=Me.sortIndex-Ve.sortIndex;return L!==0?L:Me.id-Ve.id}if(typeof performance=="object"&&typeof performance.now=="function"){var l=performance;e.unstable_now=function(){return l.now()}}else{var s=Date,c=s.now();e.unstable_now=function(){return s.now()-c}}var f=[],d=[],m=1,g=null,w=3,b=!1,_=!1,T=!1,D=typeof setTimeout=="function"?setTimeout:null,R=typeof clearTimeout=="function"?clearTimeout:null,U=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function F(Me){for(var Ve=n(d);Ve!==null;){if(Ve.callback===null)r(d);else if(Ve.startTime<=Me)r(d),Ve.sortIndex=Ve.expirationTime,t(f,Ve);else break;Ve=n(d)}}function oe(Me){if(T=!1,F(Me),!_)if(n(f)!==null)_=!0,et(ie);else{var Ve=n(d);Ve!==null&&ft(oe,Ve.startTime-Me)}}function ie(Me,Ve){_=!1,T&&(T=!1,R(Oe),Oe=-1),b=!0;var L=w;try{for(F(Ve),g=n(f);g!==null&&(!(g.expirationTime>Ve)||Me&&!ne());){var ee=g.callback;if(typeof ee=="function"){g.callback=null,w=g.priorityLevel;var Ne=ee(g.expirationTime<=Ve);Ve=e.unstable_now(),typeof Ne=="function"?g.callback=Ne:g===n(f)&&r(f),F(Ve)}else r(f);g=n(f)}if(g!==null)var H=!0;else{var He=n(d);He!==null&&ft(oe,He.startTime-Ve),H=!1}return H}finally{g=null,w=L,b=!1}}var K=!1,we=null,Oe=-1,V=5,C=-1;function ne(){return!(e.unstable_now()-CMe||125ee?(Me.sortIndex=L,t(d,Me),n(f)===null&&Me===n(d)&&(T?(R(Oe),Oe=-1):T=!0,ft(oe,L-ee))):(Me.sortIndex=Ne,t(f,Me),_||b||(_=!0,et(ie))),Me},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(Me){var Ve=w;return function(){var L=w;w=Ve;try{return Me.apply(this,arguments)}finally{w=L}}}}(bd)),bd}var _1;function j5(){return _1||(_1=1,vd.exports=U5()),vd.exports}/** + */var S1;function U5(){return S1||(S1=1,function(e){function t(Me,Ke){var L=Me.length;Me.push(Ke);e:for(;0>>1,Te=Me[ee];if(0>>1;eea(yt,L))bta(pe,yt)?(Me[ee]=pe,Me[bt]=L,ee=bt):(Me[ee]=yt,Me[Ve]=L,ee=Ve);else if(bta(pe,L))Me[ee]=pe,Me[bt]=L,ee=bt;else break e}}return Ke}function a(Me,Ke){var L=Me.sortIndex-Ke.sortIndex;return L!==0?L:Me.id-Ke.id}if(typeof performance=="object"&&typeof performance.now=="function"){var l=performance;e.unstable_now=function(){return l.now()}}else{var s=Date,c=s.now();e.unstable_now=function(){return s.now()-c}}var f=[],d=[],m=1,g=null,w=3,b=!1,_=!1,T=!1,D=typeof setTimeout=="function"?setTimeout:null,R=typeof clearTimeout=="function"?clearTimeout:null,F=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function U(Me){for(var Ke=n(d);Ke!==null;){if(Ke.callback===null)r(d);else if(Ke.startTime<=Me)r(d),Ke.sortIndex=Ke.expirationTime,t(f,Ke);else break;Ke=n(d)}}function le(Me){if(T=!1,U(Me),!_)if(n(f)!==null)_=!0,et(ie);else{var Ke=n(d);Ke!==null&&ft(le,Ke.startTime-Me)}}function ie(Me,Ke){_=!1,T&&(T=!1,R(Ee),Ee=-1),b=!0;var L=w;try{for(U(Ke),g=n(f);g!==null&&(!(g.expirationTime>Ke)||Me&&!ne());){var ee=g.callback;if(typeof ee=="function"){g.callback=null,w=g.priorityLevel;var Te=ee(g.expirationTime<=Ke);Ke=e.unstable_now(),typeof Te=="function"?g.callback=Te:g===n(f)&&r(f),U(Ke)}else r(f);g=n(f)}if(g!==null)var H=!0;else{var Ve=n(d);Ve!==null&&ft(le,Ve.startTime-Ke),H=!1}return H}finally{g=null,w=L,b=!1}}var V=!1,ve=null,Ee=-1,K=5,C=-1;function ne(){return!(e.unstable_now()-CMe||125ee?(Me.sortIndex=L,t(d,Me),n(f)===null&&Me===n(d)&&(T?(R(Ee),Ee=-1):T=!0,ft(le,L-ee))):(Me.sortIndex=Te,t(f,Me),_||b||(_=!0,et(ie))),Me},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(Me){var Ke=w;return function(){var L=w;w=Ke;try{return Me.apply(this,arguments)}finally{w=L}}}}(bd)),bd}var _1;function j5(){return _1||(_1=1,vd.exports=U5()),vd.exports}/** * @license React * react-dom.production.min.js * @@ -40,14 +40,14 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var N1;function $5(){if(N1)return hi;N1=1;var e=jh(),t=j5();function n(i){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+i,u=1;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),f=Object.prototype.hasOwnProperty,d=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,m={},g={};function w(i){return f.call(g,i)?!0:f.call(m,i)?!1:d.test(i)?g[i]=!0:(m[i]=!0,!1)}function b(i,o,u,p){if(u!==null&&u.type===0)return!1;switch(typeof o){case"function":case"symbol":return!0;case"boolean":return p?!1:u!==null?!u.acceptsBooleans:(i=i.toLowerCase().slice(0,5),i!=="data-"&&i!=="aria-");default:return!1}}function _(i,o,u,p){if(o===null||typeof o>"u"||b(i,o,u,p))return!0;if(p)return!1;if(u!==null)switch(u.type){case 3:return!o;case 4:return o===!1;case 5:return isNaN(o);case 6:return isNaN(o)||1>o}return!1}function T(i,o,u,p,v,E,I){this.acceptsBooleans=o===2||o===3||o===4,this.attributeName=p,this.attributeNamespace=v,this.mustUseProperty=u,this.propertyName=i,this.type=o,this.sanitizeURL=E,this.removeEmptyString=I}var D={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(i){D[i]=new T(i,0,!1,i,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(i){var o=i[0];D[o]=new T(o,1,!1,i[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(i){D[i]=new T(i,2,!1,i.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(i){D[i]=new T(i,2,!1,i,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(i){D[i]=new T(i,3,!1,i.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(i){D[i]=new T(i,3,!0,i,null,!1,!1)}),["capture","download"].forEach(function(i){D[i]=new T(i,4,!1,i,null,!1,!1)}),["cols","rows","size","span"].forEach(function(i){D[i]=new T(i,6,!1,i,null,!1,!1)}),["rowSpan","start"].forEach(function(i){D[i]=new T(i,5,!1,i.toLowerCase(),null,!1,!1)});var R=/[\-:]([a-z])/g;function U(i){return i[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(i){var o=i.replace(R,U);D[o]=new T(o,1,!1,i,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(i){var o=i.replace(R,U);D[o]=new T(o,1,!1,i,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(i){var o=i.replace(R,U);D[o]=new T(o,1,!1,i,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(i){D[i]=new T(i,1,!1,i.toLowerCase(),null,!1,!1)}),D.xlinkHref=new T("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(i){D[i]=new T(i,1,!1,i.toLowerCase(),null,!0,!0)});function F(i,o,u,p){var v=D.hasOwnProperty(o)?D[o]:null;(v!==null?v.type!==0:p||!(2"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),f=Object.prototype.hasOwnProperty,d=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,m={},g={};function w(i){return f.call(g,i)?!0:f.call(m,i)?!1:d.test(i)?g[i]=!0:(m[i]=!0,!1)}function b(i,o,u,p){if(u!==null&&u.type===0)return!1;switch(typeof o){case"function":case"symbol":return!0;case"boolean":return p?!1:u!==null?!u.acceptsBooleans:(i=i.toLowerCase().slice(0,5),i!=="data-"&&i!=="aria-");default:return!1}}function _(i,o,u,p){if(o===null||typeof o>"u"||b(i,o,u,p))return!0;if(p)return!1;if(u!==null)switch(u.type){case 3:return!o;case 4:return o===!1;case 5:return isNaN(o);case 6:return isNaN(o)||1>o}return!1}function T(i,o,u,p,v,E,I){this.acceptsBooleans=o===2||o===3||o===4,this.attributeName=p,this.attributeNamespace=v,this.mustUseProperty=u,this.propertyName=i,this.type=o,this.sanitizeURL=E,this.removeEmptyString=I}var D={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(i){D[i]=new T(i,0,!1,i,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(i){var o=i[0];D[o]=new T(o,1,!1,i[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(i){D[i]=new T(i,2,!1,i.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(i){D[i]=new T(i,2,!1,i,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(i){D[i]=new T(i,3,!1,i.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(i){D[i]=new T(i,3,!0,i,null,!1,!1)}),["capture","download"].forEach(function(i){D[i]=new T(i,4,!1,i,null,!1,!1)}),["cols","rows","size","span"].forEach(function(i){D[i]=new T(i,6,!1,i,null,!1,!1)}),["rowSpan","start"].forEach(function(i){D[i]=new T(i,5,!1,i.toLowerCase(),null,!1,!1)});var R=/[\-:]([a-z])/g;function F(i){return i[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(i){var o=i.replace(R,F);D[o]=new T(o,1,!1,i,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(i){var o=i.replace(R,F);D[o]=new T(o,1,!1,i,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(i){var o=i.replace(R,F);D[o]=new T(o,1,!1,i,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(i){D[i]=new T(i,1,!1,i.toLowerCase(),null,!1,!1)}),D.xlinkHref=new T("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(i){D[i]=new T(i,1,!1,i.toLowerCase(),null,!0,!0)});function U(i,o,u,p){var v=D.hasOwnProperty(o)?D[o]:null;(v!==null?v.type!==0:p||!(2X||v[I]!==E[X]){var ae=` -`+v[I].replace(" at new "," at ");return i.displayName&&ae.includes("")&&(ae=ae.replace("",i.displayName)),ae}while(1<=I&&0<=X);break}}}finally{H=!1,Error.prepareStackTrace=u}return(i=i?i.displayName||i.name:"")?Ne(i):""}function yt(i){switch(i.tag){case 5:return Ne(i.type);case 16:return Ne("Lazy");case 13:return Ne("Suspense");case 19:return Ne("SuspenseList");case 0:case 2:case 15:return i=He(i.type,!1),i;case 11:return i=He(i.type.render,!1),i;case 1:return i=He(i.type,!0),i;default:return""}}function vt(i){if(i==null)return null;if(typeof i=="function")return i.displayName||i.name||null;if(typeof i=="string")return i;switch(i){case we:return"Fragment";case K:return"Portal";case V:return"Profiler";case Oe:return"StrictMode";case De:return"Suspense";case Ee:return"SuspenseList"}if(typeof i=="object")switch(i.$$typeof){case ne:return(i.displayName||"Context")+".Consumer";case C:return(i._context.displayName||"Context")+".Provider";case G:var o=i.render;return i=i.displayName,i||(i=o.displayName||o.name||"",i=i!==""?"ForwardRef("+i+")":"ForwardRef"),i;case _e:return o=i.displayName||null,o!==null?o:vt(i.type)||"Memo";case et:o=i._payload,i=i._init;try{return vt(i(o))}catch{}}return null}function xe(i){var o=i.type;switch(i.tag){case 24:return"Cache";case 9:return(o.displayName||"Context")+".Consumer";case 10:return(o._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return i=o.render,i=i.displayName||i.name||"",o.displayName||(i!==""?"ForwardRef("+i+")":"ForwardRef");case 7:return"Fragment";case 5:return o;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return vt(o);case 8:return o===Oe?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof o=="function")return o.displayName||o.name||null;if(typeof o=="string")return o}return null}function ve(i){switch(typeof i){case"boolean":case"number":case"string":case"undefined":return i;case"object":return i;default:return""}}function le(i){var o=i.type;return(i=i.nodeName)&&i.toLowerCase()==="input"&&(o==="checkbox"||o==="radio")}function ze(i){var o=le(i)?"checked":"value",u=Object.getOwnPropertyDescriptor(i.constructor.prototype,o),p=""+i[o];if(!i.hasOwnProperty(o)&&typeof u<"u"&&typeof u.get=="function"&&typeof u.set=="function"){var v=u.get,E=u.set;return Object.defineProperty(i,o,{configurable:!0,get:function(){return v.call(this)},set:function(I){p=""+I,E.call(this,I)}}),Object.defineProperty(i,o,{enumerable:u.enumerable}),{getValue:function(){return p},setValue:function(I){p=""+I},stopTracking:function(){i._valueTracker=null,delete i[o]}}}}function We(i){i._valueTracker||(i._valueTracker=ze(i))}function Et(i){if(!i)return!1;var o=i._valueTracker;if(!o)return!0;var u=o.getValue(),p="";return i&&(p=le(i)?i.checked?"true":"false":i.value),i=p,i!==u?(o.setValue(i),!0):!1}function Ht(i){if(i=i||(typeof document<"u"?document:void 0),typeof i>"u")return null;try{return i.activeElement||i.body}catch{return i.body}}function Yt(i,o){var u=o.checked;return L({},o,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:u??i._wrapperState.initialChecked})}function _t(i,o){var u=o.defaultValue==null?"":o.defaultValue,p=o.checked!=null?o.checked:o.defaultChecked;u=ve(o.value!=null?o.value:u),i._wrapperState={initialChecked:p,initialValue:u,controlled:o.type==="checkbox"||o.type==="radio"?o.checked!=null:o.value!=null}}function $t(i,o){o=o.checked,o!=null&&F(i,"checked",o,!1)}function _n(i,o){$t(i,o);var u=ve(o.value),p=o.type;if(u!=null)p==="number"?(u===0&&i.value===""||i.value!=u)&&(i.value=""+u):i.value!==""+u&&(i.value=""+u);else if(p==="submit"||p==="reset"){i.removeAttribute("value");return}o.hasOwnProperty("value")?In(i,o.type,u):o.hasOwnProperty("defaultValue")&&In(i,o.type,ve(o.defaultValue)),o.checked==null&&o.defaultChecked!=null&&(i.defaultChecked=!!o.defaultChecked)}function Nn(i,o,u){if(o.hasOwnProperty("value")||o.hasOwnProperty("defaultValue")){var p=o.type;if(!(p!=="submit"&&p!=="reset"||o.value!==void 0&&o.value!==null))return;o=""+i._wrapperState.initialValue,u||o===i.value||(i.value=o),i.defaultValue=o}u=i.name,u!==""&&(i.name=""),i.defaultChecked=!!i._wrapperState.initialChecked,u!==""&&(i.name=u)}function In(i,o,u){(o!=="number"||Ht(i.ownerDocument)!==i)&&(u==null?i.defaultValue=""+i._wrapperState.initialValue:i.defaultValue!==""+u&&(i.defaultValue=""+u))}var Bn=Array.isArray;function On(i,o,u,p){if(i=i.options,o){o={};for(var v=0;v"+o.valueOf().toString()+"",o=St.firstChild;i.firstChild;)i.removeChild(i.firstChild);for(;o.firstChild;)i.appendChild(o.firstChild)}});function z(i,o){if(o){var u=i.firstChild;if(u&&u===i.lastChild&&u.nodeType===3){u.nodeValue=o;return}}i.textContent=o}var O={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},$=["Webkit","ms","Moz","O"];Object.keys(O).forEach(function(i){$.forEach(function(o){o=o+i.charAt(0).toUpperCase()+i.substring(1),O[o]=O[i]})});function re(i,o,u){return o==null||typeof o=="boolean"||o===""?"":u||typeof o!="number"||o===0||O.hasOwnProperty(i)&&O[i]?(""+o).trim():o+"px"}function pe(i,o){i=i.style;for(var u in o)if(o.hasOwnProperty(u)){var p=u.indexOf("--")===0,v=re(u,o[u],p);u==="float"&&(u="cssFloat"),p?i.setProperty(u,v):i[u]=v}}var Je=L({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Re(i,o){if(o){if(Je[i]&&(o.children!=null||o.dangerouslySetInnerHTML!=null))throw Error(n(137,i));if(o.dangerouslySetInnerHTML!=null){if(o.children!=null)throw Error(n(60));if(typeof o.dangerouslySetInnerHTML!="object"||!("__html"in o.dangerouslySetInnerHTML))throw Error(n(61))}if(o.style!=null&&typeof o.style!="object")throw Error(n(62))}}function Nt(i,o){if(i.indexOf("-")===-1)return typeof o.is=="string";switch(i){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var rt=null;function zt(i){return i=i.target||i.srcElement||window,i.correspondingUseElement&&(i=i.correspondingUseElement),i.nodeType===3?i.parentNode:i}var Ke=null,Qt=null,dn=null;function nn(i){if(i=Bs(i)){if(typeof Ke!="function")throw Error(n(280));var o=i.stateNode;o&&(o=Ju(o),Ke(i.stateNode,i.type,o))}}function W(i){Qt?dn?dn.push(i):dn=[i]:Qt=i}function Pe(){if(Qt){var i=Qt,o=dn;if(dn=Qt=null,nn(i),o)for(i=0;i>>=0,i===0?32:31-(yi(i)/Ma|0)|0}var mn=64,vi=4194304;function ai(i){switch(i&-i){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return i&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return i}}function oi(i,o){var u=i.pendingLanes;if(u===0)return 0;var p=0,v=i.suspendedLanes,E=i.pingedLanes,I=u&268435455;if(I!==0){var X=I&~v;X!==0?p=ai(X):(E&=I,E!==0&&(p=ai(E)))}else I=u&~v,I!==0?p=ai(I):E!==0&&(p=ai(E));if(p===0)return 0;if(o!==0&&o!==p&&!(o&v)&&(v=p&-p,E=o&-o,v>=E||v===16&&(E&4194240)!==0))return o;if(p&4&&(p|=u&16),o=i.entangledLanes,o!==0)for(i=i.entanglements,o&=p;0u;u++)o.push(i);return o}function Yi(i,o,u){i.pendingLanes|=o,o!==536870912&&(i.suspendedLanes=0,i.pingedLanes=0),i=i.eventTimes,o=31-Ut(o),i[o]=u}function Mr(i,o){var u=i.pendingLanes&~o;i.pendingLanes=o,i.suspendedLanes=0,i.pingedLanes=0,i.expiredLanes&=o,i.mutableReadLanes&=o,i.entangledLanes&=o,o=i.entanglements;var p=i.eventTimes;for(i=i.expirationTimes;0=po),Vu=" ",Gu=!1;function ol(i,o){switch(i){case"keyup":return qu.indexOf(o.keyCode)!==-1;case"keydown":return o.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Li(i){return i=i.detail,typeof i=="object"&&"data"in i?i.data:null}var Ln=!1;function ql(i,o){switch(i){case"compositionend":return Li(o);case"keypress":return o.which!==32?null:(Gu=!0,Vu);case"textInput":return i=o.data,i===Vu&&Gu?null:i;default:return null}}function li(i,o){if(Ln)return i==="compositionend"||!al&&ol(i,o)?(i=Pl(),fo=Ns=pa=null,Ln=!1,i):null;switch(i){case"paste":return null;case"keypress":if(!(o.ctrlKey||o.altKey||o.metaKey)||o.ctrlKey&&o.altKey){if(o.char&&1=o)return{node:u,offset:o-i};i=p}e:{for(;u;){if(u.nextSibling){u=u.nextSibling;break e}u=u.parentNode}u=void 0}u=ue(u)}}function be(i,o){return i&&o?i===o?!0:i&&i.nodeType===3?!1:o&&o.nodeType===3?be(i,o.parentNode):"contains"in i?i.contains(o):i.compareDocumentPosition?!!(i.compareDocumentPosition(o)&16):!1:!1}function he(){for(var i=window,o=Ht();o instanceof i.HTMLIFrameElement;){try{var u=typeof o.contentWindow.location.href=="string"}catch{u=!1}if(u)i=o.contentWindow;else break;o=Ht(i.document)}return o}function Ce(i){var o=i&&i.nodeName&&i.nodeName.toLowerCase();return o&&(o==="input"&&(i.type==="text"||i.type==="search"||i.type==="tel"||i.type==="url"||i.type==="password")||o==="textarea"||i.contentEditable==="true")}function Be(i){var o=he(),u=i.focusedElem,p=i.selectionRange;if(o!==u&&u&&u.ownerDocument&&be(u.ownerDocument.documentElement,u)){if(p!==null&&Ce(u)){if(o=p.start,i=p.end,i===void 0&&(i=o),"selectionStart"in u)u.selectionStart=o,u.selectionEnd=Math.min(i,u.value.length);else if(i=(o=u.ownerDocument||document)&&o.defaultView||window,i.getSelection){i=i.getSelection();var v=u.textContent.length,E=Math.min(p.start,v);p=p.end===void 0?E:Math.min(p.end,v),!i.extend&&E>p&&(v=p,p=E,E=v),v=me(u,E);var I=me(u,p);v&&I&&(i.rangeCount!==1||i.anchorNode!==v.node||i.anchorOffset!==v.offset||i.focusNode!==I.node||i.focusOffset!==I.offset)&&(o=o.createRange(),o.setStart(v.node,v.offset),i.removeAllRanges(),E>p?(i.addRange(o),i.extend(I.node,I.offset)):(o.setEnd(I.node,I.offset),i.addRange(o)))}}for(o=[],i=u;i=i.parentNode;)i.nodeType===1&&o.push({element:i,left:i.scrollLeft,top:i.scrollTop});for(typeof u.focus=="function"&&u.focus(),u=0;u=document.documentMode,je=null,it=null,gt=null,at=!1;function dt(i,o,u){var p=u.window===u?u.document:u.nodeType===9?u:u.ownerDocument;at||je==null||je!==Ht(p)||(p=je,"selectionStart"in p&&Ce(p)?p={start:p.selectionStart,end:p.selectionEnd}:(p=(p.ownerDocument&&p.ownerDocument.defaultView||window).getSelection(),p={anchorNode:p.anchorNode,anchorOffset:p.anchorOffset,focusNode:p.focusNode,focusOffset:p.focusOffset}),gt&&ye(gt,p)||(gt=p,p=Xu(it,"onSelect"),0Wl||(i.current=ff[Wl],ff[Wl]=null,Wl--)}function qn(i,o){Wl++,ff[Wl]=i.current,i.current=o}var vo={},Kr=yo(vo),si=yo(!1),cl=vo;function Yl(i,o){var u=i.type.contextTypes;if(!u)return vo;var p=i.stateNode;if(p&&p.__reactInternalMemoizedUnmaskedChildContext===o)return p.__reactInternalMemoizedMaskedChildContext;var v={},E;for(E in u)v[E]=o[E];return p&&(i=i.stateNode,i.__reactInternalMemoizedUnmaskedChildContext=o,i.__reactInternalMemoizedMaskedChildContext=v),v}function ui(i){return i=i.childContextTypes,i!=null}function ec(){Wn(si),Wn(Kr)}function Up(i,o,u){if(Kr.current!==vo)throw Error(n(168));qn(Kr,o),qn(si,u)}function jp(i,o,u){var p=i.stateNode;if(o=o.childContextTypes,typeof p.getChildContext!="function")return u;p=p.getChildContext();for(var v in p)if(!(v in o))throw Error(n(108,xe(i)||"Unknown",v));return L({},u,p)}function tc(i){return i=(i=i.stateNode)&&i.__reactInternalMemoizedMergedChildContext||vo,cl=Kr.current,qn(Kr,i),qn(si,si.current),!0}function $p(i,o,u){var p=i.stateNode;if(!p)throw Error(n(169));u?(i=jp(i,o,cl),p.__reactInternalMemoizedMergedChildContext=i,Wn(si),Wn(Kr),qn(Kr,i)):Wn(si),qn(si,u)}var ja=null,nc=!1,df=!1;function qp(i){ja===null?ja=[i]:ja.push(i)}function Qv(i){nc=!0,qp(i)}function bo(){if(!df&&ja!==null){df=!0;var i=0,o=Sn;try{var u=ja;for(Sn=1;i>=I,v-=I,$a=1<<32-Ut(o)+v|u<tn?(Pr=Vt,Vt=null):Pr=Vt.sibling;var An=Xe(Se,Vt,Ae[tn],pt);if(An===null){Vt===null&&(Vt=Pr);break}i&&Vt&&An.alternate===null&&o(Se,Vt),de=E(An,de,tn),Kt===null?Ft=An:Kt.sibling=An,Kt=An,Vt=Pr}if(tn===Ae.length)return u(Se,Vt),Jn&&dl(Se,tn),Ft;if(Vt===null){for(;tntn?(Pr=Vt,Vt=null):Pr=Vt.sibling;var Co=Xe(Se,Vt,An.value,pt);if(Co===null){Vt===null&&(Vt=Pr);break}i&&Vt&&Co.alternate===null&&o(Se,Vt),de=E(Co,de,tn),Kt===null?Ft=Co:Kt.sibling=Co,Kt=Co,Vt=Pr}if(An.done)return u(Se,Vt),Jn&&dl(Se,tn),Ft;if(Vt===null){for(;!An.done;tn++,An=Ae.next())An=tt(Se,An.value,pt),An!==null&&(de=E(An,de,tn),Kt===null?Ft=An:Kt.sibling=An,Kt=An);return Jn&&dl(Se,tn),Ft}for(Vt=p(Se,Vt);!An.done;tn++,An=Ae.next())An=At(Vt,Se,tn,An.value,pt),An!==null&&(i&&An.alternate!==null&&Vt.delete(An.key===null?tn:An.key),de=E(An,de,tn),Kt===null?Ft=An:Kt.sibling=An,Kt=An);return i&&Vt.forEach(function(R5){return o(Se,R5)}),Jn&&dl(Se,tn),Ft}function yr(Se,de,Ae,pt){if(typeof Ae=="object"&&Ae!==null&&Ae.type===we&&Ae.key===null&&(Ae=Ae.props.children),typeof Ae=="object"&&Ae!==null){switch(Ae.$$typeof){case ie:e:{for(var Ft=Ae.key,Kt=de;Kt!==null;){if(Kt.key===Ft){if(Ft=Ae.type,Ft===we){if(Kt.tag===7){u(Se,Kt.sibling),de=v(Kt,Ae.props.children),de.return=Se,Se=de;break e}}else if(Kt.elementType===Ft||typeof Ft=="object"&&Ft!==null&&Ft.$$typeof===et&&Yp(Ft)===Kt.type){u(Se,Kt.sibling),de=v(Kt,Ae.props),de.ref=Fs(Se,Kt,Ae),de.return=Se,Se=de;break e}u(Se,Kt);break}else o(Se,Kt);Kt=Kt.sibling}Ae.type===we?(de=wl(Ae.props.children,Se.mode,pt,Ae.key),de.return=Se,Se=de):(pt=Oc(Ae.type,Ae.key,Ae.props,null,Se.mode,pt),pt.ref=Fs(Se,de,Ae),pt.return=Se,Se=pt)}return I(Se);case K:e:{for(Kt=Ae.key;de!==null;){if(de.key===Kt)if(de.tag===4&&de.stateNode.containerInfo===Ae.containerInfo&&de.stateNode.implementation===Ae.implementation){u(Se,de.sibling),de=v(de,Ae.children||[]),de.return=Se,Se=de;break e}else{u(Se,de);break}else o(Se,de);de=de.sibling}de=ud(Ae,Se.mode,pt),de.return=Se,Se=de}return I(Se);case et:return Kt=Ae._init,yr(Se,de,Kt(Ae._payload),pt)}if(Bn(Ae))return Lt(Se,de,Ae,pt);if(Ve(Ae))return Bt(Se,de,Ae,pt);oc(Se,Ae)}return typeof Ae=="string"&&Ae!==""||typeof Ae=="number"?(Ae=""+Ae,de!==null&&de.tag===6?(u(Se,de.sibling),de=v(de,Ae),de.return=Se,Se=de):(u(Se,de),de=sd(Ae,Se.mode,pt),de.return=Se,Se=de),I(Se)):u(Se,de)}return yr}var Jl=Xp(!0),Zp=Xp(!1),lc=yo(null),sc=null,es=null,vf=null;function bf(){vf=es=sc=null}function wf(i){var o=lc.current;Wn(lc),i._currentValue=o}function xf(i,o,u){for(;i!==null;){var p=i.alternate;if((i.childLanes&o)!==o?(i.childLanes|=o,p!==null&&(p.childLanes|=o)):p!==null&&(p.childLanes&o)!==o&&(p.childLanes|=o),i===u)break;i=i.return}}function ts(i,o){sc=i,vf=es=null,i=i.dependencies,i!==null&&i.firstContext!==null&&(i.lanes&o&&(ci=!0),i.firstContext=null)}function Fi(i){var o=i._currentValue;if(vf!==i)if(i={context:i,memoizedValue:o,next:null},es===null){if(sc===null)throw Error(n(308));es=i,sc.dependencies={lanes:0,firstContext:i}}else es=es.next=i;return o}var hl=null;function kf(i){hl===null?hl=[i]:hl.push(i)}function Qp(i,o,u,p){var v=o.interleaved;return v===null?(u.next=u,kf(o)):(u.next=v.next,v.next=u),o.interleaved=u,Ha(i,p)}function Ha(i,o){i.lanes|=o;var u=i.alternate;for(u!==null&&(u.lanes|=o),u=i,i=i.return;i!==null;)i.childLanes|=o,u=i.alternate,u!==null&&(u.childLanes|=o),u=i,i=i.return;return u.tag===3?u.stateNode:null}var wo=!1;function Ef(i){i.updateQueue={baseState:i.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Jp(i,o){i=i.updateQueue,o.updateQueue===i&&(o.updateQueue={baseState:i.baseState,firstBaseUpdate:i.firstBaseUpdate,lastBaseUpdate:i.lastBaseUpdate,shared:i.shared,effects:i.effects})}function Ka(i,o){return{eventTime:i,lane:o,tag:0,payload:null,callback:null,next:null}}function xo(i,o,u){var p=i.updateQueue;if(p===null)return null;if(p=p.shared,Tn&2){var v=p.pending;return v===null?o.next=o:(o.next=v.next,v.next=o),p.pending=o,Ha(i,u)}return v=p.interleaved,v===null?(o.next=o,kf(p)):(o.next=v.next,v.next=o),p.interleaved=o,Ha(i,u)}function uc(i,o,u){if(o=o.updateQueue,o!==null&&(o=o.shared,(u&4194240)!==0)){var p=o.lanes;p&=i.pendingLanes,u|=p,o.lanes=u,lo(i,u)}}function em(i,o){var u=i.updateQueue,p=i.alternate;if(p!==null&&(p=p.updateQueue,u===p)){var v=null,E=null;if(u=u.firstBaseUpdate,u!==null){do{var I={eventTime:u.eventTime,lane:u.lane,tag:u.tag,payload:u.payload,callback:u.callback,next:null};E===null?v=E=I:E=E.next=I,u=u.next}while(u!==null);E===null?v=E=o:E=E.next=o}else v=E=o;u={baseState:p.baseState,firstBaseUpdate:v,lastBaseUpdate:E,shared:p.shared,effects:p.effects},i.updateQueue=u;return}i=u.lastBaseUpdate,i===null?u.firstBaseUpdate=o:i.next=o,u.lastBaseUpdate=o}function cc(i,o,u,p){var v=i.updateQueue;wo=!1;var E=v.firstBaseUpdate,I=v.lastBaseUpdate,X=v.shared.pending;if(X!==null){v.shared.pending=null;var ae=X,Le=ae.next;ae.next=null,I===null?E=Le:I.next=Le,I=ae;var Qe=i.alternate;Qe!==null&&(Qe=Qe.updateQueue,X=Qe.lastBaseUpdate,X!==I&&(X===null?Qe.firstBaseUpdate=Le:X.next=Le,Qe.lastBaseUpdate=ae))}if(E!==null){var tt=v.baseState;I=0,Qe=Le=ae=null,X=E;do{var Xe=X.lane,At=X.eventTime;if((p&Xe)===Xe){Qe!==null&&(Qe=Qe.next={eventTime:At,lane:0,tag:X.tag,payload:X.payload,callback:X.callback,next:null});e:{var Lt=i,Bt=X;switch(Xe=o,At=u,Bt.tag){case 1:if(Lt=Bt.payload,typeof Lt=="function"){tt=Lt.call(At,tt,Xe);break e}tt=Lt;break e;case 3:Lt.flags=Lt.flags&-65537|128;case 0:if(Lt=Bt.payload,Xe=typeof Lt=="function"?Lt.call(At,tt,Xe):Lt,Xe==null)break e;tt=L({},tt,Xe);break e;case 2:wo=!0}}X.callback!==null&&X.lane!==0&&(i.flags|=64,Xe=v.effects,Xe===null?v.effects=[X]:Xe.push(X))}else At={eventTime:At,lane:Xe,tag:X.tag,payload:X.payload,callback:X.callback,next:null},Qe===null?(Le=Qe=At,ae=tt):Qe=Qe.next=At,I|=Xe;if(X=X.next,X===null){if(X=v.shared.pending,X===null)break;Xe=X,X=Xe.next,Xe.next=null,v.lastBaseUpdate=Xe,v.shared.pending=null}}while(!0);if(Qe===null&&(ae=tt),v.baseState=ae,v.firstBaseUpdate=Le,v.lastBaseUpdate=Qe,o=v.shared.interleaved,o!==null){v=o;do I|=v.lane,v=v.next;while(v!==o)}else E===null&&(v.shared.lanes=0);gl|=I,i.lanes=I,i.memoizedState=tt}}function tm(i,o,u){if(i=o.effects,o.effects=null,i!==null)for(o=0;ou?u:4,i(!0);var p=Cf.transition;Cf.transition={};try{i(!1),o()}finally{Sn=u,Cf.transition=p}}function wm(){return Ui().memoizedState}function n5(i,o,u){var p=_o(i);if(u={lane:p,action:u,hasEagerState:!1,eagerState:null,next:null},xm(i))km(o,u);else if(u=Qp(i,o,u,p),u!==null){var v=Jr();ta(u,i,p,v),Em(u,o,p)}}function r5(i,o,u){var p=_o(i),v={lane:p,action:u,hasEagerState:!1,eagerState:null,next:null};if(xm(i))km(o,v);else{var E=i.alternate;if(i.lanes===0&&(E===null||E.lanes===0)&&(E=o.lastRenderedReducer,E!==null))try{var I=o.lastRenderedState,X=E(I,u);if(v.hasEagerState=!0,v.eagerState=X,te(X,I)){var ae=o.interleaved;ae===null?(v.next=v,kf(o)):(v.next=ae.next,ae.next=v),o.interleaved=v;return}}catch{}finally{}u=Qp(i,o,v,p),u!==null&&(v=Jr(),ta(u,i,p,v),Em(u,o,p))}}function xm(i){var o=i.alternate;return i===or||o!==null&&o===or}function km(i,o){qs=hc=!0;var u=i.pending;u===null?o.next=o:(o.next=u.next,u.next=o),i.pending=o}function Em(i,o,u){if(u&4194240){var p=o.lanes;p&=i.pendingLanes,u|=p,o.lanes=u,lo(i,u)}}var gc={readContext:Fi,useCallback:Vr,useContext:Vr,useEffect:Vr,useImperativeHandle:Vr,useInsertionEffect:Vr,useLayoutEffect:Vr,useMemo:Vr,useReducer:Vr,useRef:Vr,useState:Vr,useDebugValue:Vr,useDeferredValue:Vr,useTransition:Vr,useMutableSource:Vr,useSyncExternalStore:Vr,useId:Vr,unstable_isNewReconciler:!1},i5={readContext:Fi,useCallback:function(i,o){return va().memoizedState=[i,o===void 0?null:o],i},useContext:Fi,useEffect:dm,useImperativeHandle:function(i,o,u){return u=u!=null?u.concat([i]):null,pc(4194308,4,mm.bind(null,o,i),u)},useLayoutEffect:function(i,o){return pc(4194308,4,i,o)},useInsertionEffect:function(i,o){return pc(4,2,i,o)},useMemo:function(i,o){var u=va();return o=o===void 0?null:o,i=i(),u.memoizedState=[i,o],i},useReducer:function(i,o,u){var p=va();return o=u!==void 0?u(o):o,p.memoizedState=p.baseState=o,i={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:i,lastRenderedState:o},p.queue=i,i=i.dispatch=n5.bind(null,or,i),[p.memoizedState,i]},useRef:function(i){var o=va();return i={current:i},o.memoizedState=i},useState:cm,useDebugValue:Pf,useDeferredValue:function(i){return va().memoizedState=i},useTransition:function(){var i=cm(!1),o=i[0];return i=t5.bind(null,i[1]),va().memoizedState=i,[o,i]},useMutableSource:function(){},useSyncExternalStore:function(i,o,u){var p=or,v=va();if(Jn){if(u===void 0)throw Error(n(407));u=u()}else{if(u=o(),Dr===null)throw Error(n(349));ml&30||am(p,o,u)}v.memoizedState=u;var E={value:u,getSnapshot:o};return v.queue=E,dm(lm.bind(null,p,E,i),[i]),p.flags|=2048,Vs(9,om.bind(null,p,E,u,o),void 0,null),u},useId:function(){var i=va(),o=Dr.identifierPrefix;if(Jn){var u=qa,p=$a;u=(p&~(1<<32-Ut(p)-1)).toString(32)+u,o=":"+o+"R"+u,u=Hs++,0")&&(ae=ae.replace("",i.displayName)),ae}while(1<=I&&0<=X);break}}}finally{H=!1,Error.prepareStackTrace=u}return(i=i?i.displayName||i.name:"")?Te(i):""}function yt(i){switch(i.tag){case 5:return Te(i.type);case 16:return Te("Lazy");case 13:return Te("Suspense");case 19:return Te("SuspenseList");case 0:case 2:case 15:return i=Ve(i.type,!1),i;case 11:return i=Ve(i.type.render,!1),i;case 1:return i=Ve(i.type,!0),i;default:return""}}function bt(i){if(i==null)return null;if(typeof i=="function")return i.displayName||i.name||null;if(typeof i=="string")return i;switch(i){case ve:return"Fragment";case V:return"Portal";case K:return"Profiler";case Ee:return"StrictMode";case De:return"Suspense";case Se:return"SuspenseList"}if(typeof i=="object")switch(i.$$typeof){case ne:return(i.displayName||"Context")+".Consumer";case C:return(i._context.displayName||"Context")+".Provider";case G:var o=i.render;return i=i.displayName,i||(i=o.displayName||o.name||"",i=i!==""?"ForwardRef("+i+")":"ForwardRef"),i;case Ne:return o=i.displayName||null,o!==null?o:bt(i.type)||"Memo";case et:o=i._payload,i=i._init;try{return bt(i(o))}catch{}}return null}function pe(i){var o=i.type;switch(i.tag){case 24:return"Cache";case 9:return(o.displayName||"Context")+".Consumer";case 10:return(o._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return i=o.render,i=i.displayName||i.name||"",o.displayName||(i!==""?"ForwardRef("+i+")":"ForwardRef");case 7:return"Fragment";case 5:return o;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return bt(o);case 8:return o===Ee?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof o=="function")return o.displayName||o.name||null;if(typeof o=="string")return o}return null}function xe(i){switch(typeof i){case"boolean":case"number":case"string":case"undefined":return i;case"object":return i;default:return""}}function oe(i){var o=i.type;return(i=i.nodeName)&&i.toLowerCase()==="input"&&(o==="checkbox"||o==="radio")}function ze(i){var o=oe(i)?"checked":"value",u=Object.getOwnPropertyDescriptor(i.constructor.prototype,o),p=""+i[o];if(!i.hasOwnProperty(o)&&typeof u<"u"&&typeof u.get=="function"&&typeof u.set=="function"){var v=u.get,E=u.set;return Object.defineProperty(i,o,{configurable:!0,get:function(){return v.call(this)},set:function(I){p=""+I,E.call(this,I)}}),Object.defineProperty(i,o,{enumerable:u.enumerable}),{getValue:function(){return p},setValue:function(I){p=""+I},stopTracking:function(){i._valueTracker=null,delete i[o]}}}}function Ge(i){i._valueTracker||(i._valueTracker=ze(i))}function kt(i){if(!i)return!1;var o=i._valueTracker;if(!o)return!0;var u=o.getValue(),p="";return i&&(p=oe(i)?i.checked?"true":"false":i.value),i=p,i!==u?(o.setValue(i),!0):!1}function Ht(i){if(i=i||(typeof document<"u"?document:void 0),typeof i>"u")return null;try{return i.activeElement||i.body}catch{return i.body}}function qt(i,o){var u=o.checked;return L({},o,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:u??i._wrapperState.initialChecked})}function _t(i,o){var u=o.defaultValue==null?"":o.defaultValue,p=o.checked!=null?o.checked:o.defaultChecked;u=xe(o.value!=null?o.value:u),i._wrapperState={initialChecked:p,initialValue:u,controlled:o.type==="checkbox"||o.type==="radio"?o.checked!=null:o.value!=null}}function $t(i,o){o=o.checked,o!=null&&U(i,"checked",o,!1)}function _n(i,o){$t(i,o);var u=xe(o.value),p=o.type;if(u!=null)p==="number"?(u===0&&i.value===""||i.value!=u)&&(i.value=""+u):i.value!==""+u&&(i.value=""+u);else if(p==="submit"||p==="reset"){i.removeAttribute("value");return}o.hasOwnProperty("value")?In(i,o.type,u):o.hasOwnProperty("defaultValue")&&In(i,o.type,xe(o.defaultValue)),o.checked==null&&o.defaultChecked!=null&&(i.defaultChecked=!!o.defaultChecked)}function Nn(i,o,u){if(o.hasOwnProperty("value")||o.hasOwnProperty("defaultValue")){var p=o.type;if(!(p!=="submit"&&p!=="reset"||o.value!==void 0&&o.value!==null))return;o=""+i._wrapperState.initialValue,u||o===i.value||(i.value=o),i.defaultValue=o}u=i.name,u!==""&&(i.name=""),i.defaultChecked=!!i._wrapperState.initialChecked,u!==""&&(i.name=u)}function In(i,o,u){(o!=="number"||Ht(i.ownerDocument)!==i)&&(u==null?i.defaultValue=""+i._wrapperState.initialValue:i.defaultValue!==""+u&&(i.defaultValue=""+u))}var Bn=Array.isArray;function On(i,o,u,p){if(i=i.options,o){o={};for(var v=0;v"+o.valueOf().toString()+"",o=St.firstChild;i.firstChild;)i.removeChild(i.firstChild);for(;o.firstChild;)i.appendChild(o.firstChild)}});function z(i,o){if(o){var u=i.firstChild;if(u&&u===i.lastChild&&u.nodeType===3){u.nodeValue=o;return}}i.textContent=o}var O={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},$=["Webkit","ms","Moz","O"];Object.keys(O).forEach(function(i){$.forEach(function(o){o=o+i.charAt(0).toUpperCase()+i.substring(1),O[o]=O[i]})});function re(i,o,u){return o==null||typeof o=="boolean"||o===""?"":u||typeof o!="number"||o===0||O.hasOwnProperty(i)&&O[i]?(""+o).trim():o+"px"}function me(i,o){i=i.style;for(var u in o)if(o.hasOwnProperty(u)){var p=u.indexOf("--")===0,v=re(u,o[u],p);u==="float"&&(u="cssFloat"),p?i.setProperty(u,v):i[u]=v}}var Je=L({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Re(i,o){if(o){if(Je[i]&&(o.children!=null||o.dangerouslySetInnerHTML!=null))throw Error(n(137,i));if(o.dangerouslySetInnerHTML!=null){if(o.children!=null)throw Error(n(60));if(typeof o.dangerouslySetInnerHTML!="object"||!("__html"in o.dangerouslySetInnerHTML))throw Error(n(61))}if(o.style!=null&&typeof o.style!="object")throw Error(n(62))}}function Nt(i,o){if(i.indexOf("-")===-1)return typeof o.is=="string";switch(i){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var rt=null;function zt(i){return i=i.target||i.srcElement||window,i.correspondingUseElement&&(i=i.correspondingUseElement),i.nodeType===3?i.parentNode:i}var He=null,Qt=null,dn=null;function nn(i){if(i=Bs(i)){if(typeof He!="function")throw Error(n(280));var o=i.stateNode;o&&(o=Ju(o),He(i.stateNode,i.type,o))}}function W(i){Qt?dn?dn.push(i):dn=[i]:Qt=i}function Pe(){if(Qt){var i=Qt,o=dn;if(dn=Qt=null,nn(i),o)for(i=0;i>>=0,i===0?32:31-(yi(i)/Ma|0)|0}var mn=64,vi=4194304;function ai(i){switch(i&-i){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return i&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return i}}function oi(i,o){var u=i.pendingLanes;if(u===0)return 0;var p=0,v=i.suspendedLanes,E=i.pingedLanes,I=u&268435455;if(I!==0){var X=I&~v;X!==0?p=ai(X):(E&=I,E!==0&&(p=ai(E)))}else I=u&~v,I!==0?p=ai(I):E!==0&&(p=ai(E));if(p===0)return 0;if(o!==0&&o!==p&&!(o&v)&&(v=p&-p,E=o&-o,v>=E||v===16&&(E&4194240)!==0))return o;if(p&4&&(p|=u&16),o=i.entangledLanes,o!==0)for(i=i.entanglements,o&=p;0u;u++)o.push(i);return o}function Yi(i,o,u){i.pendingLanes|=o,o!==536870912&&(i.suspendedLanes=0,i.pingedLanes=0),i=i.eventTimes,o=31-Ut(o),i[o]=u}function Mr(i,o){var u=i.pendingLanes&~o;i.pendingLanes=o,i.suspendedLanes=0,i.pingedLanes=0,i.expiredLanes&=o,i.mutableReadLanes&=o,i.entangledLanes&=o,o=i.entanglements;var p=i.eventTimes;for(i=i.expirationTimes;0=po),Vu=" ",Gu=!1;function ol(i,o){switch(i){case"keyup":return qu.indexOf(o.keyCode)!==-1;case"keydown":return o.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Li(i){return i=i.detail,typeof i=="object"&&"data"in i?i.data:null}var Ln=!1;function ql(i,o){switch(i){case"compositionend":return Li(o);case"keypress":return o.which!==32?null:(Gu=!0,Vu);case"textInput":return i=o.data,i===Vu&&Gu?null:i;default:return null}}function li(i,o){if(Ln)return i==="compositionend"||!al&&ol(i,o)?(i=Pl(),fo=Ns=pa=null,Ln=!1,i):null;switch(i){case"paste":return null;case"keypress":if(!(o.ctrlKey||o.altKey||o.metaKey)||o.ctrlKey&&o.altKey){if(o.char&&1=o)return{node:u,offset:o-i};i=p}e:{for(;u;){if(u.nextSibling){u=u.nextSibling;break e}u=u.parentNode}u=void 0}u=ue(u)}}function we(i,o){return i&&o?i===o?!0:i&&i.nodeType===3?!1:o&&o.nodeType===3?we(i,o.parentNode):"contains"in i?i.contains(o):i.compareDocumentPosition?!!(i.compareDocumentPosition(o)&16):!1:!1}function he(){for(var i=window,o=Ht();o instanceof i.HTMLIFrameElement;){try{var u=typeof o.contentWindow.location.href=="string"}catch{u=!1}if(u)i=o.contentWindow;else break;o=Ht(i.document)}return o}function Ae(i){var o=i&&i.nodeName&&i.nodeName.toLowerCase();return o&&(o==="input"&&(i.type==="text"||i.type==="search"||i.type==="tel"||i.type==="url"||i.type==="password")||o==="textarea"||i.contentEditable==="true")}function Be(i){var o=he(),u=i.focusedElem,p=i.selectionRange;if(o!==u&&u&&u.ownerDocument&&we(u.ownerDocument.documentElement,u)){if(p!==null&&Ae(u)){if(o=p.start,i=p.end,i===void 0&&(i=o),"selectionStart"in u)u.selectionStart=o,u.selectionEnd=Math.min(i,u.value.length);else if(i=(o=u.ownerDocument||document)&&o.defaultView||window,i.getSelection){i=i.getSelection();var v=u.textContent.length,E=Math.min(p.start,v);p=p.end===void 0?E:Math.min(p.end,v),!i.extend&&E>p&&(v=p,p=E,E=v),v=ge(u,E);var I=ge(u,p);v&&I&&(i.rangeCount!==1||i.anchorNode!==v.node||i.anchorOffset!==v.offset||i.focusNode!==I.node||i.focusOffset!==I.offset)&&(o=o.createRange(),o.setStart(v.node,v.offset),i.removeAllRanges(),E>p?(i.addRange(o),i.extend(I.node,I.offset)):(o.setEnd(I.node,I.offset),i.addRange(o)))}}for(o=[],i=u;i=i.parentNode;)i.nodeType===1&&o.push({element:i,left:i.scrollLeft,top:i.scrollTop});for(typeof u.focus=="function"&&u.focus(),u=0;u=document.documentMode,je=null,it=null,gt=null,at=!1;function dt(i,o,u){var p=u.window===u?u.document:u.nodeType===9?u:u.ownerDocument;at||je==null||je!==Ht(p)||(p=je,"selectionStart"in p&&Ae(p)?p={start:p.selectionStart,end:p.selectionEnd}:(p=(p.ownerDocument&&p.ownerDocument.defaultView||window).getSelection(),p={anchorNode:p.anchorNode,anchorOffset:p.anchorOffset,focusNode:p.focusNode,focusOffset:p.focusOffset}),gt&&be(gt,p)||(gt=p,p=Xu(it,"onSelect"),0Wl||(i.current=ff[Wl],ff[Wl]=null,Wl--)}function qn(i,o){Wl++,ff[Wl]=i.current,i.current=o}var vo={},Kr=yo(vo),si=yo(!1),cl=vo;function Yl(i,o){var u=i.type.contextTypes;if(!u)return vo;var p=i.stateNode;if(p&&p.__reactInternalMemoizedUnmaskedChildContext===o)return p.__reactInternalMemoizedMaskedChildContext;var v={},E;for(E in u)v[E]=o[E];return p&&(i=i.stateNode,i.__reactInternalMemoizedUnmaskedChildContext=o,i.__reactInternalMemoizedMaskedChildContext=v),v}function ui(i){return i=i.childContextTypes,i!=null}function ec(){Wn(si),Wn(Kr)}function Up(i,o,u){if(Kr.current!==vo)throw Error(n(168));qn(Kr,o),qn(si,u)}function jp(i,o,u){var p=i.stateNode;if(o=o.childContextTypes,typeof p.getChildContext!="function")return u;p=p.getChildContext();for(var v in p)if(!(v in o))throw Error(n(108,pe(i)||"Unknown",v));return L({},u,p)}function tc(i){return i=(i=i.stateNode)&&i.__reactInternalMemoizedMergedChildContext||vo,cl=Kr.current,qn(Kr,i),qn(si,si.current),!0}function $p(i,o,u){var p=i.stateNode;if(!p)throw Error(n(169));u?(i=jp(i,o,cl),p.__reactInternalMemoizedMergedChildContext=i,Wn(si),Wn(Kr),qn(Kr,i)):Wn(si),qn(si,u)}var ja=null,nc=!1,df=!1;function qp(i){ja===null?ja=[i]:ja.push(i)}function Qv(i){nc=!0,qp(i)}function bo(){if(!df&&ja!==null){df=!0;var i=0,o=Sn;try{var u=ja;for(Sn=1;i>=I,v-=I,$a=1<<32-Ut(o)+v|u<tn?(Pr=Gt,Gt=null):Pr=Gt.sibling;var An=Xe(_e,Gt,Oe[tn],pt);if(An===null){Gt===null&&(Gt=Pr);break}i&&Gt&&An.alternate===null&&o(_e,Gt),de=E(An,de,tn),Vt===null?Ft=An:Vt.sibling=An,Vt=An,Gt=Pr}if(tn===Oe.length)return u(_e,Gt),Jn&&dl(_e,tn),Ft;if(Gt===null){for(;tntn?(Pr=Gt,Gt=null):Pr=Gt.sibling;var Co=Xe(_e,Gt,An.value,pt);if(Co===null){Gt===null&&(Gt=Pr);break}i&&Gt&&Co.alternate===null&&o(_e,Gt),de=E(Co,de,tn),Vt===null?Ft=Co:Vt.sibling=Co,Vt=Co,Gt=Pr}if(An.done)return u(_e,Gt),Jn&&dl(_e,tn),Ft;if(Gt===null){for(;!An.done;tn++,An=Oe.next())An=tt(_e,An.value,pt),An!==null&&(de=E(An,de,tn),Vt===null?Ft=An:Vt.sibling=An,Vt=An);return Jn&&dl(_e,tn),Ft}for(Gt=p(_e,Gt);!An.done;tn++,An=Oe.next())An=At(Gt,_e,tn,An.value,pt),An!==null&&(i&&An.alternate!==null&&Gt.delete(An.key===null?tn:An.key),de=E(An,de,tn),Vt===null?Ft=An:Vt.sibling=An,Vt=An);return i&&Gt.forEach(function(R5){return o(_e,R5)}),Jn&&dl(_e,tn),Ft}function yr(_e,de,Oe,pt){if(typeof Oe=="object"&&Oe!==null&&Oe.type===ve&&Oe.key===null&&(Oe=Oe.props.children),typeof Oe=="object"&&Oe!==null){switch(Oe.$$typeof){case ie:e:{for(var Ft=Oe.key,Vt=de;Vt!==null;){if(Vt.key===Ft){if(Ft=Oe.type,Ft===ve){if(Vt.tag===7){u(_e,Vt.sibling),de=v(Vt,Oe.props.children),de.return=_e,_e=de;break e}}else if(Vt.elementType===Ft||typeof Ft=="object"&&Ft!==null&&Ft.$$typeof===et&&Yp(Ft)===Vt.type){u(_e,Vt.sibling),de=v(Vt,Oe.props),de.ref=Fs(_e,Vt,Oe),de.return=_e,_e=de;break e}u(_e,Vt);break}else o(_e,Vt);Vt=Vt.sibling}Oe.type===ve?(de=wl(Oe.props.children,_e.mode,pt,Oe.key),de.return=_e,_e=de):(pt=Oc(Oe.type,Oe.key,Oe.props,null,_e.mode,pt),pt.ref=Fs(_e,de,Oe),pt.return=_e,_e=pt)}return I(_e);case V:e:{for(Vt=Oe.key;de!==null;){if(de.key===Vt)if(de.tag===4&&de.stateNode.containerInfo===Oe.containerInfo&&de.stateNode.implementation===Oe.implementation){u(_e,de.sibling),de=v(de,Oe.children||[]),de.return=_e,_e=de;break e}else{u(_e,de);break}else o(_e,de);de=de.sibling}de=ud(Oe,_e.mode,pt),de.return=_e,_e=de}return I(_e);case et:return Vt=Oe._init,yr(_e,de,Vt(Oe._payload),pt)}if(Bn(Oe))return Lt(_e,de,Oe,pt);if(Ke(Oe))return Bt(_e,de,Oe,pt);oc(_e,Oe)}return typeof Oe=="string"&&Oe!==""||typeof Oe=="number"?(Oe=""+Oe,de!==null&&de.tag===6?(u(_e,de.sibling),de=v(de,Oe),de.return=_e,_e=de):(u(_e,de),de=sd(Oe,_e.mode,pt),de.return=_e,_e=de),I(_e)):u(_e,de)}return yr}var Jl=Xp(!0),Zp=Xp(!1),lc=yo(null),sc=null,es=null,vf=null;function bf(){vf=es=sc=null}function wf(i){var o=lc.current;Wn(lc),i._currentValue=o}function xf(i,o,u){for(;i!==null;){var p=i.alternate;if((i.childLanes&o)!==o?(i.childLanes|=o,p!==null&&(p.childLanes|=o)):p!==null&&(p.childLanes&o)!==o&&(p.childLanes|=o),i===u)break;i=i.return}}function ts(i,o){sc=i,vf=es=null,i=i.dependencies,i!==null&&i.firstContext!==null&&(i.lanes&o&&(ci=!0),i.firstContext=null)}function Fi(i){var o=i._currentValue;if(vf!==i)if(i={context:i,memoizedValue:o,next:null},es===null){if(sc===null)throw Error(n(308));es=i,sc.dependencies={lanes:0,firstContext:i}}else es=es.next=i;return o}var hl=null;function kf(i){hl===null?hl=[i]:hl.push(i)}function Qp(i,o,u,p){var v=o.interleaved;return v===null?(u.next=u,kf(o)):(u.next=v.next,v.next=u),o.interleaved=u,Ha(i,p)}function Ha(i,o){i.lanes|=o;var u=i.alternate;for(u!==null&&(u.lanes|=o),u=i,i=i.return;i!==null;)i.childLanes|=o,u=i.alternate,u!==null&&(u.childLanes|=o),u=i,i=i.return;return u.tag===3?u.stateNode:null}var wo=!1;function Ef(i){i.updateQueue={baseState:i.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Jp(i,o){i=i.updateQueue,o.updateQueue===i&&(o.updateQueue={baseState:i.baseState,firstBaseUpdate:i.firstBaseUpdate,lastBaseUpdate:i.lastBaseUpdate,shared:i.shared,effects:i.effects})}function Ka(i,o){return{eventTime:i,lane:o,tag:0,payload:null,callback:null,next:null}}function xo(i,o,u){var p=i.updateQueue;if(p===null)return null;if(p=p.shared,Tn&2){var v=p.pending;return v===null?o.next=o:(o.next=v.next,v.next=o),p.pending=o,Ha(i,u)}return v=p.interleaved,v===null?(o.next=o,kf(p)):(o.next=v.next,v.next=o),p.interleaved=o,Ha(i,u)}function uc(i,o,u){if(o=o.updateQueue,o!==null&&(o=o.shared,(u&4194240)!==0)){var p=o.lanes;p&=i.pendingLanes,u|=p,o.lanes=u,lo(i,u)}}function em(i,o){var u=i.updateQueue,p=i.alternate;if(p!==null&&(p=p.updateQueue,u===p)){var v=null,E=null;if(u=u.firstBaseUpdate,u!==null){do{var I={eventTime:u.eventTime,lane:u.lane,tag:u.tag,payload:u.payload,callback:u.callback,next:null};E===null?v=E=I:E=E.next=I,u=u.next}while(u!==null);E===null?v=E=o:E=E.next=o}else v=E=o;u={baseState:p.baseState,firstBaseUpdate:v,lastBaseUpdate:E,shared:p.shared,effects:p.effects},i.updateQueue=u;return}i=u.lastBaseUpdate,i===null?u.firstBaseUpdate=o:i.next=o,u.lastBaseUpdate=o}function cc(i,o,u,p){var v=i.updateQueue;wo=!1;var E=v.firstBaseUpdate,I=v.lastBaseUpdate,X=v.shared.pending;if(X!==null){v.shared.pending=null;var ae=X,Le=ae.next;ae.next=null,I===null?E=Le:I.next=Le,I=ae;var Qe=i.alternate;Qe!==null&&(Qe=Qe.updateQueue,X=Qe.lastBaseUpdate,X!==I&&(X===null?Qe.firstBaseUpdate=Le:X.next=Le,Qe.lastBaseUpdate=ae))}if(E!==null){var tt=v.baseState;I=0,Qe=Le=ae=null,X=E;do{var Xe=X.lane,At=X.eventTime;if((p&Xe)===Xe){Qe!==null&&(Qe=Qe.next={eventTime:At,lane:0,tag:X.tag,payload:X.payload,callback:X.callback,next:null});e:{var Lt=i,Bt=X;switch(Xe=o,At=u,Bt.tag){case 1:if(Lt=Bt.payload,typeof Lt=="function"){tt=Lt.call(At,tt,Xe);break e}tt=Lt;break e;case 3:Lt.flags=Lt.flags&-65537|128;case 0:if(Lt=Bt.payload,Xe=typeof Lt=="function"?Lt.call(At,tt,Xe):Lt,Xe==null)break e;tt=L({},tt,Xe);break e;case 2:wo=!0}}X.callback!==null&&X.lane!==0&&(i.flags|=64,Xe=v.effects,Xe===null?v.effects=[X]:Xe.push(X))}else At={eventTime:At,lane:Xe,tag:X.tag,payload:X.payload,callback:X.callback,next:null},Qe===null?(Le=Qe=At,ae=tt):Qe=Qe.next=At,I|=Xe;if(X=X.next,X===null){if(X=v.shared.pending,X===null)break;Xe=X,X=Xe.next,Xe.next=null,v.lastBaseUpdate=Xe,v.shared.pending=null}}while(!0);if(Qe===null&&(ae=tt),v.baseState=ae,v.firstBaseUpdate=Le,v.lastBaseUpdate=Qe,o=v.shared.interleaved,o!==null){v=o;do I|=v.lane,v=v.next;while(v!==o)}else E===null&&(v.shared.lanes=0);gl|=I,i.lanes=I,i.memoizedState=tt}}function tm(i,o,u){if(i=o.effects,o.effects=null,i!==null)for(o=0;ou?u:4,i(!0);var p=Cf.transition;Cf.transition={};try{i(!1),o()}finally{Sn=u,Cf.transition=p}}function wm(){return Ui().memoizedState}function n5(i,o,u){var p=_o(i);if(u={lane:p,action:u,hasEagerState:!1,eagerState:null,next:null},xm(i))km(o,u);else if(u=Qp(i,o,u,p),u!==null){var v=Jr();ta(u,i,p,v),Em(u,o,p)}}function r5(i,o,u){var p=_o(i),v={lane:p,action:u,hasEagerState:!1,eagerState:null,next:null};if(xm(i))km(o,v);else{var E=i.alternate;if(i.lanes===0&&(E===null||E.lanes===0)&&(E=o.lastRenderedReducer,E!==null))try{var I=o.lastRenderedState,X=E(I,u);if(v.hasEagerState=!0,v.eagerState=X,te(X,I)){var ae=o.interleaved;ae===null?(v.next=v,kf(o)):(v.next=ae.next,ae.next=v),o.interleaved=v;return}}catch{}finally{}u=Qp(i,o,v,p),u!==null&&(v=Jr(),ta(u,i,p,v),Em(u,o,p))}}function xm(i){var o=i.alternate;return i===or||o!==null&&o===or}function km(i,o){qs=hc=!0;var u=i.pending;u===null?o.next=o:(o.next=u.next,u.next=o),i.pending=o}function Em(i,o,u){if(u&4194240){var p=o.lanes;p&=i.pendingLanes,u|=p,o.lanes=u,lo(i,u)}}var gc={readContext:Fi,useCallback:Vr,useContext:Vr,useEffect:Vr,useImperativeHandle:Vr,useInsertionEffect:Vr,useLayoutEffect:Vr,useMemo:Vr,useReducer:Vr,useRef:Vr,useState:Vr,useDebugValue:Vr,useDeferredValue:Vr,useTransition:Vr,useMutableSource:Vr,useSyncExternalStore:Vr,useId:Vr,unstable_isNewReconciler:!1},i5={readContext:Fi,useCallback:function(i,o){return va().memoizedState=[i,o===void 0?null:o],i},useContext:Fi,useEffect:dm,useImperativeHandle:function(i,o,u){return u=u!=null?u.concat([i]):null,pc(4194308,4,mm.bind(null,o,i),u)},useLayoutEffect:function(i,o){return pc(4194308,4,i,o)},useInsertionEffect:function(i,o){return pc(4,2,i,o)},useMemo:function(i,o){var u=va();return o=o===void 0?null:o,i=i(),u.memoizedState=[i,o],i},useReducer:function(i,o,u){var p=va();return o=u!==void 0?u(o):o,p.memoizedState=p.baseState=o,i={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:i,lastRenderedState:o},p.queue=i,i=i.dispatch=n5.bind(null,or,i),[p.memoizedState,i]},useRef:function(i){var o=va();return i={current:i},o.memoizedState=i},useState:cm,useDebugValue:Pf,useDeferredValue:function(i){return va().memoizedState=i},useTransition:function(){var i=cm(!1),o=i[0];return i=t5.bind(null,i[1]),va().memoizedState=i,[o,i]},useMutableSource:function(){},useSyncExternalStore:function(i,o,u){var p=or,v=va();if(Jn){if(u===void 0)throw Error(n(407));u=u()}else{if(u=o(),Dr===null)throw Error(n(349));ml&30||am(p,o,u)}v.memoizedState=u;var E={value:u,getSnapshot:o};return v.queue=E,dm(lm.bind(null,p,E,i),[i]),p.flags|=2048,Vs(9,om.bind(null,p,E,u,o),void 0,null),u},useId:function(){var i=va(),o=Dr.identifierPrefix;if(Jn){var u=qa,p=$a;u=(p&~(1<<32-Ut(p)-1)).toString(32)+u,o=":"+o+"R"+u,u=Hs++,0<\/script>",i=i.removeChild(i.firstChild)):typeof p.is=="string"?i=I.createElement(u,{is:p.is}):(i=I.createElement(u),u==="select"&&(I=i,p.multiple?I.multiple=!0:p.size&&(I.size=p.size))):i=I.createElementNS(i,u),i[ga]=o,i[zs]=p,qm(i,o,!1,!1),o.stateNode=i;e:{switch(I=Nt(u,p),u){case"dialog":Gn("cancel",i),Gn("close",i),v=p;break;case"iframe":case"object":case"embed":Gn("load",i),v=p;break;case"video":case"audio":for(v=0;vos&&(o.flags|=128,p=!0,Gs(E,!1),o.lanes=4194304)}else{if(!p)if(i=fc(I),i!==null){if(o.flags|=128,p=!0,u=i.updateQueue,u!==null&&(o.updateQueue=u,o.flags|=4),Gs(E,!0),E.tail===null&&E.tailMode==="hidden"&&!I.alternate&&!Jn)return Gr(o),null}else 2*Jt()-E.renderingStartTime>os&&u!==1073741824&&(o.flags|=128,p=!0,Gs(E,!1),o.lanes=4194304);E.isBackwards?(I.sibling=o.child,o.child=I):(u=E.last,u!==null?u.sibling=I:o.child=I,E.last=I)}return E.tail!==null?(o=E.tail,E.rendering=o,E.tail=o.sibling,E.renderingStartTime=Jt(),o.sibling=null,u=ar.current,qn(ar,p?u&1|2:u&1),o):(Gr(o),null);case 22:case 23:return ad(),p=o.memoizedState!==null,i!==null&&i.memoizedState!==null!==p&&(o.flags|=8192),p&&o.mode&1?_i&1073741824&&(Gr(o),o.subtreeFlags&6&&(o.flags|=8192)):Gr(o),null;case 24:return null;case 25:return null}throw Error(n(156,o.tag))}function d5(i,o){switch(pf(o),o.tag){case 1:return ui(o.type)&&ec(),i=o.flags,i&65536?(o.flags=i&-65537|128,o):null;case 3:return ns(),Wn(si),Wn(Kr),Tf(),i=o.flags,i&65536&&!(i&128)?(o.flags=i&-65537|128,o):null;case 5:return _f(o),null;case 13:if(Wn(ar),i=o.memoizedState,i!==null&&i.dehydrated!==null){if(o.alternate===null)throw Error(n(340));Ql()}return i=o.flags,i&65536?(o.flags=i&-65537|128,o):null;case 19:return Wn(ar),null;case 4:return ns(),null;case 10:return wf(o.type._context),null;case 22:case 23:return ad(),null;case 24:return null;default:return null}}var wc=!1,Wr=!1,h5=typeof WeakSet=="function"?WeakSet:Set,It=null;function is(i,o){var u=i.ref;if(u!==null)if(typeof u=="function")try{u(null)}catch(p){fr(i,o,p)}else u.current=null}function Gf(i,o,u){try{u()}catch(p){fr(i,o,p)}}var Vm=!1;function p5(i,o){if(af=ha,i=he(),Ce(i)){if("selectionStart"in i)var u={start:i.selectionStart,end:i.selectionEnd};else e:{u=(u=i.ownerDocument)&&u.defaultView||window;var p=u.getSelection&&u.getSelection();if(p&&p.rangeCount!==0){u=p.anchorNode;var v=p.anchorOffset,E=p.focusNode;p=p.focusOffset;try{u.nodeType,E.nodeType}catch{u=null;break e}var I=0,X=-1,ae=-1,Le=0,Qe=0,tt=i,Xe=null;t:for(;;){for(var At;tt!==u||v!==0&&tt.nodeType!==3||(X=I+v),tt!==E||p!==0&&tt.nodeType!==3||(ae=I+p),tt.nodeType===3&&(I+=tt.nodeValue.length),(At=tt.firstChild)!==null;)Xe=tt,tt=At;for(;;){if(tt===i)break t;if(Xe===u&&++Le===v&&(X=I),Xe===E&&++Qe===p&&(ae=I),(At=tt.nextSibling)!==null)break;tt=Xe,Xe=tt.parentNode}tt=At}u=X===-1||ae===-1?null:{start:X,end:ae}}else u=null}u=u||{start:0,end:0}}else u=null;for(of={focusedElem:i,selectionRange:u},ha=!1,It=o;It!==null;)if(o=It,i=o.child,(o.subtreeFlags&1028)!==0&&i!==null)i.return=o,It=i;else for(;It!==null;){o=It;try{var Lt=o.alternate;if(o.flags&1024)switch(o.tag){case 0:case 11:case 15:break;case 1:if(Lt!==null){var Bt=Lt.memoizedProps,yr=Lt.memoizedState,Se=o.stateNode,de=Se.getSnapshotBeforeUpdate(o.elementType===o.type?Bt:Qi(o.type,Bt),yr);Se.__reactInternalSnapshotBeforeUpdate=de}break;case 3:var Ae=o.stateNode.containerInfo;Ae.nodeType===1?Ae.textContent="":Ae.nodeType===9&&Ae.documentElement&&Ae.removeChild(Ae.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(n(163))}}catch(pt){fr(o,o.return,pt)}if(i=o.sibling,i!==null){i.return=o.return,It=i;break}It=o.return}return Lt=Vm,Vm=!1,Lt}function Ws(i,o,u){var p=o.updateQueue;if(p=p!==null?p.lastEffect:null,p!==null){var v=p=p.next;do{if((v.tag&i)===i){var E=v.destroy;v.destroy=void 0,E!==void 0&&Gf(o,u,E)}v=v.next}while(v!==p)}}function xc(i,o){if(o=o.updateQueue,o=o!==null?o.lastEffect:null,o!==null){var u=o=o.next;do{if((u.tag&i)===i){var p=u.create;u.destroy=p()}u=u.next}while(u!==o)}}function Wf(i){var o=i.ref;if(o!==null){var u=i.stateNode;switch(i.tag){case 5:i=u;break;default:i=u}typeof o=="function"?o(i):o.current=i}}function Gm(i){var o=i.alternate;o!==null&&(i.alternate=null,Gm(o)),i.child=null,i.deletions=null,i.sibling=null,i.tag===5&&(o=i.stateNode,o!==null&&(delete o[ga],delete o[zs],delete o[cf],delete o[Xv],delete o[Zv])),i.stateNode=null,i.return=null,i.dependencies=null,i.memoizedProps=null,i.memoizedState=null,i.pendingProps=null,i.stateNode=null,i.updateQueue=null}function Wm(i){return i.tag===5||i.tag===3||i.tag===4}function Ym(i){e:for(;;){for(;i.sibling===null;){if(i.return===null||Wm(i.return))return null;i=i.return}for(i.sibling.return=i.return,i=i.sibling;i.tag!==5&&i.tag!==6&&i.tag!==18;){if(i.flags&2||i.child===null||i.tag===4)continue e;i.child.return=i,i=i.child}if(!(i.flags&2))return i.stateNode}}function Yf(i,o,u){var p=i.tag;if(p===5||p===6)i=i.stateNode,o?u.nodeType===8?u.parentNode.insertBefore(i,o):u.insertBefore(i,o):(u.nodeType===8?(o=u.parentNode,o.insertBefore(i,u)):(o=u,o.appendChild(i)),u=u._reactRootContainer,u!=null||o.onclick!==null||(o.onclick=Qu));else if(p!==4&&(i=i.child,i!==null))for(Yf(i,o,u),i=i.sibling;i!==null;)Yf(i,o,u),i=i.sibling}function Xf(i,o,u){var p=i.tag;if(p===5||p===6)i=i.stateNode,o?u.insertBefore(i,o):u.appendChild(i);else if(p!==4&&(i=i.child,i!==null))for(Xf(i,o,u),i=i.sibling;i!==null;)Xf(i,o,u),i=i.sibling}var Ur=null,Ji=!1;function ko(i,o,u){for(u=u.child;u!==null;)Xm(i,o,u),u=u.sibling}function Xm(i,o,u){if(hn&&typeof hn.onCommitFiberUnmount=="function")try{hn.onCommitFiberUnmount(un,u)}catch{}switch(u.tag){case 5:Wr||is(u,o);case 6:var p=Ur,v=Ji;Ur=null,ko(i,o,u),Ur=p,Ji=v,Ur!==null&&(Ji?(i=Ur,u=u.stateNode,i.nodeType===8?i.parentNode.removeChild(u):i.removeChild(u)):Ur.removeChild(u.stateNode));break;case 18:Ur!==null&&(Ji?(i=Ur,u=u.stateNode,i.nodeType===8?uf(i.parentNode,u):i.nodeType===1&&uf(i,u),so(i)):uf(Ur,u.stateNode));break;case 4:p=Ur,v=Ji,Ur=u.stateNode.containerInfo,Ji=!0,ko(i,o,u),Ur=p,Ji=v;break;case 0:case 11:case 14:case 15:if(!Wr&&(p=u.updateQueue,p!==null&&(p=p.lastEffect,p!==null))){v=p=p.next;do{var E=v,I=E.destroy;E=E.tag,I!==void 0&&(E&2||E&4)&&Gf(u,o,I),v=v.next}while(v!==p)}ko(i,o,u);break;case 1:if(!Wr&&(is(u,o),p=u.stateNode,typeof p.componentWillUnmount=="function"))try{p.props=u.memoizedProps,p.state=u.memoizedState,p.componentWillUnmount()}catch(X){fr(u,o,X)}ko(i,o,u);break;case 21:ko(i,o,u);break;case 22:u.mode&1?(Wr=(p=Wr)||u.memoizedState!==null,ko(i,o,u),Wr=p):ko(i,o,u);break;default:ko(i,o,u)}}function Zm(i){var o=i.updateQueue;if(o!==null){i.updateQueue=null;var u=i.stateNode;u===null&&(u=i.stateNode=new h5),o.forEach(function(p){var v=E5.bind(null,i,p);u.has(p)||(u.add(p),p.then(v,v))})}}function ea(i,o){var u=o.deletions;if(u!==null)for(var p=0;pv&&(v=I),p&=~E}if(p=v,p=Jt()-p,p=(120>p?120:480>p?480:1080>p?1080:1920>p?1920:3e3>p?3e3:4320>p?4320:1960*g5(p/1960))-p,10i?16:i,So===null)var p=!1;else{if(i=So,So=null,Nc=0,Tn&6)throw Error(n(331));var v=Tn;for(Tn|=4,It=i.current;It!==null;){var E=It,I=E.child;if(It.flags&16){var X=E.deletions;if(X!==null){for(var ae=0;aeJt()-Jf?vl(i,0):Qf|=u),di(i,o)}function c1(i,o){o===0&&(i.mode&1?(o=vi,vi<<=1,!(vi&130023424)&&(vi=4194304)):o=1);var u=Jr();i=Ha(i,o),i!==null&&(Yi(i,o,u),di(i,u))}function k5(i){var o=i.memoizedState,u=0;o!==null&&(u=o.retryLane),c1(i,u)}function E5(i,o){var u=0;switch(i.tag){case 13:var p=i.stateNode,v=i.memoizedState;v!==null&&(u=v.retryLane);break;case 19:p=i.stateNode;break;default:throw Error(n(314))}p!==null&&p.delete(o),c1(i,u)}var f1;f1=function(i,o,u){if(i!==null)if(i.memoizedProps!==o.pendingProps||si.current)ci=!0;else{if(!(i.lanes&u)&&!(o.flags&128))return ci=!1,c5(i,o,u);ci=!!(i.flags&131072)}else ci=!1,Jn&&o.flags&1048576&&Hp(o,ic,o.index);switch(o.lanes=0,o.tag){case 2:var p=o.type;bc(i,o),i=o.pendingProps;var v=Yl(o,Kr.current);ts(o,u),v=Of(null,o,p,i,v,u);var E=Mf();return o.flags|=1,typeof v=="object"&&v!==null&&typeof v.render=="function"&&v.$$typeof===void 0?(o.tag=1,o.memoizedState=null,o.updateQueue=null,ui(p)?(E=!0,tc(o)):E=!1,o.memoizedState=v.state!==null&&v.state!==void 0?v.state:null,Ef(o),v.updater=yc,o.stateNode=v,v._reactInternals=o,zf(o,p,i,u),o=jf(null,o,p,!0,E,u)):(o.tag=0,Jn&&E&&hf(o),Qr(null,o,v,u),o=o.child),o;case 16:p=o.elementType;e:{switch(bc(i,o),i=o.pendingProps,v=p._init,p=v(p._payload),o.type=p,v=o.tag=_5(p),i=Qi(p,i),v){case 0:o=Uf(null,o,p,i,u);break e;case 1:o=zm(null,o,p,i,u);break e;case 11:o=Rm(null,o,p,i,u);break e;case 14:o=Im(null,o,p,Qi(p.type,i),u);break e}throw Error(n(306,p,""))}return o;case 0:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),Uf(i,o,p,v,u);case 1:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),zm(i,o,p,v,u);case 3:e:{if(Bm(o),i===null)throw Error(n(387));p=o.pendingProps,E=o.memoizedState,v=E.element,Jp(i,o),cc(o,p,null,u);var I=o.memoizedState;if(p=I.element,E.isDehydrated)if(E={element:p,isDehydrated:!1,cache:I.cache,pendingSuspenseBoundaries:I.pendingSuspenseBoundaries,transitions:I.transitions},o.updateQueue.baseState=E,o.memoizedState=E,o.flags&256){v=rs(Error(n(423)),o),o=Fm(i,o,p,u,v);break e}else if(p!==v){v=rs(Error(n(424)),o),o=Fm(i,o,p,u,v);break e}else for(Si=go(o.stateNode.containerInfo.firstChild),Ei=o,Jn=!0,Zi=null,u=Zp(o,null,p,u),o.child=u;u;)u.flags=u.flags&-3|4096,u=u.sibling;else{if(Ql(),p===v){o=Va(i,o,u);break e}Qr(i,o,p,u)}o=o.child}return o;case 5:return nm(o),i===null&&gf(o),p=o.type,v=o.pendingProps,E=i!==null?i.memoizedProps:null,I=v.children,lf(p,v)?I=null:E!==null&&lf(p,E)&&(o.flags|=32),Lm(i,o),Qr(i,o,I,u),o.child;case 6:return i===null&&gf(o),null;case 13:return Um(i,o,u);case 4:return Sf(o,o.stateNode.containerInfo),p=o.pendingProps,i===null?o.child=Jl(o,null,p,u):Qr(i,o,p,u),o.child;case 11:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),Rm(i,o,p,v,u);case 7:return Qr(i,o,o.pendingProps,u),o.child;case 8:return Qr(i,o,o.pendingProps.children,u),o.child;case 12:return Qr(i,o,o.pendingProps.children,u),o.child;case 10:e:{if(p=o.type._context,v=o.pendingProps,E=o.memoizedProps,I=v.value,qn(lc,p._currentValue),p._currentValue=I,E!==null)if(te(E.value,I)){if(E.children===v.children&&!si.current){o=Va(i,o,u);break e}}else for(E=o.child,E!==null&&(E.return=o);E!==null;){var X=E.dependencies;if(X!==null){I=E.child;for(var ae=X.firstContext;ae!==null;){if(ae.context===p){if(E.tag===1){ae=Ka(-1,u&-u),ae.tag=2;var Le=E.updateQueue;if(Le!==null){Le=Le.shared;var Qe=Le.pending;Qe===null?ae.next=ae:(ae.next=Qe.next,Qe.next=ae),Le.pending=ae}}E.lanes|=u,ae=E.alternate,ae!==null&&(ae.lanes|=u),xf(E.return,u,o),X.lanes|=u;break}ae=ae.next}}else if(E.tag===10)I=E.type===o.type?null:E.child;else if(E.tag===18){if(I=E.return,I===null)throw Error(n(341));I.lanes|=u,X=I.alternate,X!==null&&(X.lanes|=u),xf(I,u,o),I=E.sibling}else I=E.child;if(I!==null)I.return=E;else for(I=E;I!==null;){if(I===o){I=null;break}if(E=I.sibling,E!==null){E.return=I.return,I=E;break}I=I.return}E=I}Qr(i,o,v.children,u),o=o.child}return o;case 9:return v=o.type,p=o.pendingProps.children,ts(o,u),v=Fi(v),p=p(v),o.flags|=1,Qr(i,o,p,u),o.child;case 14:return p=o.type,v=Qi(p,o.pendingProps),v=Qi(p.type,v),Im(i,o,p,v,u);case 15:return Dm(i,o,o.type,o.pendingProps,u);case 17:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),bc(i,o),o.tag=1,ui(p)?(i=!0,tc(o)):i=!1,ts(o,u),_m(o,p,v),zf(o,p,v,u),jf(null,o,p,!0,i,u);case 19:return $m(i,o,u);case 22:return Pm(i,o,u)}throw Error(n(156,o.tag))};function d1(i,o){return Ii(i,o)}function S5(i,o,u,p){this.tag=i,this.key=u,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=o,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=p,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function $i(i,o,u,p){return new S5(i,o,u,p)}function ld(i){return i=i.prototype,!(!i||!i.isReactComponent)}function _5(i){if(typeof i=="function")return ld(i)?1:0;if(i!=null){if(i=i.$$typeof,i===G)return 11;if(i===_e)return 14}return 2}function To(i,o){var u=i.alternate;return u===null?(u=$i(i.tag,o,i.key,i.mode),u.elementType=i.elementType,u.type=i.type,u.stateNode=i.stateNode,u.alternate=i,i.alternate=u):(u.pendingProps=o,u.type=i.type,u.flags=0,u.subtreeFlags=0,u.deletions=null),u.flags=i.flags&14680064,u.childLanes=i.childLanes,u.lanes=i.lanes,u.child=i.child,u.memoizedProps=i.memoizedProps,u.memoizedState=i.memoizedState,u.updateQueue=i.updateQueue,o=i.dependencies,u.dependencies=o===null?null:{lanes:o.lanes,firstContext:o.firstContext},u.sibling=i.sibling,u.index=i.index,u.ref=i.ref,u}function Oc(i,o,u,p,v,E){var I=2;if(p=i,typeof i=="function")ld(i)&&(I=1);else if(typeof i=="string")I=5;else e:switch(i){case we:return wl(u.children,v,E,o);case Oe:I=8,v|=8;break;case V:return i=$i(12,u,o,v|2),i.elementType=V,i.lanes=E,i;case De:return i=$i(13,u,o,v),i.elementType=De,i.lanes=E,i;case Ee:return i=$i(19,u,o,v),i.elementType=Ee,i.lanes=E,i;case ft:return Mc(u,v,E,o);default:if(typeof i=="object"&&i!==null)switch(i.$$typeof){case C:I=10;break e;case ne:I=9;break e;case G:I=11;break e;case _e:I=14;break e;case et:I=16,p=null;break e}throw Error(n(130,i==null?i:typeof i,""))}return o=$i(I,u,o,v),o.elementType=i,o.type=p,o.lanes=E,o}function wl(i,o,u,p){return i=$i(7,i,p,o),i.lanes=u,i}function Mc(i,o,u,p){return i=$i(22,i,p,o),i.elementType=ft,i.lanes=u,i.stateNode={isHidden:!1},i}function sd(i,o,u){return i=$i(6,i,null,o),i.lanes=u,i}function ud(i,o,u){return o=$i(4,i.children!==null?i.children:[],i.key,o),o.lanes=u,o.stateNode={containerInfo:i.containerInfo,pendingChildren:null,implementation:i.implementation},o}function N5(i,o,u,p,v){this.tag=o,this.containerInfo=i,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=bi(0),this.expirationTimes=bi(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=bi(0),this.identifierPrefix=p,this.onRecoverableError=v,this.mutableSourceEagerHydrationData=null}function cd(i,o,u,p,v,E,I,X,ae){return i=new N5(i,o,u,X,ae),o===1?(o=1,E===!0&&(o|=8)):o=0,E=$i(3,null,null,o),i.current=E,E.stateNode=i,E.memoizedState={element:p,isDehydrated:u,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ef(E),i}function T5(i,o,u){var p=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),yd.exports=$5(),yd.exports}var C1;function H5(){if(C1)return Bc;C1=1;var e=q5();return Bc.createRoot=e.createRoot,Bc.hydrateRoot=e.hydrateRoot,Bc}var K5=H5(),tu={},A1;function V5(){if(A1)return tu;A1=1,Object.defineProperty(tu,"__esModule",{value:!0}),tu.parse=s,tu.serialize=d;const e=/^[\u0021-\u003A\u003C\u003E-\u007E]+$/,t=/^[\u0021-\u003A\u003C-\u007E]*$/,n=/^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,r=/^[\u0020-\u003A\u003D-\u007E]*$/,a=Object.prototype.toString,l=(()=>{const w=function(){};return w.prototype=Object.create(null),w})();function s(w,b){const _=new l,T=w.length;if(T<2)return _;const D=(b==null?void 0:b.decode)||m;let R=0;do{const U=w.indexOf("=",R);if(U===-1)break;const F=w.indexOf(";",R),oe=F===-1?T:F;if(U>oe){R=w.lastIndexOf(";",U-1)+1;continue}const ie=c(w,R,U),K=f(w,U,ie),we=w.slice(ie,K);if(_[we]===void 0){let Oe=c(w,U+1,oe),V=f(w,oe,Oe);const C=D(w.slice(Oe,V));_[we]=C}R=oe+1}while(R_;){const T=w.charCodeAt(--b);if(T!==32&&T!==9)return b+1}return _}function d(w,b,_){const T=(_==null?void 0:_.encode)||encodeURIComponent;if(!e.test(w))throw new TypeError(`argument name is invalid: ${w}`);const D=T(b);if(!t.test(D))throw new TypeError(`argument val is invalid: ${b}`);let R=w+"="+D;if(!_)return R;if(_.maxAge!==void 0){if(!Number.isInteger(_.maxAge))throw new TypeError(`option maxAge is invalid: ${_.maxAge}`);R+="; Max-Age="+_.maxAge}if(_.domain){if(!n.test(_.domain))throw new TypeError(`option domain is invalid: ${_.domain}`);R+="; Domain="+_.domain}if(_.path){if(!r.test(_.path))throw new TypeError(`option path is invalid: ${_.path}`);R+="; Path="+_.path}if(_.expires){if(!g(_.expires)||!Number.isFinite(_.expires.valueOf()))throw new TypeError(`option expires is invalid: ${_.expires}`);R+="; Expires="+_.expires.toUTCString()}if(_.httpOnly&&(R+="; HttpOnly"),_.secure&&(R+="; Secure"),_.partitioned&&(R+="; Partitioned"),_.priority)switch(typeof _.priority=="string"?_.priority.toLowerCase():void 0){case"low":R+="; Priority=Low";break;case"medium":R+="; Priority=Medium";break;case"high":R+="; Priority=High";break;default:throw new TypeError(`option priority is invalid: ${_.priority}`)}if(_.sameSite)switch(typeof _.sameSite=="string"?_.sameSite.toLowerCase():_.sameSite){case!0:case"strict":R+="; SameSite=Strict";break;case"lax":R+="; SameSite=Lax";break;case"none":R+="; SameSite=None";break;default:throw new TypeError(`option sameSite is invalid: ${_.sameSite}`)}return R}function m(w){if(w.indexOf("%")===-1)return w;try{return decodeURIComponent(w)}catch{return w}}function g(w){return a.call(w)==="[object Date]"}return tu}V5();/** +`+E.stack}return{value:i,source:o,stack:v,digest:null}}function Bf(i,o,u){return{value:i,source:null,stack:u??null,digest:o??null}}function Ff(i,o){try{console.error(o.value)}catch(u){setTimeout(function(){throw u})}}var l5=typeof WeakMap=="function"?WeakMap:Map;function Tm(i,o,u){u=Ka(-1,u),u.tag=3,u.payload={element:null};var p=o.value;return u.callback=function(){Sc||(Sc=!0,ed=p),Ff(i,o)},u}function Cm(i,o,u){u=Ka(-1,u),u.tag=3;var p=i.type.getDerivedStateFromError;if(typeof p=="function"){var v=o.value;u.payload=function(){return p(v)},u.callback=function(){Ff(i,o)}}var E=i.stateNode;return E!==null&&typeof E.componentDidCatch=="function"&&(u.callback=function(){Ff(i,o),typeof p!="function"&&(Eo===null?Eo=new Set([this]):Eo.add(this));var I=o.stack;this.componentDidCatch(o.value,{componentStack:I!==null?I:""})}),u}function Am(i,o,u){var p=i.pingCache;if(p===null){p=i.pingCache=new l5;var v=new Set;p.set(o,v)}else v=p.get(o),v===void 0&&(v=new Set,p.set(o,v));v.has(u)||(v.add(u),i=x5.bind(null,i,o,u),o.then(i,i))}function Om(i){do{var o;if((o=i.tag===13)&&(o=i.memoizedState,o=o!==null?o.dehydrated!==null:!0),o)return i;i=i.return}while(i!==null);return null}function Mm(i,o,u,p,v){return i.mode&1?(i.flags|=65536,i.lanes=v,i):(i===o?i.flags|=65536:(i.flags|=128,u.flags|=131072,u.flags&=-52805,u.tag===1&&(u.alternate===null?u.tag=17:(o=Ka(-1,1),o.tag=2,xo(u,o,1))),u.lanes|=1),i)}var s5=le.ReactCurrentOwner,ci=!1;function Qr(i,o,u,p){o.child=i===null?Zp(o,null,u,p):Jl(o,i.child,u,p)}function Rm(i,o,u,p,v){u=u.render;var E=o.ref;return ts(o,v),p=Of(i,o,u,p,E,v),u=Mf(),i!==null&&!ci?(o.updateQueue=i.updateQueue,o.flags&=-2053,i.lanes&=~v,Va(i,o,v)):(Jn&&u&&hf(o),o.flags|=1,Qr(i,o,p,v),o.child)}function Im(i,o,u,p,v){if(i===null){var E=u.type;return typeof E=="function"&&!ld(E)&&E.defaultProps===void 0&&u.compare===null&&u.defaultProps===void 0?(o.tag=15,o.type=E,Dm(i,o,E,p,v)):(i=Oc(u.type,null,p,o,o.mode,v),i.ref=o.ref,i.return=o,o.child=i)}if(E=i.child,!(i.lanes&v)){var I=E.memoizedProps;if(u=u.compare,u=u!==null?u:be,u(I,p)&&i.ref===o.ref)return Va(i,o,v)}return o.flags|=1,i=To(E,p),i.ref=o.ref,i.return=o,o.child=i}function Dm(i,o,u,p,v){if(i!==null){var E=i.memoizedProps;if(be(E,p)&&i.ref===o.ref)if(ci=!1,o.pendingProps=p=E,(i.lanes&v)!==0)i.flags&131072&&(ci=!0);else return o.lanes=i.lanes,Va(i,o,v)}return Uf(i,o,u,p,v)}function Pm(i,o,u){var p=o.pendingProps,v=p.children,E=i!==null?i.memoizedState:null;if(p.mode==="hidden")if(!(o.mode&1))o.memoizedState={baseLanes:0,cachePool:null,transitions:null},qn(as,_i),_i|=u;else{if(!(u&1073741824))return i=E!==null?E.baseLanes|u:u,o.lanes=o.childLanes=1073741824,o.memoizedState={baseLanes:i,cachePool:null,transitions:null},o.updateQueue=null,qn(as,_i),_i|=i,null;o.memoizedState={baseLanes:0,cachePool:null,transitions:null},p=E!==null?E.baseLanes:u,qn(as,_i),_i|=p}else E!==null?(p=E.baseLanes|u,o.memoizedState=null):p=u,qn(as,_i),_i|=p;return Qr(i,o,v,u),o.child}function Lm(i,o){var u=o.ref;(i===null&&u!==null||i!==null&&i.ref!==u)&&(o.flags|=512,o.flags|=2097152)}function Uf(i,o,u,p,v){var E=ui(u)?cl:Kr.current;return E=Yl(o,E),ts(o,v),u=Of(i,o,u,p,E,v),p=Mf(),i!==null&&!ci?(o.updateQueue=i.updateQueue,o.flags&=-2053,i.lanes&=~v,Va(i,o,v)):(Jn&&p&&hf(o),o.flags|=1,Qr(i,o,u,v),o.child)}function zm(i,o,u,p,v){if(ui(u)){var E=!0;tc(o)}else E=!1;if(ts(o,v),o.stateNode===null)bc(i,o),_m(o,u,p),zf(o,u,p,v),p=!0;else if(i===null){var I=o.stateNode,X=o.memoizedProps;I.props=X;var ae=I.context,Le=u.contextType;typeof Le=="object"&&Le!==null?Le=Fi(Le):(Le=ui(u)?cl:Kr.current,Le=Yl(o,Le));var Qe=u.getDerivedStateFromProps,tt=typeof Qe=="function"||typeof I.getSnapshotBeforeUpdate=="function";tt||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(X!==p||ae!==Le)&&Nm(o,I,p,Le),wo=!1;var Xe=o.memoizedState;I.state=Xe,cc(o,p,I,v),ae=o.memoizedState,X!==p||Xe!==ae||si.current||wo?(typeof Qe=="function"&&(Lf(o,u,Qe,p),ae=o.memoizedState),(X=wo||Sm(o,u,X,p,Xe,ae,Le))?(tt||typeof I.UNSAFE_componentWillMount!="function"&&typeof I.componentWillMount!="function"||(typeof I.componentWillMount=="function"&&I.componentWillMount(),typeof I.UNSAFE_componentWillMount=="function"&&I.UNSAFE_componentWillMount()),typeof I.componentDidMount=="function"&&(o.flags|=4194308)):(typeof I.componentDidMount=="function"&&(o.flags|=4194308),o.memoizedProps=p,o.memoizedState=ae),I.props=p,I.state=ae,I.context=Le,p=X):(typeof I.componentDidMount=="function"&&(o.flags|=4194308),p=!1)}else{I=o.stateNode,Jp(i,o),X=o.memoizedProps,Le=o.type===o.elementType?X:Qi(o.type,X),I.props=Le,tt=o.pendingProps,Xe=I.context,ae=u.contextType,typeof ae=="object"&&ae!==null?ae=Fi(ae):(ae=ui(u)?cl:Kr.current,ae=Yl(o,ae));var At=u.getDerivedStateFromProps;(Qe=typeof At=="function"||typeof I.getSnapshotBeforeUpdate=="function")||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(X!==tt||Xe!==ae)&&Nm(o,I,p,ae),wo=!1,Xe=o.memoizedState,I.state=Xe,cc(o,p,I,v);var Lt=o.memoizedState;X!==tt||Xe!==Lt||si.current||wo?(typeof At=="function"&&(Lf(o,u,At,p),Lt=o.memoizedState),(Le=wo||Sm(o,u,Le,p,Xe,Lt,ae)||!1)?(Qe||typeof I.UNSAFE_componentWillUpdate!="function"&&typeof I.componentWillUpdate!="function"||(typeof I.componentWillUpdate=="function"&&I.componentWillUpdate(p,Lt,ae),typeof I.UNSAFE_componentWillUpdate=="function"&&I.UNSAFE_componentWillUpdate(p,Lt,ae)),typeof I.componentDidUpdate=="function"&&(o.flags|=4),typeof I.getSnapshotBeforeUpdate=="function"&&(o.flags|=1024)):(typeof I.componentDidUpdate!="function"||X===i.memoizedProps&&Xe===i.memoizedState||(o.flags|=4),typeof I.getSnapshotBeforeUpdate!="function"||X===i.memoizedProps&&Xe===i.memoizedState||(o.flags|=1024),o.memoizedProps=p,o.memoizedState=Lt),I.props=p,I.state=Lt,I.context=ae,p=Le):(typeof I.componentDidUpdate!="function"||X===i.memoizedProps&&Xe===i.memoizedState||(o.flags|=4),typeof I.getSnapshotBeforeUpdate!="function"||X===i.memoizedProps&&Xe===i.memoizedState||(o.flags|=1024),p=!1)}return jf(i,o,u,p,E,v)}function jf(i,o,u,p,v,E){Lm(i,o);var I=(o.flags&128)!==0;if(!p&&!I)return v&&$p(o,u,!1),Va(i,o,E);p=o.stateNode,s5.current=o;var X=I&&typeof u.getDerivedStateFromError!="function"?null:p.render();return o.flags|=1,i!==null&&I?(o.child=Jl(o,i.child,null,E),o.child=Jl(o,null,X,E)):Qr(i,o,X,E),o.memoizedState=p.state,v&&$p(o,u,!0),o.child}function Bm(i){var o=i.stateNode;o.pendingContext?Up(i,o.pendingContext,o.pendingContext!==o.context):o.context&&Up(i,o.context,!1),Sf(i,o.containerInfo)}function Fm(i,o,u,p,v){return Ql(),yf(v),o.flags|=256,Qr(i,o,u,p),o.child}var $f={dehydrated:null,treeContext:null,retryLane:0};function qf(i){return{baseLanes:i,cachePool:null,transitions:null}}function Um(i,o,u){var p=o.pendingProps,v=ar.current,E=!1,I=(o.flags&128)!==0,X;if((X=I)||(X=i!==null&&i.memoizedState===null?!1:(v&2)!==0),X?(E=!0,o.flags&=-129):(i===null||i.memoizedState!==null)&&(v|=1),qn(ar,v&1),i===null)return gf(o),i=o.memoizedState,i!==null&&(i=i.dehydrated,i!==null)?(o.mode&1?i.data==="$!"?o.lanes=8:o.lanes=1073741824:o.lanes=1,null):(I=p.children,i=p.fallback,E?(p=o.mode,E=o.child,I={mode:"hidden",children:I},!(p&1)&&E!==null?(E.childLanes=0,E.pendingProps=I):E=Mc(I,p,0,null),i=wl(i,p,u,null),E.return=o,i.return=o,E.sibling=i,o.child=E,o.child.memoizedState=qf(u),o.memoizedState=$f,i):Hf(o,I));if(v=i.memoizedState,v!==null&&(X=v.dehydrated,X!==null))return u5(i,o,I,p,X,v,u);if(E){E=p.fallback,I=o.mode,v=i.child,X=v.sibling;var ae={mode:"hidden",children:p.children};return!(I&1)&&o.child!==v?(p=o.child,p.childLanes=0,p.pendingProps=ae,o.deletions=null):(p=To(v,ae),p.subtreeFlags=v.subtreeFlags&14680064),X!==null?E=To(X,E):(E=wl(E,I,u,null),E.flags|=2),E.return=o,p.return=o,p.sibling=E,o.child=p,p=E,E=o.child,I=i.child.memoizedState,I=I===null?qf(u):{baseLanes:I.baseLanes|u,cachePool:null,transitions:I.transitions},E.memoizedState=I,E.childLanes=i.childLanes&~u,o.memoizedState=$f,p}return E=i.child,i=E.sibling,p=To(E,{mode:"visible",children:p.children}),!(o.mode&1)&&(p.lanes=u),p.return=o,p.sibling=null,i!==null&&(u=o.deletions,u===null?(o.deletions=[i],o.flags|=16):u.push(i)),o.child=p,o.memoizedState=null,p}function Hf(i,o){return o=Mc({mode:"visible",children:o},i.mode,0,null),o.return=i,i.child=o}function vc(i,o,u,p){return p!==null&&yf(p),Jl(o,i.child,null,u),i=Hf(o,o.pendingProps.children),i.flags|=2,o.memoizedState=null,i}function u5(i,o,u,p,v,E,I){if(u)return o.flags&256?(o.flags&=-257,p=Bf(Error(n(422))),vc(i,o,I,p)):o.memoizedState!==null?(o.child=i.child,o.flags|=128,null):(E=p.fallback,v=o.mode,p=Mc({mode:"visible",children:p.children},v,0,null),E=wl(E,v,I,null),E.flags|=2,p.return=o,E.return=o,p.sibling=E,o.child=p,o.mode&1&&Jl(o,i.child,null,I),o.child.memoizedState=qf(I),o.memoizedState=$f,E);if(!(o.mode&1))return vc(i,o,I,null);if(v.data==="$!"){if(p=v.nextSibling&&v.nextSibling.dataset,p)var X=p.dgst;return p=X,E=Error(n(419)),p=Bf(E,p,void 0),vc(i,o,I,p)}if(X=(I&i.childLanes)!==0,ci||X){if(p=Dr,p!==null){switch(I&-I){case 4:v=2;break;case 16:v=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:v=32;break;case 536870912:v=268435456;break;default:v=0}v=v&(p.suspendedLanes|I)?0:v,v!==0&&v!==E.retryLane&&(E.retryLane=v,Ha(i,v),ta(p,i,v,-1))}return od(),p=Bf(Error(n(421))),vc(i,o,I,p)}return v.data==="$?"?(o.flags|=128,o.child=i.child,o=k5.bind(null,i),v._reactRetry=o,null):(i=E.treeContext,Si=go(v.nextSibling),Ei=o,Jn=!0,Zi=null,i!==null&&(zi[Bi++]=$a,zi[Bi++]=qa,zi[Bi++]=fl,$a=i.id,qa=i.overflow,fl=o),o=Hf(o,p.children),o.flags|=4096,o)}function jm(i,o,u){i.lanes|=o;var p=i.alternate;p!==null&&(p.lanes|=o),xf(i.return,o,u)}function Kf(i,o,u,p,v){var E=i.memoizedState;E===null?i.memoizedState={isBackwards:o,rendering:null,renderingStartTime:0,last:p,tail:u,tailMode:v}:(E.isBackwards=o,E.rendering=null,E.renderingStartTime=0,E.last=p,E.tail=u,E.tailMode=v)}function $m(i,o,u){var p=o.pendingProps,v=p.revealOrder,E=p.tail;if(Qr(i,o,p.children,u),p=ar.current,p&2)p=p&1|2,o.flags|=128;else{if(i!==null&&i.flags&128)e:for(i=o.child;i!==null;){if(i.tag===13)i.memoizedState!==null&&jm(i,u,o);else if(i.tag===19)jm(i,u,o);else if(i.child!==null){i.child.return=i,i=i.child;continue}if(i===o)break e;for(;i.sibling===null;){if(i.return===null||i.return===o)break e;i=i.return}i.sibling.return=i.return,i=i.sibling}p&=1}if(qn(ar,p),!(o.mode&1))o.memoizedState=null;else switch(v){case"forwards":for(u=o.child,v=null;u!==null;)i=u.alternate,i!==null&&fc(i)===null&&(v=u),u=u.sibling;u=v,u===null?(v=o.child,o.child=null):(v=u.sibling,u.sibling=null),Kf(o,!1,v,u,E);break;case"backwards":for(u=null,v=o.child,o.child=null;v!==null;){if(i=v.alternate,i!==null&&fc(i)===null){o.child=v;break}i=v.sibling,v.sibling=u,u=v,v=i}Kf(o,!0,u,null,E);break;case"together":Kf(o,!1,null,null,void 0);break;default:o.memoizedState=null}return o.child}function bc(i,o){!(o.mode&1)&&i!==null&&(i.alternate=null,o.alternate=null,o.flags|=2)}function Va(i,o,u){if(i!==null&&(o.dependencies=i.dependencies),gl|=o.lanes,!(u&o.childLanes))return null;if(i!==null&&o.child!==i.child)throw Error(n(153));if(o.child!==null){for(i=o.child,u=To(i,i.pendingProps),o.child=u,u.return=o;i.sibling!==null;)i=i.sibling,u=u.sibling=To(i,i.pendingProps),u.return=o;u.sibling=null}return o.child}function c5(i,o,u){switch(o.tag){case 3:Bm(o),Ql();break;case 5:nm(o);break;case 1:ui(o.type)&&tc(o);break;case 4:Sf(o,o.stateNode.containerInfo);break;case 10:var p=o.type._context,v=o.memoizedProps.value;qn(lc,p._currentValue),p._currentValue=v;break;case 13:if(p=o.memoizedState,p!==null)return p.dehydrated!==null?(qn(ar,ar.current&1),o.flags|=128,null):u&o.child.childLanes?Um(i,o,u):(qn(ar,ar.current&1),i=Va(i,o,u),i!==null?i.sibling:null);qn(ar,ar.current&1);break;case 19:if(p=(u&o.childLanes)!==0,i.flags&128){if(p)return $m(i,o,u);o.flags|=128}if(v=o.memoizedState,v!==null&&(v.rendering=null,v.tail=null,v.lastEffect=null),qn(ar,ar.current),p)break;return null;case 22:case 23:return o.lanes=0,Pm(i,o,u)}return Va(i,o,u)}var qm,Vf,Hm,Km;qm=function(i,o){for(var u=o.child;u!==null;){if(u.tag===5||u.tag===6)i.appendChild(u.stateNode);else if(u.tag!==4&&u.child!==null){u.child.return=u,u=u.child;continue}if(u===o)break;for(;u.sibling===null;){if(u.return===null||u.return===o)return;u=u.return}u.sibling.return=u.return,u=u.sibling}},Vf=function(){},Hm=function(i,o,u,p){var v=i.memoizedProps;if(v!==p){i=o.stateNode,pl(ya.current);var E=null;switch(u){case"input":v=qt(i,v),p=qt(i,p),E=[];break;case"select":v=L({},v,{value:void 0}),p=L({},p,{value:void 0}),E=[];break;case"textarea":v=Yn(i,v),p=Yn(i,p),E=[];break;default:typeof v.onClick!="function"&&typeof p.onClick=="function"&&(i.onclick=Qu)}Re(u,p);var I;u=null;for(Le in v)if(!p.hasOwnProperty(Le)&&v.hasOwnProperty(Le)&&v[Le]!=null)if(Le==="style"){var X=v[Le];for(I in X)X.hasOwnProperty(I)&&(u||(u={}),u[I]="")}else Le!=="dangerouslySetInnerHTML"&&Le!=="children"&&Le!=="suppressContentEditableWarning"&&Le!=="suppressHydrationWarning"&&Le!=="autoFocus"&&(a.hasOwnProperty(Le)?E||(E=[]):(E=E||[]).push(Le,null));for(Le in p){var ae=p[Le];if(X=v!=null?v[Le]:void 0,p.hasOwnProperty(Le)&&ae!==X&&(ae!=null||X!=null))if(Le==="style")if(X){for(I in X)!X.hasOwnProperty(I)||ae&&ae.hasOwnProperty(I)||(u||(u={}),u[I]="");for(I in ae)ae.hasOwnProperty(I)&&X[I]!==ae[I]&&(u||(u={}),u[I]=ae[I])}else u||(E||(E=[]),E.push(Le,u)),u=ae;else Le==="dangerouslySetInnerHTML"?(ae=ae?ae.__html:void 0,X=X?X.__html:void 0,ae!=null&&X!==ae&&(E=E||[]).push(Le,ae)):Le==="children"?typeof ae!="string"&&typeof ae!="number"||(E=E||[]).push(Le,""+ae):Le!=="suppressContentEditableWarning"&&Le!=="suppressHydrationWarning"&&(a.hasOwnProperty(Le)?(ae!=null&&Le==="onScroll"&&Gn("scroll",i),E||X===ae||(E=[])):(E=E||[]).push(Le,ae))}u&&(E=E||[]).push("style",u);var Le=E;(o.updateQueue=Le)&&(o.flags|=4)}},Km=function(i,o,u,p){u!==p&&(o.flags|=4)};function Gs(i,o){if(!Jn)switch(i.tailMode){case"hidden":o=i.tail;for(var u=null;o!==null;)o.alternate!==null&&(u=o),o=o.sibling;u===null?i.tail=null:u.sibling=null;break;case"collapsed":u=i.tail;for(var p=null;u!==null;)u.alternate!==null&&(p=u),u=u.sibling;p===null?o||i.tail===null?i.tail=null:i.tail.sibling=null:p.sibling=null}}function Gr(i){var o=i.alternate!==null&&i.alternate.child===i.child,u=0,p=0;if(o)for(var v=i.child;v!==null;)u|=v.lanes|v.childLanes,p|=v.subtreeFlags&14680064,p|=v.flags&14680064,v.return=i,v=v.sibling;else for(v=i.child;v!==null;)u|=v.lanes|v.childLanes,p|=v.subtreeFlags,p|=v.flags,v.return=i,v=v.sibling;return i.subtreeFlags|=p,i.childLanes=u,o}function f5(i,o,u){var p=o.pendingProps;switch(pf(o),o.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Gr(o),null;case 1:return ui(o.type)&&ec(),Gr(o),null;case 3:return p=o.stateNode,ns(),Wn(si),Wn(Kr),Tf(),p.pendingContext&&(p.context=p.pendingContext,p.pendingContext=null),(i===null||i.child===null)&&(ac(o)?o.flags|=4:i===null||i.memoizedState.isDehydrated&&!(o.flags&256)||(o.flags|=1024,Zi!==null&&(rd(Zi),Zi=null))),Vf(i,o),Gr(o),null;case 5:_f(o);var v=pl($s.current);if(u=o.type,i!==null&&o.stateNode!=null)Hm(i,o,u,p,v),i.ref!==o.ref&&(o.flags|=512,o.flags|=2097152);else{if(!p){if(o.stateNode===null)throw Error(n(166));return Gr(o),null}if(i=pl(ya.current),ac(o)){p=o.stateNode,u=o.type;var E=o.memoizedProps;switch(p[ga]=o,p[zs]=E,i=(o.mode&1)!==0,u){case"dialog":Gn("cancel",p),Gn("close",p);break;case"iframe":case"object":case"embed":Gn("load",p);break;case"video":case"audio":for(v=0;v<\/script>",i=i.removeChild(i.firstChild)):typeof p.is=="string"?i=I.createElement(u,{is:p.is}):(i=I.createElement(u),u==="select"&&(I=i,p.multiple?I.multiple=!0:p.size&&(I.size=p.size))):i=I.createElementNS(i,u),i[ga]=o,i[zs]=p,qm(i,o,!1,!1),o.stateNode=i;e:{switch(I=Nt(u,p),u){case"dialog":Gn("cancel",i),Gn("close",i),v=p;break;case"iframe":case"object":case"embed":Gn("load",i),v=p;break;case"video":case"audio":for(v=0;vos&&(o.flags|=128,p=!0,Gs(E,!1),o.lanes=4194304)}else{if(!p)if(i=fc(I),i!==null){if(o.flags|=128,p=!0,u=i.updateQueue,u!==null&&(o.updateQueue=u,o.flags|=4),Gs(E,!0),E.tail===null&&E.tailMode==="hidden"&&!I.alternate&&!Jn)return Gr(o),null}else 2*Jt()-E.renderingStartTime>os&&u!==1073741824&&(o.flags|=128,p=!0,Gs(E,!1),o.lanes=4194304);E.isBackwards?(I.sibling=o.child,o.child=I):(u=E.last,u!==null?u.sibling=I:o.child=I,E.last=I)}return E.tail!==null?(o=E.tail,E.rendering=o,E.tail=o.sibling,E.renderingStartTime=Jt(),o.sibling=null,u=ar.current,qn(ar,p?u&1|2:u&1),o):(Gr(o),null);case 22:case 23:return ad(),p=o.memoizedState!==null,i!==null&&i.memoizedState!==null!==p&&(o.flags|=8192),p&&o.mode&1?_i&1073741824&&(Gr(o),o.subtreeFlags&6&&(o.flags|=8192)):Gr(o),null;case 24:return null;case 25:return null}throw Error(n(156,o.tag))}function d5(i,o){switch(pf(o),o.tag){case 1:return ui(o.type)&&ec(),i=o.flags,i&65536?(o.flags=i&-65537|128,o):null;case 3:return ns(),Wn(si),Wn(Kr),Tf(),i=o.flags,i&65536&&!(i&128)?(o.flags=i&-65537|128,o):null;case 5:return _f(o),null;case 13:if(Wn(ar),i=o.memoizedState,i!==null&&i.dehydrated!==null){if(o.alternate===null)throw Error(n(340));Ql()}return i=o.flags,i&65536?(o.flags=i&-65537|128,o):null;case 19:return Wn(ar),null;case 4:return ns(),null;case 10:return wf(o.type._context),null;case 22:case 23:return ad(),null;case 24:return null;default:return null}}var wc=!1,Wr=!1,h5=typeof WeakSet=="function"?WeakSet:Set,It=null;function is(i,o){var u=i.ref;if(u!==null)if(typeof u=="function")try{u(null)}catch(p){fr(i,o,p)}else u.current=null}function Gf(i,o,u){try{u()}catch(p){fr(i,o,p)}}var Vm=!1;function p5(i,o){if(af=ha,i=he(),Ae(i)){if("selectionStart"in i)var u={start:i.selectionStart,end:i.selectionEnd};else e:{u=(u=i.ownerDocument)&&u.defaultView||window;var p=u.getSelection&&u.getSelection();if(p&&p.rangeCount!==0){u=p.anchorNode;var v=p.anchorOffset,E=p.focusNode;p=p.focusOffset;try{u.nodeType,E.nodeType}catch{u=null;break e}var I=0,X=-1,ae=-1,Le=0,Qe=0,tt=i,Xe=null;t:for(;;){for(var At;tt!==u||v!==0&&tt.nodeType!==3||(X=I+v),tt!==E||p!==0&&tt.nodeType!==3||(ae=I+p),tt.nodeType===3&&(I+=tt.nodeValue.length),(At=tt.firstChild)!==null;)Xe=tt,tt=At;for(;;){if(tt===i)break t;if(Xe===u&&++Le===v&&(X=I),Xe===E&&++Qe===p&&(ae=I),(At=tt.nextSibling)!==null)break;tt=Xe,Xe=tt.parentNode}tt=At}u=X===-1||ae===-1?null:{start:X,end:ae}}else u=null}u=u||{start:0,end:0}}else u=null;for(of={focusedElem:i,selectionRange:u},ha=!1,It=o;It!==null;)if(o=It,i=o.child,(o.subtreeFlags&1028)!==0&&i!==null)i.return=o,It=i;else for(;It!==null;){o=It;try{var Lt=o.alternate;if(o.flags&1024)switch(o.tag){case 0:case 11:case 15:break;case 1:if(Lt!==null){var Bt=Lt.memoizedProps,yr=Lt.memoizedState,_e=o.stateNode,de=_e.getSnapshotBeforeUpdate(o.elementType===o.type?Bt:Qi(o.type,Bt),yr);_e.__reactInternalSnapshotBeforeUpdate=de}break;case 3:var Oe=o.stateNode.containerInfo;Oe.nodeType===1?Oe.textContent="":Oe.nodeType===9&&Oe.documentElement&&Oe.removeChild(Oe.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(n(163))}}catch(pt){fr(o,o.return,pt)}if(i=o.sibling,i!==null){i.return=o.return,It=i;break}It=o.return}return Lt=Vm,Vm=!1,Lt}function Ws(i,o,u){var p=o.updateQueue;if(p=p!==null?p.lastEffect:null,p!==null){var v=p=p.next;do{if((v.tag&i)===i){var E=v.destroy;v.destroy=void 0,E!==void 0&&Gf(o,u,E)}v=v.next}while(v!==p)}}function xc(i,o){if(o=o.updateQueue,o=o!==null?o.lastEffect:null,o!==null){var u=o=o.next;do{if((u.tag&i)===i){var p=u.create;u.destroy=p()}u=u.next}while(u!==o)}}function Wf(i){var o=i.ref;if(o!==null){var u=i.stateNode;switch(i.tag){case 5:i=u;break;default:i=u}typeof o=="function"?o(i):o.current=i}}function Gm(i){var o=i.alternate;o!==null&&(i.alternate=null,Gm(o)),i.child=null,i.deletions=null,i.sibling=null,i.tag===5&&(o=i.stateNode,o!==null&&(delete o[ga],delete o[zs],delete o[cf],delete o[Xv],delete o[Zv])),i.stateNode=null,i.return=null,i.dependencies=null,i.memoizedProps=null,i.memoizedState=null,i.pendingProps=null,i.stateNode=null,i.updateQueue=null}function Wm(i){return i.tag===5||i.tag===3||i.tag===4}function Ym(i){e:for(;;){for(;i.sibling===null;){if(i.return===null||Wm(i.return))return null;i=i.return}for(i.sibling.return=i.return,i=i.sibling;i.tag!==5&&i.tag!==6&&i.tag!==18;){if(i.flags&2||i.child===null||i.tag===4)continue e;i.child.return=i,i=i.child}if(!(i.flags&2))return i.stateNode}}function Yf(i,o,u){var p=i.tag;if(p===5||p===6)i=i.stateNode,o?u.nodeType===8?u.parentNode.insertBefore(i,o):u.insertBefore(i,o):(u.nodeType===8?(o=u.parentNode,o.insertBefore(i,u)):(o=u,o.appendChild(i)),u=u._reactRootContainer,u!=null||o.onclick!==null||(o.onclick=Qu));else if(p!==4&&(i=i.child,i!==null))for(Yf(i,o,u),i=i.sibling;i!==null;)Yf(i,o,u),i=i.sibling}function Xf(i,o,u){var p=i.tag;if(p===5||p===6)i=i.stateNode,o?u.insertBefore(i,o):u.appendChild(i);else if(p!==4&&(i=i.child,i!==null))for(Xf(i,o,u),i=i.sibling;i!==null;)Xf(i,o,u),i=i.sibling}var Ur=null,Ji=!1;function ko(i,o,u){for(u=u.child;u!==null;)Xm(i,o,u),u=u.sibling}function Xm(i,o,u){if(hn&&typeof hn.onCommitFiberUnmount=="function")try{hn.onCommitFiberUnmount(un,u)}catch{}switch(u.tag){case 5:Wr||is(u,o);case 6:var p=Ur,v=Ji;Ur=null,ko(i,o,u),Ur=p,Ji=v,Ur!==null&&(Ji?(i=Ur,u=u.stateNode,i.nodeType===8?i.parentNode.removeChild(u):i.removeChild(u)):Ur.removeChild(u.stateNode));break;case 18:Ur!==null&&(Ji?(i=Ur,u=u.stateNode,i.nodeType===8?uf(i.parentNode,u):i.nodeType===1&&uf(i,u),so(i)):uf(Ur,u.stateNode));break;case 4:p=Ur,v=Ji,Ur=u.stateNode.containerInfo,Ji=!0,ko(i,o,u),Ur=p,Ji=v;break;case 0:case 11:case 14:case 15:if(!Wr&&(p=u.updateQueue,p!==null&&(p=p.lastEffect,p!==null))){v=p=p.next;do{var E=v,I=E.destroy;E=E.tag,I!==void 0&&(E&2||E&4)&&Gf(u,o,I),v=v.next}while(v!==p)}ko(i,o,u);break;case 1:if(!Wr&&(is(u,o),p=u.stateNode,typeof p.componentWillUnmount=="function"))try{p.props=u.memoizedProps,p.state=u.memoizedState,p.componentWillUnmount()}catch(X){fr(u,o,X)}ko(i,o,u);break;case 21:ko(i,o,u);break;case 22:u.mode&1?(Wr=(p=Wr)||u.memoizedState!==null,ko(i,o,u),Wr=p):ko(i,o,u);break;default:ko(i,o,u)}}function Zm(i){var o=i.updateQueue;if(o!==null){i.updateQueue=null;var u=i.stateNode;u===null&&(u=i.stateNode=new h5),o.forEach(function(p){var v=E5.bind(null,i,p);u.has(p)||(u.add(p),p.then(v,v))})}}function ea(i,o){var u=o.deletions;if(u!==null)for(var p=0;pv&&(v=I),p&=~E}if(p=v,p=Jt()-p,p=(120>p?120:480>p?480:1080>p?1080:1920>p?1920:3e3>p?3e3:4320>p?4320:1960*g5(p/1960))-p,10i?16:i,So===null)var p=!1;else{if(i=So,So=null,Nc=0,Tn&6)throw Error(n(331));var v=Tn;for(Tn|=4,It=i.current;It!==null;){var E=It,I=E.child;if(It.flags&16){var X=E.deletions;if(X!==null){for(var ae=0;aeJt()-Jf?vl(i,0):Qf|=u),di(i,o)}function c1(i,o){o===0&&(i.mode&1?(o=vi,vi<<=1,!(vi&130023424)&&(vi=4194304)):o=1);var u=Jr();i=Ha(i,o),i!==null&&(Yi(i,o,u),di(i,u))}function k5(i){var o=i.memoizedState,u=0;o!==null&&(u=o.retryLane),c1(i,u)}function E5(i,o){var u=0;switch(i.tag){case 13:var p=i.stateNode,v=i.memoizedState;v!==null&&(u=v.retryLane);break;case 19:p=i.stateNode;break;default:throw Error(n(314))}p!==null&&p.delete(o),c1(i,u)}var f1;f1=function(i,o,u){if(i!==null)if(i.memoizedProps!==o.pendingProps||si.current)ci=!0;else{if(!(i.lanes&u)&&!(o.flags&128))return ci=!1,c5(i,o,u);ci=!!(i.flags&131072)}else ci=!1,Jn&&o.flags&1048576&&Hp(o,ic,o.index);switch(o.lanes=0,o.tag){case 2:var p=o.type;bc(i,o),i=o.pendingProps;var v=Yl(o,Kr.current);ts(o,u),v=Of(null,o,p,i,v,u);var E=Mf();return o.flags|=1,typeof v=="object"&&v!==null&&typeof v.render=="function"&&v.$$typeof===void 0?(o.tag=1,o.memoizedState=null,o.updateQueue=null,ui(p)?(E=!0,tc(o)):E=!1,o.memoizedState=v.state!==null&&v.state!==void 0?v.state:null,Ef(o),v.updater=yc,o.stateNode=v,v._reactInternals=o,zf(o,p,i,u),o=jf(null,o,p,!0,E,u)):(o.tag=0,Jn&&E&&hf(o),Qr(null,o,v,u),o=o.child),o;case 16:p=o.elementType;e:{switch(bc(i,o),i=o.pendingProps,v=p._init,p=v(p._payload),o.type=p,v=o.tag=_5(p),i=Qi(p,i),v){case 0:o=Uf(null,o,p,i,u);break e;case 1:o=zm(null,o,p,i,u);break e;case 11:o=Rm(null,o,p,i,u);break e;case 14:o=Im(null,o,p,Qi(p.type,i),u);break e}throw Error(n(306,p,""))}return o;case 0:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),Uf(i,o,p,v,u);case 1:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),zm(i,o,p,v,u);case 3:e:{if(Bm(o),i===null)throw Error(n(387));p=o.pendingProps,E=o.memoizedState,v=E.element,Jp(i,o),cc(o,p,null,u);var I=o.memoizedState;if(p=I.element,E.isDehydrated)if(E={element:p,isDehydrated:!1,cache:I.cache,pendingSuspenseBoundaries:I.pendingSuspenseBoundaries,transitions:I.transitions},o.updateQueue.baseState=E,o.memoizedState=E,o.flags&256){v=rs(Error(n(423)),o),o=Fm(i,o,p,u,v);break e}else if(p!==v){v=rs(Error(n(424)),o),o=Fm(i,o,p,u,v);break e}else for(Si=go(o.stateNode.containerInfo.firstChild),Ei=o,Jn=!0,Zi=null,u=Zp(o,null,p,u),o.child=u;u;)u.flags=u.flags&-3|4096,u=u.sibling;else{if(Ql(),p===v){o=Va(i,o,u);break e}Qr(i,o,p,u)}o=o.child}return o;case 5:return nm(o),i===null&&gf(o),p=o.type,v=o.pendingProps,E=i!==null?i.memoizedProps:null,I=v.children,lf(p,v)?I=null:E!==null&&lf(p,E)&&(o.flags|=32),Lm(i,o),Qr(i,o,I,u),o.child;case 6:return i===null&&gf(o),null;case 13:return Um(i,o,u);case 4:return Sf(o,o.stateNode.containerInfo),p=o.pendingProps,i===null?o.child=Jl(o,null,p,u):Qr(i,o,p,u),o.child;case 11:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),Rm(i,o,p,v,u);case 7:return Qr(i,o,o.pendingProps,u),o.child;case 8:return Qr(i,o,o.pendingProps.children,u),o.child;case 12:return Qr(i,o,o.pendingProps.children,u),o.child;case 10:e:{if(p=o.type._context,v=o.pendingProps,E=o.memoizedProps,I=v.value,qn(lc,p._currentValue),p._currentValue=I,E!==null)if(te(E.value,I)){if(E.children===v.children&&!si.current){o=Va(i,o,u);break e}}else for(E=o.child,E!==null&&(E.return=o);E!==null;){var X=E.dependencies;if(X!==null){I=E.child;for(var ae=X.firstContext;ae!==null;){if(ae.context===p){if(E.tag===1){ae=Ka(-1,u&-u),ae.tag=2;var Le=E.updateQueue;if(Le!==null){Le=Le.shared;var Qe=Le.pending;Qe===null?ae.next=ae:(ae.next=Qe.next,Qe.next=ae),Le.pending=ae}}E.lanes|=u,ae=E.alternate,ae!==null&&(ae.lanes|=u),xf(E.return,u,o),X.lanes|=u;break}ae=ae.next}}else if(E.tag===10)I=E.type===o.type?null:E.child;else if(E.tag===18){if(I=E.return,I===null)throw Error(n(341));I.lanes|=u,X=I.alternate,X!==null&&(X.lanes|=u),xf(I,u,o),I=E.sibling}else I=E.child;if(I!==null)I.return=E;else for(I=E;I!==null;){if(I===o){I=null;break}if(E=I.sibling,E!==null){E.return=I.return,I=E;break}I=I.return}E=I}Qr(i,o,v.children,u),o=o.child}return o;case 9:return v=o.type,p=o.pendingProps.children,ts(o,u),v=Fi(v),p=p(v),o.flags|=1,Qr(i,o,p,u),o.child;case 14:return p=o.type,v=Qi(p,o.pendingProps),v=Qi(p.type,v),Im(i,o,p,v,u);case 15:return Dm(i,o,o.type,o.pendingProps,u);case 17:return p=o.type,v=o.pendingProps,v=o.elementType===p?v:Qi(p,v),bc(i,o),o.tag=1,ui(p)?(i=!0,tc(o)):i=!1,ts(o,u),_m(o,p,v),zf(o,p,v,u),jf(null,o,p,!0,i,u);case 19:return $m(i,o,u);case 22:return Pm(i,o,u)}throw Error(n(156,o.tag))};function d1(i,o){return Ii(i,o)}function S5(i,o,u,p){this.tag=i,this.key=u,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=o,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=p,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function $i(i,o,u,p){return new S5(i,o,u,p)}function ld(i){return i=i.prototype,!(!i||!i.isReactComponent)}function _5(i){if(typeof i=="function")return ld(i)?1:0;if(i!=null){if(i=i.$$typeof,i===G)return 11;if(i===Ne)return 14}return 2}function To(i,o){var u=i.alternate;return u===null?(u=$i(i.tag,o,i.key,i.mode),u.elementType=i.elementType,u.type=i.type,u.stateNode=i.stateNode,u.alternate=i,i.alternate=u):(u.pendingProps=o,u.type=i.type,u.flags=0,u.subtreeFlags=0,u.deletions=null),u.flags=i.flags&14680064,u.childLanes=i.childLanes,u.lanes=i.lanes,u.child=i.child,u.memoizedProps=i.memoizedProps,u.memoizedState=i.memoizedState,u.updateQueue=i.updateQueue,o=i.dependencies,u.dependencies=o===null?null:{lanes:o.lanes,firstContext:o.firstContext},u.sibling=i.sibling,u.index=i.index,u.ref=i.ref,u}function Oc(i,o,u,p,v,E){var I=2;if(p=i,typeof i=="function")ld(i)&&(I=1);else if(typeof i=="string")I=5;else e:switch(i){case ve:return wl(u.children,v,E,o);case Ee:I=8,v|=8;break;case K:return i=$i(12,u,o,v|2),i.elementType=K,i.lanes=E,i;case De:return i=$i(13,u,o,v),i.elementType=De,i.lanes=E,i;case Se:return i=$i(19,u,o,v),i.elementType=Se,i.lanes=E,i;case ft:return Mc(u,v,E,o);default:if(typeof i=="object"&&i!==null)switch(i.$$typeof){case C:I=10;break e;case ne:I=9;break e;case G:I=11;break e;case Ne:I=14;break e;case et:I=16,p=null;break e}throw Error(n(130,i==null?i:typeof i,""))}return o=$i(I,u,o,v),o.elementType=i,o.type=p,o.lanes=E,o}function wl(i,o,u,p){return i=$i(7,i,p,o),i.lanes=u,i}function Mc(i,o,u,p){return i=$i(22,i,p,o),i.elementType=ft,i.lanes=u,i.stateNode={isHidden:!1},i}function sd(i,o,u){return i=$i(6,i,null,o),i.lanes=u,i}function ud(i,o,u){return o=$i(4,i.children!==null?i.children:[],i.key,o),o.lanes=u,o.stateNode={containerInfo:i.containerInfo,pendingChildren:null,implementation:i.implementation},o}function N5(i,o,u,p,v){this.tag=o,this.containerInfo=i,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=bi(0),this.expirationTimes=bi(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=bi(0),this.identifierPrefix=p,this.onRecoverableError=v,this.mutableSourceEagerHydrationData=null}function cd(i,o,u,p,v,E,I,X,ae){return i=new N5(i,o,u,X,ae),o===1?(o=1,E===!0&&(o|=8)):o=0,E=$i(3,null,null,o),i.current=E,E.stateNode=i,E.memoizedState={element:p,isDehydrated:u,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ef(E),i}function T5(i,o,u){var p=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),yd.exports=$5(),yd.exports}var C1;function H5(){if(C1)return Bc;C1=1;var e=q5();return Bc.createRoot=e.createRoot,Bc.hydrateRoot=e.hydrateRoot,Bc}var K5=H5(),tu={},A1;function V5(){if(A1)return tu;A1=1,Object.defineProperty(tu,"__esModule",{value:!0}),tu.parse=s,tu.serialize=d;const e=/^[\u0021-\u003A\u003C\u003E-\u007E]+$/,t=/^[\u0021-\u003A\u003C-\u007E]*$/,n=/^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,r=/^[\u0020-\u003A\u003D-\u007E]*$/,a=Object.prototype.toString,l=(()=>{const w=function(){};return w.prototype=Object.create(null),w})();function s(w,b){const _=new l,T=w.length;if(T<2)return _;const D=(b==null?void 0:b.decode)||m;let R=0;do{const F=w.indexOf("=",R);if(F===-1)break;const U=w.indexOf(";",R),le=U===-1?T:U;if(F>le){R=w.lastIndexOf(";",F-1)+1;continue}const ie=c(w,R,F),V=f(w,F,ie),ve=w.slice(ie,V);if(_[ve]===void 0){let Ee=c(w,F+1,le),K=f(w,le,Ee);const C=D(w.slice(Ee,K));_[ve]=C}R=le+1}while(R_;){const T=w.charCodeAt(--b);if(T!==32&&T!==9)return b+1}return _}function d(w,b,_){const T=(_==null?void 0:_.encode)||encodeURIComponent;if(!e.test(w))throw new TypeError(`argument name is invalid: ${w}`);const D=T(b);if(!t.test(D))throw new TypeError(`argument val is invalid: ${b}`);let R=w+"="+D;if(!_)return R;if(_.maxAge!==void 0){if(!Number.isInteger(_.maxAge))throw new TypeError(`option maxAge is invalid: ${_.maxAge}`);R+="; Max-Age="+_.maxAge}if(_.domain){if(!n.test(_.domain))throw new TypeError(`option domain is invalid: ${_.domain}`);R+="; Domain="+_.domain}if(_.path){if(!r.test(_.path))throw new TypeError(`option path is invalid: ${_.path}`);R+="; Path="+_.path}if(_.expires){if(!g(_.expires)||!Number.isFinite(_.expires.valueOf()))throw new TypeError(`option expires is invalid: ${_.expires}`);R+="; Expires="+_.expires.toUTCString()}if(_.httpOnly&&(R+="; HttpOnly"),_.secure&&(R+="; Secure"),_.partitioned&&(R+="; Partitioned"),_.priority)switch(typeof _.priority=="string"?_.priority.toLowerCase():void 0){case"low":R+="; Priority=Low";break;case"medium":R+="; Priority=Medium";break;case"high":R+="; Priority=High";break;default:throw new TypeError(`option priority is invalid: ${_.priority}`)}if(_.sameSite)switch(typeof _.sameSite=="string"?_.sameSite.toLowerCase():_.sameSite){case!0:case"strict":R+="; SameSite=Strict";break;case"lax":R+="; SameSite=Lax";break;case"none":R+="; SameSite=None";break;default:throw new TypeError(`option sameSite is invalid: ${_.sameSite}`)}return R}function m(w){if(w.indexOf("%")===-1)return w;try{return decodeURIComponent(w)}catch{return w}}function g(w){return a.call(w)==="[object Date]"}return tu}V5();/** * react-router v7.1.5 * * Copyright (c) Remix Software Inc. @@ -56,15 +56,15 @@ * LICENSE.md file in the root directory of this source tree. * * @license MIT - */var O1="popstate";function G5(e={}){function t(a,l){let{pathname:s="/",search:c="",hash:f=""}=Al(a.location.hash.substring(1));return!s.startsWith("/")&&!s.startsWith(".")&&(s="/"+s),sh("",{pathname:s,search:c,hash:f},l.state&&l.state.usr||null,l.state&&l.state.key||"default")}function n(a,l){let s=a.document.querySelector("base"),c="";if(s&&s.getAttribute("href")){let f=a.location.href,d=f.indexOf("#");c=d===-1?f:f.slice(0,d)}return c+"#"+(typeof l=="string"?l:hu(l))}function r(a,l){sa(a.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(l)})`)}return Y5(t,n,r,e)}function ur(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function sa(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function W5(){return Math.random().toString(36).substring(2,10)}function M1(e,t){return{usr:e.state,key:e.key,idx:t}}function sh(e,t,n=null,r){return{pathname:typeof e=="string"?e:e.pathname,search:"",hash:"",...typeof t=="string"?Al(t):t,state:n,key:t&&t.key||r||W5()}}function hu({pathname:e="/",search:t="",hash:n=""}){return t&&t!=="?"&&(e+=t.charAt(0)==="?"?t:"?"+t),n&&n!=="#"&&(e+=n.charAt(0)==="#"?n:"#"+n),e}function Al(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function Y5(e,t,n,r={}){let{window:a=document.defaultView,v5Compat:l=!1}=r,s=a.history,c="POP",f=null,d=m();d==null&&(d=0,s.replaceState({...s.state,idx:d},""));function m(){return(s.state||{idx:null}).idx}function g(){c="POP";let D=m(),R=D==null?null:D-d;d=D,f&&f({action:c,location:T.location,delta:R})}function w(D,R){c="PUSH";let U=sh(T.location,D,R);n&&n(U,D),d=m()+1;let F=M1(U,d),oe=T.createHref(U);try{s.pushState(F,"",oe)}catch(ie){if(ie instanceof DOMException&&ie.name==="DataCloneError")throw ie;a.location.assign(oe)}l&&f&&f({action:c,location:T.location,delta:1})}function b(D,R){c="REPLACE";let U=sh(T.location,D,R);n&&n(U,D),d=m();let F=M1(U,d),oe=T.createHref(U);s.replaceState(F,"",oe),l&&f&&f({action:c,location:T.location,delta:0})}function _(D){let R=a.location.origin!=="null"?a.location.origin:a.location.href,U=typeof D=="string"?D:hu(D);return U=U.replace(/ $/,"%20"),ur(R,`No window.location.(origin|href) available to create URL for href: ${U}`),new URL(U,R)}let T={get action(){return c},get location(){return e(a,s)},listen(D){if(f)throw new Error("A history only accepts one active listener");return a.addEventListener(O1,g),f=D,()=>{a.removeEventListener(O1,g),f=null}},createHref(D){return t(a,D)},createURL:_,encodeLocation(D){let R=_(D);return{pathname:R.pathname,search:R.search,hash:R.hash}},push:w,replace:b,go(D){return s.go(D)}};return T}function Ry(e,t,n="/"){return X5(e,t,n,!1)}function X5(e,t,n,r){let a=typeof t=="string"?Al(t):t,l=Lo(a.pathname||"/",n);if(l==null)return null;let s=Iy(e);Z5(s);let c=null;for(let f=0;c==null&&f{let f={relativePath:c===void 0?l.path||"":c,caseSensitive:l.caseSensitive===!0,childrenIndex:s,route:l};f.relativePath.startsWith("/")&&(ur(f.relativePath.startsWith(r),`Absolute route path "${f.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),f.relativePath=f.relativePath.slice(r.length));let d=Ja([r,f.relativePath]),m=n.concat(f);l.children&&l.children.length>0&&(ur(l.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${d}".`),Iy(l.children,t,m,d)),!(l.path==null&&!l.index)&&t.push({path:d,score:ib(d,l.index),routesMeta:m})};return e.forEach((l,s)=>{var c;if(l.path===""||!((c=l.path)!=null&&c.includes("?")))a(l,s);else for(let f of Dy(l.path))a(l,s,f)}),t}function Dy(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,a=n.endsWith("?"),l=n.replace(/\?$/,"");if(r.length===0)return a?[l,""]:[l];let s=Dy(r.join("/")),c=[];return c.push(...s.map(f=>f===""?l:[l,f].join("/"))),a&&c.push(...s),c.map(f=>e.startsWith("/")&&f===""?"/":f)}function Z5(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:ab(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}var Q5=/^:[\w-]+$/,J5=3,eb=2,tb=1,nb=10,rb=-2,R1=e=>e==="*";function ib(e,t){let n=e.split("/"),r=n.length;return n.some(R1)&&(r+=rb),t&&(r+=eb),n.filter(a=>!R1(a)).reduce((a,l)=>a+(Q5.test(l)?J5:l===""?tb:nb),r)}function ab(e,t){return e.length===t.length&&e.slice(0,-1).every((r,a)=>r===t[a])?e[e.length-1]-t[t.length-1]:0}function ob(e,t,n=!1){let{routesMeta:r}=e,a={},l="/",s=[];for(let c=0;c{if(m==="*"){let _=c[w]||"";s=l.slice(0,l.length-_.length).replace(/(.)\/+$/,"$1")}const b=c[w];return g&&!b?d[m]=void 0:d[m]=(b||"").replace(/%2F/g,"/"),d},{}),pathname:l,pathnameBase:s,pattern:e}}function lb(e,t=!1,n=!0){sa(e==="*"||!e.endsWith("*")||e.endsWith("/*"),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,"/*")}".`);let r=[],a="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(s,c,f)=>(r.push({paramName:c,isOptional:f!=null}),f?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),a+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?a+="\\/*$":e!==""&&e!=="/"&&(a+="(?:(?=\\/|$))"),[new RegExp(a,t?void 0:"i"),r]}function sb(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return sa(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function Lo(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}function ub(e,t="/"){let{pathname:n,search:r="",hash:a=""}=typeof e=="string"?Al(e):e;return{pathname:n?n.startsWith("/")?n:cb(n,t):t,search:hb(r),hash:pb(a)}}function cb(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(a=>{a===".."?n.length>1&&n.pop():a!=="."&&n.push(a)}),n.length>1?n.join("/"):"/"}function wd(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function fb(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function Py(e){let t=fb(e);return t.map((n,r)=>r===t.length-1?n.pathname:n.pathnameBase)}function Ly(e,t,n,r=!1){let a;typeof e=="string"?a=Al(e):(a={...e},ur(!a.pathname||!a.pathname.includes("?"),wd("?","pathname","search",a)),ur(!a.pathname||!a.pathname.includes("#"),wd("#","pathname","hash",a)),ur(!a.search||!a.search.includes("#"),wd("#","search","hash",a)));let l=e===""||a.pathname==="",s=l?"/":a.pathname,c;if(s==null)c=n;else{let g=t.length-1;if(!r&&s.startsWith("..")){let w=s.split("/");for(;w[0]==="..";)w.shift(),g-=1;a.pathname=w.join("/")}c=g>=0?t[g]:"/"}let f=ub(a,c),d=s&&s!=="/"&&s.endsWith("/"),m=(l||s===".")&&n.endsWith("/");return!f.pathname.endsWith("/")&&(d||m)&&(f.pathname+="/"),f}var Ja=e=>e.join("/").replace(/\/\/+/g,"/"),db=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),hb=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,pb=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function mb(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}var zy=["POST","PUT","PATCH","DELETE"];new Set(zy);var gb=["GET",...zy];new Set(gb);var xs=Y.createContext(null);xs.displayName="DataRouter";var A0=Y.createContext(null);A0.displayName="DataRouterState";var By=Y.createContext({isTransitioning:!1});By.displayName="ViewTransition";var yb=Y.createContext(new Map);yb.displayName="Fetchers";var vb=Y.createContext(null);vb.displayName="Await";var Na=Y.createContext(null);Na.displayName="Navigation";var _u=Y.createContext(null);_u.displayName="Location";var ca=Y.createContext({outlet:null,matches:[],isDataRoute:!1});ca.displayName="Route";var $h=Y.createContext(null);$h.displayName="RouteError";function bb(e,{relative:t}={}){ur(Nu(),"useHref() may be used only in the context of a component.");let{basename:n,navigator:r}=Y.useContext(Na),{hash:a,pathname:l,search:s}=Tu(e,{relative:t}),c=l;return n!=="/"&&(c=l==="/"?n:Ja([n,l])),r.createHref({pathname:c,search:s,hash:a})}function Nu(){return Y.useContext(_u)!=null}function jo(){return ur(Nu(),"useLocation() may be used only in the context of a component."),Y.useContext(_u).location}var Fy="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Uy(e){Y.useContext(Na).static||Y.useLayoutEffect(e)}function O0(){let{isDataRoute:e}=Y.useContext(ca);return e?Pb():wb()}function wb(){ur(Nu(),"useNavigate() may be used only in the context of a component.");let e=Y.useContext(xs),{basename:t,navigator:n}=Y.useContext(Na),{matches:r}=Y.useContext(ca),{pathname:a}=jo(),l=JSON.stringify(Py(r)),s=Y.useRef(!1);return Uy(()=>{s.current=!0}),Y.useCallback((f,d={})=>{if(sa(s.current,Fy),!s.current)return;if(typeof f=="number"){n.go(f);return}let m=Ly(f,JSON.parse(l),a,d.relative==="path");e==null&&t!=="/"&&(m.pathname=m.pathname==="/"?t:Ja([t,m.pathname])),(d.replace?n.replace:n.push)(m,d.state,d)},[t,n,l,a,e])}var xb=Y.createContext(null);function kb(e){let t=Y.useContext(ca).outlet;return t&&Y.createElement(xb.Provider,{value:e},t)}function Eb(){let{matches:e}=Y.useContext(ca),t=e[e.length-1];return t?t.params:{}}function Tu(e,{relative:t}={}){let{matches:n}=Y.useContext(ca),{pathname:r}=jo(),a=JSON.stringify(Py(n));return Y.useMemo(()=>Ly(e,JSON.parse(a),r,t==="path"),[e,a,r,t])}function Sb(e,t){return jy(e,t)}function jy(e,t,n,r){var U;ur(Nu(),"useRoutes() may be used only in the context of a component.");let{navigator:a,static:l}=Y.useContext(Na),{matches:s}=Y.useContext(ca),c=s[s.length-1],f=c?c.params:{},d=c?c.pathname:"/",m=c?c.pathnameBase:"/",g=c&&c.route;{let F=g&&g.path||"";$y(d,!g||F.endsWith("*")||F.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${d}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + */var O1="popstate";function G5(e={}){function t(a,l){let{pathname:s="/",search:c="",hash:f=""}=Al(a.location.hash.substring(1));return!s.startsWith("/")&&!s.startsWith(".")&&(s="/"+s),sh("",{pathname:s,search:c,hash:f},l.state&&l.state.usr||null,l.state&&l.state.key||"default")}function n(a,l){let s=a.document.querySelector("base"),c="";if(s&&s.getAttribute("href")){let f=a.location.href,d=f.indexOf("#");c=d===-1?f:f.slice(0,d)}return c+"#"+(typeof l=="string"?l:hu(l))}function r(a,l){sa(a.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(l)})`)}return Y5(t,n,r,e)}function ur(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function sa(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function W5(){return Math.random().toString(36).substring(2,10)}function M1(e,t){return{usr:e.state,key:e.key,idx:t}}function sh(e,t,n=null,r){return{pathname:typeof e=="string"?e:e.pathname,search:"",hash:"",...typeof t=="string"?Al(t):t,state:n,key:t&&t.key||r||W5()}}function hu({pathname:e="/",search:t="",hash:n=""}){return t&&t!=="?"&&(e+=t.charAt(0)==="?"?t:"?"+t),n&&n!=="#"&&(e+=n.charAt(0)==="#"?n:"#"+n),e}function Al(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function Y5(e,t,n,r={}){let{window:a=document.defaultView,v5Compat:l=!1}=r,s=a.history,c="POP",f=null,d=m();d==null&&(d=0,s.replaceState({...s.state,idx:d},""));function m(){return(s.state||{idx:null}).idx}function g(){c="POP";let D=m(),R=D==null?null:D-d;d=D,f&&f({action:c,location:T.location,delta:R})}function w(D,R){c="PUSH";let F=sh(T.location,D,R);n&&n(F,D),d=m()+1;let U=M1(F,d),le=T.createHref(F);try{s.pushState(U,"",le)}catch(ie){if(ie instanceof DOMException&&ie.name==="DataCloneError")throw ie;a.location.assign(le)}l&&f&&f({action:c,location:T.location,delta:1})}function b(D,R){c="REPLACE";let F=sh(T.location,D,R);n&&n(F,D),d=m();let U=M1(F,d),le=T.createHref(F);s.replaceState(U,"",le),l&&f&&f({action:c,location:T.location,delta:0})}function _(D){let R=a.location.origin!=="null"?a.location.origin:a.location.href,F=typeof D=="string"?D:hu(D);return F=F.replace(/ $/,"%20"),ur(R,`No window.location.(origin|href) available to create URL for href: ${F}`),new URL(F,R)}let T={get action(){return c},get location(){return e(a,s)},listen(D){if(f)throw new Error("A history only accepts one active listener");return a.addEventListener(O1,g),f=D,()=>{a.removeEventListener(O1,g),f=null}},createHref(D){return t(a,D)},createURL:_,encodeLocation(D){let R=_(D);return{pathname:R.pathname,search:R.search,hash:R.hash}},push:w,replace:b,go(D){return s.go(D)}};return T}function Ry(e,t,n="/"){return X5(e,t,n,!1)}function X5(e,t,n,r){let a=typeof t=="string"?Al(t):t,l=Lo(a.pathname||"/",n);if(l==null)return null;let s=Iy(e);Z5(s);let c=null;for(let f=0;c==null&&f{let f={relativePath:c===void 0?l.path||"":c,caseSensitive:l.caseSensitive===!0,childrenIndex:s,route:l};f.relativePath.startsWith("/")&&(ur(f.relativePath.startsWith(r),`Absolute route path "${f.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),f.relativePath=f.relativePath.slice(r.length));let d=Ja([r,f.relativePath]),m=n.concat(f);l.children&&l.children.length>0&&(ur(l.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${d}".`),Iy(l.children,t,m,d)),!(l.path==null&&!l.index)&&t.push({path:d,score:ib(d,l.index),routesMeta:m})};return e.forEach((l,s)=>{var c;if(l.path===""||!((c=l.path)!=null&&c.includes("?")))a(l,s);else for(let f of Dy(l.path))a(l,s,f)}),t}function Dy(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,a=n.endsWith("?"),l=n.replace(/\?$/,"");if(r.length===0)return a?[l,""]:[l];let s=Dy(r.join("/")),c=[];return c.push(...s.map(f=>f===""?l:[l,f].join("/"))),a&&c.push(...s),c.map(f=>e.startsWith("/")&&f===""?"/":f)}function Z5(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:ab(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}var Q5=/^:[\w-]+$/,J5=3,eb=2,tb=1,nb=10,rb=-2,R1=e=>e==="*";function ib(e,t){let n=e.split("/"),r=n.length;return n.some(R1)&&(r+=rb),t&&(r+=eb),n.filter(a=>!R1(a)).reduce((a,l)=>a+(Q5.test(l)?J5:l===""?tb:nb),r)}function ab(e,t){return e.length===t.length&&e.slice(0,-1).every((r,a)=>r===t[a])?e[e.length-1]-t[t.length-1]:0}function ob(e,t,n=!1){let{routesMeta:r}=e,a={},l="/",s=[];for(let c=0;c{if(m==="*"){let _=c[w]||"";s=l.slice(0,l.length-_.length).replace(/(.)\/+$/,"$1")}const b=c[w];return g&&!b?d[m]=void 0:d[m]=(b||"").replace(/%2F/g,"/"),d},{}),pathname:l,pathnameBase:s,pattern:e}}function lb(e,t=!1,n=!0){sa(e==="*"||!e.endsWith("*")||e.endsWith("/*"),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,"/*")}".`);let r=[],a="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(s,c,f)=>(r.push({paramName:c,isOptional:f!=null}),f?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),a+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?a+="\\/*$":e!==""&&e!=="/"&&(a+="(?:(?=\\/|$))"),[new RegExp(a,t?void 0:"i"),r]}function sb(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return sa(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function Lo(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}function ub(e,t="/"){let{pathname:n,search:r="",hash:a=""}=typeof e=="string"?Al(e):e;return{pathname:n?n.startsWith("/")?n:cb(n,t):t,search:hb(r),hash:pb(a)}}function cb(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(a=>{a===".."?n.length>1&&n.pop():a!=="."&&n.push(a)}),n.length>1?n.join("/"):"/"}function wd(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function fb(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function Py(e){let t=fb(e);return t.map((n,r)=>r===t.length-1?n.pathname:n.pathnameBase)}function Ly(e,t,n,r=!1){let a;typeof e=="string"?a=Al(e):(a={...e},ur(!a.pathname||!a.pathname.includes("?"),wd("?","pathname","search",a)),ur(!a.pathname||!a.pathname.includes("#"),wd("#","pathname","hash",a)),ur(!a.search||!a.search.includes("#"),wd("#","search","hash",a)));let l=e===""||a.pathname==="",s=l?"/":a.pathname,c;if(s==null)c=n;else{let g=t.length-1;if(!r&&s.startsWith("..")){let w=s.split("/");for(;w[0]==="..";)w.shift(),g-=1;a.pathname=w.join("/")}c=g>=0?t[g]:"/"}let f=ub(a,c),d=s&&s!=="/"&&s.endsWith("/"),m=(l||s===".")&&n.endsWith("/");return!f.pathname.endsWith("/")&&(d||m)&&(f.pathname+="/"),f}var Ja=e=>e.join("/").replace(/\/\/+/g,"/"),db=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),hb=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,pb=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function mb(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}var zy=["POST","PUT","PATCH","DELETE"];new Set(zy);var gb=["GET",...zy];new Set(gb);var xs=Y.createContext(null);xs.displayName="DataRouter";var A0=Y.createContext(null);A0.displayName="DataRouterState";var By=Y.createContext({isTransitioning:!1});By.displayName="ViewTransition";var yb=Y.createContext(new Map);yb.displayName="Fetchers";var vb=Y.createContext(null);vb.displayName="Await";var Na=Y.createContext(null);Na.displayName="Navigation";var _u=Y.createContext(null);_u.displayName="Location";var ca=Y.createContext({outlet:null,matches:[],isDataRoute:!1});ca.displayName="Route";var $h=Y.createContext(null);$h.displayName="RouteError";function bb(e,{relative:t}={}){ur(Nu(),"useHref() may be used only in the context of a component.");let{basename:n,navigator:r}=Y.useContext(Na),{hash:a,pathname:l,search:s}=Tu(e,{relative:t}),c=l;return n!=="/"&&(c=l==="/"?n:Ja([n,l])),r.createHref({pathname:c,search:s,hash:a})}function Nu(){return Y.useContext(_u)!=null}function jo(){return ur(Nu(),"useLocation() may be used only in the context of a component."),Y.useContext(_u).location}var Fy="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Uy(e){Y.useContext(Na).static||Y.useLayoutEffect(e)}function O0(){let{isDataRoute:e}=Y.useContext(ca);return e?Pb():wb()}function wb(){ur(Nu(),"useNavigate() may be used only in the context of a component.");let e=Y.useContext(xs),{basename:t,navigator:n}=Y.useContext(Na),{matches:r}=Y.useContext(ca),{pathname:a}=jo(),l=JSON.stringify(Py(r)),s=Y.useRef(!1);return Uy(()=>{s.current=!0}),Y.useCallback((f,d={})=>{if(sa(s.current,Fy),!s.current)return;if(typeof f=="number"){n.go(f);return}let m=Ly(f,JSON.parse(l),a,d.relative==="path");e==null&&t!=="/"&&(m.pathname=m.pathname==="/"?t:Ja([t,m.pathname])),(d.replace?n.replace:n.push)(m,d.state,d)},[t,n,l,a,e])}var xb=Y.createContext(null);function kb(e){let t=Y.useContext(ca).outlet;return t&&Y.createElement(xb.Provider,{value:e},t)}function Eb(){let{matches:e}=Y.useContext(ca),t=e[e.length-1];return t?t.params:{}}function Tu(e,{relative:t}={}){let{matches:n}=Y.useContext(ca),{pathname:r}=jo(),a=JSON.stringify(Py(n));return Y.useMemo(()=>Ly(e,JSON.parse(a),r,t==="path"),[e,a,r,t])}function Sb(e,t){return jy(e,t)}function jy(e,t,n,r){var F;ur(Nu(),"useRoutes() may be used only in the context of a component.");let{navigator:a,static:l}=Y.useContext(Na),{matches:s}=Y.useContext(ca),c=s[s.length-1],f=c?c.params:{},d=c?c.pathname:"/",m=c?c.pathnameBase:"/",g=c&&c.route;{let U=g&&g.path||"";$y(d,!g||U.endsWith("*")||U.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${d}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. -Please change the parent to .`)}let w=jo(),b;if(t){let F=typeof t=="string"?Al(t):t;ur(m==="/"||((U=F.pathname)==null?void 0:U.startsWith(m)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${m}" but pathname "${F.pathname}" was given in the \`location\` prop.`),b=F}else b=w;let _=b.pathname||"/",T=_;if(m!=="/"){let F=m.replace(/^\//,"").split("/");T="/"+_.replace(/^\//,"").split("/").slice(F.length).join("/")}let D=!l&&n&&n.matches&&n.matches.length>0?n.matches:Ry(e,{pathname:T});sa(g||D!=null,`No routes matched location "${b.pathname}${b.search}${b.hash}" `),sa(D==null||D[D.length-1].route.element!==void 0||D[D.length-1].route.Component!==void 0||D[D.length-1].route.lazy!==void 0,`Matched leaf route at location "${b.pathname}${b.search}${b.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let R=Ab(D&&D.map(F=>Object.assign({},F,{params:Object.assign({},f,F.params),pathname:Ja([m,a.encodeLocation?a.encodeLocation(F.pathname).pathname:F.pathname]),pathnameBase:F.pathnameBase==="/"?m:Ja([m,a.encodeLocation?a.encodeLocation(F.pathnameBase).pathname:F.pathnameBase])})),s,n,r);return t&&R?Y.createElement(_u.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...b},navigationType:"POP"}},R):R}function _b(){let e=Db(),t=mb(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r="rgba(200,200,200, 0.5)",a={padding:"0.5rem",backgroundColor:r},l={padding:"2px 4px",backgroundColor:r},s=null;return console.error("Error handled by React Router default ErrorBoundary:",e),s=Y.createElement(Y.Fragment,null,Y.createElement("p",null,"💿 Hey developer 👋"),Y.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",Y.createElement("code",{style:l},"ErrorBoundary")," or"," ",Y.createElement("code",{style:l},"errorElement")," prop on your route.")),Y.createElement(Y.Fragment,null,Y.createElement("h2",null,"Unexpected Application Error!"),Y.createElement("h3",{style:{fontStyle:"italic"}},t),n?Y.createElement("pre",{style:a},n):null,s)}var Nb=Y.createElement(_b,null),Tb=class extends Y.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!=="idle"&&e.revalidation==="idle"?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error!==void 0?e.error:t.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){console.error("React Router caught the following error during render",e,t)}render(){return this.state.error!==void 0?Y.createElement(ca.Provider,{value:this.props.routeContext},Y.createElement($h.Provider,{value:this.state.error,children:this.props.component})):this.props.children}};function Cb({routeContext:e,match:t,children:n}){let r=Y.useContext(xs);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),Y.createElement(ca.Provider,{value:e},n)}function Ab(e,t=[],n=null,r=null){if(e==null){if(!n)return null;if(n.errors)e=n.matches;else if(t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let a=e,l=n==null?void 0:n.errors;if(l!=null){let f=a.findIndex(d=>d.route.id&&(l==null?void 0:l[d.route.id])!==void 0);ur(f>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(l).join(",")}`),a=a.slice(0,Math.min(a.length,f+1))}let s=!1,c=-1;if(n)for(let f=0;f=0?a=a.slice(0,c+1):a=[a[0]];break}}}return a.reduceRight((f,d,m)=>{let g,w=!1,b=null,_=null;n&&(g=l&&d.route.id?l[d.route.id]:void 0,b=d.route.errorElement||Nb,s&&(c<0&&m===0?($y("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),w=!0,_=null):c===m&&(w=!0,_=d.route.hydrateFallbackElement||null)));let T=t.concat(a.slice(0,m+1)),D=()=>{let R;return g?R=b:w?R=_:d.route.Component?R=Y.createElement(d.route.Component,null):d.route.element?R=d.route.element:R=f,Y.createElement(Cb,{match:d,routeContext:{outlet:f,matches:T,isDataRoute:n!=null},children:R})};return n&&(d.route.ErrorBoundary||d.route.errorElement||m===0)?Y.createElement(Tb,{location:n.location,revalidation:n.revalidation,component:b,error:g,children:D(),routeContext:{outlet:null,matches:T,isDataRoute:!0}}):D()},null)}function qh(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function Ob(e){let t=Y.useContext(xs);return ur(t,qh(e)),t}function Mb(e){let t=Y.useContext(A0);return ur(t,qh(e)),t}function Rb(e){let t=Y.useContext(ca);return ur(t,qh(e)),t}function Hh(e){let t=Rb(e),n=t.matches[t.matches.length-1];return ur(n.route.id,`${e} can only be used on routes that contain a unique "id"`),n.route.id}function Ib(){return Hh("useRouteId")}function Db(){var r;let e=Y.useContext($h),t=Mb("useRouteError"),n=Hh("useRouteError");return e!==void 0?e:(r=t.errors)==null?void 0:r[n]}function Pb(){let{router:e}=Ob("useNavigate"),t=Hh("useNavigate"),n=Y.useRef(!1);return Uy(()=>{n.current=!0}),Y.useCallback(async(a,l={})=>{sa(n.current,Fy),n.current&&(typeof a=="number"?e.navigate(a):await e.navigate(a,{fromRouteId:t,...l}))},[e,t])}var I1={};function $y(e,t,n){!t&&!I1[e]&&(I1[e]=!0,sa(!1,n))}Y.memo(Lb);function Lb({routes:e,future:t,state:n}){return jy(e,void 0,n,t)}function zb(e){return kb(e.context)}function l0(e){ur(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function Bb({basename:e="/",children:t=null,location:n,navigationType:r="POP",navigator:a,static:l=!1}){ur(!Nu(),"You cannot render a inside another . You should never have more than one in your app.");let s=e.replace(/^\/*/,"/"),c=Y.useMemo(()=>({basename:s,navigator:a,static:l,future:{}}),[s,a,l]);typeof n=="string"&&(n=Al(n));let{pathname:f="/",search:d="",hash:m="",state:g=null,key:w="default"}=n,b=Y.useMemo(()=>{let _=Lo(f,s);return _==null?null:{location:{pathname:_,search:d,hash:m,state:g,key:w},navigationType:r}},[s,f,d,m,g,w,r]);return sa(b!=null,` is not able to match the URL "${f}${d}${m}" because it does not start with the basename, so the won't render anything.`),b==null?null:Y.createElement(Na.Provider,{value:c},Y.createElement(_u.Provider,{children:t,value:b}))}function Fb({children:e,location:t}){return Sb(uh(e),t)}function uh(e,t=[]){let n=[];return Y.Children.forEach(e,(r,a)=>{if(!Y.isValidElement(r))return;let l=[...t,a];if(r.type===Y.Fragment){n.push.apply(n,uh(r.props.children,l));return}ur(r.type===l0,`[${typeof r.type=="string"?r.type:r.type.name}] is not a component. All component children of must be a or `),ur(!r.props.index||!r.props.children,"An index route cannot have child routes.");let s={id:r.props.id||l.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,loader:r.props.loader,action:r.props.action,hydrateFallbackElement:r.props.hydrateFallbackElement,HydrateFallback:r.props.HydrateFallback,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.hasErrorBoundary===!0||r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(s.children=uh(r.props.children,l)),n.push(s)}),n}var s0="get",u0="application/x-www-form-urlencoded";function M0(e){return e!=null&&typeof e.tagName=="string"}function Ub(e){return M0(e)&&e.tagName.toLowerCase()==="button"}function jb(e){return M0(e)&&e.tagName.toLowerCase()==="form"}function $b(e){return M0(e)&&e.tagName.toLowerCase()==="input"}function qb(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Hb(e,t){return e.button===0&&(!t||t==="_self")&&!qb(e)}var Fc=null;function Kb(){if(Fc===null)try{new FormData(document.createElement("form"),0),Fc=!1}catch{Fc=!0}return Fc}var Vb=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function xd(e){return e!=null&&!Vb.has(e)?(sa(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${u0}"`),null):e}function Gb(e,t){let n,r,a,l,s;if(jb(e)){let c=e.getAttribute("action");r=c?Lo(c,t):null,n=e.getAttribute("method")||s0,a=xd(e.getAttribute("enctype"))||u0,l=new FormData(e)}else if(Ub(e)||$b(e)&&(e.type==="submit"||e.type==="image")){let c=e.form;if(c==null)throw new Error('Cannot submit a