192 lines
8.5 KiB
C++
192 lines
8.5 KiB
C++
#include <iostream>
|
||
#include <chrono>
|
||
#include <thread>
|
||
#include <vector>
|
||
#include <string>
|
||
#include "./xenith/libs/cpp-httplib-0.57.1/httplib.hpp"
|
||
#include "./xenith/libs/json-3.12.0/nlohmann/json.hpp"
|
||
|
||
|
||
using json = nlohmann::json;
|
||
|
||
void log_ollama_options(const std::string& request_body) {
|
||
try {
|
||
auto j = json::parse(request_body);
|
||
|
||
if (j.contains("options") && j["options"].is_object()) {
|
||
std::cout << "\n🎛️ [НАСТРОЙКИ МОДЕЛИ] Получены пользовательские параметры:\n";
|
||
auto options = j["options"];
|
||
|
||
for (auto& [key, value] : options.items()) {
|
||
std::cout << " • " << key << " = " << value << "\n";
|
||
}
|
||
std::cout << "-----------------------------------\n\n";
|
||
} else {
|
||
std::cout << "ℹ️ [НАСТРОЙКИ МОДЕЛИ] Параметры не переданы (используются дефолтные).\n\n";
|
||
}
|
||
} catch (const std::exception& e) {
|
||
std::cout << "⚠️ [ОШИБКА JSON] Не удалось распарсить тело запроса: " << e.what() << "\n";
|
||
}
|
||
}
|
||
|
||
int main_() {
|
||
httplib::Server svr;
|
||
|
||
svr.set_logger([](const httplib::Request& req, const httplib::Response& res) {
|
||
std::cout << "[ЛОГ] " << req.method << " " << req.path << " -> Статус: " << res.status << "\n";
|
||
if (!req.body.empty() && req.path != "/api/generate" && req.path != "/api/chat") {
|
||
std::cout << "[ЛОГ] Тело запроса: " << req.body << "\n";
|
||
}
|
||
std::cout << "-----------------------------------\n";
|
||
});
|
||
|
||
svr.Get("/api/tags", [](const httplib::Request& req, httplib::Response& res) {
|
||
std::string json_response = R"({
|
||
"models": [
|
||
{
|
||
"name": "BiPy:latest",
|
||
"model": "BiPy:latest",
|
||
"modified_at": "2026-09-25T16:45:00.1234567Z",
|
||
"size": 4661224679,
|
||
"digest": "365c0bd3c000a256fbf3c88083a2166cb9e931444146a7826317d745d0b4b2cb",
|
||
"details": {
|
||
"parent_model": "",
|
||
"format": "gguf",
|
||
"family": "llama",
|
||
"families": ["llama"],
|
||
"parameter_size": "8.0B",
|
||
"quantization_level": "FP16"
|
||
}
|
||
}
|
||
]
|
||
})";
|
||
res.set_content(json_response, "application/json");
|
||
});
|
||
|
||
// 2. Максимальный список запущенных моделей
|
||
svr.Get("/api/ps", [](const httplib::Request& req, httplib::Response& res) {
|
||
std::string json_response = R"({
|
||
"models": [
|
||
{
|
||
"name": "BiPy:latest",
|
||
"model": "BiPy:latest",
|
||
"size": 4661224679,
|
||
"digest": "365c0bd3c000a256fbf3c88083a2166cb9e931444146a7826317d745d0b4b2cb",
|
||
"details": {
|
||
"parent_model": "",
|
||
"format": "gguf",
|
||
"family": "llama",
|
||
"families": ["llama"],
|
||
"parameter_size": "8.0B",
|
||
"quantization_level": "FP16"
|
||
},
|
||
"expires_at": "2026-09-25T17:15:00Z",
|
||
"size_vram": 4661224679
|
||
}
|
||
]
|
||
})";
|
||
res.set_content(json_response, "application/json");
|
||
});
|
||
|
||
// 3. Версия сервера
|
||
svr.Get("/api/version", [](const httplib::Request& req, httplib::Response& res) {
|
||
res.set_content(R"({"version": "0.1.48"})", "application/json");
|
||
});
|
||
|
||
// 4. Полноценный эндпоинт генерации текста (POST /api/generate)
|
||
svr.Post("/api/generate", [](const httplib::Request& req, httplib::Response& res) {
|
||
std::cout << "-> Сработал POST /api/generate. Нейросеть BiPy генерирует ответ...\n";
|
||
|
||
// Разбираем и выводим опции в консоль
|
||
log_ollama_options(req.body);
|
||
|
||
bool is_stream = (req.body.find("\"stream\": false") == std::string::npos);
|
||
|
||
if (is_stream) {
|
||
std::vector<std::string> tokens = { "Привет!", " Я", " полнофункциональная", " тестовая", " модель", " BiPy.", " Настройки", " успешно", " выведены." };
|
||
|
||
res.set_chunked_content_provider("application/x-ndjson",
|
||
[tokens](size_t offset, httplib::DataSink &sink) {
|
||
if (offset < tokens.size()) {
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||
|
||
bool is_done = (offset == tokens.size() - 1);
|
||
std::string chunk;
|
||
|
||
if (!is_done) {
|
||
chunk = "{\"model\":\"BiPy:latest\",\"created_at\":\"2026-09-25T16:45:10.123456Z\",\"response\":\"" + tokens[offset] + "\",\"done\":false}\n";
|
||
} else {
|
||
chunk = "{\"model\":\"BiPy:latest\",\"created_at\":\"2026-09-25T16:45:11.123456Z\",\"response\":\"" + tokens[offset] + "\",\"done\":true,"
|
||
"\"context\":[1,2,3],"
|
||
"\"total_duration\":1500000000,\"load_duration\":5000000,\"prompt_eval_count\":12,"
|
||
"\"prompt_eval_duration\":120000000,\"eval_count\":9,\"eval_duration\":880000000}\n";
|
||
}
|
||
|
||
sink.write(chunk.c_str(), chunk.size());
|
||
return true;
|
||
}
|
||
sink.done();
|
||
return true;
|
||
}
|
||
);
|
||
} else {
|
||
// ИСПРАВЛЕНО: Заменено `"context":,` на `"context":[]`
|
||
std::string full_response = R"({
|
||
"model": "BiPy:latest",
|
||
"created_at": "2026-09-25T16:45:10.123456Z",
|
||
"response": "Привет! Я полнофункциональная тестовая модель BiPy. Настройки прочитаны!",
|
||
"done": true,
|
||
"context": [],
|
||
"total_duration": 1500000000,
|
||
"load_duration": 4500000,
|
||
"prompt_eval_count": 10,
|
||
"prompt_eval_duration": 100000000,
|
||
"eval_count": 15,
|
||
"eval_duration": 1300000000
|
||
})";
|
||
res.set_content(full_response, "application/x-ndjson");
|
||
}
|
||
});
|
||
|
||
// 5. Полноценный эндпоинт чата (POST /api/chat)
|
||
svr.Post("/api/chat", [](const httplib::Request& req, httplib::Response& res) {
|
||
std::cout << "-> Сработал POST /api/chat. Имитируем продвинутый диалог...\n";
|
||
|
||
// Разбираем и выводим опции в консоль
|
||
log_ollama_options(req.body);
|
||
|
||
std::vector<std::string> tokens = { "Привет", " в", " режиме", " полноценного", " чата!", " Все", " ползунки", " зафиксированы." };
|
||
|
||
res.set_chunked_content_provider("application/x-ndjson",
|
||
[tokens](size_t offset, httplib::DataSink &sink) {
|
||
if (offset < tokens.size()) {
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||
|
||
bool is_done = (offset == tokens.size() - 1);
|
||
std::string chunk;
|
||
|
||
if (!is_done) {
|
||
chunk = "{\"model\":\"BiPy:latest\",\"created_at\":\"2026-09-25T16:45:20.123456Z\",\"message\":{\"role\":\"assistant\",\"content\":\"" + tokens[offset] + "\"},\"done\":false}\n";
|
||
} else {
|
||
chunk = "{\"model\":\"BiPy:latest\",\"created_at\":\"2026-09-25T16:45:21.123456Z\",\"message\":{\"role\":\"assistant\",\"content\":\"" + tokens[offset] + "\"},\"done\":true,"
|
||
"\"total_duration\":1200000000,\"load_duration\":4000000,\"prompt_eval_count\":15,"
|
||
"\"prompt_eval_duration\":150000000,\"eval_count\":8,\"eval_duration\":750000000}\n";
|
||
}
|
||
|
||
sink.write(chunk.c_str(), chunk.size());
|
||
return true;
|
||
}
|
||
sink.done();
|
||
return true;
|
||
}
|
||
);
|
||
});
|
||
|
||
std::cout << "Сервер запущен на http://localhost:11423\n";
|
||
if (!svr.listen("0.0.0.0", 11423)) {
|
||
std::cerr << "Ошибка: Не удалось запустить сервер! Проверьте порт.\n";
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|