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

Commit 554bb84

Browse files
authored
feat: cortex models delete command (#1189)
* feat: cortex models delete command * feat: add server API * feat: add server API * chore: add e2e tests for model delete
1 parent 6abf575 commit 554bb84

File tree

8 files changed

+136
-8
lines changed

8 files changed

+136
-8
lines changed

engine/commands/model_del_cmd.cc

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include "model_del_cmd.h"
2+
#include "cmd_info.h"
3+
#include "config/yaml_config.h"
4+
#include "utils/file_manager_utils.h"
5+
6+
namespace commands {
7+
bool ModelDelCmd::Exec(const std::string& model_id) {
8+
// TODO this implentation may be changed after we have a decision
9+
// on https://github.com/janhq/cortex.cpp/issues/1154 but the logic should be similar
10+
CmdInfo ci(model_id);
11+
std::string model_file =
12+
ci.branch == "main" ? ci.model_name : ci.model_name + "-" + ci.branch;
13+
auto models_path = file_manager_utils::GetModelsContainerPath();
14+
if (std::filesystem::exists(models_path) &&
15+
std::filesystem::is_directory(models_path)) {
16+
// Iterate through directory
17+
for (const auto& entry : std::filesystem::directory_iterator(models_path)) {
18+
if (entry.is_regular_file() && entry.path().extension() == ".yaml") {
19+
try {
20+
config::YamlHandler handler;
21+
handler.ModelConfigFromFile(entry.path().string());
22+
auto cfg = handler.GetModelConfig();
23+
if (entry.path().stem().string() == model_file) {
24+
// Delete data
25+
if (cfg.files.size() > 0) {
26+
std::filesystem::path f(cfg.files[0]);
27+
auto rel = std::filesystem::relative(f, models_path);
28+
// Only delete model data if it is stored in our models folder
29+
if (!rel.empty()) {
30+
if (cfg.engine == "cortex.llamacpp") {
31+
std::filesystem::remove_all(f.parent_path());
32+
} else {
33+
std::filesystem::remove_all(f);
34+
}
35+
}
36+
}
37+
38+
// Delete yaml file
39+
std::filesystem::remove(entry);
40+
CLI_LOG("The model " << model_id << " was deleted");
41+
return true;
42+
}
43+
} catch (const std::exception& e) {
44+
CTL_WRN("Error reading yaml file '" << entry.path().string()
45+
<< "': " << e.what());
46+
return false;
47+
}
48+
}
49+
}
50+
}
51+
52+
CLI_LOG("Model does not exist: " << model_id);
53+
54+
return false;
55+
}
56+
} // namespace commands

engine/commands/model_del_cmd.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace commands {
6+
7+
class ModelDelCmd {
8+
public:
9+
bool Exec(const std::string& model_id);
10+
};
11+
}

engine/controllers/command_line_parser.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "commands/model_stop_cmd.h"
1414
#include "commands/run_cmd.h"
1515
#include "commands/server_stop_cmd.h"
16+
#include "commands/model_del_cmd.h"
1617
#include "config/yaml_config.h"
1718
#include "services/engine_service.h"
1819
#include "utils/file_manager_utils.h"
@@ -91,8 +92,15 @@ bool CommandLineParser::SetupCommand(int argc, char** argv) {
9192
command.Exec();
9293
});
9394

94-
auto remove_cmd =
95-
models_cmd->add_subcommand("remove", "Remove a model by ID locally");
95+
auto model_del_cmd =
96+
models_cmd->add_subcommand("delete", "Delete a model by ID locally");
97+
model_del_cmd->add_option("model_id", model_id, "");
98+
99+
model_del_cmd->callback([&model_id]() {
100+
commands::ModelDelCmd mdc;
101+
mdc.Exec(model_id);
102+
});
103+
96104
auto update_cmd =
97105
models_cmd->add_subcommand("update", "Update configuration of a model");
98106
}

engine/controllers/models.cc

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#include "models.h"
2+
#include "commands/model_del_cmd.h"
23
#include "config/yaml_config.h"
34
#include "trantor/utils/Logger.h"
45
#include "utils/cortex_utils.h"
56
#include "utils/file_manager_utils.h"
67
#include "utils/model_callback_utils.h"
78

8-
void Models::PullModel(
9-
const HttpRequestPtr& req,
10-
std::function<void(const HttpResponsePtr&)>&& callback) const {
9+
void
10+
Models::PullModel(
11+
const HttpRequestPtr& req,
12+
std::function<void(const HttpResponsePtr&)>&& callback) const {
1113
if (!http_util::HasFieldInReq(req, callback, "modelId")) {
1214
return;
1315
}
@@ -168,4 +170,26 @@ void Models::GetModel(
168170
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
169171
resp->setStatusCode(k200OK);
170172
callback(resp);
173+
}
174+
175+
void Models::DeleteModel(const HttpRequestPtr& req,
176+
std::function<void(const HttpResponsePtr&)>&& callback,
177+
const std::string& model_id) const {
178+
LOG_DEBUG << "DeleteModel, Model handle: " << model_id;
179+
commands::ModelDelCmd mdc;
180+
if (mdc.Exec(model_id)) {
181+
Json::Value ret;
182+
ret["result"] = "OK";
183+
ret["modelHandle"] = model_id;
184+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
185+
resp->setStatusCode(k200OK);
186+
callback(resp);
187+
} else {
188+
Json::Value ret;
189+
ret["result"] = "Not Found";
190+
ret["modelHandle"] = model_id;
191+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
192+
resp->setStatusCode(k404NotFound);
193+
callback(resp);
194+
}
171195
}

engine/controllers/models.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ class Models : public drogon::HttpController<Models> {
1515
METHOD_ADD(Models::PullModel, "/pull", Post);
1616
METHOD_ADD(Models::ListModel, "/list", Get);
1717
METHOD_ADD(Models::GetModel, "/get", Post);
18+
METHOD_ADD(Models::DeleteModel, "/{1}", Delete);
1819
METHOD_LIST_END
1920

2021
void PullModel(const HttpRequestPtr& req,
2122
std::function<void(const HttpResponsePtr&)>&& callback) const;
2223
void ListModel(const HttpRequestPtr& req,
2324
std::function<void(const HttpResponsePtr&)>&& callback) const;
2425
void GetModel(const HttpRequestPtr& req,
25-
std::function<void(const HttpResponsePtr&)>&& callback) const;
26+
std::function<void(const HttpResponsePtr&)>&& callback) const;
27+
void DeleteModel(const HttpRequestPtr& req,
28+
std::function<void(const HttpResponsePtr&)>&& callback,
29+
const std::string& model_id) const;
2630
};

engine/e2e-test/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from test_cortex_update import TestCortexUpdate
88
from test_cli_server_start import TestCliServerStart
99
from test_create_log_folder import TestCreateLogFolder
10+
from test_cli_model_delete import TestCliModelDelete
1011

1112
if __name__ == "__main__":
1213
pytest.main([__file__, "-v"])
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pytest
2+
from test_runner import run
3+
4+
5+
class TestCliModelDelete:
6+
7+
@pytest.fixture(autouse=True)
8+
def setup_and_teardown(self):
9+
# Setup
10+
# Pull model
11+
run("Pull Model", ["pull", "tinyllama"], 120)
12+
13+
yield
14+
15+
# Teardown
16+
# Clean up
17+
run("Delete model", ["models", "delete", "tinyllama"])
18+
19+
def test_models_delete_should_be_successful(self):
20+
exit_code, output, error = run(
21+
"Delete model", ["models", "delete", "tinyllama"]
22+
)
23+
assert "The model tinyllama was deleted" in output
24+
assert exit_code == 0, f"Model does not exist: {error}"

engine/e2e-test/test_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ def getExecutablePath() -> str:
2424

2525

2626
# Execute a command
27-
def run(test_name: str, arguments: List[str]):
27+
def run(test_name: str, arguments: List[str], timeout_sec = 5):
2828
executable_path = getExecutablePath()
2929
print("Running:", test_name)
3030
print("Command:", [executable_path] + arguments)
3131

3232
result = subprocess.run(
33-
[executable_path] + arguments, capture_output=True, text=True, timeout=timeout
33+
[executable_path] + arguments, capture_output=True, text=True, timeout=timeout_sec
3434
)
3535
return result.returncode, result.stdout, result.stderr
3636

0 commit comments

Comments
 (0)