diff --git a/engine/commands/engine_get_cmd.cc b/engine/commands/engine_get_cmd.cc index a7ec8fbc1..aa4cbabf2 100644 --- a/engine/commands/engine_get_cmd.cc +++ b/engine/commands/engine_get_cmd.cc @@ -6,22 +6,17 @@ namespace commands { -void EngineGetCmd::Exec() const { - CTL_INF("[EngineGetCmd] engine: " << engine_); - - auto engine_service = EngineService(); - try { - auto status = engine_service.GetEngineInfo(engine_); - tabulate::Table table; - table.add_row({"Name", "Supported Formats", "Version", "Status"}); - table.add_row( - {status.product_name, status.format, status.version, status.status}); - std::cout << table << std::endl; - } catch (const std::runtime_error& e) { - std::cerr << "Engine " << engine_ << " is not supported!" << "\n"; - } catch (const std::exception& e) { - std::cerr << "Failed to get engine info for " << engine_ << ": " << e.what() - << "\n"; +void EngineGetCmd::Exec(const std::string& engine_name) const { + auto engine = engine_service_.GetEngineInfo(engine_name); + if (engine == std::nullopt) { + CLI_LOG("Engine " + engine_name + " is not supported!"); + return; } + + tabulate::Table table; + table.add_row({"Name", "Supported Formats", "Version", "Status"}); + table.add_row( + {engine->product_name, engine->format, engine->version, engine->status}); + std::cout << table << std::endl; } }; // namespace commands diff --git a/engine/commands/engine_get_cmd.h b/engine/commands/engine_get_cmd.h index 505ee5120..8fbc20d08 100644 --- a/engine/commands/engine_get_cmd.h +++ b/engine/commands/engine_get_cmd.h @@ -1,15 +1,15 @@ #pragma once -#include <string> +#include "services/engine_service.h" namespace commands { class EngineGetCmd { public: - EngineGetCmd(const std::string& engine) : engine_{engine} {}; + explicit EngineGetCmd() : engine_service_{EngineService()} {}; - void Exec() const; + void Exec(const std::string& engineName) const; private: - std::string engine_; + EngineService engine_service_; }; } // namespace commands diff --git a/engine/commands/engine_init_cmd.cc b/engine/commands/engine_init_cmd.cc deleted file mode 100644 index 350f3c6b1..000000000 --- a/engine/commands/engine_init_cmd.cc +++ /dev/null @@ -1,220 +0,0 @@ -#include "engine_init_cmd.h" -#include <utility> -#include "services/download_service.h" -#include "trantor/utils/Logger.h" -// clang-format off -#include "utils/cortexso_parser.h" -#include "utils/archive_utils.h" -#include "utils/system_info_utils.h" -// clang-format on -#include "utils/cuda_toolkit_utils.h" -#include "utils/engine_matcher_utils.h" -#include "utils/file_manager_utils.h" - -namespace commands { - -EngineInitCmd::EngineInitCmd(std::string engineName, std::string version) - : engineName_(std::move(engineName)), version_(std::move(version)) {} - -bool EngineInitCmd::Exec() const { - auto system_info = system_info_utils::GetSystemInfo(); - constexpr auto gitHubHost = "https://api.github.com"; - std::string version = version_.empty() ? "latest" : version_; - std::ostringstream engineReleasePath; - engineReleasePath << "/repos/janhq/" << engineName_ << "/releases/" - << version; - CTL_INF("Engine release path: " << gitHubHost << engineReleasePath.str()); - using namespace nlohmann; - - httplib::Client cli(gitHubHost); - if (auto res = cli.Get(engineReleasePath.str())) { - if (res->status == httplib::StatusCode::OK_200) { - try { - auto jsonResponse = json::parse(res->body); - auto assets = jsonResponse["assets"]; - auto os_arch{system_info.os + "-" + system_info.arch}; - - std::vector<std::string> variants; - for (auto& asset : assets) { - auto asset_name = asset["name"].get<std::string>(); - variants.push_back(asset_name); - } - - auto cuda_driver_version = system_info_utils::GetCudaVersion(); - CTL_INF("engineName_: " << engineName_); - CTL_INF("CUDA version: " << cuda_driver_version); - std::string matched_variant = ""; - - if (engineName_ == "cortex.tensorrt-llm") { - matched_variant = engine_matcher_utils::ValidateTensorrtLlm( - variants, system_info.os, cuda_driver_version); - } else if (engineName_ == "cortex.onnx") { - matched_variant = engine_matcher_utils::ValidateOnnx( - variants, system_info.os, system_info.arch); - } else if (engineName_ == "cortex.llamacpp") { - cortex::cpuid::CpuInfo cpu_info; - auto suitable_avx = - engine_matcher_utils::GetSuitableAvxVariant(cpu_info); - matched_variant = engine_matcher_utils::Validate( - variants, system_info.os, system_info.arch, suitable_avx, - cuda_driver_version); - } - CTL_INF("Matched variant: " << matched_variant); - if (matched_variant.empty()) { - CTL_ERR("No variant found for " << os_arch); - return false; - } - - for (auto& asset : assets) { - auto assetName = asset["name"].get<std::string>(); - if (assetName == matched_variant) { - auto download_url = - asset["browser_download_url"].get<std::string>(); - auto file_name = asset["name"].get<std::string>(); - CTL_INF("Download url: " << download_url); - - std::filesystem::path engine_folder_path = - file_manager_utils::GetContainerFolderPath( - file_manager_utils::DownloadTypeToString( - DownloadType::Engine)) / - engineName_; - - if (!std::filesystem::exists(engine_folder_path)) { - CTL_INF("Creating " << engine_folder_path.string()); - std::filesystem::create_directories(engine_folder_path); - } - - CTL_INF("Engine folder path: " << engine_folder_path.string() - << "\n"); - auto local_path = engine_folder_path / file_name; - auto downloadTask{DownloadTask{.id = engineName_, - .type = DownloadType::Engine, - .items = {DownloadItem{ - .id = engineName_, - .downloadUrl = download_url, - .localPath = local_path, - }}}}; - - DownloadService download_service; - download_service.AddDownloadTask( - downloadTask, [](const DownloadTask& finishedTask) { - // try to unzip the downloaded file - CTL_INF("Engine zip path: " - << finishedTask.items[0].localPath.string()); - - std::filesystem::path extract_path = - finishedTask.items[0] - .localPath.parent_path() - .parent_path(); - - archive_utils::ExtractArchive( - finishedTask.items[0].localPath.string(), - extract_path.string()); - - // remove the downloaded file - try { - std::filesystem::remove(finishedTask.items[0].localPath); - } catch (const std::exception& e) { - CTL_WRN("Could not delete file: " << e.what()); - } - CTL_INF("Finished!"); - }); - if (system_info.os == "mac" || engineName_ == "cortex.onnx") { - // mac and onnx engine does not require cuda toolkit - return true; - } - - if (cuda_driver_version.empty()) { - CTL_WRN("No cuda driver, continue with CPU"); - return true; - } - - // download cuda toolkit - const std::string jan_host = "https://catalog.jan.ai"; - const std::string cuda_toolkit_file_name = "cuda.tar.gz"; - const std::string download_id = "cuda"; - - // TODO: we don't have API to retrieve list of cuda toolkit dependencies atm because we hosting it at jan - // will have better logic after https://github.com/janhq/cortex/issues/1046 finished - // for now, assume that we have only 11.7 and 12.4 - auto suitable_toolkit_version = ""; - if (engineName_ == "cortex.tensorrt-llm") { - // for tensorrt-llm, we need to download cuda toolkit v12.4 - suitable_toolkit_version = "12.4"; - } else { - // llamacpp - auto cuda_driver_semver = - semantic_version_utils::SplitVersion(cuda_driver_version); - if (cuda_driver_semver.major == 11) { - suitable_toolkit_version = "11.7"; - } else if (cuda_driver_semver.major == 12) { - suitable_toolkit_version = "12.4"; - } - } - - // compare cuda driver version with cuda toolkit version - // cuda driver version should be greater than toolkit version to ensure compatibility - if (semantic_version_utils::CompareSemanticVersion( - cuda_driver_version, suitable_toolkit_version) < 0) { - CTL_ERR("Your Cuda driver version " - << cuda_driver_version - << " is not compatible with cuda toolkit version " - << suitable_toolkit_version); - return false; - } - - std::ostringstream cuda_toolkit_url; - cuda_toolkit_url << jan_host << "/" << "dist/cuda-dependencies/" - << cuda_driver_version << "/" << system_info.os - << "/" << cuda_toolkit_file_name; - - LOG_DEBUG << "Cuda toolkit download url: " - << cuda_toolkit_url.str(); - auto cuda_toolkit_local_path = - file_manager_utils::GetContainerFolderPath( - file_manager_utils::DownloadTypeToString( - DownloadType::CudaToolkit)) / - cuda_toolkit_file_name; - LOG_DEBUG << "Download to: " << cuda_toolkit_local_path.string(); - auto downloadCudaToolkitTask{DownloadTask{ - .id = download_id, - .type = DownloadType::CudaToolkit, - .items = {DownloadItem{.id = download_id, - .downloadUrl = cuda_toolkit_url.str(), - .localPath = cuda_toolkit_local_path}}, - }}; - - download_service.AddDownloadTask( - downloadCudaToolkitTask, [&](const DownloadTask& finishedTask) { - auto engine_path = - file_manager_utils::GetEnginesContainerPath() / - engineName_; - archive_utils::ExtractArchive( - finishedTask.items[0].localPath.string(), - engine_path.string()); - - try { - std::filesystem::remove(finishedTask.items[0].localPath); - } catch (std::exception& e) { - CTL_ERR("Error removing downloaded file: " << e.what()); - } - }); - return true; - } - } - } catch (const json::parse_error& e) { - std::cerr << "JSON parse error: " << e.what() << std::endl; - return false; - } - } else { - CTL_ERR("HTTP error: " << res->status); - return false; - } - } else { - auto err = res.error(); - CTL_ERR("HTTP error: " << httplib::to_string(err)); - return false; - } - return true; -} -}; // namespace commands diff --git a/engine/commands/engine_init_cmd.h b/engine/commands/engine_init_cmd.h deleted file mode 100644 index c75d76f9d..000000000 --- a/engine/commands/engine_init_cmd.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include <array> -#include <string> - -namespace commands { - -class EngineInitCmd { - public: - EngineInitCmd(std::string engineName, std::string version); - - bool Exec() const; - - private: - std::string engineName_; - std::string version_; -}; -} // namespace commands \ No newline at end of file diff --git a/engine/commands/engine_install_cmd.cc b/engine/commands/engine_install_cmd.cc new file mode 100644 index 000000000..36f7a040b --- /dev/null +++ b/engine/commands/engine_install_cmd.cc @@ -0,0 +1,17 @@ +#include "engine_install_cmd.h" +// clang-format off +#include "utils/cortexso_parser.h" +#include "utils/archive_utils.h" +// clang-format on +#include "utils/cuda_toolkit_utils.h" +#include "utils/engine_matcher_utils.h" +#include "utils/logging_utils.h" + +namespace commands { + +void EngineInstallCmd::Exec(const std::string& engine, + const std::string& version) { + engine_service_.InstallEngine(engine, version); + CLI_LOG("Engine " << engine << " installed successfully!"); +} +}; // namespace commands diff --git a/engine/commands/engine_install_cmd.h b/engine/commands/engine_install_cmd.h new file mode 100644 index 000000000..c6ba6f135 --- /dev/null +++ b/engine/commands/engine_install_cmd.h @@ -0,0 +1,17 @@ +#pragma once + +#include <string> +#include "services/engine_service.h" + +namespace commands { + +class EngineInstallCmd { + public: + explicit EngineInstallCmd() : engine_service_{EngineService()} {}; + + void Exec(const std::string& engine, const std::string& version = "latest"); + + private: + EngineService engine_service_; +}; +} // namespace commands diff --git a/engine/commands/engine_uninstall_cmd.cc b/engine/commands/engine_uninstall_cmd.cc index 4ea41cac9..f74c0179f 100644 --- a/engine/commands/engine_uninstall_cmd.cc +++ b/engine/commands/engine_uninstall_cmd.cc @@ -4,18 +4,8 @@ namespace commands { -EngineUninstallCmd::EngineUninstallCmd(std::string engine) - : engine_{std::move(engine)} {} - -void EngineUninstallCmd::Exec() const { - CTL_INF("Uninstall engine " + engine_); - auto engine_service = EngineService(); - - try { - engine_service.UninstallEngine(engine_); - CLI_LOG("Engine " << engine_ << " uninstalled successfully!") - } catch (const std::exception& e) { - CLI_LOG("Failed to uninstall engine " << engine_ << ": " << e.what()); - } +void EngineUninstallCmd::Exec(const std::string& engine) { + engine_service_.UninstallEngine(engine); + CLI_LOG("Engine " << engine << " uninstalled successfully!"); } }; // namespace commands diff --git a/engine/commands/engine_uninstall_cmd.h b/engine/commands/engine_uninstall_cmd.h index a8a760403..5d0606d3f 100644 --- a/engine/commands/engine_uninstall_cmd.h +++ b/engine/commands/engine_uninstall_cmd.h @@ -1,15 +1,16 @@ #pragma once #include <string> +#include "services/engine_service.h" namespace commands { class EngineUninstallCmd { public: - EngineUninstallCmd(std::string engine); + explicit EngineUninstallCmd() : engine_service_{EngineService()} {}; - void Exec() const; + void Exec(const std::string& engine); private: - std::string engine_; + EngineService engine_service_; }; } // namespace commands diff --git a/engine/commands/model_list_cmd.cc b/engine/commands/model_list_cmd.cc index 70d1e79ea..e0ca88bd3 100644 --- a/engine/commands/model_list_cmd.cc +++ b/engine/commands/model_list_cmd.cc @@ -1,13 +1,9 @@ -// clang-format off -#include "utils/cortex_utils.h" -// clang-format on #include "model_list_cmd.h" #include <filesystem> #include <iostream> #include <tabulate/table.hpp> #include <vector> #include "config/yaml_config.h" -#include "trantor/utils/Logger.h" #include "utils/file_manager_utils.h" #include "utils/logging_utils.h" @@ -57,4 +53,4 @@ void ModelListCmd::Exec() { std::cout << table << std::endl; } } -}; // namespace commands \ No newline at end of file +}; // namespace commands diff --git a/engine/commands/model_list_cmd.h b/engine/commands/model_list_cmd.h index 6a9f2aa5f..a24da8928 100644 --- a/engine/commands/model_list_cmd.h +++ b/engine/commands/model_list_cmd.h @@ -1,11 +1,9 @@ #pragma once -#include <string> - namespace commands { class ModelListCmd { public: void Exec(); }; -} // namespace commands \ No newline at end of file +} // namespace commands diff --git a/engine/commands/model_pull_cmd.cc b/engine/commands/model_pull_cmd.cc index 1ae637817..e46d78981 100644 --- a/engine/commands/model_pull_cmd.cc +++ b/engine/commands/model_pull_cmd.cc @@ -1,25 +1,8 @@ #include "model_pull_cmd.h" -#include <utility> -#include "services/download_service.h" #include "utils/cortexso_parser.h" -#include "utils/logging_utils.h" -#include "utils/model_callback_utils.h" namespace commands { -ModelPullCmd::ModelPullCmd(std::string model_handle, std::string branch) - : model_handle_(std::move(model_handle)), branch_(std::move(branch)) {} - -bool ModelPullCmd::Exec() { - auto downloadTask = cortexso_parser::getDownloadTask(model_handle_, branch_); - if (downloadTask.has_value()) { - DownloadService().AddDownloadTask(downloadTask.value(), - model_callback_utils::DownloadModelCb); - CTL_INF("Download finished"); - return true; - } else { - CTL_ERR("Model not found"); - return false; - } +void ModelPullCmd::Exec(const std::string& input) { + model_service_.DownloadModel(input); } - }; // namespace commands diff --git a/engine/commands/model_pull_cmd.h b/engine/commands/model_pull_cmd.h index da5713bdf..9a90e1950 100644 --- a/engine/commands/model_pull_cmd.h +++ b/engine/commands/model_pull_cmd.h @@ -1,16 +1,15 @@ #pragma once -#include <string> +#include "services/model_service.h" namespace commands { class ModelPullCmd { public: -explicit ModelPullCmd(std::string model_handle, std::string branch); - bool Exec(); + explicit ModelPullCmd() : model_service_{ModelService()} {}; + void Exec(const std::string& input); private: - std::string model_handle_; - std::string branch_; + ModelService model_service_; }; -} // namespace commands \ No newline at end of file +} // namespace commands diff --git a/engine/commands/run_cmd.cc b/engine/commands/run_cmd.cc index c8bde1b14..64bc50d6f 100644 --- a/engine/commands/run_cmd.cc +++ b/engine/commands/run_cmd.cc @@ -2,18 +2,11 @@ #include "chat_cmd.h" #include "cmd_info.h" #include "config/yaml_config.h" -#include "engine_init_cmd.h" -#include "model_pull_cmd.h" #include "model_start_cmd.h" -#include "trantor/utils/Logger.h" -#include "utils/cortex_utils.h" #include "utils/file_manager_utils.h" namespace commands { -RunCmd::RunCmd(std::string host, int port, std::string model_id) - : host_(std::move(host)), port_(port), model_id_(std::move(model_id)) {} - void RunCmd::Exec() { auto address = host_ + ":" + std::to_string(port_); CmdInfo ci(model_id_); @@ -22,22 +15,23 @@ void RunCmd::Exec() { // TODO should we clean all resource if something fails? // Check if model existed. If not, download it { - if (!IsModelExisted(model_file)) { - ModelPullCmd model_pull_cmd(ci.model_name, ci.branch); - if (!model_pull_cmd.Exec()) { - return; - } + auto model_conf = model_service_.GetDownloadedModel(model_id_); + if (!model_conf.has_value()) { + model_service_.DownloadModel(model_id_); } } // Check if engine existed. If not, download it { - if (!IsEngineExisted(ci.engine_name)) { - EngineInitCmd eic(ci.engine_name, ""); - if (!eic.Exec()) { - LOG_INFO << "Failed to install engine"; - return; - } + auto required_engine = engine_service_.GetEngineInfo(ci.engine_name); + if (!required_engine.has_value()) { + throw std::runtime_error("Engine not found: " + ci.engine_name); + } + if (required_engine.value().status == EngineService::kIncompatible) { + throw std::runtime_error("Engine " + ci.engine_name + " is incompatible"); + } + if (required_engine.value().status == EngineService::kNotInstalled) { + engine_service_.InstallEngine(ci.engine_name); } } @@ -59,37 +53,4 @@ void RunCmd::Exec() { cc.Exec(""); } } - -bool RunCmd::IsModelExisted(const std::string& model_id) { - auto models_path = file_manager_utils::GetModelsContainerPath(); - if (std::filesystem::exists(models_path) && - std::filesystem::is_directory(models_path)) { - // Iterate through directory - for (const auto& entry : std::filesystem::directory_iterator(models_path)) { - if (entry.is_regular_file() && entry.path().extension() == ".yaml") { - try { - config::YamlHandler handler; - handler.ModelConfigFromFile(entry.path().string()); - if (entry.path().stem().string() == model_id) { - return true; - } - } catch (const std::exception& e) { - LOG_ERROR << "Error reading yaml file '" << entry.path().string() - << "': " << e.what(); - } - } - } - } - return false; -} - -bool RunCmd::IsEngineExisted(const std::string& e) { - auto engines_path = file_manager_utils::GetEnginesContainerPath(); - if (std::filesystem::exists(engines_path) && - std::filesystem::exists(engines_path.string() + "/" + e)) { - return true; - } - return false; -} - }; // namespace commands diff --git a/engine/commands/run_cmd.h b/engine/commands/run_cmd.h index ca44b9d24..c862926a6 100644 --- a/engine/commands/run_cmd.h +++ b/engine/commands/run_cmd.h @@ -1,22 +1,25 @@ #pragma once #include <string> -#include <vector> -#include "config/model_config.h" -#include "nlohmann/json.hpp" +#include "services/engine_service.h" +#include "services/model_service.h" namespace commands { class RunCmd { public: - explicit RunCmd(std::string host, int port, std::string model_id); - void Exec(); + explicit RunCmd(std::string host, int port, std::string model_id) + : host_{std::move(host)}, + port_{port}, + model_id_{std::move(model_id)}, + model_service_{ModelService()} {}; - private: - bool IsModelExisted(const std::string& model_id); - bool IsEngineExisted(const std::string& e); + void Exec(); private: std::string host_; int port_; std::string model_id_; + + ModelService model_service_; + EngineService engine_service_; }; -} // namespace commands \ No newline at end of file +} // namespace commands diff --git a/engine/config/gguf_parser.cc b/engine/config/gguf_parser.cc index 00b461719..f2f058336 100644 --- a/engine/config/gguf_parser.cc +++ b/engine/config/gguf_parser.cc @@ -321,7 +321,11 @@ void GGUFHandler::Parse(const std::string& file_path) { offset += value_byte_length; LOG_INFO << "-------------------------------------------- " << "\n"; } - PrintMetadata(); + try { + PrintMetadata(); + } catch (const std::exception& e) { + LOG_ERROR << "Error parsing metadata: " << e.what() << "\n"; + } ModelConfigFromMetadata(); CloseFile(); } @@ -579,4 +583,4 @@ void GGUFHandler::ModelConfigFromMetadata() { const ModelConfig& GGUFHandler::GetModelConfig() const { return model_config_; } -} // namespace config \ No newline at end of file +} // namespace config diff --git a/engine/config/model_config.h b/engine/config/model_config.h index b7cd15810..a8d7f4fda 100644 --- a/engine/config/model_config.h +++ b/engine/config/model_config.h @@ -1,5 +1,5 @@ #pragma once -#include <cmath> + #include <limits> #include <string> #include <vector> @@ -37,4 +37,4 @@ struct ModelConfig { std::string object; std::string owned_by = ""; }; -} // namespace config \ No newline at end of file +} // namespace config diff --git a/engine/config/yaml_config.cc b/engine/config/yaml_config.cc index fe3e57370..81f62df9d 100644 --- a/engine/config/yaml_config.cc +++ b/engine/config/yaml_config.cc @@ -2,11 +2,9 @@ #include <ctime> #include <fstream> #include <iostream> -#include <limits> #include <string> using namespace std; -#include "yaml-cpp/yaml.h" #include "yaml_config.h" namespace config { @@ -209,4 +207,4 @@ void YamlHandler::WriteYamlFile(const std::string& file_path) const { throw; } } -} // namespace config \ No newline at end of file +} // namespace config diff --git a/engine/config/yaml_config.h b/engine/config/yaml_config.h index 3f8af5400..87b9083a1 100644 --- a/engine/config/yaml_config.h +++ b/engine/config/yaml_config.h @@ -1,13 +1,11 @@ #pragma once -#include <cmath> + #include <ctime> -#include <fstream> -#include <iostream> -#include <limits> #include <string> -#include "yaml-cpp/yaml.h" #include "model_config.h" +#include "yaml-cpp/yaml.h" + namespace config { class YamlHandler { private: @@ -29,4 +27,4 @@ class YamlHandler { // Method to write all attributes to a YAML file void WriteYamlFile(const std::string& file_path) const; }; -} \ No newline at end of file +} // namespace config diff --git a/engine/controllers/command_line_parser.cc b/engine/controllers/command_line_parser.cc index b8baf3466..8c0378abf 100644 --- a/engine/controllers/command_line_parser.cc +++ b/engine/controllers/command_line_parser.cc @@ -3,9 +3,10 @@ #include "commands/cmd_info.h" #include "commands/cortex_upd_cmd.h" #include "commands/engine_get_cmd.h" -#include "commands/engine_init_cmd.h" +#include "commands/engine_install_cmd.h" #include "commands/engine_list_cmd.h" #include "commands/engine_uninstall_cmd.h" +#include "commands/model_del_cmd.h" #include "commands/model_get_cmd.h" #include "commands/model_list_cmd.h" #include "commands/model_pull_cmd.h" @@ -13,7 +14,6 @@ #include "commands/model_stop_cmd.h" #include "commands/run_cmd.h" #include "commands/server_stop_cmd.h" -#include "commands/model_del_cmd.h" #include "config/yaml_config.h" #include "services/engine_service.h" #include "utils/file_manager_utils.h" @@ -66,10 +66,7 @@ bool CommandLineParser::SetupCommand(int argc, char** argv) { auto list_models_cmd = models_cmd->add_subcommand("list", "List all models locally"); - list_models_cmd->callback([]() { - commands::ModelListCmd command; - command.Exec(); - }); + list_models_cmd->callback([]() { commands::ModelListCmd().Exec(); }); auto get_models_cmd = models_cmd->add_subcommand("get", "Get info of {model_id} locally"); @@ -79,18 +76,21 @@ bool CommandLineParser::SetupCommand(int argc, char** argv) { command.Exec(); }); - auto model_pull_cmd = - app_.add_subcommand("pull", - "Download a model from a registry. Working with " - "HuggingFace repositories. For available models, " - "please visit https://huggingface.co/cortexso"); - model_pull_cmd->add_option("model_id", model_id, ""); - - model_pull_cmd->callback([&model_id]() { - commands::CmdInfo ci(model_id); - commands::ModelPullCmd command(ci.model_name, ci.branch); - command.Exec(); - }); + { + auto model_pull_cmd = + app_.add_subcommand("pull", + "Download a model from a registry. Working with " + "HuggingFace repositories. For available models, " + "please visit https://huggingface.co/cortexso"); + model_pull_cmd->add_option("model_id", model_id, ""); + model_pull_cmd->callback([&model_id]() { + try { + commands::ModelPullCmd().Exec(model_id); + } catch (const std::exception& e) { + CLI_LOG(e.what()); + } + }); + } auto model_del_cmd = models_cmd->add_subcommand("delete", "Delete a model by ID locally"); @@ -224,9 +224,12 @@ void CommandLineParser::EngineInstall(CLI::App* parent, std::string& version) { auto install_engine_cmd = parent->add_subcommand(engine_name, ""); - install_engine_cmd->callback([=] { - commands::EngineInitCmd eic(engine_name, version); - eic.Exec(); + install_engine_cmd->callback([engine_name, version] { + try { + commands::EngineInstallCmd().Exec(engine_name, version); + } catch (const std::exception& e) { + CTL_ERR(e.what()); + } }); } @@ -234,9 +237,12 @@ void CommandLineParser::EngineUninstall(CLI::App* parent, const std::string& engine_name) { auto uninstall_engine_cmd = parent->add_subcommand(engine_name, ""); - uninstall_engine_cmd->callback([=] { - commands::EngineUninstallCmd cmd(engine_name); - cmd.Exec(); + uninstall_engine_cmd->callback([engine_name] { + try { + commands::EngineUninstallCmd().Exec(engine_name); + } catch (const std::exception& e) { + CTL_ERR(e.what()); + } }); } @@ -248,9 +254,7 @@ void CommandLineParser::EngineGet(CLI::App* parent) { std::string desc = "Get " + engine_name + " status"; auto engine_get_cmd = get_cmd->add_subcommand(engine_name, desc); - engine_get_cmd->callback([engine_name] { - commands::EngineGetCmd cmd(engine_name); - cmd.Exec(); - }); + engine_get_cmd->callback( + [engine_name] { commands::EngineGetCmd().Exec(engine_name); }); } } diff --git a/engine/controllers/engines.cc b/engine/controllers/engines.cc index c9e2ba1b4..e35002e1f 100644 --- a/engine/controllers/engines.cc +++ b/engine/controllers/engines.cc @@ -144,11 +144,11 @@ void Engines::GetEngine(const HttpRequestPtr& req, try { auto status = engine_service.GetEngineInfo(engine); Json::Value ret; - ret["name"] = status.name; - ret["description"] = status.description; - ret["version"] = status.version; - ret["productName"] = status.product_name; - ret["status"] = status.status; + ret["name"] = status->name; + ret["description"] = status->description; + ret["version"] = status->version; + ret["productName"] = status->product_name; + ret["status"] = status->status; auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); resp->setStatusCode(k200OK); diff --git a/engine/controllers/server.cc b/engine/controllers/server.cc index ee9951968..8aa4a7e86 100644 --- a/engine/controllers/server.cc +++ b/engine/controllers/server.cc @@ -1,14 +1,9 @@ #include "server.h" -#include <chrono> -#include <fstream> -#include <iostream> - #include "trantor/utils/Logger.h" #include "utils/cortex_utils.h" #include "utils/cpuid/cpu_info.h" #include "utils/file_manager_utils.h" -#include "utils/logging_utils.h" using namespace inferences; using json = nlohmann::json; @@ -462,4 +457,4 @@ bool server::HasFieldInReq( return true; } -} // namespace inferences \ No newline at end of file +} // namespace inferences diff --git a/engine/e2e-test/main.py b/engine/e2e-test/main.py index da1197c33..1df424e65 100644 --- a/engine/e2e-test/main.py +++ b/engine/e2e-test/main.py @@ -4,10 +4,11 @@ from test_cli_engine_install import TestCliEngineInstall from test_cli_engine_list import TestCliEngineList from test_cli_engine_uninstall import TestCliEngineUninstall -from test_cortex_update import TestCortexUpdate +from test_cli_model_delete import TestCliModelDelete +from test_cli_model_pull_direct_url import TestCliModelPullDirectUrl from test_cli_server_start import TestCliServerStart +from test_cortex_update import TestCortexUpdate from test_create_log_folder import TestCreateLogFolder -from test_cli_model_delete import TestCliModelDelete if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/engine/e2e-test/test_cli_engine_install.py b/engine/e2e-test/test_cli_engine_install.py index 84af8b777..be23167be 100644 --- a/engine/e2e-test/test_cli_engine_install.py +++ b/engine/e2e-test/test_cli_engine_install.py @@ -8,7 +8,7 @@ class TestCliEngineInstall: def test_engines_install_llamacpp_should_be_successfully(self): exit_code, output, error = run( - "Install Engine", ["engines", "install", "cortex.llamacpp"] + "Install Engine", ["engines", "install", "cortex.llamacpp"], timeout=60 ) assert "Start downloading" in output, "Should display downloading message" assert exit_code == 0, f"Install engine failed with error: {error}" diff --git a/engine/e2e-test/test_cli_model_pull_direct_url.py b/engine/e2e-test/test_cli_model_pull_direct_url.py new file mode 100644 index 000000000..7d6fa677b --- /dev/null +++ b/engine/e2e-test/test_cli_model_pull_direct_url.py @@ -0,0 +1,17 @@ +import platform + +import pytest +from test_runner import run + + +class TestCliModelPullDirectUrl: + + @pytest.mark.skipif(True, reason="Expensive test. Only test when needed.") + def test_model_pull_with_direct_url_should_be_success(self): + exit_code, output, error = run( + "Pull model", ["pull", "https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v0.3-GGUF/blob/main/tinyllama-1.1b-chat-v0.3.Q2_K.gguf"], + timeout=None + ) + assert exit_code == 0, f"Model pull failed with error: {error}" + # TODO: verify that the model has been pull successfully + # TODO: skip this test. since download model is taking too long \ No newline at end of file diff --git a/engine/e2e-test/test_cli_model_pull_from_cortexso.py b/engine/e2e-test/test_cli_model_pull_from_cortexso.py new file mode 100644 index 000000000..e5651ce2f --- /dev/null +++ b/engine/e2e-test/test_cli_model_pull_from_cortexso.py @@ -0,0 +1,17 @@ +import platform + +import pytest +from test_runner import run + + +class TestCliModelPullCortexso: + + @pytest.mark.skipif(True, reason="Expensive test. Only test when needed.") + def test_model_pull_with_direct_url_should_be_success(self): + exit_code, output, error = run( + "Pull model", ["pull", "tinyllama"], + timeout=None + ) + assert exit_code == 0, f"Model pull failed with error: {error}" + # TODO: verify that the model has been pull successfully + # TODO: skip this test. since download model is taking too long \ No newline at end of file diff --git a/engine/e2e-test/test_runner.py b/engine/e2e-test/test_runner.py index bc89fb836..bedf8d39d 100644 --- a/engine/e2e-test/test_runner.py +++ b/engine/e2e-test/test_runner.py @@ -24,13 +24,16 @@ def getExecutablePath() -> str: # Execute a command -def run(test_name: str, arguments: List[str], timeout_sec = 5): +def run(test_name: str, arguments: List[str], timeout=timeout) -> (int, str, str): executable_path = getExecutablePath() print("Running:", test_name) print("Command:", [executable_path] + arguments) result = subprocess.run( - [executable_path] + arguments, capture_output=True, text=True, timeout=timeout_sec + [executable_path] + arguments, + capture_output=True, + text=True, + timeout=timeout, ) return result.returncode, result.stdout, result.stderr diff --git a/engine/services/download_service.h b/engine/services/download_service.h index 2583f629f..5015ec29c 100644 --- a/engine/services/download_service.h +++ b/engine/services/download_service.h @@ -62,7 +62,7 @@ class DownloadService { std::optional<OnDownloadTaskSuccessfully> callback = std::nullopt); /** - * Getting file size for a provided url. + * Getting file size for a provided url. Can be used to validating the download url. * * @param url - url to get file size */ diff --git a/engine/services/engine_service.cc b/engine/services/engine_service.cc index 99e946b56..5691d728c 100644 --- a/engine/services/engine_service.cc +++ b/engine/services/engine_service.cc @@ -1,20 +1,23 @@ #include "engine_service.h" +#include <httplib.h> #include <stdexcept> #include "algorithm" +#include "utils/archive_utils.h" +#include "utils/engine_matcher_utils.h" #include "utils/file_manager_utils.h" +#include "utils/json.hpp" +#include "utils/semantic_version_utils.h" +#include "utils/system_info_utils.h" +#include "utils/url_parser.h" -namespace { -constexpr static auto kIncompatible = "Incompatible"; -constexpr static auto kReady = "Ready"; -constexpr static auto kNotInstalled = "Not Installed"; -} // namespace +using json = nlohmann::json; -EngineInfo EngineService::GetEngineInfo(const std::string& engine) const { +std::optional<EngineInfo> EngineService::GetEngineInfo( + const std::string& engine) const { // if engine is not found in kSupportEngine, throw runtime error if (std::find(kSupportEngines.begin(), kSupportEngines.end(), engine) == kSupportEngines.end()) { - // TODO: create a custom exception class - throw std::runtime_error("Engine " + engine + " is not supported!"); + return std::nullopt; } auto engine_status_list = GetEngineInfoList(); @@ -70,6 +73,189 @@ std::vector<EngineInfo> EngineService::GetEngineInfoList() const { return engines; } +void EngineService::InstallEngine(const std::string& engine, + const std::string& version) { + auto system_info = system_info_utils::GetSystemInfo(); + auto url_obj = url_parser::Url{ + .protocol = "https", + .host = "api.github.com", + .pathParams = {"repos", "janhq", engine, "releases", version}, + }; + + httplib::Client cli(url_obj.GetProtocolAndHost()); + if (auto res = cli.Get(url_obj.GetPathAndQuery()); + res->status == httplib::StatusCode::OK_200) { + auto body = json::parse(res->body); + auto assets = body["assets"]; + auto os_arch{system_info.os + "-" + system_info.arch}; + + std::vector<std::string> variants; + for (auto& asset : assets) { + auto asset_name = asset["name"].get<std::string>(); + variants.push_back(asset_name); + } + + auto cuda_driver_version = system_info_utils::GetCudaVersion(); + CTL_INF("engine: " << engine); + CTL_INF("CUDA version: " << cuda_driver_version); + std::string matched_variant = ""; + + if (engine == "cortex.tensorrt-llm") { + matched_variant = engine_matcher_utils::ValidateTensorrtLlm( + variants, system_info.os, cuda_driver_version); + } else if (engine == "cortex.onnx") { + matched_variant = engine_matcher_utils::ValidateOnnx( + variants, system_info.os, system_info.arch); + } else if (engine == "cortex.llamacpp") { + cortex::cpuid::CpuInfo cpu_info; + auto suitable_avx = engine_matcher_utils::GetSuitableAvxVariant(cpu_info); + matched_variant = engine_matcher_utils::Validate( + variants, system_info.os, system_info.arch, suitable_avx, + cuda_driver_version); + } + CTL_INF("Matched variant: " << matched_variant); + if (matched_variant.empty()) { + CTL_ERR("No variant found for " << os_arch); + throw std::runtime_error("No variant found for " + os_arch); + } + + for (auto& asset : assets) { + auto assetName = asset["name"].get<std::string>(); + if (assetName == matched_variant) { + auto download_url = asset["browser_download_url"].get<std::string>(); + auto file_name = asset["name"].get<std::string>(); + CTL_INF("Download url: " << download_url); + + std::filesystem::path engine_folder_path = + file_manager_utils::GetContainerFolderPath( + file_manager_utils::DownloadTypeToString( + DownloadType::Engine)) / + engine; + + if (!std::filesystem::exists(engine_folder_path)) { + CTL_INF("Creating " << engine_folder_path.string()); + std::filesystem::create_directories(engine_folder_path); + } + + CTL_INF("Engine folder path: " << engine_folder_path.string() << "\n"); + auto local_path = engine_folder_path / file_name; + auto downloadTask{DownloadTask{.id = engine, + .type = DownloadType::Engine, + .items = {DownloadItem{ + .id = engine, + .downloadUrl = download_url, + .localPath = local_path, + }}}}; + + DownloadService download_service; + download_service.AddDownloadTask( + downloadTask, [](const DownloadTask& finishedTask) { + // try to unzip the downloaded file + CTL_INF("Engine zip path: " + << finishedTask.items[0].localPath.string()); + + std::filesystem::path extract_path = + finishedTask.items[0].localPath.parent_path().parent_path(); + + archive_utils::ExtractArchive( + finishedTask.items[0].localPath.string(), + extract_path.string()); + + // remove the downloaded file + try { + std::filesystem::remove(finishedTask.items[0].localPath); + } catch (const std::exception& e) { + CTL_WRN("Could not delete file: " << e.what()); + } + CTL_INF("Finished!"); + }); + if (system_info.os == "mac" || engine == "cortex.onnx") { + // mac and onnx engine does not require cuda toolkit + return; + } + + if (cuda_driver_version.empty()) { + CTL_WRN("No cuda driver, continue with CPU"); + return; + } + + // download cuda toolkit + const std::string jan_host = "https://catalog.jan.ai"; + const std::string cuda_toolkit_file_name = "cuda.tar.gz"; + const std::string download_id = "cuda"; + + // TODO: we don't have API to retrieve list of cuda toolkit dependencies atm because we hosting it at jan + // will have better logic after https://github.com/janhq/cortex/issues/1046 finished + // for now, assume that we have only 11.7 and 12.4 + auto suitable_toolkit_version = ""; + if (engine == "cortex.tensorrt-llm") { + // for tensorrt-llm, we need to download cuda toolkit v12.4 + suitable_toolkit_version = "12.4"; + } else { + // llamacpp + auto cuda_driver_semver = + semantic_version_utils::SplitVersion(cuda_driver_version); + if (cuda_driver_semver.major == 11) { + suitable_toolkit_version = "11.7"; + } else if (cuda_driver_semver.major == 12) { + suitable_toolkit_version = "12.4"; + } + } + + // compare cuda driver version with cuda toolkit version + // cuda driver version should be greater than toolkit version to ensure compatibility + if (semantic_version_utils::CompareSemanticVersion( + cuda_driver_version, suitable_toolkit_version) < 0) { + CTL_ERR("Your Cuda driver version " + << cuda_driver_version + << " is not compatible with cuda toolkit version " + << suitable_toolkit_version); + throw std::runtime_error( + "Cuda driver is not compatible with cuda toolkit"); + } + + std::ostringstream cuda_toolkit_url; + cuda_toolkit_url << jan_host << "/" << "dist/cuda-dependencies/" + << cuda_driver_version << "/" << system_info.os << "/" + << cuda_toolkit_file_name; + + LOG_DEBUG << "Cuda toolkit download url: " << cuda_toolkit_url.str(); + auto cuda_toolkit_local_path = + file_manager_utils::GetContainerFolderPath( + file_manager_utils::DownloadTypeToString( + DownloadType::CudaToolkit)) / + cuda_toolkit_file_name; + LOG_DEBUG << "Download to: " << cuda_toolkit_local_path.string(); + auto downloadCudaToolkitTask{DownloadTask{ + .id = download_id, + .type = DownloadType::CudaToolkit, + .items = {DownloadItem{.id = download_id, + .downloadUrl = cuda_toolkit_url.str(), + .localPath = cuda_toolkit_local_path}}, + }}; + + download_service.AddDownloadTask( + downloadCudaToolkitTask, [&](const DownloadTask& finishedTask) { + auto engine_path = + file_manager_utils::GetEnginesContainerPath() / engine; + archive_utils::ExtractArchive( + finishedTask.items[0].localPath.string(), + engine_path.string()); + + try { + std::filesystem::remove(finishedTask.items[0].localPath); + } catch (std::exception& e) { + CTL_ERR("Error removing downloaded file: " << e.what()); + } + }); + return; + } + } + } else { + throw std::runtime_error("Failed to fetch engine release: " + engine); + } +} + void EngineService::UninstallEngine(const std::string& engine) { // TODO: Unload the model which is currently running on engine_ diff --git a/engine/services/engine_service.h b/engine/services/engine_service.h index c6bd4f83a..442923356 100644 --- a/engine/services/engine_service.h +++ b/engine/services/engine_service.h @@ -1,5 +1,6 @@ #pragma once +#include <optional> #include <string> #include <string_view> #include <vector> @@ -15,14 +16,19 @@ struct EngineInfo { class EngineService { public: + constexpr static auto kIncompatible = "Incompatible"; + constexpr static auto kReady = "Ready"; + constexpr static auto kNotInstalled = "Not Installed"; + const std::vector<std::string_view> kSupportEngines = { "cortex.llamacpp", "cortex.onnx", "cortex.tensorrt-llm"}; - EngineInfo GetEngineInfo(const std::string& engine) const; + std::optional<EngineInfo> GetEngineInfo(const std::string& engine) const; std::vector<EngineInfo> GetEngineInfoList() const; - void InstallEngine(const std::string& engine); + void InstallEngine(const std::string& engine, + const std::string& version = "latest"); void UninstallEngine(const std::string& engine); }; diff --git a/engine/services/model_service.cc b/engine/services/model_service.cc new file mode 100644 index 000000000..7943cace4 --- /dev/null +++ b/engine/services/model_service.cc @@ -0,0 +1,116 @@ +#include "model_service.h" +#include <filesystem> +#include <iostream> +#include "commands/cmd_info.h" +#include "utils/cortexso_parser.h" +#include "utils/file_manager_utils.h" +#include "utils/logging_utils.h" +#include "utils/model_callback_utils.h" +#include "utils/url_parser.h" + +void ModelService::DownloadModel(const std::string& input) { + if (input.empty()) { + throw std::runtime_error( + "Input must be Cortex Model Hub handle or HuggingFace url!"); + } + + // case input is a direct url + auto url_obj = url_parser::FromUrlString(input); + // TODO: handle case user paste url from cortexso + if (url_obj.protocol == "https") { + if (url_obj.host != kHuggingFaceHost) { + CLI_LOG("Only huggingface.co is supported for now"); + return; + } + return DownloadModelByDirectUrl(input); + } else { + commands::CmdInfo ci(input); + return DownloadModelFromCortexso(ci.model_name, ci.branch); + } +} + +std::optional<config::ModelConfig> ModelService::GetDownloadedModel( + const std::string& modelId) const { + auto models_path = file_manager_utils::GetModelsContainerPath(); + if (!std::filesystem::exists(models_path) || + !std::filesystem::is_directory(models_path)) { + return std::nullopt; + } + + for (const auto& entry : std::filesystem::directory_iterator(models_path)) { + if (entry.is_regular_file() && + entry.path().filename().string() == modelId && + entry.path().extension() == ".yaml") { + try { + config::YamlHandler handler; + handler.ModelConfigFromFile(entry.path().string()); + auto model_conf = handler.GetModelConfig(); + return model_conf; + } catch (const std::exception& e) { + LOG_ERROR << "Error reading yaml file '" << entry.path().string() + << "': " << e.what(); + } + } + } + return std::nullopt; +} + +void ModelService::DownloadModelByDirectUrl(const std::string& url) { + // check for malformed url + // question: What if the url is from cortexso itself + // answer: then route to download from cortexso + auto url_obj = url_parser::FromUrlString(url); + + if (url_obj.host == kHuggingFaceHost) { + // goto hugging face parser to normalize the url + // loop through path params, replace blob to resolve if any + if (url_obj.pathParams[2] == "blob") { + url_obj.pathParams[2] = "resolve"; + } + } + + // should separate this function out + auto model_id{url_obj.pathParams[1]}; + auto file_name{url_obj.pathParams.back()}; + + auto local_path = + file_manager_utils::GetModelsContainerPath() / model_id / model_id; + + try { + std::filesystem::create_directories(local_path.parent_path()); + } catch (const std::filesystem::filesystem_error& e) { + // if file exist, remove it + std::filesystem::remove(local_path.parent_path()); + std::filesystem::create_directories(local_path.parent_path()); + } + + auto download_url = url_parser::FromUrl(url_obj); + // this assume that the model being downloaded is a single gguf file + auto downloadTask{DownloadTask{.id = url_obj.pathParams.back(), + .type = DownloadType::Model, + .items = {DownloadItem{ + .id = url_obj.pathParams.back(), + .downloadUrl = download_url, + .localPath = local_path, + }}}}; + + auto on_finished = [](const DownloadTask& finishedTask) { + std::cout << "Download success" << std::endl; + auto gguf_download_item = finishedTask.items[0]; + model_callback_utils::ParseGguf(gguf_download_item); + }; + + download_service_.AddDownloadTask(downloadTask, on_finished); +} + +void ModelService::DownloadModelFromCortexso(const std::string& name, + const std::string& branch) { + auto downloadTask = cortexso_parser::getDownloadTask(name, branch); + if (downloadTask.has_value()) { + DownloadService().AddDownloadTask(downloadTask.value(), + model_callback_utils::DownloadModelCb); + CTL_INF("Download finished"); + } else { + CTL_ERR("Model not found"); + } +} diff --git a/engine/services/model_service.h b/engine/services/model_service.h new file mode 100644 index 000000000..81ec4e4b3 --- /dev/null +++ b/engine/services/model_service.h @@ -0,0 +1,25 @@ +#pragma once + +#include <string> +#include "config/model_config.h" +#include "services/download_service.h" + +class ModelService { + public: + ModelService() : download_service_{DownloadService()} {}; + + void DownloadModel(const std::string& input); + + std::optional<config::ModelConfig> GetDownloadedModel( + const std::string& modelId) const; + + private: + void DownloadModelByDirectUrl(const std::string& url); + + void DownloadModelFromCortexso(const std::string& name, + const std::string& branch); + + DownloadService download_service_; + + constexpr auto static kHuggingFaceHost = "huggingface.co"; +}; diff --git a/engine/test/components/test_url_parser.cc b/engine/test/components/test_url_parser.cc index 375ce385d..decffcbf8 100644 --- a/engine/test/components/test_url_parser.cc +++ b/engine/test/components/test_url_parser.cc @@ -1,3 +1,4 @@ +#include <iostream> #include "gtest/gtest.h" #include "utils/url_parser.h" @@ -24,3 +25,48 @@ TEST_F(UrlParserTestSuite, ConstructUrlCorrectly) { EXPECT_EQ(url_str, kValidUrlWithOnlyPaths); } + +TEST_F(UrlParserTestSuite, ConstructUrlWithQueryCorrectly) { + auto url = url_parser::Url{ + .protocol = "https", + .host = "jan.ai", + .pathParams = {"path1", "path2"}, + .queries = {{"key1", "value1"}, {"key2", 2}, {"key3", true}}, + }; + auto url_str = url_parser::FromUrl(url); + + auto contains_key1 = url_str.find("key1=value1") != std::string::npos; + auto contains_key2 = url_str.find("key2=2") != std::string::npos; + auto contains_key3 = url_str.find("key3=true") != std::string::npos; + + EXPECT_TRUE(contains_key1); + EXPECT_TRUE(contains_key2); + EXPECT_TRUE(contains_key3); +} + +TEST_F(UrlParserTestSuite, ConstructUrlWithEmptyPathCorrectly) { + auto url = url_parser::Url{ + .protocol = "https", + .host = "jan.ai", + .pathParams = {}, + }; + auto url_str = url_parser::FromUrl(url); + + EXPECT_EQ(url_str, "https://jan.ai"); +} + +TEST_F(UrlParserTestSuite, GetProtocolAndHostCorrectly) { + auto url = url_parser::Url{.protocol = "https", .host = "jan.ai"}; + auto protocol_and_host = url.GetProtocolAndHost(); + EXPECT_EQ(protocol_and_host, "https://jan.ai"); +} + +TEST_F(UrlParserTestSuite, GetPathAndQueryCorrectly) { + auto url = url_parser::Url{ + .protocol = "https", + .host = "jan.ai", + .pathParams = {"path1", "path2"}, + }; + auto path_and_query = url.GetPathAndQuery(); + EXPECT_EQ(path_and_query, "/path1/path2"); +} diff --git a/engine/utils/cortexso_parser.h b/engine/utils/cortexso_parser.h index a9592d98d..d4e85bee9 100644 --- a/engine/utils/cortexso_parser.h +++ b/engine/utils/cortexso_parser.h @@ -7,6 +7,7 @@ #include <nlohmann/json.hpp> #include "httplib.h" #include "utils/file_manager_utils.h" +#include "utils/logging_utils.h" namespace cortexso_parser { constexpr static auto kHuggingFaceHost = "https://huggingface.co"; @@ -34,7 +35,7 @@ inline std::optional<DownloadTask> getDownloadTask( file_manager_utils::CreateDirectoryRecursively( model_container_path.string()); - for (auto& [key, value] : jsonResponse.items()) { + for (const auto& [key, value] : jsonResponse.items()) { std::ostringstream downloadUrlOutput; auto path = value["path"].get<std::string>(); if (path == ".gitattributes" || path == ".gitignore" || @@ -58,7 +59,7 @@ inline std::optional<DownloadTask> getDownloadTask( return downloadTask; } catch (const json::parse_error& e) { - std::cerr << "JSON parse error: " << e.what() << std::endl; + CTL_ERR("JSON parse error: {}" << e.what()); } } } else { diff --git a/engine/utils/file_manager_utils.h b/engine/utils/file_manager_utils.h index 8e449b3d2..7b12fab43 100644 --- a/engine/utils/file_manager_utils.h +++ b/engine/utils/file_manager_utils.h @@ -208,7 +208,7 @@ inline std::filesystem::path GetModelsContainerPath() { if (!std::filesystem::exists(models_container_path)) { CTL_INF("Model container folder not found. Create one: " << models_container_path.string()); - std::filesystem::create_directory(models_container_path); + std::filesystem::create_directories(models_container_path); } return models_container_path; diff --git a/engine/utils/url_parser.h b/engine/utils/url_parser.h index dba3ec0a5..55dd557b8 100644 --- a/engine/utils/url_parser.h +++ b/engine/utils/url_parser.h @@ -7,14 +7,29 @@ #include "exceptions/malformed_url_exception.h" namespace url_parser { -// TODO: add an unordered map to store the query -// TODO: add a function to construct a string from Url - struct Url { std::string protocol; std::string host; std::vector<std::string> pathParams; std::unordered_map<std::string, std::variant<std::string, int, bool>> queries; + + std::string GetProtocolAndHost() const { return protocol + "://" + host; } + + std::string GetPathAndQuery() const { + std::string path; + for (const auto& path_param : pathParams) { + path += "/" + path_param; + } + std::string query; + for (const auto& [key, value] : queries) { + query += key + "=" + std::get<std::string>(value) + "&"; + } + if (!query.empty()) { + query.pop_back(); + return path + "?" + query; + } + return path; + }; }; const std::regex url_regex( @@ -81,7 +96,31 @@ inline std::string FromUrl(const Url& url) { url_string << "/" << path; } - // TODO: handle queries + std::string query_string; + for (const auto& [key, value] : url.queries) { + try { + std::string value_str; + if (std::holds_alternative<std::string>(value)) { + value_str = std::get<std::string>(value); + } else if (std::holds_alternative<int>(value)) { + value_str = std::to_string(std::get<int>(value)); + } else if (std::holds_alternative<bool>(value)) { + value_str = std::get<bool>(value) ? "true" : "false"; + } + if (!query_string.empty()) { + query_string += "&"; + } + query_string += key + "=" + value_str; + } catch (const std::bad_variant_access& e) { + // Handle the case where the variant does not match any of the expected types + // This should not happen if the map was created correctly + throw std::runtime_error("Invalid variant type in queries map"); + } + } + + if (!query_string.empty()) { + url_string << "?" << query_string; + } return url_string.str(); }