diff --git a/engine/commands/model_alias_cmd.cc b/engine/commands/model_alias_cmd.cc index 4a4ef98af..cd7f647a5 100644 --- a/engine/commands/model_alias_cmd.cc +++ b/engine/commands/model_alias_cmd.cc @@ -7,13 +7,19 @@ void ModelAliasCmd::Exec(const std::string& model_handle, const std::string& model_alias) { cortex::db::Models modellist_handler; try { - if (modellist_handler.UpdateModelAlias(model_handle, model_alias)) { - CLI_LOG("Successfully set model alias '" + model_alias + - "' for modeID '" + model_handle + "'."); + auto result = modellist_handler.UpdateModelAlias(model_handle, model_alias); + if (result.has_error()) { + CLI_LOG(result.error()); } else { - CLI_LOG("Unable to set model alias for modelID '" + model_handle + - "': model alias '" + model_alias + "' is not unique!"); + if (result.value()) { + CLI_LOG("Successfully set model alias '" + model_alias + + "' for modeID '" + model_handle + "'."); + } else { + CLI_LOG("Unable to set model alias for modelID '" + model_handle + + "': model alias '" + model_alias + "' is not unique!"); + } } + } catch (const std::exception& e) { CLI_LOG("Error when setting model alias ('" + model_alias + "') for modelID '" + model_handle + "':" + e.what()); diff --git a/engine/controllers/models.cc b/engine/controllers/models.cc index 0b3754aab..e28865ec1 100644 --- a/engine/controllers/models.cc +++ b/engine/controllers/models.cc @@ -120,7 +120,13 @@ void Models::GetModel( config::YamlHandler yaml_handler; auto model_entry = modellist_handler.GetModelInfo(model_handle); if (model_entry.has_error()) { - CLI_LOG("Error: " + model_entry.error()); + // CLI_LOG("Error: " + model_entry.error()); + ret["data"] = data; + ret["result"] = "Fail to get model information"; + ret["message"] = "Error: " + model_entry.error(); + auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); + resp->setStatusCode(k400BadRequest); + callback(resp); return; } yaml_handler.ModelConfigFromFile(model_entry.value().path_to_model_yaml); @@ -234,7 +240,7 @@ void Models::ImportModel( gguf_handler.Parse(modelPath); config::ModelConfig model_config = gguf_handler.GetModelConfig(); model_config.files.push_back(modelPath); - model_config.name = modelHandle; + model_config.model = modelHandle; yaml_handler.UpdateModelConfig(model_config); if (modellist_utils_obj.AddModelEntry(model_entry).value()) { @@ -293,29 +299,42 @@ void Models::SetModelAlias( cortex::db::Models modellist_handler; try { - if (modellist_handler.UpdateModelAlias(model_handle, model_alias)) { - std::string message = "Successfully set model alias '" + model_alias + - "' for modeID '" + model_handle + "'."; - LOG_INFO << message; - Json::Value ret; - ret["result"] = "OK"; - ret["modelHandle"] = model_handle; - ret["message"] = message; - auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); - resp->setStatusCode(k200OK); - callback(resp); - } else { - std::string message = "Unable to set model alias for modelID '" + - model_handle + "': model alias '" + model_alias + - "' is not unique!"; + auto result = modellist_handler.UpdateModelAlias(model_handle, model_alias); + if (result.has_error()) { + std::string message = result.error(); LOG_ERROR << message; - Json::Value ret; - ret["result"] = "Set alias failed!"; - ret["modelHandle"] = model_handle; - ret["message"] = message; - auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); - resp->setStatusCode(k400BadRequest); - callback(resp); + Json::Value ret; + ret["result"] = "Set alias failed!"; + ret["modelHandle"] = model_handle; + ret["message"] = message; + auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); + resp->setStatusCode(k400BadRequest); + callback(resp); + } else { + if (result.value()) { + std::string message = "Successfully set model alias '" + model_alias + + "' for modeID '" + model_handle + "'."; + LOG_INFO << message; + Json::Value ret; + ret["result"] = "OK"; + ret["modelHandle"] = model_handle; + ret["message"] = message; + auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); + resp->setStatusCode(k200OK); + callback(resp); + } else { + std::string message = "Unable to set model alias for modelID '" + + model_handle + "': model alias '" + model_alias + + "' is not unique!"; + LOG_ERROR << message; + Json::Value ret; + ret["result"] = "Set alias failed!"; + ret["modelHandle"] = model_handle; + ret["message"] = message; + auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret); + resp->setStatusCode(k400BadRequest); + callback(resp); + } } } catch (const std::exception& e) { std::string message = "Error when setting model alias ('" + model_alias + diff --git a/engine/controllers/models.h b/engine/controllers/models.h index 41511ebc7..544baafeb 100644 --- a/engine/controllers/models.h +++ b/engine/controllers/models.h @@ -14,7 +14,7 @@ class Models : public drogon::HttpController { METHOD_ADD(Models::PullModel, "/pull", Post); METHOD_ADD(Models::ListModel, "", Get); METHOD_ADD(Models::GetModel, "/get", Post); - METHOD_ADD(Models::UpdateModel, "/update/", Post); + METHOD_ADD(Models::UpdateModel, "/update", Post); METHOD_ADD(Models::ImportModel, "/import", Post); METHOD_ADD(Models::DeleteModel, "/{1}", Delete); METHOD_ADD(Models::SetModelAlias, "/alias", Post); diff --git a/engine/controllers/swagger.cc b/engine/controllers/swagger.cc new file mode 100644 index 000000000..2ef36dace --- /dev/null +++ b/engine/controllers/swagger.cc @@ -0,0 +1,766 @@ +#include "swagger.h" + +const std::string SwaggerController::swaggerUIHTML = R"( + + + + + Swagger UI + + + + +
+ + + + + +)"; + +Json::Value SwaggerController::generateOpenAPISpec() { + Json::Value spec; + spec["openapi"] = "3.0.0"; + spec["info"]["title"] = "Cortex API swagger"; + spec["info"]["version"] = "1.0.0"; + + // Health Endpoint + { + Json::Value& path = spec["paths"]["/healthz"]["get"]; + path["summary"] = "Check system health"; + path["description"] = "Returns the health status of the cortex-cpp service"; + + Json::Value& responses = path["responses"]; + responses["200"]["description"] = "Service is healthy"; + responses["200"]["content"]["text/html"]["schema"]["type"] = "string"; + responses["200"]["content"]["text/html"]["schema"]["example"] = + "cortex-cpp is alive!!!"; + } + + // Engines endpoints + // Install Engine + { + Json::Value& path = spec["paths"]["/engines/{engine}/install"]["post"]; + path["summary"] = "Install an engine"; + path["parameters"][0]["name"] = "engine"; + path["parameters"][0]["in"] = "path"; + path["parameters"][0]["required"] = true; + path["parameters"][0]["schema"]["type"] = "string"; + + Json::Value& responses = path["responses"]; + responses["200"]["description"] = "Engine installed successfully"; + responses["200"]["content"]["application/json"]["schema"]["type"] = + "object"; + responses["200"]["content"]["application/json"]["schema"]["properties"] + ["message"]["type"] = "string"; + + responses["400"]["description"] = "Bad request"; + responses["400"]["content"]["application/json"]["schema"]["type"] = + "object"; + responses["400"]["content"]["application/json"]["schema"]["properties"] + ["message"]["type"] = "string"; + + responses["409"]["description"] = "Conflict"; + responses["409"]["content"]["application/json"]["schema"]["type"] = + "object"; + responses["409"]["content"]["application/json"]["schema"]["properties"] + ["message"]["type"] = "string"; + } + + // Uninstall Engine + { + Json::Value& path = spec["paths"]["/engines/{engine}"]["delete"]; + path["summary"] = "Uninstall an engine"; + path["parameters"][0]["name"] = "engine"; + path["parameters"][0]["in"] = "path"; + path["parameters"][0]["required"] = true; + path["parameters"][0]["schema"]["type"] = "string"; + + Json::Value& responses = path["responses"]; + responses["200"]["description"] = "Engine uninstalled successfully"; + responses["200"]["content"]["application/json"]["schema"]["type"] = + "object"; + responses["200"]["content"]["application/json"]["schema"]["properties"] + ["message"]["type"] = "string"; + + responses["400"]["description"] = "Bad request"; + responses["400"]["content"]["application/json"]["schema"]["type"] = + "object"; + responses["400"]["content"]["application/json"]["schema"]["properties"] + ["message"]["type"] = "string"; + } + + // List Engines + { + Json::Value& path = spec["paths"]["/engines"]["get"]; + path["summary"] = "List all engines"; + + Json::Value& response = path["responses"]["200"]; + response["description"] = "List of engines retrieved successfully"; + response["content"]["application/json"]["schema"]["type"] = "object"; + response["content"]["application/json"]["schema"]["properties"]["object"] + ["type"] = "string"; + response["content"]["application/json"]["schema"]["properties"]["data"] + ["type"] = "array"; + response["content"]["application/json"]["schema"]["properties"]["data"] + ["items"]["type"] = "object"; + Json::Value& itemProperties = + response["content"]["application/json"]["schema"]["properties"]["data"] + ["items"]["properties"]; + itemProperties["name"]["type"] = "string"; + itemProperties["description"]["type"] = "string"; + itemProperties["version"]["type"] = "string"; + itemProperties["variant"]["type"] = "string"; + itemProperties["productName"]["type"] = "string"; + itemProperties["status"]["type"] = "string"; + response["content"]["application/json"]["schema"]["properties"]["result"] + ["type"] = "string"; + } + + // Get Engine + { + Json::Value& path = spec["paths"]["/engines/{engine}"]["get"]; + path["summary"] = "Get engine details"; + path["parameters"][0]["name"] = "engine"; + path["parameters"][0]["in"] = "path"; + path["parameters"][0]["required"] = true; + path["parameters"][0]["schema"]["type"] = "string"; + + Json::Value& responses = path["responses"]; + responses["200"]["description"] = "Engine details retrieved successfully"; + Json::Value& schema = + responses["200"]["content"]["application/json"]["schema"]; + schema["type"] = "object"; + schema["properties"]["name"]["type"] = "string"; + schema["properties"]["description"]["type"] = "string"; + schema["properties"]["version"]["type"] = "string"; + schema["properties"]["variant"]["type"] = "string"; + schema["properties"]["productName"]["type"] = "string"; + schema["properties"]["status"]["type"] = "string"; + + responses["400"]["description"] = "Engine not found"; + responses["400"]["content"]["application/json"]["schema"]["type"] = + "object"; + responses["400"]["content"]["application/json"]["schema"]["properties"] + ["message"]["type"] = "string"; + } + + // Models Endpoints + { + // PullModel + Json::Value& pull = spec["paths"]["/models/pull"]["post"]; + pull["summary"] = "Pull a model"; + pull["requestBody"]["content"]["application/json"]["schema"]["type"] = + "object"; + pull["requestBody"]["content"]["application/json"]["schema"]["properties"] + ["modelId"]["type"] = "string"; + pull["requestBody"]["content"]["application/json"]["schema"]["required"] = + Json::Value(Json::arrayValue); + pull["requestBody"]["content"]["application/json"]["schema"]["required"] + .append("modelId"); + pull["responses"]["200"]["description"] = "Model start downloading"; + pull["responses"]["400"]["description"] = "Bad request"; + + // ListModel + Json::Value& list = spec["paths"]["/models"]["get"]; + list["summary"] = "List all models"; + list["responses"]["200"]["description"] = + "List of models retrieved successfully"; + list["responses"]["400"]["description"] = + "Failed to get list model information"; + + // GetModel + Json::Value& get = spec["paths"]["/models/get"]["post"]; + get["summary"] = "Get model details"; + get["requestBody"]["content"]["application/json"]["schema"]["type"] = + "object"; + get["requestBody"]["content"]["application/json"]["schema"]["properties"] + ["modelId"]["type"] = "string"; + get["requestBody"]["content"]["application/json"]["schema"]["required"] = + Json::Value(Json::arrayValue); + get["requestBody"]["content"]["application/json"]["schema"]["required"] + .append("modelId"); + get["responses"]["200"]["description"] = + "Model details retrieved successfully"; + get["responses"]["400"]["description"] = "Failed to get model information"; + + // UpdateModel Endpoint + Json::Value& update = spec["paths"]["/models/update"]["post"]; + update["summary"] = "Update model details"; + update["description"] = + "Update various attributes of a model based on the ModelConfig " + "structure"; + + Json::Value& updateSchema = + update["requestBody"]["content"]["application/json"]["schema"]; + updateSchema["type"] = "object"; + updateSchema["required"] = Json::Value(Json::arrayValue); + updateSchema["required"].append("modelId"); + + Json::Value& properties = updateSchema["properties"]; + properties["modelId"]["type"] = "string"; + properties["modelId"]["description"] = + "Unique identifier for the model (cannot be updated)"; + + properties["name"]["type"] = "string"; + properties["name"]["description"] = "Name of the model"; + + properties["version"]["type"] = "string"; + properties["version"]["description"] = "Version of the model"; + + properties["stop"]["type"] = "array"; + properties["stop"]["items"]["type"] = "string"; + properties["stop"]["description"] = "List of stop sequences"; + + properties["top_p"]["type"] = "number"; + properties["top_p"]["format"] = "float"; + properties["top_p"]["description"] = "Top-p sampling parameter"; + + properties["temperature"]["type"] = "number"; + properties["temperature"]["format"] = "float"; + properties["temperature"]["description"] = "Temperature for sampling"; + + properties["frequency_penalty"]["type"] = "number"; + properties["frequency_penalty"]["format"] = "float"; + properties["frequency_penalty"]["description"] = + "Frequency penalty for sampling"; + + properties["presence_penalty"]["type"] = "number"; + properties["presence_penalty"]["format"] = "float"; + properties["presence_penalty"]["description"] = + "Presence penalty for sampling"; + + properties["max_tokens"]["type"] = "integer"; + properties["max_tokens"]["description"] = + "Maximum number of tokens to generate"; + + properties["stream"]["type"] = "boolean"; + properties["stream"]["description"] = "Whether to stream the output"; + + properties["ngl"]["type"] = "integer"; + properties["ngl"]["description"] = "Number of GPU layers"; + + properties["ctx_len"]["type"] = "integer"; + properties["ctx_len"]["description"] = "Context length"; + + properties["engine"]["type"] = "string"; + properties["engine"]["description"] = "Engine used for the model"; + + properties["prompt_template"]["type"] = "string"; + properties["prompt_template"]["description"] = "Template for prompts"; + + properties["system_template"]["type"] = "string"; + properties["system_template"]["description"] = + "Template for system messages"; + + properties["user_template"]["type"] = "string"; + properties["user_template"]["description"] = "Template for user messages"; + + properties["ai_template"]["type"] = "string"; + properties["ai_template"]["description"] = "Template for AI responses"; + + properties["os"]["type"] = "string"; + properties["os"]["description"] = "Operating system"; + + properties["gpu_arch"]["type"] = "string"; + properties["gpu_arch"]["description"] = "GPU architecture"; + + properties["quantization_method"]["type"] = "string"; + properties["quantization_method"]["description"] = + "Method used for quantization"; + + properties["precision"]["type"] = "string"; + properties["precision"]["description"] = "Precision of the model"; + + properties["files"]["type"] = "array"; + properties["files"]["items"]["type"] = "string"; + properties["files"]["description"] = + "List of files associated with the model"; + + properties["seed"]["type"] = "integer"; + properties["seed"]["description"] = "Seed for random number generation"; + + properties["dynatemp_range"]["type"] = "number"; + properties["dynatemp_range"]["format"] = "float"; + properties["dynatemp_range"]["description"] = "Dynamic temperature range"; + + properties["dynatemp_exponent"]["type"] = "number"; + properties["dynatemp_exponent"]["format"] = "float"; + properties["dynatemp_exponent"]["description"] = + "Dynamic temperature exponent"; + + properties["top_k"]["type"] = "integer"; + properties["top_k"]["description"] = "Top-k sampling parameter"; + + properties["min_p"]["type"] = "number"; + properties["min_p"]["format"] = "float"; + properties["min_p"]["description"] = "Minimum probability for sampling"; + + properties["tfs_z"]["type"] = "number"; + properties["tfs_z"]["format"] = "float"; + properties["tfs_z"]["description"] = "TFS-Z parameter"; + + properties["typ_p"]["type"] = "number"; + properties["typ_p"]["format"] = "float"; + properties["typ_p"]["description"] = "Typical p parameter"; + + properties["repeat_last_n"]["type"] = "integer"; + properties["repeat_last_n"]["description"] = + "Number of tokens to consider for repeat penalty"; + + properties["repeat_penalty"]["type"] = "number"; + properties["repeat_penalty"]["format"] = "float"; + properties["repeat_penalty"]["description"] = "Penalty for repeated tokens"; + + properties["mirostat"]["type"] = "boolean"; + properties["mirostat"]["description"] = "Whether to use Mirostat sampling"; + + properties["mirostat_tau"]["type"] = "number"; + properties["mirostat_tau"]["format"] = "float"; + properties["mirostat_tau"]["description"] = "Mirostat tau parameter"; + + properties["mirostat_eta"]["type"] = "number"; + properties["mirostat_eta"]["format"] = "float"; + properties["mirostat_eta"]["description"] = "Mirostat eta parameter"; + + properties["penalize_nl"]["type"] = "boolean"; + properties["penalize_nl"]["description"] = "Whether to penalize newlines"; + + properties["ignore_eos"]["type"] = "boolean"; + properties["ignore_eos"]["description"] = + "Whether to ignore end-of-sequence token"; + + properties["n_probs"]["type"] = "integer"; + properties["n_probs"]["description"] = "Number of probabilities to return"; + + properties["min_keep"]["type"] = "integer"; + properties["min_keep"]["description"] = "Minimum number of tokens to keep"; + + update["responses"]["200"]["description"] = "Model updated successfully"; + update["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/SuccessResponse"; + + update["responses"]["400"]["description"] = "Failed to update model"; + update["responses"]["400"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/ErrorResponse"; + + // Define the schemas + Json::Value& schemas = spec["components"]["schemas"]; + + schemas["SuccessResponse"]["type"] = "object"; + schemas["SuccessResponse"]["properties"]["result"]["type"] = "string"; + schemas["SuccessResponse"]["properties"]["result"]["description"] = + "Result of the operation"; + schemas["SuccessResponse"]["properties"]["modelHandle"]["type"] = "string"; + schemas["SuccessResponse"]["properties"]["modelHandle"]["description"] = + "Handle of the affected model"; + schemas["SuccessResponse"]["properties"]["message"]["type"] = "string"; + schemas["SuccessResponse"]["properties"]["message"]["description"] = + "Detailed message about the operation"; + + schemas["ErrorResponse"]["type"] = "object"; + schemas["ErrorResponse"]["properties"]["result"]["type"] = "string"; + schemas["ErrorResponse"]["properties"]["result"]["description"] = + "Error result"; + schemas["ErrorResponse"]["properties"]["modelHandle"]["type"] = "string"; + schemas["ErrorResponse"]["properties"]["modelHandle"]["description"] = + "Handle of the model that caused the error"; + schemas["ErrorResponse"]["properties"]["message"]["type"] = "string"; + schemas["ErrorResponse"]["properties"]["message"]["description"] = + "Detailed error message"; + + // ImportModel + Json::Value& import = spec["paths"]["/models/import"]["post"]; + import["summary"] = "Import a model"; + import["requestBody"]["content"]["application/json"]["schema"]["type"] = + "object"; + import["requestBody"]["content"]["application/json"]["schema"]["properties"] + ["modelId"]["type"] = "string"; + import["requestBody"]["content"]["application/json"]["schema"]["properties"] + ["modelPath"]["type"] = "string"; + import["requestBody"]["content"]["application/json"]["schema"]["required"] = + Json::Value(Json::arrayValue); + import["requestBody"]["content"]["application/json"]["schema"]["required"] + .append("modelId"); + import["requestBody"]["content"]["application/json"]["schema"]["required"] + .append("modelPath"); + import["responses"]["200"]["description"] = "Model imported successfully"; + import["responses"]["400"]["description"] = "Failed to import model"; + + // DeleteModel + Json::Value& del = spec["paths"]["/models/{model_id}"]["delete"]; + del["summary"] = "Delete a model"; + del["parameters"][0]["name"] = "model_id"; + del["parameters"][0]["in"] = "path"; + del["parameters"][0]["required"] = true; + del["parameters"][0]["schema"]["type"] = "string"; + del["responses"]["200"]["description"] = "Model deleted successfully"; + del["responses"]["400"]["description"] = "Failed to delete model"; + + // SetModelAlias + Json::Value& alias = spec["paths"]["/models/alias"]["post"]; + alias["summary"] = "Set model alias"; + alias["requestBody"]["content"]["application/json"]["schema"]["type"] = + "object"; + alias["requestBody"]["content"]["application/json"]["schema"]["properties"] + ["modelId"]["type"] = "string"; + alias["requestBody"]["content"]["application/json"]["schema"]["properties"] + ["modelAlias"]["type"] = "string"; + alias["requestBody"]["content"]["application/json"]["schema"]["required"] = + Json::Value(Json::arrayValue); + alias["requestBody"]["content"]["application/json"]["schema"]["required"] + .append("modelId"); + alias["requestBody"]["content"]["application/json"]["schema"]["required"] + .append("modelAlias"); + alias["responses"]["200"]["description"] = "Model alias set successfully"; + alias["responses"]["400"]["description"] = "Failed to set model alias"; + } + + // OpenAI Compatible Endpoints + { + // Chat Completions + Json::Value& chat = spec["paths"]["/v1/chat/completions"]["post"]; + chat["summary"] = "Create chat completion"; + chat["description"] = "Creates a completion for the chat message"; + chat["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/ChatCompletionRequest"; + chat["responses"]["200"]["description"] = "Successful response"; + chat["responses"]["200"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/ChatCompletionResponse"; + + // List Models + Json::Value& models = spec["paths"]["/v1/models"]["get"]; + models["summary"] = "List models"; + models["description"] = "Lists the currently available models"; + models["responses"]["200"]["description"] = "Successful response"; + models["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/ModelList"; + + // Create Fine-tuning Job + Json::Value& finetune = spec["paths"]["/v1/fine_tuning/job"]["post"]; + finetune["summary"] = "Create fine-tuning job"; + finetune["description"] = "Creates a job that fine-tunes a specified model"; + finetune["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/FineTuningRequest"; + finetune["responses"]["200"]["description"] = "Successful response"; + finetune["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/FineTuningResponse"; + + // Create Embeddings + Json::Value& embed = spec["paths"]["/v1/embeddings"]["post"]; + embed["summary"] = "Create embeddings"; + embed["description"] = + "Creates an embedding vector representing the input text"; + embed["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/EmbeddingRequest"; + embed["responses"]["200"]["description"] = "Successful response"; + embed["responses"]["200"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/EmbeddingResponse"; + } + + // Custom Cortex Endpoints + { + // Chat Completion + Json::Value& chat = + spec["paths"]["/inferences/server/chat_completion"]["post"]; + chat["summary"] = "Create chat completion (Cortex)"; + chat["description"] = + "Creates a completion for the chat message using Cortex engine"; + chat["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/CortexChatCompletionRequest"; + chat["responses"]["200"]["description"] = "Successful response"; + chat["responses"]["200"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/CortexChatCompletionResponse"; + + // Embedding + Json::Value& embed = spec["paths"]["/inferences/server/embedding"]["post"]; + embed["summary"] = "Create embeddings (Cortex)"; + embed["description"] = "Creates an embedding vector using Cortex engine"; + embed["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/CortexEmbeddingRequest"; + embed["responses"]["200"]["description"] = "Successful response"; + embed["responses"]["200"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/CortexEmbeddingResponse"; + + // Load Model + Json::Value& load = spec["paths"]["/inferences/server/loadmodel"]["post"]; + load["summary"] = "Load a model"; + load["description"] = "Loads a specified model into the engine"; + load["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/LoadModelRequest"; + load["responses"]["200"]["description"] = "Model loaded successfully"; + load["responses"]["200"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/SuccessResponse"; + + // Unload Model + Json::Value& unload = + spec["paths"]["/inferences/server/unloadmodel"]["post"]; + unload["summary"] = "Unload a model"; + unload["description"] = "Unloads a specified model from the engine"; + unload["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/UnloadModelRequest"; + unload["responses"]["200"]["description"] = "Model unloaded successfully"; + unload["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/SuccessResponse"; + + // Model Status + Json::Value& status = + spec["paths"]["/inferences/server/modelstatus"]["post"]; + status["summary"] = "Get model status"; + status["description"] = "Retrieves the status of a specified model"; + status["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/ModelStatusRequest"; + status["responses"]["200"]["description"] = + "Model status retrieved successfully"; + status["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/ModelStatusResponse"; + + // Get Models + Json::Value& getModels = spec["paths"]["/inferences/server/models"]["get"]; + getModels["summary"] = "Get all models (Cortex)"; + getModels["description"] = + "Retrieves a list of all available models in Cortex"; + getModels["responses"]["200"]["description"] = + "Models retrieved successfully"; + getModels["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/CortexModelList"; + + // Get Engines + Json::Value& getEngines = + spec["paths"]["/inferences/server/engines"]["get"]; + getEngines["summary"] = "Get all engines"; + getEngines["description"] = "Retrieves a list of all available engines"; + getEngines["responses"]["200"]["description"] = + "Engines retrieved successfully"; + getEngines["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/EngineList"; + + // Fine Tuning + Json::Value& fineTuning = + spec["paths"]["/inferences/server/finetuning"]["post"]; + fineTuning["summary"] = "Create fine-tuning job (Cortex)"; + fineTuning["description"] = + "Creates a job that fine-tunes a specified model using Cortex engine"; + fineTuning["requestBody"]["content"]["application/json"]["schema"]["$ref"] = + "#/components/schemas/CortexFineTuningRequest"; + fineTuning["responses"]["200"]["description"] = + "Fine-tuning job created successfully"; + fineTuning["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/CortexFineTuningResponse"; + + // Unload Engine + Json::Value& unloadEngine = + spec["paths"]["/inferences/server/unloadengine"]["post"]; + unloadEngine["summary"] = "Unload an engine"; + unloadEngine["description"] = "Unloads a specified engine"; + unloadEngine["requestBody"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/UnloadEngineRequest"; + unloadEngine["responses"]["200"]["description"] = + "Engine unloaded successfully"; + unloadEngine["responses"]["200"]["content"]["application/json"]["schema"] + ["$ref"] = "#/components/schemas/SuccessResponse"; + } + + // Define schemas + Json::Value& schemas = spec["components"]["schemas"]; + + schemas["ChatCompletionRequest"]["type"] = "object"; + schemas["ChatCompletionRequest"]["properties"]["model"]["type"] = "string"; + schemas["ChatCompletionRequest"]["properties"]["messages"]["type"] = "array"; + schemas["ChatCompletionRequest"]["properties"]["messages"]["items"]["$ref"] = + "#/components/schemas/ChatMessage"; + schemas["ChatCompletionRequest"]["properties"]["stream"]["type"] = "boolean"; + schemas["ChatCompletionRequest"]["properties"]["engine"]["type"] = "string"; + + schemas["ChatMessage"]["type"] = "object"; + schemas["ChatMessage"]["properties"]["role"]["type"] = "string"; + schemas["ChatMessage"]["properties"]["content"]["type"] = "string"; + + schemas["ChatCompletionResponse"]["type"] = "object"; + // Add properties based on your implementation + + schemas["ModelList"]["type"] = "object"; + schemas["ModelList"]["properties"]["object"]["type"] = "string"; + schemas["ModelList"]["properties"]["data"]["type"] = "array"; + schemas["ModelList"]["properties"]["data"]["items"]["$ref"] = + "#/components/schemas/Model"; + + schemas["Model"]["type"] = "object"; + schemas["Model"]["properties"]["id"]["type"] = "string"; + schemas["Model"]["properties"]["object"]["type"] = "string"; + + schemas["FineTuningRequest"]["type"] = "object"; + schemas["FineTuningRequest"]["properties"]["model"]["type"] = "string"; + schemas["FineTuningRequest"]["properties"]["training_file"]["type"] = + "string"; + + schemas["FineTuningResponse"]["type"] = "object"; + // Add properties based on your implementation + + schemas["EmbeddingRequest"]["type"] = "object"; + schemas["EmbeddingRequest"]["properties"]["model"]["type"] = "string"; + schemas["EmbeddingRequest"]["properties"]["input"]["type"] = "string"; + + schemas["EmbeddingResponse"]["type"] = "object"; + schemas["EmbeddingResponse"]["properties"]["object"]["type"] = "string"; + schemas["EmbeddingResponse"]["properties"]["data"]["type"] = "array"; + schemas["EmbeddingResponse"]["properties"]["data"]["items"]["type"] = + "object"; + schemas["EmbeddingResponse"]["properties"]["data"]["items"]["properties"] + ["embedding"]["type"] = "array"; + schemas["EmbeddingResponse"]["properties"]["data"]["items"]["properties"] + ["embedding"]["items"]["type"] = "number"; + + schemas["CortexChatCompletionRequest"]["type"] = "object"; + schemas["CortexChatCompletionRequest"]["properties"]["engine"]["type"] = + "string"; + schemas["CortexChatCompletionRequest"]["properties"]["model"]["type"] = + "string"; + schemas["CortexChatCompletionRequest"]["properties"]["messages"]["type"] = + "array"; + schemas["CortexChatCompletionRequest"]["properties"]["messages"]["items"] + ["$ref"] = "#/components/schemas/ChatMessage"; + schemas["CortexChatCompletionRequest"]["properties"]["stream"]["type"] = + "boolean"; + // Add other properties based on your implementation + + schemas["CortexChatCompletionResponse"]["type"] = "object"; + // Add properties based on your implementation + + schemas["CortexEmbeddingRequest"]["type"] = "object"; + schemas["CortexEmbeddingRequest"]["properties"]["engine"]["type"] = "string"; + schemas["CortexEmbeddingRequest"]["properties"]["input"]["type"] = "string"; + + schemas["CortexEmbeddingResponse"]["type"] = "object"; + // Add properties based on your implementation + + schemas["LoadModelRequest"]["type"] = "object"; + schemas["LoadModelRequest"]["properties"]["engine"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["model_path"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["model"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["engine"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["stop"]["type"] = "array"; + schemas["LoadModelRequest"]["properties"]["stop"]["items"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["stop"]["description"] = + "List of stop sequences"; + + schemas["LoadModelRequest"]["properties"]["stream"]["type"] = "boolean"; + schemas["LoadModelRequest"]["properties"]["stream"]["description"] = + "Whether to stream the output"; + + schemas["LoadModelRequest"]["properties"]["ngl"]["type"] = "integer"; + schemas["LoadModelRequest"]["properties"]["ngl"]["description"] = + "Number of GPU layers"; + + schemas["LoadModelRequest"]["properties"]["ctx_len"]["type"] = "integer"; + schemas["LoadModelRequest"]["properties"]["ctx_len"]["description"] = + "Context length"; + + schemas["LoadModelRequest"]["properties"]["engine"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["engine"]["description"] = + "Engine used for the model"; + + schemas["LoadModelRequest"]["properties"]["system_template"]["type"] = + "string"; + schemas["LoadModelRequest"]["properties"]["system_template"]["description"] = + "Template for system messages"; + + schemas["LoadModelRequest"]["properties"]["user_template"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["user_template"]["description"] = + "Template for user messages"; + + schemas["LoadModelRequest"]["properties"]["ai_template"]["type"] = "string"; + schemas["LoadModelRequest"]["properties"]["ai_template"]["description"] = + "Template for AI responses"; + + schemas["LoadModelRequest"]["properties"]["n_probs"]["type"] = "integer"; + schemas["LoadModelRequest"]["properties"]["n_probs"]["description"] = + "Number of probabilities to return"; + + // Add other properties based on your implementation + + schemas["UnloadModelRequest"]["type"] = "object"; + schemas["UnloadModelRequest"]["properties"]["engine"]["type"] = "string"; + schemas["UnloadModelRequest"]["properties"]["model"]["type"] = "string"; + // Add other properties based on your implementation + + schemas["ModelStatusRequest"]["type"] = "object"; + schemas["ModelStatusRequest"]["properties"]["engine"]["type"] = "string"; + schemas["ModelStatusRequest"]["properties"]["model"]["type"] = "string"; + // Add other properties based on your implementation + + schemas["ModelStatusResponse"]["type"] = "object"; + // Add properties based on your implementation + + schemas["CortexModelList"]["type"] = "object"; + schemas["CortexModelList"]["properties"]["data"]["type"] = "array"; + schemas["CortexModelList"]["properties"]["data"]["items"]["$ref"] = + "#/components/schemas/CortexModel"; + + schemas["CortexModel"]["type"] = "object"; + // Add properties based on your implementation + + schemas["EngineList"]["type"] = "object"; + schemas["EngineList"]["properties"]["object"]["type"] = "string"; + schemas["EngineList"]["properties"]["data"]["type"] = "array"; + schemas["EngineList"]["properties"]["data"]["items"]["$ref"] = + "#/components/schemas/Engine"; + + schemas["Engine"]["type"] = "object"; + schemas["Engine"]["properties"]["id"]["type"] = "string"; + schemas["Engine"]["properties"]["object"]["type"] = "string"; + + schemas["CortexFineTuningRequest"]["type"] = "object"; + schemas["CortexFineTuningRequest"]["properties"]["engine"]["type"] = "string"; + // Add other properties based on your implementation + + schemas["CortexFineTuningResponse"]["type"] = "object"; + // Add properties based on your implementation + + schemas["UnloadEngineRequest"]["type"] = "object"; + schemas["UnloadEngineRequest"]["properties"]["engine"]["type"] = "string"; + + schemas["SuccessResponse"]["type"] = "object"; + schemas["SuccessResponse"]["properties"]["message"]["type"] = "string"; + // TODO: Add more paths and details based on your API + + return spec; +} + +void SwaggerController::serveSwaggerUI( + const drogon::HttpRequestPtr& req, + std::function&& callback) const { + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setBody(swaggerUIHTML); + resp->setContentTypeCode(drogon::CT_TEXT_HTML); + callback(resp); +} + +void SwaggerController::serveOpenAPISpec( + const drogon::HttpRequestPtr& req, + std::function&& callback) const { + Json::Value spec = generateOpenAPISpec(); + auto resp = drogon::HttpResponse::newHttpJsonResponse(spec); + callback(resp); +} \ No newline at end of file diff --git a/engine/controllers/swagger.h b/engine/controllers/swagger.h new file mode 100644 index 000000000..2f2976024 --- /dev/null +++ b/engine/controllers/swagger.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +using namespace drogon; + +class SwaggerController : public drogon::HttpController { + public: + METHOD_LIST_BEGIN + ADD_METHOD_TO(SwaggerController::serveSwaggerUI, "/swagger", Get); + ADD_METHOD_TO(SwaggerController::serveOpenAPISpec, "/openapi.json", Get); + METHOD_LIST_END + + void serveSwaggerUI( + const drogon::HttpRequestPtr& req, + std::function&& callback) const; + + void serveOpenAPISpec( + const drogon::HttpRequestPtr& req, + std::function&& callback) const; + + private: + static const std::string swaggerUIHTML; + static Json::Value generateOpenAPISpec(); +}; \ No newline at end of file diff --git a/engine/database/models.cc b/engine/database/models.cc index cfaf275e7..5c637f1a2 100644 --- a/engine/database/models.cc +++ b/engine/database/models.cc @@ -216,6 +216,9 @@ cpp::result Models::AddModelEntry(ModelEntry new_entry, cpp::result Models::UpdateModelEntry( const std::string& identifier, const ModelEntry& updated_entry) { + if (!HasModel(identifier)) { + return cpp::fail("Model not found: " + identifier); + } try { SQLite::Statement upd(db_, "UPDATE models " @@ -235,6 +238,9 @@ cpp::result Models::UpdateModelEntry( cpp::result Models::UpdateModelAlias( const std::string& model_id, const std::string& new_model_alias) { + if (!HasModel(model_id)) { + return cpp::fail("Model not found: " + model_id); + } try { db_.exec("BEGIN TRANSACTION;"); utils::ScopeExit se([this] { db_.exec("COMMIT;"); }); diff --git a/engine/test/components/test_models_db.cc b/engine/test/components/test_models_db.cc index ee418d851..fa5bb16ef 100644 --- a/engine/test/components/test_models_db.cc +++ b/engine/test/components/test_models_db.cc @@ -23,9 +23,9 @@ class ModelsTestSuite : public ::testing::Test { SQLite::Database db_; cortex::db::Models model_list_; - const cortex::db::ModelEntry kTestModel{ - "test_model_id", "test_author", "main", - "/path/to/model.yaml", "test_alias"}; + const cortex::db::ModelEntry kTestModel{"test_model_id", "test_author", + "main", "/path/to/model.yaml", + "test_alias"}; }; TEST_F(ModelsTestSuite, TestAddModelEntry) { @@ -131,8 +131,8 @@ TEST_F(ModelsTestSuite, TestUpdateModelAlias) { EXPECT_EQ(updated_model.value().model_id, kTestModel.model_id); // Test update with non-existent model - EXPECT_FALSE( - model_list_.UpdateModelAlias(kNonExistentModel, kAnotherAlias).value()); + EXPECT_TRUE(model_list_.UpdateModelAlias(kNonExistentModel, kAnotherAlias) + .has_error()); // Test update with non-unique alias cortex::db::ModelEntry another_model = kTestModel;