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

Commit c0a115f

Browse files
authored
Merge branch 'dev' into chore/models-api
2 parents 38cde94 + 206650f commit c0a115f

File tree

8 files changed

+59
-52
lines changed

8 files changed

+59
-52
lines changed

docs/docs/installation/mac.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Before installation, make sure that you have met the required [dependencies](#de
2222
- Stable: https://github.com/janhq/cortex.cpp/releases
2323
- Beta: https://github.com/janhq/cortex.cpp/releases
2424
- Nightly: https://github.com/janhq/cortex.cpp/releases
25-
2. Ensure that Cortex.cpp is sucessfulyy installed:
25+
2. Ensure that Cortex.cpp is sucessfully installed:
2626
```bash
2727
# Stable
2828
cortex

docs/docs/overview.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Cortex.cpp allows users to pull models from multiple Model Hubs, offering flexib
5151
| Model /Engine | llama.cpp | Command |
5252
| -------------- | --------------------- | ----------------------------- |
5353
| phi-3.5 || cortex run phi3.5 |
54-
| llama3.2 || cortex run llama3.1 |
54+
| llama3.2 || cortex run llama3.2 |
5555
| llama3.1 || cortex run llama3.1 |
5656
| codestral || cortex run codestral |
5757
| gemma2 || cortex run gemma2 |

docs/static/openapi/cortex.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,8 @@
644644
"example": {
645645
"model": "model-id",
646646
"modelPath": "/path/to/gguf",
647-
"name": "model display name"
647+
"name": "model display name",
648+
"option": "symlink"
648649
}
649650
}
650651
}
@@ -3248,6 +3249,11 @@
32483249
"name": {
32493250
"type": "string",
32503251
"description": "The display name of the model."
3252+
},
3253+
"option": {
3254+
"type": "string",
3255+
"description": "Import options such as symlink or copy.",
3256+
"enum": ["symlink", "copy"]
32513257
}
32523258
},
32533259
"required": [

engine/config/gguf_parser.cc

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <stdexcept>
1010
#include <string>
1111
#include <vector>
12+
#include <filesystem>
1213

1314
#ifdef _WIN32
1415
#include <io.h>
@@ -71,24 +72,9 @@ void GGUFHandler::OpenFile(const std::string& file_path) {
7172
CloseHandle(file_handle_);
7273

7374
#else
74-
FILE* fd = fopen(file_path.c_str(), "rb");
75-
if (!fd) {
76-
perror("Error opening file");
77-
throw std::runtime_error("Failed to open file");
78-
}
79-
80-
// Get file size
81-
// file_size_ = lseek(fd, 0, SEEK_END);
82-
fseek(fd, 0, SEEK_END); // move file pointer to end of file
83-
file_size_ = ftell(fd); // get the file size, in bytes
84-
fclose(fd);
85-
if (file_size_ == -1) {
86-
perror("Error getting file size");
87-
// close(fd);
88-
throw std::runtime_error("Failed to get file size");
89-
}
75+
file_size_ = std::filesystem::file_size(file_path);
76+
9077
int file_descriptor = open(file_path.c_str(), O_RDONLY);
91-
;
9278
// Memory-map the file
9379
data_ = static_cast<uint8_t*>(
9480
mmap(nullptr, file_size_, PROT_READ, MAP_PRIVATE, file_descriptor, 0));

engine/controllers/models.cc

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "database/models.h"
22
#include <drogon/HttpTypes.h>
3+
#include <filesystem>
34
#include <optional>
45
#include "config/gguf_parser.h"
56
#include "config/yaml_config.h"
@@ -320,6 +321,7 @@ void Models::ImportModel(
320321
auto modelHandle = (*(req->getJsonObject())).get("model", "").asString();
321322
auto modelPath = (*(req->getJsonObject())).get("modelPath", "").asString();
322323
auto modelName = (*(req->getJsonObject())).get("name", "").asString();
324+
auto option = (*(req->getJsonObject())).get("option", "symlink").asString();
323325
config::GGUFHandler gguf_handler;
324326
config::YamlHandler yaml_handler;
325327
cortex::db::Models modellist_utils_obj;
@@ -339,7 +341,23 @@ void Models::ImportModel(
339341
std::filesystem::path(model_yaml_path).parent_path());
340342
gguf_handler.Parse(modelPath);
341343
config::ModelConfig model_config = gguf_handler.GetModelConfig();
342-
model_config.files.push_back(modelPath);
344+
// There are 2 options: symlink and copy
345+
if (option == "copy") {
346+
// Copy GGUF file to the destination path
347+
std::filesystem::path file_path =
348+
std::filesystem::path(model_yaml_path).parent_path() /
349+
std::filesystem::path(modelPath).filename();
350+
std::filesystem::copy_file(
351+
modelPath, file_path,
352+
std::filesystem::copy_options::update_existing);
353+
model_config.files.push_back(file_path.string());
354+
auto size = std::filesystem::file_size(file_path);
355+
model_config.size = size;
356+
} else {
357+
model_config.files.push_back(modelPath);
358+
auto size = std::filesystem::file_size(modelPath);
359+
model_config.size = size;
360+
}
343361
model_config.model = modelHandle;
344362
model_config.name = modelName.empty() ? model_config.name : modelName;
345363
yaml_handler.UpdateModelConfig(model_config);

engine/e2e-test/test_api_model_import.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ def test_model_import_with_name_should_be_success(self):
2929
response = requests.post("http://localhost:3928/models/import", json=body_json)
3030
assert response.status_code == 200
3131

32+
@pytest.mark.skipif(True, reason="Expensive test. Only test when you have local gguf file.")
33+
def test_model_import_with_name_should_be_success(self):
34+
body_json = {'model': 'testing-model',
35+
'modelPath': '/path/to/local/gguf',
36+
'name': 'test_model',
37+
'option': 'copy'}
38+
response = requests.post("http://localhost:3928/models/import", json=body_json)
39+
assert response.status_code == 200
40+
# Test imported path
41+
response = requests.get("http://localhost:3928/models/testing-model")
42+
assert response.status_code == 200
43+
# Since this is a dynamic test - require actual file path
44+
# it's not safe to assert with the gguf file name
45+
assert response.json()['files'][0] != '/path/to/local/gguf'
46+
3247
def test_model_import_with_invalid_path_should_fail(self):
3348
body_json = {'model': 'tinyllama:gguf',
3449
'modelPath': '/invalid/path/to/gguf'}

engine/services/download_service.cc

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#include <optional>
88
#include <ostream>
99
#include <utility>
10-
#include "download_service.h"
1110
#include "utils/format_utils.h"
1211
#include "utils/huggingface_utils.h"
1312
#include "utils/logging_utils.h"
@@ -148,11 +147,10 @@ cpp::result<bool, std::string> DownloadService::Download(
148147
std::string mode = "wb";
149148
if (std::filesystem::exists(download_item.localPath) &&
150149
download_item.bytes.has_value()) {
151-
curl_off_t existing_file_size = GetLocalFileSize(download_item.localPath);
152-
if (existing_file_size == -1) {
153-
CLI_LOG("Cannot get file size: " << download_item.localPath.string()
154-
<< " . Start download over!");
155-
} else {
150+
try {
151+
curl_off_t existing_file_size =
152+
std::filesystem::file_size(download_item.localPath);
153+
156154
CTL_INF("Existing file size: " << download_item.downloadUrl << " - "
157155
<< download_item.localPath.string()
158156
<< " - " << existing_file_size);
@@ -186,6 +184,9 @@ cpp::result<bool, std::string> DownloadService::Download(
186184
return false;
187185
}
188186
}
187+
} catch (const std::filesystem::filesystem_error& e) {
188+
CLI_LOG("Cannot get file size: "
189+
<< e.what() << download_item.localPath.string() << "\n");
189190
}
190191
}
191192

@@ -205,12 +206,12 @@ cpp::result<bool, std::string> DownloadService::Download(
205206
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
206207

207208
if (mode == "ab") {
208-
auto local_file_size = GetLocalFileSize(download_item.localPath);
209-
if (local_file_size != -1) {
210-
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE,
211-
GetLocalFileSize(download_item.localPath));
212-
} else {
213-
CTL_ERR("Cannot get file size: " << download_item.localPath.string());
209+
try {
210+
curl_off_t local_file_size =
211+
std::filesystem::file_size(download_item.localPath);
212+
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, local_file_size);
213+
} catch (const std::filesystem::filesystem_error& e) {
214+
CTL_ERR("Cannot get file size: " << e.what() << '\n');
214215
}
215216
}
216217

@@ -225,23 +226,6 @@ cpp::result<bool, std::string> DownloadService::Download(
225226
curl_easy_cleanup(curl);
226227
return true;
227228
}
228-
229-
curl_off_t DownloadService::GetLocalFileSize(
230-
const std::filesystem::path& path) const {
231-
auto file = fopen(path.string().c_str(), "r");
232-
if (!file) {
233-
return -1;
234-
}
235-
236-
if (fseek64(file, 0, SEEK_END) != 0) {
237-
return -1;
238-
}
239-
240-
auto file_size = ftell64(file);
241-
fclose(file);
242-
return file_size;
243-
}
244-
245229
void DownloadService::WorkerThread() {
246230
while (!stop_flag_) {
247231
DownloadTask task;

engine/services/download_service.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@ class DownloadService {
9090
const std::string& download_id,
9191
const DownloadItem& download_item) noexcept;
9292

93-
curl_off_t GetLocalFileSize(const std::filesystem::path& path) const;
94-
9593
std::shared_ptr<EventQueue> event_queue_;
9694

9795
CURLM* multi_handle_;

0 commit comments

Comments
 (0)