Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit c7765ec

Browse files
authored
Merge pull request #1650 from janhq/j/update-engine-mng
chore: some update for engine management
2 parents 1c516f0 + 639b89f commit c7765ec

14 files changed

+234
-45
lines changed

engine/cli/command_line_parser.cc

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -88,34 +88,39 @@ bool CommandLineParser::SetupCommand(int argc, char** argv) {
8888

8989
// Check new update
9090
#ifdef CORTEX_CPP_VERSION
91-
if (cml_data_.check_upd) {
92-
if (strcmp(CORTEX_CPP_VERSION, "default_version") != 0) {
93-
// TODO(sang) find a better way to handle
94-
// This is an extremely ugly way to deal with connection
95-
// hang when network down
96-
std::atomic<bool> done = false;
97-
std::thread t([&]() {
98-
if (auto latest_version =
99-
commands::CheckNewUpdate(commands::kTimeoutCheckUpdate);
100-
latest_version.has_value() &&
101-
*latest_version != CORTEX_CPP_VERSION) {
102-
CLI_LOG("\nNew Cortex release available: "
103-
<< CORTEX_CPP_VERSION << " -> " << *latest_version);
104-
CLI_LOG("To update, run: " << commands::GetRole()
105-
<< commands::GetCortexBinary()
106-
<< " update");
107-
}
108-
done = true;
109-
});
110-
// Do not wait for http connection timeout
111-
t.detach();
112-
int retry = 10;
113-
while (!done && retry--) {
114-
std::this_thread::sleep_for(commands::kTimeoutCheckUpdate / 10);
91+
if (cml_data_.check_upd &&
92+
strcmp(CORTEX_CPP_VERSION, "default_version") != 0) {
93+
// TODO(sang) find a better way to handle
94+
// This is an extremely ugly way to deal with connection
95+
// hang when network down
96+
std::atomic<bool> done = false;
97+
std::thread t([&]() {
98+
if (auto latest_version =
99+
commands::CheckNewUpdate(commands::kTimeoutCheckUpdate);
100+
latest_version.has_value() && *latest_version != CORTEX_CPP_VERSION) {
101+
CLI_LOG("\nNew Cortex release available: "
102+
<< CORTEX_CPP_VERSION << " -> " << *latest_version);
103+
CLI_LOG("To update, run: " << commands::GetRole()
104+
<< commands::GetCortexBinary() << " update");
115105
}
106+
done = true;
107+
});
108+
// Do not wait for http connection timeout
109+
t.detach();
110+
int retry = 10;
111+
while (!done && retry--) {
112+
std::this_thread::sleep_for(commands::kTimeoutCheckUpdate / 10);
116113
}
117114
}
118115
#endif
116+
auto config = file_manager_utils::GetCortexConfig();
117+
if (!config.llamacppVersion.empty() &&
118+
config.latestLlamacppRelease != config.llamacppVersion) {
119+
CLI_LOG(
120+
"\nNew llama.cpp version available: " << config.latestLlamacppRelease);
121+
CLI_LOG("To update, run: " << commands::GetCortexBinary()
122+
<< " engines update llama-cpp");
123+
}
119124

120125
return true;
121126
}

engine/cli/commands/cortex_upd_cmd.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,13 @@ std::optional<std::string> CheckNewUpdate(
174174
CTL_INF("Got the latest release, update to the config file: "
175175
<< latest_version)
176176
config.latestRelease = latest_version;
177-
config_yaml_utils::DumpYamlConfig(
177+
auto result = config_yaml_utils::DumpYamlConfig(
178178
config, file_manager_utils::GetConfigurationPath().string());
179+
if (result.has_error()) {
180+
CTL_ERR("Error update "
181+
<< file_manager_utils::GetConfigurationPath().string()
182+
<< result.error());
183+
}
179184
if (current_version != latest_version) {
180185
return latest_version;
181186
}

engine/cli/commands/engine_install_cmd.cc

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,25 @@ bool EngineInstallCmd::Exec(const std::string& engine,
8484
std::vector<std::string> variant_selections;
8585
for (const auto& variant : variant_result.value()) {
8686
auto v_name = variant["name"].asString();
87-
if (string_utils::StringContainsIgnoreCase(v_name, hw_inf_.sys_inf->os)) {
87+
if (string_utils::StringContainsIgnoreCase(v_name, hw_inf_.sys_inf->os) &&
88+
string_utils::StringContainsIgnoreCase(v_name,
89+
hw_inf_.sys_inf->arch)) {
8890
variant_selections.push_back(variant["name"].asString());
8991
}
9092
}
91-
auto selected_variant =
92-
cli_selection_utils::PrintSelection(variant_selections);
93+
if (variant_selections.empty()) {
94+
CTL_ERR("No suitable variant found for " << hw_inf_.sys_inf->os << " "
95+
<< hw_inf_.sys_inf->arch);
96+
return false;
97+
}
98+
99+
std::optional<std::string> selected_variant = std::nullopt;
100+
if (variant_selections.size() == 1) {
101+
selected_variant = variant_selections[0];
102+
} else {
103+
selected_variant =
104+
cli_selection_utils::PrintSelection(variant_selections);
105+
}
93106
if (selected_variant == std::nullopt) {
94107
CTL_ERR("Invalid variant selection");
95108
return false;

engine/cli/commands/engine_update_cmd.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "utils/cli_selection_utils.h"
55
#include "utils/curl_utils.h"
66
#include "utils/download_progress.h"
7+
#include "utils/json_helper.h"
78
#include "utils/logging_utils.h"
89
#include "utils/system_info_utils.h"
910
#include "utils/url_parser.h"
@@ -26,7 +27,6 @@ bool EngineUpdateCmd::Exec(const std::string& host, int port,
2627
auto dp_res = std::async(std::launch::deferred, [&dp, &engine] {
2728
return dp.Handle(DownloadType::Engine);
2829
});
29-
CLI_LOG("Validating download items, please wait..")
3030

3131
auto update_url = url_parser::Url{
3232
.protocol = "http",
@@ -35,7 +35,13 @@ bool EngineUpdateCmd::Exec(const std::string& host, int port,
3535
};
3636
auto update_result = curl_utils::SimplePostJson(update_url.ToFullPath());
3737
if (update_result.has_error()) {
38-
CTL_ERR(update_result.error());
38+
try {
39+
Json::Value json = json_helper::ParseJsonString(update_result.error());
40+
std::cout << json["message"].asString() << std::endl;
41+
} catch (const std::exception& e) {
42+
CTL_ERR(update_result.error());
43+
}
44+
3945
return false;
4046
}
4147

engine/cli/main.cc

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,68 @@ int main(int argc, char* argv[]) {
101101

102102
RemoveBinaryTempFileIfExists();
103103

104+
auto should_check_for_latest_llamacpp_version = true;
105+
auto now = std::chrono::system_clock::now();
106+
107+
// read the yaml to see the last time we check for update
108+
auto config = file_manager_utils::GetCortexConfig();
109+
if (config.checkedForLlamacppUpdateAt != 0) {
110+
// if it passed a day, then we should check
111+
auto last_check =
112+
std::chrono::system_clock::time_point(
113+
std::chrono::milliseconds(config.checkedForLlamacppUpdateAt)) +
114+
std::chrono::hours(24);
115+
should_check_for_latest_llamacpp_version = now > last_check;
116+
}
117+
118+
if (should_check_for_latest_llamacpp_version) {
119+
std::thread t1([]() {
120+
auto config = file_manager_utils::GetCortexConfig();
121+
// TODO: namh current we only check for llamacpp. Need to add support for other engine
122+
auto get_latest_version = []() -> cpp::result<std::string, std::string> {
123+
try {
124+
auto res = github_release_utils::GetReleaseByVersion(
125+
"janhq", "cortex.llamacpp", "latest");
126+
if (res.has_error()) {
127+
CTL_ERR("Failed to get latest llama.cpp version: " << res.error());
128+
return cpp::fail("Failed to get latest llama.cpp version: " +
129+
res.error());
130+
}
131+
CTL_INF("Latest llamacpp version: " << res->tag_name);
132+
return res->tag_name;
133+
} catch (const std::exception& e) {
134+
CTL_ERR("Failed to get latest llama.cpp version: " << e.what());
135+
return cpp::fail("Failed to get latest llama.cpp version: " +
136+
std::string(e.what()));
137+
}
138+
};
139+
140+
auto res = get_latest_version();
141+
if (res.has_error()) {
142+
CTL_ERR("Failed to get latest llama.cpp version: " << res.error());
143+
return;
144+
}
145+
146+
auto now = std::chrono::system_clock::now();
147+
CTL_DBG("latest llama.cpp version: " << res.value());
148+
config.checkedForLlamacppUpdateAt =
149+
std::chrono::duration_cast<std::chrono::milliseconds>(
150+
now.time_since_epoch())
151+
.count();
152+
config.latestLlamacppRelease = res.value();
153+
154+
auto upd_config_res = config_yaml_utils::DumpYamlConfig(
155+
config, file_manager_utils::GetConfigurationPath().string());
156+
if (upd_config_res.has_error()) {
157+
CTL_ERR("Failed to update config file: " << upd_config_res.error());
158+
} else {
159+
CTL_INF("Updated config file with latest llama.cpp version: "
160+
<< res.value());
161+
}
162+
});
163+
t1.detach();
164+
}
165+
104166
trantor::FileLogger async_file_logger;
105167
SetupLogger(async_file_logger, verbose);
106168

engine/controllers/engines.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ class Engines : public drogon::HttpController<Engines, false> {
2424
METHOD_ADD(Engines::UnloadEngine, "/{1}/load", Options, Delete);
2525
METHOD_ADD(Engines::UpdateEngine, "/{1}/update", Post);
2626
METHOD_ADD(Engines::ListEngine, "", Get);
27+
2728
METHOD_ADD(Engines::GetEngineVersions, "/{1}/versions", Get);
2829
METHOD_ADD(Engines::GetEngineVariants, "/{1}/versions/{2}", Get);
30+
METHOD_ADD(Engines::GetLatestEngineVersion, "/{1}/latest", Get);
2931

3032
ADD_METHOD_TO(Engines::GetInstalledEngineVariants, "/v1/engines/{1}", Get);
3133
ADD_METHOD_TO(Engines::InstallEngine,

engine/services/engine_service.cc

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,16 @@ cpp::result<bool, std::string> EngineService::UninstallEngineVariant(
181181
const std::string& engine, const std::optional<std::string> version,
182182
const std::optional<std::string> variant) {
183183
auto ne = NormalizeEngine(engine);
184+
if (IsEngineLoaded(ne)) {
185+
CTL_INF("Engine " << ne << " is already loaded, unloading it");
186+
auto unload_res = UnloadEngine(ne);
187+
if (unload_res.has_error()) {
188+
CTL_INF("Failed to unload engine: " << unload_res.error());
189+
return cpp::fail(unload_res.error());
190+
} else {
191+
CTL_INF("Engine " << ne << " unloaded successfully");
192+
}
193+
}
184194

185195
std::optional<std::filesystem::path> path_to_remove = std::nullopt;
186196
if (version == std::nullopt && variant == std::nullopt) {
@@ -264,6 +274,16 @@ cpp::result<void, std::string> EngineService::DownloadEngineV2(
264274
if (selected_variant == std::nullopt) {
265275
return cpp::fail("Failed to find a suitable variant for " + engine);
266276
}
277+
if (IsEngineLoaded(engine)) {
278+
CTL_INF("Engine " << engine << " is already loaded, unloading it");
279+
auto unload_res = UnloadEngine(engine);
280+
if (unload_res.has_error()) {
281+
CTL_INF("Failed to unload engine: " << unload_res.error());
282+
return cpp::fail(unload_res.error());
283+
} else {
284+
CTL_INF("Engine " << engine << " unloaded successfully");
285+
}
286+
}
267287
auto normalize_version = "v" + selected_variant->version;
268288

269289
auto variant_folder_name = engine_matcher_utils::GetVariantFromNameAndVersion(
@@ -276,7 +296,7 @@ cpp::result<void, std::string> EngineService::DownloadEngineV2(
276296
auto variant_path = variant_folder_path / selected_variant->name;
277297
std::filesystem::create_directories(variant_folder_path);
278298
CLI_LOG("variant_folder_path: " + variant_folder_path.string());
279-
auto on_finished = [this, engine, selected_variant,
299+
auto on_finished = [this, engine, selected_variant, variant_folder_path,
280300
normalize_version](const DownloadTask& finishedTask) {
281301
// try to unzip the downloaded file
282302
CLI_LOG("Engine zip path: " << finishedTask.items[0].localPath.string());
@@ -299,6 +319,22 @@ cpp::result<void, std::string> EngineService::DownloadEngineV2(
299319
CTL_INF("Set default engine variant: " << res.value().variant);
300320
}
301321

322+
// remove other engines
323+
auto engine_directories = file_manager_utils::GetEnginesContainerPath() /
324+
engine / selected_variant->name;
325+
326+
for (const auto& entry : std::filesystem::directory_iterator(
327+
variant_folder_path.parent_path())) {
328+
if (entry.is_directory() &&
329+
entry.path().filename() != normalize_version) {
330+
try {
331+
std::filesystem::remove_all(entry.path());
332+
} catch (const std::exception& e) {
333+
CTL_WRN("Could not delete directory: " << e.what());
334+
}
335+
}
336+
}
337+
302338
// remove the downloaded file
303339
try {
304340
std::filesystem::remove(finishedTask.items[0].localPath);
@@ -364,6 +400,16 @@ cpp::result<bool, std::string> EngineService::DownloadEngine(
364400
CTL_INF("Creating " << engine_folder_path.string());
365401
std::filesystem::create_directories(engine_folder_path);
366402
}
403+
if (IsEngineLoaded(engine)) {
404+
CTL_INF("Engine " << engine << " is already loaded, unloading it");
405+
auto unload_res = UnloadEngine(engine);
406+
if (unload_res.has_error()) {
407+
CTL_INF("Failed to unload engine: " << unload_res.error());
408+
return cpp::fail(unload_res.error());
409+
} else {
410+
CTL_INF("Engine " << engine << " unloaded successfully");
411+
}
412+
}
367413
CTL_INF("Engine folder path: " << engine_folder_path.string() << "\n");
368414
auto local_path = engine_folder_path / asset.name;
369415
auto downloadTask{
@@ -918,6 +964,17 @@ cpp::result<EngineUpdateResult, std::string> EngineService::UpdateEngine(
918964
CTL_INF("Default variant: " << default_variant->variant
919965
<< ", version: " + default_variant->version);
920966

967+
if (IsEngineLoaded(ne)) {
968+
CTL_INF("Engine " << ne << " is already loaded, unloading it");
969+
auto unload_res = UnloadEngine(ne);
970+
if (unload_res.has_error()) {
971+
CTL_INF("Failed to unload engine: " << unload_res.error());
972+
return cpp::fail(unload_res.error());
973+
} else {
974+
CTL_INF("Engine " << ne << " unloaded successfully");
975+
}
976+
}
977+
921978
auto latest_version = GetLatestEngineVersion(ne);
922979
if (latest_version.has_error()) {
923980
// if can't get latest version, stop

engine/test/components/test_cortex_config.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class CortexConfigTest : public ::testing::Test {
1818
kDefaultHost,
1919
kDefaultPort,
2020
kDefaultCheckedForUpdateAt,
21+
kDefaultCheckedForLlamacppUpdateAt,
2122
kDefaultLatestRelease};
2223
}
2324

@@ -39,9 +40,11 @@ TEST_F(CortexConfigTest, DumpYamlConfig_WritesCorrectly) {
3940
"localhost",
4041
"8080",
4142
123456789,
43+
123456789,
4244
"v1.0.0"};
4345

44-
DumpYamlConfig(config, test_file_path);
46+
auto result = DumpYamlConfig(config, test_file_path);
47+
EXPECT_FALSE(result.has_error());
4548

4649
// Verify that the file was created and contains the expected data
4750
YAML::Node node = YAML::LoadFile(test_file_path);
@@ -66,9 +69,11 @@ TEST_F(CortexConfigTest, FromYaml_ReadsCorrectly) {
6669
"localhost",
6770
"8080",
6871
123456789,
72+
123456789,
6973
"v1.0.0"};
7074

71-
DumpYamlConfig(config, test_file_path);
75+
auto result = DumpYamlConfig(config, test_file_path);
76+
EXPECT_FALSE(result.has_error());
7277

7378
// Now read from the YAML file
7479
CortexConfig loaded_config = FromYaml(test_file_path, default_config);
@@ -115,4 +120,4 @@ TEST_F(CortexConfigTest, FromYaml_IncompleteConfigUsesDefaults) {
115120
default_config.latestRelease); // Default value
116121
}
117122

118-
} // namespace config_yaml_utils
123+
} // namespace config_yaml_utils

engine/test/components/test_file_manager_config_yaml_utils.cc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ TEST_F(FileManagerConfigTest, GetDefaultDataFolderName) {
3737

3838
TEST_F(FileManagerConfigTest, CreateConfigFileIfNotExist) {
3939

40-
file_manager_utils::CreateConfigFileIfNotExist();
40+
auto result = file_manager_utils::CreateConfigFileIfNotExist();
41+
EXPECT_FALSE(result.has_error());
4142
EXPECT_TRUE(
4243
std::filesystem::exists(file_manager_utils::GetConfigurationPath()));
4344
std::filesystem::remove(file_manager_utils::GetConfigurationPath());
4445
}
4546

4647
TEST_F(FileManagerConfigTest, GetCortexConfig) {
47-
file_manager_utils::CreateConfigFileIfNotExist();
48+
auto result = file_manager_utils::CreateConfigFileIfNotExist();
49+
EXPECT_FALSE(result.has_error());
4850
auto config = file_manager_utils::GetCortexConfig();
4951
EXPECT_FALSE(config.dataFolderPath.empty());
5052
EXPECT_FALSE(config.logFolderPath.empty());
@@ -61,8 +63,8 @@ TEST_F(FileManagerConfigTest, DumpYamlConfig) {
6163
.apiServerPort = "8080"};
6264

6365
std::string test_file = "test_config.yaml";
64-
config_yaml_utils::DumpYamlConfig(config, test_file);
65-
66+
auto result = config_yaml_utils::DumpYamlConfig(config, test_file);
67+
EXPECT_FALSE(result.has_error());
6668
EXPECT_TRUE(std::filesystem::exists(test_file));
6769

6870
// Clean up
@@ -91,4 +93,4 @@ TEST_F(FileManagerConfigTest, FromYaml) {
9193

9294
// Clean up
9395
std::filesystem::remove(test_file);
94-
}
96+
}

0 commit comments

Comments
 (0)