llama-cpp-pro 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +295 -0
- package/LICENSE +21 -0
- package/LlamaCpp.podspec +33 -0
- package/LlamaCppCapacitor.podspec +33 -0
- package/Package.swift +29 -0
- package/README.md +93 -0
- package/android/build.gradle +88 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/CMakeLists-arm64.txt +138 -0
- package/android/src/main/CMakeLists-x86_64.txt +141 -0
- package/android/src/main/CMakeLists.txt +138 -0
- package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCpp.java +1340 -0
- package/android/src/main/java/ai/annadata/plugin/capacitor/LlamaCppPlugin.java +814 -0
- package/android/src/main/java/ai/annadata/plugin/capacitor/ModelAdmissionController.java +214 -0
- package/android/src/main/jni-chat-session.cpp +261 -0
- package/android/src/main/jni-lora.cpp +197 -0
- package/android/src/main/jni-multimodal.cpp +167 -0
- package/android/src/main/jni-tts.cpp +290 -0
- package/android/src/main/jni-utils.h +148 -0
- package/android/src/main/jni.cpp +1808 -0
- package/android/src/main/jniLibs/arm64-v8a/libllama-cpp-arm64.so +0 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/build-native.sh +280 -0
- package/cmake/desktop-metal-embed.cmake +30 -0
- package/cmake/desktop-sources.cmake +100 -0
- package/cmake/ggml-backends.cmake +44 -0
- package/cpp/LICENSE +21 -0
- package/cpp/README.md +15 -0
- package/cpp/anyascii.c +22223 -0
- package/cpp/anyascii.h +42 -0
- package/cpp/cap-completion.cpp +942 -0
- package/cpp/cap-completion.h +127 -0
- package/cpp/cap-embedding.cpp +193 -0
- package/cpp/cap-embedding.h +35 -0
- package/cpp/cap-ios-bridge.cpp +1810 -0
- package/cpp/cap-ios-bridge.h +61 -0
- package/cpp/cap-llama.cpp +412 -0
- package/cpp/cap-llama.h +161 -0
- package/cpp/cap-mtmd.hpp +602 -0
- package/cpp/cap-native-server.cpp +1095 -0
- package/cpp/cap-native-server.h +40 -0
- package/cpp/cap-tts.cpp +591 -0
- package/cpp/cap-tts.h +59 -0
- package/cpp/cap-wasm-fs.cpp +156 -0
- package/cpp/cap-wasm-jspi.cpp +19 -0
- package/cpp/cap-wasm-jspi.h +30 -0
- package/cpp/cap-wasm-vfs.cpp +22 -0
- package/cpp/chat-parser.cpp +393 -0
- package/cpp/chat-parser.h +120 -0
- package/cpp/chat.cpp +2315 -0
- package/cpp/chat.h +221 -0
- package/cpp/common.cpp +1664 -0
- package/cpp/common.h +744 -0
- package/cpp/ggml-alloc.c +1028 -0
- package/cpp/ggml-alloc.h +76 -0
- package/cpp/ggml-backend-impl.h +255 -0
- package/cpp/ggml-backend-reg.cpp +600 -0
- package/cpp/ggml-backend.cpp +2121 -0
- package/cpp/ggml-backend.h +354 -0
- package/cpp/ggml-common.h +1878 -0
- package/cpp/ggml-cpp.h +39 -0
- package/cpp/ggml-cpu/amx/amx.cpp +221 -0
- package/cpp/ggml-cpu/amx/amx.h +8 -0
- package/cpp/ggml-cpu/amx/common.h +91 -0
- package/cpp/ggml-cpu/amx/mmq.cpp +2512 -0
- package/cpp/ggml-cpu/amx/mmq.h +10 -0
- package/cpp/ggml-cpu/arch/arm/cpu-feats.cpp +94 -0
- package/cpp/ggml-cpu/arch/arm/quants.c +3650 -0
- package/cpp/ggml-cpu/arch/arm/repack.cpp +1891 -0
- package/cpp/ggml-cpu/arch/x86/cpu-feats.cpp +327 -0
- package/cpp/ggml-cpu/arch/x86/quants.c +3820 -0
- package/cpp/ggml-cpu/arch/x86/repack.cpp +6307 -0
- package/cpp/ggml-cpu/arch-fallback.h +215 -0
- package/cpp/ggml-cpu/binary-ops.cpp +158 -0
- package/cpp/ggml-cpu/binary-ops.h +16 -0
- package/cpp/ggml-cpu/common.h +73 -0
- package/cpp/ggml-cpu/ggml-cpu-impl.h +559 -0
- package/cpp/ggml-cpu/ggml-cpu.c +3578 -0
- package/cpp/ggml-cpu/ggml-cpu.cpp +672 -0
- package/cpp/ggml-cpu/ops.cpp +10587 -0
- package/cpp/ggml-cpu/ops.h +114 -0
- package/cpp/ggml-cpu/quants.c +1193 -0
- package/cpp/ggml-cpu/quants.h +97 -0
- package/cpp/ggml-cpu/repack.cpp +1982 -0
- package/cpp/ggml-cpu/repack.h +120 -0
- package/cpp/ggml-cpu/simd-mappings.h +1184 -0
- package/cpp/ggml-cpu/traits.cpp +36 -0
- package/cpp/ggml-cpu/traits.h +38 -0
- package/cpp/ggml-cpu/unary-ops.cpp +186 -0
- package/cpp/ggml-cpu/unary-ops.h +28 -0
- package/cpp/ggml-cpu/vec.cpp +348 -0
- package/cpp/ggml-cpu/vec.h +1121 -0
- package/cpp/ggml-cpu.h +145 -0
- package/cpp/ggml-impl.h +622 -0
- package/cpp/ggml-metal-impl.h +688 -0
- package/cpp/ggml-metal.h +66 -0
- package/cpp/ggml-metal.m +6833 -0
- package/cpp/ggml-metal.metal +10754 -0
- package/cpp/ggml-opt.cpp +1093 -0
- package/cpp/ggml-opt.h +256 -0
- package/cpp/ggml-quants.c +5324 -0
- package/cpp/ggml-quants.h +106 -0
- package/cpp/ggml-threading.cpp +12 -0
- package/cpp/ggml-threading.h +14 -0
- package/cpp/ggml.c +7108 -0
- package/cpp/ggml.h +2492 -0
- package/cpp/gguf.cpp +1358 -0
- package/cpp/gguf.h +202 -0
- package/cpp/json-partial.cpp +256 -0
- package/cpp/json-partial.h +38 -0
- package/cpp/json-schema-to-grammar.cpp +985 -0
- package/cpp/json-schema-to-grammar.h +21 -0
- package/cpp/llama-adapter.cpp +388 -0
- package/cpp/llama-adapter.h +76 -0
- package/cpp/llama-arch.cpp +2355 -0
- package/cpp/llama-arch.h +499 -0
- package/cpp/llama-batch.cpp +875 -0
- package/cpp/llama-batch.h +160 -0
- package/cpp/llama-chat.cpp +783 -0
- package/cpp/llama-chat.h +65 -0
- package/cpp/llama-context.cpp +2788 -0
- package/cpp/llama-context.h +306 -0
- package/cpp/llama-cparams.cpp +5 -0
- package/cpp/llama-cparams.h +41 -0
- package/cpp/llama-cpp.h +30 -0
- package/cpp/llama-grammar.cpp +1229 -0
- package/cpp/llama-grammar.h +173 -0
- package/cpp/llama-graph.cpp +1891 -0
- package/cpp/llama-graph.h +810 -0
- package/cpp/llama-hparams.cpp +180 -0
- package/cpp/llama-hparams.h +233 -0
- package/cpp/llama-impl.cpp +167 -0
- package/cpp/llama-impl.h +61 -0
- package/cpp/llama-io.cpp +15 -0
- package/cpp/llama-io.h +35 -0
- package/cpp/llama-kv-cache-iswa.cpp +318 -0
- package/cpp/llama-kv-cache-iswa.h +135 -0
- package/cpp/llama-kv-cache.cpp +2059 -0
- package/cpp/llama-kv-cache.h +374 -0
- package/cpp/llama-kv-cells.h +491 -0
- package/cpp/llama-memory-hybrid.cpp +258 -0
- package/cpp/llama-memory-hybrid.h +137 -0
- package/cpp/llama-memory-recurrent.cpp +1146 -0
- package/cpp/llama-memory-recurrent.h +179 -0
- package/cpp/llama-memory.cpp +59 -0
- package/cpp/llama-memory.h +119 -0
- package/cpp/llama-mmap.cpp +609 -0
- package/cpp/llama-mmap.h +68 -0
- package/cpp/llama-model-loader.cpp +1166 -0
- package/cpp/llama-model-loader.h +170 -0
- package/cpp/llama-model-saver.cpp +282 -0
- package/cpp/llama-model-saver.h +37 -0
- package/cpp/llama-model.cpp +19061 -0
- package/cpp/llama-model.h +491 -0
- package/cpp/llama-sampling.cpp +2575 -0
- package/cpp/llama-sampling.h +32 -0
- package/cpp/llama-vocab.cpp +3792 -0
- package/cpp/llama-vocab.h +176 -0
- package/cpp/llama.cpp +358 -0
- package/cpp/llama.h +1373 -0
- package/cpp/log.cpp +428 -0
- package/cpp/log.h +103 -0
- package/cpp/minja/chat-template.hpp +550 -0
- package/cpp/minja/minja.hpp +3009 -0
- package/cpp/nlohmann/json.hpp +25526 -0
- package/cpp/nlohmann/json_fwd.hpp +187 -0
- package/cpp/regex-partial.cpp +204 -0
- package/cpp/regex-partial.h +56 -0
- package/cpp/sampling.cpp +579 -0
- package/cpp/sampling.h +107 -0
- package/cpp/tools/mtmd/clip-impl.h +473 -0
- package/cpp/tools/mtmd/clip.cpp +4322 -0
- package/cpp/tools/mtmd/clip.h +106 -0
- package/cpp/tools/mtmd/miniaudio/miniaudio.h +93468 -0
- package/cpp/tools/mtmd/mtmd-audio.cpp +769 -0
- package/cpp/tools/mtmd/mtmd-audio.h +47 -0
- package/cpp/tools/mtmd/mtmd-helper.cpp +460 -0
- package/cpp/tools/mtmd/mtmd-helper.h +95 -0
- package/cpp/tools/mtmd/mtmd.cpp +1066 -0
- package/cpp/tools/mtmd/mtmd.h +302 -0
- package/cpp/tools/mtmd/stb/stb_image.h +7988 -0
- package/cpp/unicode-data.cpp +7034 -0
- package/cpp/unicode-data.h +20 -0
- package/cpp/unicode.cpp +1061 -0
- package/cpp/unicode.h +68 -0
- package/cpp/vendor/cpp-httplib/httplib.cpp +16509 -0
- package/cpp/vendor/cpp-httplib/httplib.h +3883 -0
- package/desktop/electron-builder.config.cjs +157 -0
- package/desktop/entitlements.mac.plist +12 -0
- package/desktop/package.json +11 -0
- package/desktop/resolve-package-root.cjs +88 -0
- package/desktop/src/main/backend-selector.cjs +148 -0
- package/desktop/src/main/gpu-probe.cjs +142 -0
- package/desktop/src/main/index.cjs +58 -0
- package/desktop/src/main/ipc-handlers.cjs +212 -0
- package/desktop/src/main/model-store.cjs +50 -0
- package/desktop/src/main/preload.cjs +39 -0
- package/desktop/src/main/sidecar-client.cjs +76 -0
- package/desktop/src/main/sidecar-manager.cjs +364 -0
- package/dist/docs.json +14357 -0
- package/dist/esm/definitions.d.ts +731 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/desktop.d.ts +37 -0
- package/dist/esm/desktop.js +133 -0
- package/dist/esm/desktop.js.map +1 -0
- package/dist/esm/index.d.ts +200 -0
- package/dist/esm/index.js +612 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/isomorphic/desktop.runtime.d.ts +37 -0
- package/dist/esm/isomorphic/desktop.runtime.js +36 -0
- package/dist/esm/isomorphic/desktop.runtime.js.map +1 -0
- package/dist/esm/isomorphic/errors.d.ts +6 -0
- package/dist/esm/isomorphic/errors.js +9 -0
- package/dist/esm/isomorphic/errors.js.map +1 -0
- package/dist/esm/isomorphic/model.admission.d.ts +17 -0
- package/dist/esm/isomorphic/model.admission.js +27 -0
- package/dist/esm/isomorphic/model.admission.js.map +1 -0
- package/dist/esm/isomorphic/model.scheduler.d.ts +31 -0
- package/dist/esm/isomorphic/model.scheduler.js +95 -0
- package/dist/esm/isomorphic/model.scheduler.js.map +1 -0
- package/dist/esm/isomorphic/provider.desktop.d.ts +40 -0
- package/dist/esm/isomorphic/provider.desktop.js +411 -0
- package/dist/esm/isomorphic/provider.desktop.js.map +1 -0
- package/dist/esm/isomorphic/provider.factory.d.ts +2 -0
- package/dist/esm/isomorphic/provider.factory.js +16 -0
- package/dist/esm/isomorphic/provider.factory.js.map +1 -0
- package/dist/esm/isomorphic/provider.interface.d.ts +76 -0
- package/dist/esm/isomorphic/provider.interface.js +2 -0
- package/dist/esm/isomorphic/provider.interface.js.map +1 -0
- package/dist/esm/isomorphic/provider.native.d.ts +18 -0
- package/dist/esm/isomorphic/provider.native.js +173 -0
- package/dist/esm/isomorphic/provider.native.js.map +1 -0
- package/dist/esm/isomorphic/provider.web.d.ts +88 -0
- package/dist/esm/isomorphic/provider.web.js +573 -0
- package/dist/esm/isomorphic/provider.web.js.map +1 -0
- package/dist/esm/isomorphic/sidecar-sse.d.ts +14 -0
- package/dist/esm/isomorphic/sidecar-sse.js +76 -0
- package/dist/esm/isomorphic/sidecar-sse.js.map +1 -0
- package/dist/esm/isomorphic/wasmMemoryCalibration.d.ts +26 -0
- package/dist/esm/isomorphic/wasmMemoryCalibration.js +60 -0
- package/dist/esm/isomorphic/wasmMemoryCalibration.js.map +1 -0
- package/dist/esm/isomorphic/wasmMemoryPolicy.d.ts +55 -0
- package/dist/esm/isomorphic/wasmMemoryPolicy.js +93 -0
- package/dist/esm/isomorphic/wasmMemoryPolicy.js.map +1 -0
- package/dist/esm/storage/manifest.d.ts +13 -0
- package/dist/esm/storage/manifest.js +87 -0
- package/dist/esm/storage/manifest.js.map +1 -0
- package/dist/esm/storage/opfs.store.d.ts +31 -0
- package/dist/esm/storage/opfs.store.js +241 -0
- package/dist/esm/storage/opfs.store.js.map +1 -0
- package/dist/esm/web.d.ts +206 -0
- package/dist/esm/web.js +521 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/esm/workers/async-file.d.ts +13 -0
- package/dist/esm/workers/async-file.js +12 -0
- package/dist/esm/workers/async-file.js.map +1 -0
- package/dist/esm/workers/heapfs.d.ts +55 -0
- package/dist/esm/workers/heapfs.js +115 -0
- package/dist/esm/workers/heapfs.js.map +1 -0
- package/dist/esm/workers/wasm.engine.d.ts +86 -0
- package/dist/esm/workers/wasm.engine.js +504 -0
- package/dist/esm/workers/wasm.engine.js.map +1 -0
- package/dist/esm/workers/worker.protocol.d.ts +160 -0
- package/dist/esm/workers/worker.protocol.js +2 -0
- package/dist/esm/workers/worker.protocol.js.map +1 -0
- package/dist/plugin.cjs +3179 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.js +3181 -0
- package/dist/plugin.js.map +1 -0
- package/dist/wasm/llama_engine.d.ts +74 -0
- package/dist/wasm/llama_engine.js +1829 -0
- package/dist/wasm/llama_engine.wasm +0 -0
- package/dist/wasm/llama_engine_emscripten.mjs +2 -0
- package/dist/wasm/package.json +16 -0
- package/dist/workers/llm.worker.js +1226 -0
- package/dist/workers/llm.worker.js.map +7 -0
- package/extraResources/llama-wasm/llama_engine.d.ts +74 -0
- package/extraResources/llama-wasm/llama_engine.js +1829 -0
- package/extraResources/llama-wasm/llama_engine.wasm +0 -0
- package/extraResources/llama-wasm/llama_engine_emscripten.mjs +2 -0
- package/extraResources/llama-wasm/package.json +16 -0
- package/extraResources/sidecar/README.md +11 -0
- package/extraResources/sidecar/darwin-arm64 +0 -0
- package/extraResources/sidecar/darwin-x64 +0 -0
- package/ios/CMakeLists-arm64.txt +161 -0
- package/ios/CMakeLists-x86_64.txt +188 -0
- package/ios/CMakeLists.txt +161 -0
- package/ios/Frameworks/llama-cpp.framework/Info.plist +28 -0
- package/ios/Frameworks/llama-cpp.framework/llama-cpp +0 -0
- package/ios/Sources/LlamaCppCapacitor/LlamaCpp.swift +1365 -0
- package/ios/Sources/LlamaCppCapacitor/LlamaCppPlugin.swift +694 -0
- package/ios/Sources/LlamaCppCapacitor/LlamaNativeBridge.swift +349 -0
- package/ios/Sources/LlamaCppCapacitor/ModelAdmissionController.swift +177 -0
- package/ios/embed-metal-shaders.sh +24 -0
- package/ios/metal-embed.cmake +29 -0
- package/package.json +281 -0
- package/scripts/build-js-preserve-wasm.cjs +53 -0
- package/scripts/build-sidecar-linux.sh +6 -0
- package/scripts/build-sidecar-win.bat +19 -0
- package/scripts/build-sidecar.sh +184 -0
- package/scripts/embed-llama-ios-app-framework.sh +63 -0
- package/scripts/ensure-desktop-sidecar-bundle.cjs +103 -0
- package/scripts/ensure-llama-ios-xcframework.sh +108 -0
- package/scripts/fix-esm-extensions.cjs +45 -0
- package/scripts/prepare-js-dist.cjs +28 -0
- package/scripts/stage-desktop-resources.cjs +116 -0
- package/sidecar/CMakeLists.txt +192 -0
- package/sidecar/cap-sidecar-main.cpp +68 -0
- package/types/llama-cpp-pro.d.ts +441 -0
|
@@ -0,0 +1,1095 @@
|
|
|
1
|
+
#include "cap-native-server.h"
|
|
2
|
+
#include "cap-embedding.h"
|
|
3
|
+
|
|
4
|
+
#include "httplib.h"
|
|
5
|
+
|
|
6
|
+
#include "nlohmann/json.hpp"
|
|
7
|
+
|
|
8
|
+
#include <atomic>
|
|
9
|
+
#include <chrono>
|
|
10
|
+
#include <condition_variable>
|
|
11
|
+
#include <cstdlib>
|
|
12
|
+
#include <cstring>
|
|
13
|
+
#include <deque>
|
|
14
|
+
#include <map>
|
|
15
|
+
#include <string>
|
|
16
|
+
#include <thread>
|
|
17
|
+
#include <vector>
|
|
18
|
+
|
|
19
|
+
using json = nlohmann::ordered_json;
|
|
20
|
+
|
|
21
|
+
extern "C" {
|
|
22
|
+
int64_t llama_init_context(const char * model_path, const char * params_json);
|
|
23
|
+
void llama_release_context(int64_t context_id);
|
|
24
|
+
const char * llama_completion(int64_t context_id, const char * params_json);
|
|
25
|
+
const char * llama_completion_stream(int64_t context_id, const char * params_json,
|
|
26
|
+
void (*token_callback)(const char * token_text, void * user_data, int token_index),
|
|
27
|
+
void * user_data);
|
|
28
|
+
const char * llama_get_formatted_chat(int64_t context_id, const char * messages_json, const char * chat_template,
|
|
29
|
+
const char * params_json);
|
|
30
|
+
const char * llama_get_context_model_json(int64_t context_id);
|
|
31
|
+
const char * llama_cap_tokenize(int64_t context_id, const char * text, const char * image_paths_json);
|
|
32
|
+
const char * llama_cap_detokenize(int64_t context_id, const char * tokens_json);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
namespace {
|
|
36
|
+
|
|
37
|
+
std::mutex g_mu;
|
|
38
|
+
std::unique_ptr<httplib::Server> g_srv;
|
|
39
|
+
std::thread g_thr;
|
|
40
|
+
std::atomic<bool> g_started{false};
|
|
41
|
+
|
|
42
|
+
static constexpr int kMaxConcurrentModels = 5;
|
|
43
|
+
|
|
44
|
+
struct ModelRegistry {
|
|
45
|
+
std::mutex mu;
|
|
46
|
+
std::map<std::string, int64_t> models;
|
|
47
|
+
std::map<std::string, std::string> paths;
|
|
48
|
+
int max_models = kMaxConcurrentModels;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
ModelRegistry g_registry;
|
|
52
|
+
|
|
53
|
+
std::string path_basename(const std::string & path) {
|
|
54
|
+
const size_t pos = path.find_last_of("/\\");
|
|
55
|
+
return pos == std::string::npos ? path : path.substr(pos + 1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
int64_t registry_resolve_ctx(const std::string & model_id) {
|
|
59
|
+
std::lock_guard<std::mutex> lock(g_registry.mu);
|
|
60
|
+
if (!model_id.empty()) {
|
|
61
|
+
const auto it = g_registry.models.find(model_id);
|
|
62
|
+
if (it != g_registry.models.end()) {
|
|
63
|
+
return it->second;
|
|
64
|
+
}
|
|
65
|
+
return -1;
|
|
66
|
+
}
|
|
67
|
+
if (g_registry.models.size() == 1) {
|
|
68
|
+
return g_registry.models.begin()->second;
|
|
69
|
+
}
|
|
70
|
+
return -1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
bool registry_unload_model(const std::string & model_id) {
|
|
74
|
+
std::lock_guard<std::mutex> lock(g_registry.mu);
|
|
75
|
+
const auto it = g_registry.models.find(model_id);
|
|
76
|
+
if (it == g_registry.models.end()) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
llama_release_context(it->second);
|
|
80
|
+
g_registry.models.erase(it);
|
|
81
|
+
g_registry.paths.erase(model_id);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void registry_release_all() {
|
|
86
|
+
std::lock_guard<std::mutex> lock(g_registry.mu);
|
|
87
|
+
for (const auto & kv : g_registry.models) {
|
|
88
|
+
llama_release_context(kv.second);
|
|
89
|
+
}
|
|
90
|
+
g_registry.models.clear();
|
|
91
|
+
g_registry.paths.clear();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
json registry_status_json() {
|
|
95
|
+
std::lock_guard<std::mutex> lock(g_registry.mu);
|
|
96
|
+
json models = json::array();
|
|
97
|
+
for (const auto & kv : g_registry.models) {
|
|
98
|
+
const auto path_it = g_registry.paths.find(kv.first);
|
|
99
|
+
models.push_back(json{{"id", kv.first},
|
|
100
|
+
{"context_id", kv.second},
|
|
101
|
+
{"path", path_it != g_registry.paths.end() ? path_it->second : ""}});
|
|
102
|
+
}
|
|
103
|
+
return json{{"loaded_count", g_registry.models.size()},
|
|
104
|
+
{"max_models", g_registry.max_models},
|
|
105
|
+
{"loaded_models", models}};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void set_cors(httplib::Response & res) {
|
|
109
|
+
res.set_header("Access-Control-Allow-Origin", "*");
|
|
110
|
+
res.set_header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
111
|
+
res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
bool parse_openai_chat(const std::string & body, std::string & model_out, json & messages_out, int & max_tokens_out,
|
|
115
|
+
double & temperature_out, bool & stream_out) {
|
|
116
|
+
try {
|
|
117
|
+
json b = json::parse(body);
|
|
118
|
+
if (b.contains("model") && b["model"].is_string()) {
|
|
119
|
+
model_out = b["model"].get<std::string>();
|
|
120
|
+
}
|
|
121
|
+
if (!b.contains("messages") || !b["messages"].is_array()) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
messages_out = b["messages"];
|
|
125
|
+
max_tokens_out = b.value("max_tokens", 256);
|
|
126
|
+
if (b.contains("temperature") && b["temperature"].is_number()) {
|
|
127
|
+
temperature_out = b["temperature"].get<double>();
|
|
128
|
+
} else {
|
|
129
|
+
temperature_out = 0.7;
|
|
130
|
+
}
|
|
131
|
+
stream_out = b.value("stream", false);
|
|
132
|
+
return true;
|
|
133
|
+
} catch (...) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
bool parse_openai_completion(const std::string & body, std::string & model_out, std::string & prompt_out, int & max_tokens_out,
|
|
139
|
+
double & temperature_out, bool & stream_out) {
|
|
140
|
+
try {
|
|
141
|
+
json b = json::parse(body);
|
|
142
|
+
if (b.contains("model") && b["model"].is_string()) {
|
|
143
|
+
model_out = b["model"].get<std::string>();
|
|
144
|
+
}
|
|
145
|
+
if (!b.contains("prompt") || !b["prompt"].is_string()) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
prompt_out = b["prompt"].get<std::string>();
|
|
149
|
+
max_tokens_out = b.value("max_tokens", 256);
|
|
150
|
+
if (b.contains("temperature") && b["temperature"].is_number()) {
|
|
151
|
+
temperature_out = b["temperature"].get<double>();
|
|
152
|
+
} else {
|
|
153
|
+
temperature_out = 0.7;
|
|
154
|
+
}
|
|
155
|
+
stream_out = b.value("stream", false);
|
|
156
|
+
return true;
|
|
157
|
+
} catch (...) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
json make_openai_error(const std::string & message, const std::string & type = "invalid_request_error") {
|
|
163
|
+
return json{{"error", json{{"message", message}, {"type", type}}}};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
bool registry_load_model(const std::string & model_id, const std::string & path, const json & params, json & err_out) {
|
|
167
|
+
if (model_id.empty() || path.empty()) {
|
|
168
|
+
err_out = make_openai_error("model_id and path are required");
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
std::lock_guard<std::mutex> lock(g_registry.mu);
|
|
172
|
+
if (g_registry.models.count(model_id)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
if (static_cast<int>(g_registry.models.size()) >= g_registry.max_models) {
|
|
176
|
+
err_out = make_openai_error("model limit reached (" + std::to_string(g_registry.max_models) + ")",
|
|
177
|
+
"model_limit_reached");
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
const std::string params_json = params.is_object() ? params.dump() : "{}";
|
|
181
|
+
const int64_t ctx_id = llama_init_context(path.c_str(), params_json.c_str());
|
|
182
|
+
if (ctx_id < 0) {
|
|
183
|
+
err_out = make_openai_error("failed to load model", "server_error");
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
g_registry.models[model_id] = ctx_id;
|
|
187
|
+
g_registry.paths[model_id] = path;
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
bool registry_require_ctx(const std::string & model_id, httplib::Response & res, int64_t & ctx_out) {
|
|
192
|
+
ctx_out = registry_resolve_ctx(model_id);
|
|
193
|
+
if (ctx_out >= 0) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
set_cors(res);
|
|
197
|
+
res.status = 404;
|
|
198
|
+
const std::string msg =
|
|
199
|
+
model_id.empty() ? "no model loaded — call POST /v1/internal/models/load first"
|
|
200
|
+
: "model not loaded: " + model_id;
|
|
201
|
+
res.set_content(make_openai_error(msg, "model_not_found").dump(), "application/json");
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
json openai_completion_response(const std::string & model, const std::string & text, int prompt_tokens, int completion_tokens,
|
|
206
|
+
int64_t created) {
|
|
207
|
+
json choice = json::object(
|
|
208
|
+
{{"index", 0},
|
|
209
|
+
{"text", text},
|
|
210
|
+
{"finish_reason", "stop"}});
|
|
211
|
+
return {{"id", std::string("cmpl-cap-") + std::to_string(created)},
|
|
212
|
+
{"object", "text_completion"},
|
|
213
|
+
{"created", created},
|
|
214
|
+
{"model", model.empty() ? "local" : model},
|
|
215
|
+
{"choices", json::array({choice})},
|
|
216
|
+
{"usage", json{{"prompt_tokens", prompt_tokens},
|
|
217
|
+
{"completion_tokens", completion_tokens},
|
|
218
|
+
{"total_tokens", prompt_tokens + completion_tokens}}}};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
bool parse_embedding_request(const std::string & body, std::string & model_out, std::vector<std::string> & inputs_out) {
|
|
222
|
+
try {
|
|
223
|
+
json b = json::parse(body);
|
|
224
|
+
if (b.contains("model") && b["model"].is_string()) {
|
|
225
|
+
model_out = b["model"].get<std::string>();
|
|
226
|
+
}
|
|
227
|
+
if (!b.contains("input")) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const auto & input = b["input"];
|
|
231
|
+
if (input.is_string()) {
|
|
232
|
+
inputs_out.push_back(input.get<std::string>());
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (input.is_array()) {
|
|
236
|
+
for (const auto & item : input) {
|
|
237
|
+
if (!item.is_string()) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
inputs_out.push_back(item.get<std::string>());
|
|
241
|
+
}
|
|
242
|
+
return !inputs_out.empty();
|
|
243
|
+
}
|
|
244
|
+
return false;
|
|
245
|
+
} catch (...) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
bool parse_openai_responses(const std::string & body, std::string & model_out, std::string & prompt_out, int & max_tokens_out,
|
|
251
|
+
double & temperature_out, bool & stream_out) {
|
|
252
|
+
try {
|
|
253
|
+
json b = json::parse(body);
|
|
254
|
+
if (b.contains("model") && b["model"].is_string()) {
|
|
255
|
+
model_out = b["model"].get<std::string>();
|
|
256
|
+
}
|
|
257
|
+
std::string instructions = b.value("instructions", "");
|
|
258
|
+
std::vector<std::string> input_parts;
|
|
259
|
+
if (!b.contains("input")) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (b["input"].is_string()) {
|
|
263
|
+
input_parts.push_back(b["input"].get<std::string>());
|
|
264
|
+
} else if (b["input"].is_array()) {
|
|
265
|
+
for (const auto & item : b["input"]) {
|
|
266
|
+
if (!item.is_string()) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
input_parts.push_back(item.get<std::string>());
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
if (input_parts.empty()) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
prompt_out.clear();
|
|
279
|
+
if (!instructions.empty()) {
|
|
280
|
+
prompt_out += instructions;
|
|
281
|
+
prompt_out += "\n\n";
|
|
282
|
+
}
|
|
283
|
+
for (size_t i = 0; i < input_parts.size(); ++i) {
|
|
284
|
+
if (i > 0) {
|
|
285
|
+
prompt_out += "\n";
|
|
286
|
+
}
|
|
287
|
+
prompt_out += input_parts[i];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
max_tokens_out = b.value("max_output_tokens", b.value("max_tokens", 256));
|
|
291
|
+
if (b.contains("temperature") && b["temperature"].is_number()) {
|
|
292
|
+
temperature_out = b["temperature"].get<double>();
|
|
293
|
+
} else {
|
|
294
|
+
temperature_out = 0.7;
|
|
295
|
+
}
|
|
296
|
+
stream_out = b.value("stream", false);
|
|
297
|
+
return true;
|
|
298
|
+
} catch (...) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
std::vector<std::string> chunk_text_for_stream(const std::string & text, size_t max_chunk_chars = 24) {
|
|
304
|
+
std::vector<std::string> chunks;
|
|
305
|
+
if (text.empty()) {
|
|
306
|
+
return chunks;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
std::string current;
|
|
310
|
+
for (const char ch : text) {
|
|
311
|
+
current.push_back(ch);
|
|
312
|
+
const bool at_boundary = (ch == ' ' || ch == '\n' || ch == '\t');
|
|
313
|
+
if (current.size() >= max_chunk_chars && at_boundary) {
|
|
314
|
+
chunks.push_back(current);
|
|
315
|
+
current.clear();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (!current.empty()) {
|
|
319
|
+
chunks.push_back(current);
|
|
320
|
+
}
|
|
321
|
+
return chunks;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
std::vector<std::string> token_level_chunks_for_stream(int64_t ctx_id, const std::string & text) {
|
|
325
|
+
if (text.empty()) {
|
|
326
|
+
return {};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const char * tokenized = llama_cap_tokenize(ctx_id, text.c_str(), "[]");
|
|
330
|
+
if (!tokenized) {
|
|
331
|
+
return chunk_text_for_stream(text);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
std::vector<std::string> chunks;
|
|
335
|
+
try {
|
|
336
|
+
json tk = json::parse(tokenized);
|
|
337
|
+
if (!tk.contains("tokens") || !tk["tokens"].is_array()) {
|
|
338
|
+
return chunk_text_for_stream(text);
|
|
339
|
+
}
|
|
340
|
+
for (const auto & tok : tk["tokens"]) {
|
|
341
|
+
if (!tok.is_number_integer()) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
json one = json::array({tok.get<int>()});
|
|
345
|
+
const std::string one_json = one.dump();
|
|
346
|
+
const char * piece = llama_cap_detokenize(ctx_id, one_json.c_str());
|
|
347
|
+
if (!piece) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
std::string s(piece);
|
|
351
|
+
if (!s.empty()) {
|
|
352
|
+
chunks.push_back(std::move(s));
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} catch (...) {
|
|
356
|
+
return chunk_text_for_stream(text);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (chunks.empty()) {
|
|
360
|
+
return chunk_text_for_stream(text);
|
|
361
|
+
}
|
|
362
|
+
return chunks;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
int embedding_size_from_context(int64_t ctx_id) {
|
|
366
|
+
const char * model_info = llama_get_context_model_json(ctx_id);
|
|
367
|
+
if (!model_info) {
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
json mi = json::parse(model_info);
|
|
372
|
+
if (mi.contains("nEmbd") && mi["nEmbd"].is_number_integer()) {
|
|
373
|
+
return mi["nEmbd"].get<int>();
|
|
374
|
+
}
|
|
375
|
+
} catch (...) {
|
|
376
|
+
}
|
|
377
|
+
return 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
bool run_prompt_completion(int64_t ctx_id, const std::string & prompt, int max_tokens, double temperature, json & cr_out,
|
|
381
|
+
std::string & text_out, int & prompt_tokens_out, int & completion_tokens_out, json & err_out) {
|
|
382
|
+
json comp = json::object();
|
|
383
|
+
comp["prompt"] = prompt;
|
|
384
|
+
comp["n_predict"] = max_tokens;
|
|
385
|
+
comp["temperature"] = temperature;
|
|
386
|
+
|
|
387
|
+
const char * out = llama_completion(ctx_id, comp.dump().c_str());
|
|
388
|
+
if (!out) {
|
|
389
|
+
err_out = make_openai_error("completion failed", "server_error");
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
json cr;
|
|
394
|
+
try {
|
|
395
|
+
cr = json::parse(out);
|
|
396
|
+
} catch (...) {
|
|
397
|
+
err_out = make_openai_error("bad completion JSON", "server_error");
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
if (cr.contains("error")) {
|
|
401
|
+
err_out = cr;
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
std::string text;
|
|
406
|
+
if (cr.contains("content") && cr["content"].is_string()) {
|
|
407
|
+
text = cr["content"].get<std::string>();
|
|
408
|
+
} else if (cr.contains("text") && cr["text"].is_string()) {
|
|
409
|
+
text = cr["text"].get<std::string>();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
cr_out = std::move(cr);
|
|
413
|
+
text_out = std::move(text);
|
|
414
|
+
prompt_tokens_out = cr_out.value("tokens_evaluated", 0);
|
|
415
|
+
completion_tokens_out = cr_out.value("tokens_predicted", 0);
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
enum class SseStreamKind { Chat, Completion };
|
|
420
|
+
|
|
421
|
+
struct LiveSseState {
|
|
422
|
+
std::mutex mu;
|
|
423
|
+
std::condition_variable cv;
|
|
424
|
+
std::deque<std::string> pending;
|
|
425
|
+
bool producer_done = false;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
struct LiveSseTokenCtx {
|
|
429
|
+
std::shared_ptr<LiveSseState> state;
|
|
430
|
+
std::string id;
|
|
431
|
+
std::string model_name;
|
|
432
|
+
int64_t created = 0;
|
|
433
|
+
SseStreamKind kind = SseStreamKind::Chat;
|
|
434
|
+
bool sent_role = false;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
void sse_enqueue(const std::shared_ptr<LiveSseState> & state, const std::string & line) {
|
|
438
|
+
{
|
|
439
|
+
std::lock_guard<std::mutex> lock(state->mu);
|
|
440
|
+
state->pending.push_back(line);
|
|
441
|
+
}
|
|
442
|
+
state->cv.notify_one();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
void sse_token_callback(const char * token_text, void * user_data, int /*token_index*/) {
|
|
446
|
+
if (!user_data || !token_text) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
auto * ctx = static_cast<LiveSseTokenCtx *>(user_data);
|
|
450
|
+
if (!ctx->state || token_text[0] == '\0') {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
std::string line;
|
|
455
|
+
if (ctx->kind == SseStreamKind::Chat) {
|
|
456
|
+
if (!ctx->sent_role) {
|
|
457
|
+
ctx->sent_role = true;
|
|
458
|
+
json first = {{"id", ctx->id},
|
|
459
|
+
{"object", "chat.completion.chunk"},
|
|
460
|
+
{"created", ctx->created},
|
|
461
|
+
{"model", ctx->model_name},
|
|
462
|
+
{"choices", json::array({json{{"index", 0},
|
|
463
|
+
{"delta", json{{"role", "assistant"}}},
|
|
464
|
+
{"finish_reason", nullptr}}})}};
|
|
465
|
+
sse_enqueue(ctx->state, std::string("data: ") + first.dump() + "\n\n");
|
|
466
|
+
}
|
|
467
|
+
json chunk = {{"id", ctx->id},
|
|
468
|
+
{"object", "chat.completion.chunk"},
|
|
469
|
+
{"created", ctx->created},
|
|
470
|
+
{"model", ctx->model_name},
|
|
471
|
+
{"choices", json::array({json{{"index", 0},
|
|
472
|
+
{"delta", json{{"content", token_text}}},
|
|
473
|
+
{"finish_reason", nullptr}}})}};
|
|
474
|
+
line = std::string("data: ") + chunk.dump() + "\n\n";
|
|
475
|
+
} else {
|
|
476
|
+
json chunk = {{"id", ctx->id},
|
|
477
|
+
{"object", "text_completion"},
|
|
478
|
+
{"created", ctx->created},
|
|
479
|
+
{"model", ctx->model_name},
|
|
480
|
+
{"choices", json::array({json{{"index", 0},
|
|
481
|
+
{"text", token_text},
|
|
482
|
+
{"finish_reason", nullptr}}})}};
|
|
483
|
+
line = std::string("data: ") + chunk.dump() + "\n\n";
|
|
484
|
+
}
|
|
485
|
+
sse_enqueue(ctx->state, line);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
void sse_finish_stream(const std::shared_ptr<LiveSseState> & state, const LiveSseTokenCtx & ctx) {
|
|
489
|
+
std::string line;
|
|
490
|
+
if (ctx.kind == SseStreamKind::Chat) {
|
|
491
|
+
json final_chunk = {{"id", ctx.id},
|
|
492
|
+
{"object", "chat.completion.chunk"},
|
|
493
|
+
{"created", ctx.created},
|
|
494
|
+
{"model", ctx.model_name},
|
|
495
|
+
{"choices", json::array({json{{"index", 0},
|
|
496
|
+
{"delta", json::object()},
|
|
497
|
+
{"finish_reason", "stop"}}})}};
|
|
498
|
+
line = std::string("data: ") + final_chunk.dump() + "\n\n";
|
|
499
|
+
} else {
|
|
500
|
+
json final_chunk = {{"id", ctx.id},
|
|
501
|
+
{"object", "text_completion"},
|
|
502
|
+
{"created", ctx.created},
|
|
503
|
+
{"model", ctx.model_name},
|
|
504
|
+
{"choices", json::array({json{{"index", 0},
|
|
505
|
+
{"text", ""},
|
|
506
|
+
{"finish_reason", "stop"}}})}};
|
|
507
|
+
line = std::string("data: ") + final_chunk.dump() + "\n\n";
|
|
508
|
+
}
|
|
509
|
+
{
|
|
510
|
+
std::lock_guard<std::mutex> lock(state->mu);
|
|
511
|
+
state->pending.push_back(line);
|
|
512
|
+
state->pending.push_back("data: [DONE]\n\n");
|
|
513
|
+
state->producer_done = true;
|
|
514
|
+
}
|
|
515
|
+
state->cv.notify_one();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
void attach_live_sse_provider(httplib::Response & res, const std::shared_ptr<LiveSseState> & state) {
|
|
519
|
+
res.set_header("Cache-Control", "no-cache");
|
|
520
|
+
res.set_header("Connection", "keep-alive");
|
|
521
|
+
res.set_chunked_content_provider(
|
|
522
|
+
"text/event-stream",
|
|
523
|
+
[state](size_t /*offset*/, httplib::DataSink & sink) {
|
|
524
|
+
for (;;) {
|
|
525
|
+
std::deque<std::string> batch;
|
|
526
|
+
bool done = false;
|
|
527
|
+
{
|
|
528
|
+
std::unique_lock<std::mutex> lock(state->mu);
|
|
529
|
+
while (state->pending.empty() && !state->producer_done) {
|
|
530
|
+
state->cv.wait_for(lock, std::chrono::milliseconds(100));
|
|
531
|
+
}
|
|
532
|
+
batch.swap(state->pending);
|
|
533
|
+
done = state->producer_done;
|
|
534
|
+
}
|
|
535
|
+
for (const auto & line : batch) {
|
|
536
|
+
if (!sink.write(line.data(), line.size())) {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (done) {
|
|
541
|
+
std::lock_guard<std::mutex> lock(state->mu);
|
|
542
|
+
if (state->pending.empty()) {
|
|
543
|
+
sink.done();
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (!batch.empty()) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
void start_live_completion_stream(int64_t ctx_id, const std::string & prompt, int max_tokens, double temperature,
|
|
557
|
+
const std::string & id, const std::string & model_name, int64_t created,
|
|
558
|
+
SseStreamKind kind, httplib::Response & res) {
|
|
559
|
+
json comp = json::object();
|
|
560
|
+
comp["prompt"] = prompt;
|
|
561
|
+
comp["n_predict"] = max_tokens;
|
|
562
|
+
comp["temperature"] = temperature;
|
|
563
|
+
const std::string comp_json = comp.dump();
|
|
564
|
+
|
|
565
|
+
auto state = std::make_shared<LiveSseState>();
|
|
566
|
+
attach_live_sse_provider(res, state);
|
|
567
|
+
|
|
568
|
+
std::thread([state, ctx_id, comp_json, id, model_name, created, kind]() {
|
|
569
|
+
LiveSseTokenCtx token_ctx{state, id, model_name, created, kind, false};
|
|
570
|
+
const char * result = llama_completion_stream(ctx_id, comp_json.c_str(), sse_token_callback, &token_ctx);
|
|
571
|
+
if (result) {
|
|
572
|
+
try {
|
|
573
|
+
json r = json::parse(result);
|
|
574
|
+
if (r.contains("error")) {
|
|
575
|
+
json err_chunk = {{"error", r["error"]}};
|
|
576
|
+
sse_enqueue(state, std::string("data: ") + err_chunk.dump() + "\n\n");
|
|
577
|
+
}
|
|
578
|
+
} catch (...) {
|
|
579
|
+
/* ignore parse errors on trailing result */
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
sse_finish_stream(state, token_ctx);
|
|
583
|
+
}).detach();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
void register_routes(httplib::Server & svr) {
|
|
587
|
+
// CORS is applied per-response via set_cors() — do not also put it in
|
|
588
|
+
// default headers or clients see duplicated Access-Control-Allow-Origin: *, *
|
|
589
|
+
svr.set_default_headers({{"Server", "llama-cpp-pro-native"}});
|
|
590
|
+
|
|
591
|
+
svr.Options(".*", [](const httplib::Request &, httplib::Response & res) {
|
|
592
|
+
set_cors(res);
|
|
593
|
+
res.status = 204;
|
|
594
|
+
return;
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
svr.Get("/health", [](const httplib::Request &, httplib::Response & res) {
|
|
598
|
+
set_cors(res);
|
|
599
|
+
res.set_content(json{{"status", "ok"}, {"registry", registry_status_json()}}.dump(), "application/json");
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
svr.Get("/v1/health", [](const httplib::Request &, httplib::Response & res) {
|
|
603
|
+
set_cors(res);
|
|
604
|
+
res.set_content(json{{"status", "ok"}, {"registry", registry_status_json()}}.dump(), "application/json");
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
svr.Get("/v1/models", [](const httplib::Request &, httplib::Response & res) {
|
|
608
|
+
set_cors(res);
|
|
609
|
+
json data = json::array();
|
|
610
|
+
{
|
|
611
|
+
std::lock_guard<std::mutex> lock(g_registry.mu);
|
|
612
|
+
for (const auto & kv : g_registry.models) {
|
|
613
|
+
data.push_back(json{{"id", kv.first}, {"object", "model"}, {"owned_by", "local"}});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
res.set_content(json{{"object", "list"}, {"data", data}}.dump(), "application/json");
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
svr.Get("/v1/internal/memory", [](const httplib::Request &, httplib::Response & res) {
|
|
620
|
+
set_cors(res);
|
|
621
|
+
res.set_content(registry_status_json().dump(), "application/json");
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
svr.Post("/v1/internal/context-limit", [](const httplib::Request & req, httplib::Response & res) {
|
|
625
|
+
set_cors(res);
|
|
626
|
+
try {
|
|
627
|
+
json b = json::parse(req.body);
|
|
628
|
+
int limit = b.value("limit", kMaxConcurrentModels);
|
|
629
|
+
if (limit < 1) {
|
|
630
|
+
limit = 1;
|
|
631
|
+
}
|
|
632
|
+
if (limit > kMaxConcurrentModels) {
|
|
633
|
+
limit = kMaxConcurrentModels;
|
|
634
|
+
}
|
|
635
|
+
{
|
|
636
|
+
std::lock_guard<std::mutex> lock(g_registry.mu);
|
|
637
|
+
g_registry.max_models = limit;
|
|
638
|
+
}
|
|
639
|
+
res.set_content(json{{"ok", true}, {"limit", limit}}.dump(), "application/json");
|
|
640
|
+
} catch (...) {
|
|
641
|
+
res.status = 400;
|
|
642
|
+
res.set_content(make_openai_error("invalid JSON").dump(), "application/json");
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
svr.Post("/v1/internal/models/load", [](const httplib::Request & req, httplib::Response & res) {
|
|
647
|
+
set_cors(res);
|
|
648
|
+
try {
|
|
649
|
+
json b = json::parse(req.body);
|
|
650
|
+
std::string model_id = b.value("model_id", "");
|
|
651
|
+
std::string path = b.value("path", "");
|
|
652
|
+
if (path.empty() && b.contains("model") && b["model"].is_string()) {
|
|
653
|
+
path = b["model"].get<std::string>();
|
|
654
|
+
}
|
|
655
|
+
if (model_id.empty() && !path.empty()) {
|
|
656
|
+
model_id = path_basename(path);
|
|
657
|
+
}
|
|
658
|
+
json params = json::object();
|
|
659
|
+
if (b.contains("n_ctx")) {
|
|
660
|
+
params["n_ctx"] = b["n_ctx"];
|
|
661
|
+
}
|
|
662
|
+
if (b.contains("n_gpu_layers")) {
|
|
663
|
+
params["n_gpu_layers"] = b["n_gpu_layers"];
|
|
664
|
+
}
|
|
665
|
+
if (b.contains("n_threads")) {
|
|
666
|
+
params["n_threads"] = b["n_threads"];
|
|
667
|
+
}
|
|
668
|
+
if (b.value("embedding", false)) {
|
|
669
|
+
params["embedding"] = true;
|
|
670
|
+
}
|
|
671
|
+
json err;
|
|
672
|
+
if (!registry_load_model(model_id, path, params, err)) {
|
|
673
|
+
res.status = err.contains("error") && err["error"].is_object()
|
|
674
|
+
&& err["error"].value("type", "") == "model_limit_reached"
|
|
675
|
+
? 429
|
|
676
|
+
: 400;
|
|
677
|
+
res.set_content(err.dump(), "application/json");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
res.set_content(json{{"ok", true}, {"model_id", model_id}, {"registry", registry_status_json()}}.dump(),
|
|
681
|
+
"application/json");
|
|
682
|
+
} catch (...) {
|
|
683
|
+
res.status = 400;
|
|
684
|
+
res.set_content(make_openai_error("invalid JSON").dump(), "application/json");
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
svr.Delete(R"(/v1/internal/models/([^/]+))", [](const httplib::Request & req, httplib::Response & res) {
|
|
689
|
+
set_cors(res);
|
|
690
|
+
const std::string model_id = req.matches.size() > 1 ? req.matches[1].str() : "";
|
|
691
|
+
if (model_id.empty() || !registry_unload_model(model_id)) {
|
|
692
|
+
res.status = 404;
|
|
693
|
+
res.set_content(make_openai_error("model not loaded: " + model_id, "model_not_found").dump(),
|
|
694
|
+
"application/json");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
res.set_content(json{{"ok", true}, {"model_id", model_id}, {"registry", registry_status_json()}}.dump(),
|
|
698
|
+
"application/json");
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
auto chat_handler = [](const httplib::Request & req, httplib::Response & res) {
|
|
702
|
+
set_cors(res);
|
|
703
|
+
std::string model;
|
|
704
|
+
json messages;
|
|
705
|
+
int max_tokens = 256;
|
|
706
|
+
double temperature = 0.7;
|
|
707
|
+
bool stream = false;
|
|
708
|
+
if (!parse_openai_chat(req.body, model, messages, max_tokens, temperature, stream)) {
|
|
709
|
+
res.status = 400;
|
|
710
|
+
res.set_content(make_openai_error("invalid JSON or missing messages").dump(), "application/json");
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
int64_t ctx_id = -1;
|
|
714
|
+
if (!registry_require_ctx(model, res, ctx_id)) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const std::string messages_str = messages.dump();
|
|
718
|
+
const char * formatted = llama_get_formatted_chat(ctx_id, messages_str.c_str(), "", nullptr);
|
|
719
|
+
if (!formatted) {
|
|
720
|
+
res.status = 500;
|
|
721
|
+
res.set_content(make_openai_error("format chat failed", "server_error").dump(), "application/json");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
json fc;
|
|
725
|
+
try {
|
|
726
|
+
fc = json::parse(formatted);
|
|
727
|
+
} catch (...) {
|
|
728
|
+
res.status = 500;
|
|
729
|
+
res.set_content(make_openai_error("bad formatted chat JSON", "server_error").dump(), "application/json");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (fc.contains("error")) {
|
|
733
|
+
res.status = 400;
|
|
734
|
+
res.set_content(fc.dump(), "application/json");
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (!fc.contains("prompt") || !fc["prompt"].is_string()) {
|
|
738
|
+
res.status = 400;
|
|
739
|
+
res.set_content(make_openai_error("no prompt in formatted chat").dump(), "application/json");
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const int64_t created =
|
|
744
|
+
static_cast<int64_t>(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
|
|
745
|
+
const std::string id = std::string("chatcmpl-cap-") + std::to_string(created);
|
|
746
|
+
const std::string model_name = model.empty() ? "local" : model;
|
|
747
|
+
const std::string prompt = fc["prompt"].get<std::string>();
|
|
748
|
+
|
|
749
|
+
if (stream) {
|
|
750
|
+
start_live_completion_stream(ctx_id, prompt, max_tokens, temperature, id, model_name, created,
|
|
751
|
+
SseStreamKind::Chat, res);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
json cr;
|
|
756
|
+
std::string text;
|
|
757
|
+
int prompt_tokens = 0;
|
|
758
|
+
int completion_tokens = 0;
|
|
759
|
+
json err;
|
|
760
|
+
if (!run_prompt_completion(ctx_id, prompt, max_tokens, temperature, cr, text, prompt_tokens, completion_tokens,
|
|
761
|
+
err)) {
|
|
762
|
+
res.status = err.contains("error") && err["error"].is_object() ? 500 : 400;
|
|
763
|
+
res.set_content(err.dump(), "application/json");
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
json choice = json::object(
|
|
768
|
+
{{"index", 0},
|
|
769
|
+
{"message", json::object({{"role", "assistant"}, {"content", text}})},
|
|
770
|
+
{"finish_reason", "stop"}});
|
|
771
|
+
json out = {{"id", id},
|
|
772
|
+
{"object", "chat.completion"},
|
|
773
|
+
{"created", created},
|
|
774
|
+
{"model", model_name},
|
|
775
|
+
{"choices", json::array({choice})},
|
|
776
|
+
{"usage", json{{"prompt_tokens", prompt_tokens},
|
|
777
|
+
{"completion_tokens", completion_tokens},
|
|
778
|
+
{"total_tokens", prompt_tokens + completion_tokens}}}};
|
|
779
|
+
res.set_content(out.dump(), "application/json");
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
svr.Post("/v1/chat/completions", chat_handler);
|
|
783
|
+
// Alias used by some clients and llama.cpp server.
|
|
784
|
+
svr.Post("/chat/completions", chat_handler);
|
|
785
|
+
|
|
786
|
+
auto completion_handler = [](const httplib::Request & req, httplib::Response & res) {
|
|
787
|
+
set_cors(res);
|
|
788
|
+
std::string model;
|
|
789
|
+
std::string prompt;
|
|
790
|
+
int max_tokens = 256;
|
|
791
|
+
double temperature = 0.7;
|
|
792
|
+
bool stream = false;
|
|
793
|
+
if (!parse_openai_completion(req.body, model, prompt, max_tokens, temperature, stream)) {
|
|
794
|
+
res.status = 400;
|
|
795
|
+
res.set_content(make_openai_error("invalid JSON or missing prompt").dump(), "application/json");
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
int64_t ctx_id = -1;
|
|
799
|
+
if (!registry_require_ctx(model, res, ctx_id)) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const int64_t created =
|
|
804
|
+
static_cast<int64_t>(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
|
|
805
|
+
const std::string id = std::string("cmpl-cap-") + std::to_string(created);
|
|
806
|
+
const std::string model_name = model.empty() ? "local" : model;
|
|
807
|
+
|
|
808
|
+
if (stream) {
|
|
809
|
+
start_live_completion_stream(ctx_id, prompt, max_tokens, temperature, id, model_name, created,
|
|
810
|
+
SseStreamKind::Completion, res);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
json cr;
|
|
815
|
+
std::string text;
|
|
816
|
+
int prompt_tokens = 0;
|
|
817
|
+
int completion_tokens = 0;
|
|
818
|
+
json err;
|
|
819
|
+
if (!run_prompt_completion(ctx_id, prompt, max_tokens, temperature, cr, text, prompt_tokens, completion_tokens,
|
|
820
|
+
err)) {
|
|
821
|
+
res.status = err.contains("error") && err["error"].is_object() ? 500 : 400;
|
|
822
|
+
res.set_content(err.dump(), "application/json");
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
json payload = openai_completion_response(model_name, text, prompt_tokens, completion_tokens, created);
|
|
827
|
+
res.set_content(payload.dump(), "application/json");
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
svr.Post("/v1/completions", completion_handler);
|
|
831
|
+
svr.Post("/completions", completion_handler);
|
|
832
|
+
|
|
833
|
+
auto responses_handler = [](const httplib::Request & req, httplib::Response & res) {
|
|
834
|
+
set_cors(res);
|
|
835
|
+
std::string model;
|
|
836
|
+
std::string prompt;
|
|
837
|
+
int max_tokens = 256;
|
|
838
|
+
double temperature = 0.7;
|
|
839
|
+
bool stream = false;
|
|
840
|
+
if (!parse_openai_responses(req.body, model, prompt, max_tokens, temperature, stream)) {
|
|
841
|
+
res.status = 400;
|
|
842
|
+
res.set_content(make_openai_error("invalid JSON or missing input").dump(), "application/json");
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
int64_t ctx_id = -1;
|
|
846
|
+
if (!registry_require_ctx(model, res, ctx_id)) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
json cr;
|
|
851
|
+
std::string text;
|
|
852
|
+
int prompt_tokens = 0;
|
|
853
|
+
int completion_tokens = 0;
|
|
854
|
+
json err;
|
|
855
|
+
if (!run_prompt_completion(ctx_id, prompt, max_tokens, temperature, cr, text, prompt_tokens, completion_tokens, err)) {
|
|
856
|
+
res.status = err.contains("error") && err["error"].is_object() ? 500 : 400;
|
|
857
|
+
res.set_content(err.dump(), "application/json");
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const int64_t created =
|
|
862
|
+
static_cast<int64_t>(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
|
|
863
|
+
const std::string response_id = std::string("resp-cap-") + std::to_string(created);
|
|
864
|
+
const std::string model_name = model.empty() ? "local" : model;
|
|
865
|
+
|
|
866
|
+
json message = {{"id", std::string("msg-cap-") + std::to_string(created)},
|
|
867
|
+
{"type", "message"},
|
|
868
|
+
{"role", "assistant"},
|
|
869
|
+
{"content", json::array({json{{"type", "output_text"}, {"text", text}}})}};
|
|
870
|
+
|
|
871
|
+
json response_obj = {{"id", response_id},
|
|
872
|
+
{"object", "response"},
|
|
873
|
+
{"created_at", created},
|
|
874
|
+
{"model", model_name},
|
|
875
|
+
{"status", "completed"},
|
|
876
|
+
{"output", json::array({message})},
|
|
877
|
+
{"output_text", text},
|
|
878
|
+
{"usage", json{{"input_tokens", prompt_tokens},
|
|
879
|
+
{"output_tokens", completion_tokens},
|
|
880
|
+
{"total_tokens", prompt_tokens + completion_tokens}}}};
|
|
881
|
+
json response_created = response_obj;
|
|
882
|
+
response_created["status"] = "in_progress";
|
|
883
|
+
response_created["output"] = json::array();
|
|
884
|
+
response_created["output_text"] = "";
|
|
885
|
+
response_created["usage"] = json{{"input_tokens", prompt_tokens}, {"output_tokens", 0}, {"total_tokens", prompt_tokens}};
|
|
886
|
+
|
|
887
|
+
if (!stream) {
|
|
888
|
+
res.set_content(response_obj.dump(), "application/json");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
std::vector<std::string> chunks = token_level_chunks_for_stream(ctx_id, text);
|
|
893
|
+
res.set_header("Cache-Control", "no-cache");
|
|
894
|
+
res.set_header("Connection", "keep-alive");
|
|
895
|
+
res.set_chunked_content_provider(
|
|
896
|
+
"text/event-stream",
|
|
897
|
+
[response_id, response_created, response_obj, chunks = std::move(chunks)](size_t, httplib::DataSink & sink) {
|
|
898
|
+
json created_event = {{"type", "response.created"}, {"response", response_created}};
|
|
899
|
+
const std::string created_line = std::string("data: ") + created_event.dump() + "\n\n";
|
|
900
|
+
sink.write(created_line.c_str(), created_line.size());
|
|
901
|
+
|
|
902
|
+
for (const auto & ch : chunks) {
|
|
903
|
+
json delta_event = {{"type", "response.output_text.delta"}, {"response_id", response_id}, {"delta", ch}};
|
|
904
|
+
const std::string delta_line = std::string("data: ") + delta_event.dump() + "\n\n";
|
|
905
|
+
sink.write(delta_line.c_str(), delta_line.size());
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
json completed_event = {{"type", "response.completed"}, {"response", response_obj}};
|
|
909
|
+
const std::string completed_line = std::string("data: ") + completed_event.dump() + "\n\n";
|
|
910
|
+
sink.write(completed_line.c_str(), completed_line.size());
|
|
911
|
+
sink.write("data: [DONE]\n\n", 14);
|
|
912
|
+
sink.done();
|
|
913
|
+
return true;
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
svr.Post("/v1/responses", responses_handler);
|
|
918
|
+
svr.Post("/responses", responses_handler);
|
|
919
|
+
|
|
920
|
+
auto embeddings_handler = [](const httplib::Request & req, httplib::Response & res) {
|
|
921
|
+
set_cors(res);
|
|
922
|
+
std::string model;
|
|
923
|
+
std::vector<std::string> inputs;
|
|
924
|
+
if (!parse_embedding_request(req.body, model, inputs)) {
|
|
925
|
+
res.status = 400;
|
|
926
|
+
res.set_content(make_openai_error("invalid JSON or missing input").dump(), "application/json");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
int64_t ctx_id = -1;
|
|
930
|
+
if (!registry_require_ctx(model, res, ctx_id)) {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const int n_embd = embedding_size_from_context(ctx_id);
|
|
935
|
+
if (n_embd <= 0) {
|
|
936
|
+
res.status = 500;
|
|
937
|
+
res.set_content(make_openai_error("embedding size unavailable", "server_error").dump(), "application/json");
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
json data = json::array();
|
|
942
|
+
int index = 0;
|
|
943
|
+
for (const auto & input : inputs) {
|
|
944
|
+
float * vec = llama_embedding(ctx_id, input.c_str(), "{}");
|
|
945
|
+
if (!vec) {
|
|
946
|
+
res.status = 500;
|
|
947
|
+
res.set_content(make_openai_error("embedding failed", "server_error").dump(), "application/json");
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
json emb = json::array();
|
|
951
|
+
for (int i = 0; i < n_embd; ++i) {
|
|
952
|
+
emb.push_back(vec[i]);
|
|
953
|
+
}
|
|
954
|
+
data.push_back(json{{"object", "embedding"}, {"embedding", emb}, {"index", index++}});
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
json payload = {{"object", "list"},
|
|
958
|
+
{"data", data},
|
|
959
|
+
{"model", model.empty() ? "local" : model},
|
|
960
|
+
{"usage", json{{"prompt_tokens", 0}, {"total_tokens", 0}}}};
|
|
961
|
+
res.set_content(payload.dump(), "application/json");
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
svr.Post("/v1/embeddings", embeddings_handler);
|
|
965
|
+
svr.Post("/embeddings", embeddings_handler);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
} // namespace
|
|
969
|
+
|
|
970
|
+
int cap_llama_server_start(const char * model_path, const char * host, int port, const char * params_json) {
|
|
971
|
+
if (!host) {
|
|
972
|
+
return 0;
|
|
973
|
+
}
|
|
974
|
+
std::lock_guard<std::mutex> lock(g_mu);
|
|
975
|
+
if (g_started.load()) {
|
|
976
|
+
return 0;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
json boot_params = json::object();
|
|
980
|
+
if (params_json && params_json[0] != '\0') {
|
|
981
|
+
try {
|
|
982
|
+
boot_params = json::parse(params_json);
|
|
983
|
+
} catch (...) {
|
|
984
|
+
boot_params = json::object();
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
auto svr = std::make_unique<httplib::Server>();
|
|
989
|
+
register_routes(*svr);
|
|
990
|
+
|
|
991
|
+
g_srv = std::move(svr);
|
|
992
|
+
const std::string host_owned(host);
|
|
993
|
+
g_thr = std::thread([host_owned, port]() {
|
|
994
|
+
if (g_srv) {
|
|
995
|
+
g_srv->listen(host_owned.c_str(), port);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
if (g_srv) {
|
|
1000
|
+
g_srv->wait_until_ready();
|
|
1001
|
+
if (!g_srv->is_running()) {
|
|
1002
|
+
g_srv->stop();
|
|
1003
|
+
if (g_thr.joinable()) {
|
|
1004
|
+
g_thr.join();
|
|
1005
|
+
}
|
|
1006
|
+
g_srv.reset();
|
|
1007
|
+
return 0;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (model_path && model_path[0] != '\0') {
|
|
1012
|
+
std::string model_id = boot_params.value("model_id", path_basename(model_path));
|
|
1013
|
+
json err;
|
|
1014
|
+
if (!registry_load_model(model_id, model_path, boot_params, err)) {
|
|
1015
|
+
if (g_srv) {
|
|
1016
|
+
g_srv->stop();
|
|
1017
|
+
}
|
|
1018
|
+
if (g_thr.joinable()) {
|
|
1019
|
+
g_thr.join();
|
|
1020
|
+
}
|
|
1021
|
+
g_srv.reset();
|
|
1022
|
+
registry_release_all();
|
|
1023
|
+
return 0;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
g_started.store(true);
|
|
1028
|
+
return 1;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
void cap_llama_server_stop(void) {
|
|
1032
|
+
std::unique_ptr<httplib::Server> srv;
|
|
1033
|
+
std::thread thr;
|
|
1034
|
+
{
|
|
1035
|
+
std::lock_guard<std::mutex> lock(g_mu);
|
|
1036
|
+
if (!g_started.load()) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
srv = std::move(g_srv);
|
|
1040
|
+
thr = std::move(g_thr);
|
|
1041
|
+
g_started.store(false);
|
|
1042
|
+
}
|
|
1043
|
+
if (srv) {
|
|
1044
|
+
srv->stop();
|
|
1045
|
+
}
|
|
1046
|
+
if (thr.joinable()) {
|
|
1047
|
+
thr.join();
|
|
1048
|
+
}
|
|
1049
|
+
{
|
|
1050
|
+
std::lock_guard<std::mutex> lock(g_mu);
|
|
1051
|
+
g_srv.reset();
|
|
1052
|
+
}
|
|
1053
|
+
registry_release_all();
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
int cap_llama_server_is_running(void) {
|
|
1057
|
+
std::lock_guard<std::mutex> lock(g_mu);
|
|
1058
|
+
return (g_started.load() && g_srv && g_srv->is_running()) ? 1 : 0;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
int cap_llama_server_main(int argc, char ** argv) {
|
|
1062
|
+
const char * model = nullptr;
|
|
1063
|
+
std::string host = "127.0.0.1";
|
|
1064
|
+
int port = 8080;
|
|
1065
|
+
json pj;
|
|
1066
|
+
|
|
1067
|
+
for (int i = 1; i < argc; i++) {
|
|
1068
|
+
if (!argv[i]) {
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
std::string a = argv[i];
|
|
1072
|
+
if ((a == "-m" || a == "--model") && i + 1 < argc) {
|
|
1073
|
+
model = argv[++i];
|
|
1074
|
+
} else if ((a == "--model-id") && i + 1 < argc) {
|
|
1075
|
+
pj["model_id"] = argv[++i];
|
|
1076
|
+
} else if (a == "--host" && i + 1 < argc) {
|
|
1077
|
+
host = argv[++i];
|
|
1078
|
+
} else if (a == "--port" && i + 1 < argc) {
|
|
1079
|
+
port = std::atoi(argv[++i]);
|
|
1080
|
+
} else if ((a == "-c" || a == "--ctx-size" || a == "-n") && i + 1 < argc) {
|
|
1081
|
+
pj["n_ctx"] = std::atoi(argv[++i]);
|
|
1082
|
+
} else if ((a == "-ngl" || a == "--n-gpu-layers") && i + 1 < argc) {
|
|
1083
|
+
pj["n_gpu_layers"] = std::atoi(argv[++i]);
|
|
1084
|
+
} else if ((a == "-t" || a == "--threads") && i + 1 < argc) {
|
|
1085
|
+
pj["n_threads"] = std::atoi(argv[++i]);
|
|
1086
|
+
} else if (a == "--no-gpu") {
|
|
1087
|
+
pj["n_gpu_layers"] = 0;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
std::string pj_str;
|
|
1091
|
+
if (pj.is_object() && !pj.empty()) {
|
|
1092
|
+
pj_str = pj.dump();
|
|
1093
|
+
}
|
|
1094
|
+
return cap_llama_server_start(model, host.c_str(), port, pj_str.empty() ? nullptr : pj_str.c_str()) ? 0 : 1;
|
|
1095
|
+
}
|