Skip to content

Commit

Permalink
chore: add model import option parameter (#1627)
Browse files Browse the repository at this point in the history
* chore: add model import option parameter

* chore: update openai doc schema to enum

* chore: change back skipped test case

* fix: C2664 - push_back path
  • Loading branch information
louis-jan authored Nov 4, 2024
1 parent 98a0438 commit 55bbe0d
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 2 deletions.
8 changes: 7 additions & 1 deletion docs/static/openapi/cortex.json
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,8 @@
"example": {
"model": "model-id",
"modelPath": "/path/to/gguf",
"name": "model display name"
"name": "model display name",
"option": "symlink"
}
}
}
Expand Down Expand Up @@ -3187,6 +3188,11 @@
"name": {
"type": "string",
"description": "The display name of the model."
},
"option": {
"type": "string",
"description": "Import options such as symlink or copy.",
"enum": ["symlink", "copy"]
}
},
"required": ["model", "modelPath"]
Expand Down
16 changes: 15 additions & 1 deletion engine/controllers/models.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "database/models.h"
#include <drogon/HttpTypes.h>
#include <filesystem>
#include <optional>
#include "config/gguf_parser.h"
#include "config/yaml_config.h"
Expand Down Expand Up @@ -320,6 +321,7 @@ void Models::ImportModel(
auto modelHandle = (*(req->getJsonObject())).get("model", "").asString();
auto modelPath = (*(req->getJsonObject())).get("modelPath", "").asString();
auto modelName = (*(req->getJsonObject())).get("name", "").asString();
auto option = (*(req->getJsonObject())).get("option", "symlink").asString();
config::GGUFHandler gguf_handler;
config::YamlHandler yaml_handler;
cortex::db::Models modellist_utils_obj;
Expand All @@ -339,7 +341,19 @@ void Models::ImportModel(
std::filesystem::path(model_yaml_path).parent_path());
gguf_handler.Parse(modelPath);
config::ModelConfig model_config = gguf_handler.GetModelConfig();
model_config.files.push_back(modelPath);
// There are 2 options: symlink and copy
if (option == "copy") {
// Copy GGUF file to the destination path
std::filesystem::path file_path =
std::filesystem::path(model_yaml_path).parent_path() /
std::filesystem::path(modelPath).filename();
std::filesystem::copy_file(
modelPath, file_path,
std::filesystem::copy_options::update_existing);
model_config.files.push_back(file_path.string());
} else {
model_config.files.push_back(modelPath);
}
model_config.model = modelHandle;
model_config.name = modelName.empty() ? model_config.name : modelName;
yaml_handler.UpdateModelConfig(model_config);
Expand Down
15 changes: 15 additions & 0 deletions engine/e2e-test/test_api_model_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ def test_model_import_with_name_should_be_success(self):
response = requests.post("http://localhost:3928/models/import", json=body_json)
assert response.status_code == 200

@pytest.mark.skipif(True, reason="Expensive test. Only test when you have local gguf file.")
def test_model_import_with_name_should_be_success(self):
body_json = {'model': 'testing-model',
'modelPath': '/path/to/local/gguf',
'name': 'test_model',
'option': 'copy'}
response = requests.post("http://localhost:3928/models/import", json=body_json)
assert response.status_code == 200
# Test imported path
response = requests.get("http://localhost:3928/models/testing-model")
assert response.status_code == 200
# Since this is a dynamic test - require actual file path
# it's not safe to assert with the gguf file name
assert response.json()['files'][0] != '/path/to/local/gguf'

def test_model_import_with_invalid_path_should_fail(self):
body_json = {'model': 'tinyllama:gguf',
'modelPath': '/invalid/path/to/gguf'}
Expand Down

0 comments on commit 55bbe0d

Please sign in to comment.