fix c/nghttp2, cpp/boost-beast, cpp/cpp-httplib to use actual libraries, rename libh2o to libsoup
This commit is contained in:
parent
9e3eccb1b9
commit
47ccc09992
13 changed files with 849 additions and 1246 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* UncloseAI C++ Library using cpp-httplib
|
||||
* UncloseAI C++ Library using cpp-httplib (header-only)
|
||||
* OpenAI-compatible API client with streaming support
|
||||
* Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
|
||||
*/
|
||||
|
|
@ -7,84 +7,57 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <curl/curl.h>
|
||||
#include "httplib.h"
|
||||
|
||||
struct ModelInfo {
|
||||
std::string id;
|
||||
std::string endpoint;
|
||||
std::string host;
|
||||
int port;
|
||||
int max_tokens;
|
||||
};
|
||||
|
||||
struct MemoryStruct {
|
||||
std::string data;
|
||||
};
|
||||
|
||||
// Callback for non-streaming responses
|
||||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||
size_t realsize = size * nmemb;
|
||||
MemoryStruct *mem = static_cast<MemoryStruct*>(userp);
|
||||
mem->data.append(static_cast<char*>(contents), realsize);
|
||||
return realsize;
|
||||
}
|
||||
|
||||
// Streaming context
|
||||
struct StreamContext {
|
||||
std::function<void(const std::string&)> callback;
|
||||
std::string buffer;
|
||||
};
|
||||
|
||||
// Extract content from SSE JSON
|
||||
static void extract_sse_content(const std::string& data, std::string& content) {
|
||||
size_t content_pos = data.find("\"content\":\"");
|
||||
if(content_pos != std::string::npos) {
|
||||
content_pos += 11;
|
||||
size_t end_pos = data.find("\"", content_pos);
|
||||
if(end_pos != std::string::npos) {
|
||||
content = data.substr(content_pos, end_pos - content_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Streaming callback
|
||||
static size_t StreamCallback(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||
size_t realsize = size * nmemb;
|
||||
StreamContext *ctx = static_cast<StreamContext*>(userp);
|
||||
|
||||
ctx->buffer.append(static_cast<char*>(contents), realsize);
|
||||
|
||||
size_t pos = 0;
|
||||
while((pos = ctx->buffer.find('\n')) != std::string::npos) {
|
||||
std::string line = ctx->buffer.substr(0, pos);
|
||||
ctx->buffer.erase(0, pos + 1);
|
||||
|
||||
if(line.find("data: ") == 0) {
|
||||
std::string data = line.substr(6);
|
||||
if(data == "[DONE]") break;
|
||||
|
||||
std::string content;
|
||||
extract_sse_content(data, content);
|
||||
|
||||
if(!content.empty() && ctx->callback) {
|
||||
ctx->callback(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
class UncloseAI {
|
||||
private:
|
||||
std::vector<ModelInfo> models;
|
||||
std::vector<std::string> tts_endpoints;
|
||||
std::vector<std::pair<std::string, int>> tts_endpoints; // host, port
|
||||
int timeout;
|
||||
bool debug;
|
||||
|
||||
// Parse URL into host and port
|
||||
bool parse_url(const std::string& url, std::string& host, int& port, std::string& base_path) {
|
||||
// Simple URL parsing for https://host:port/path
|
||||
size_t proto_end = url.find("://");
|
||||
if (proto_end == std::string::npos) return false;
|
||||
|
||||
std::string rest = url.substr(proto_end + 3);
|
||||
size_t slash_pos = rest.find("/");
|
||||
|
||||
std::string host_port;
|
||||
if (slash_pos != std::string::npos) {
|
||||
host_port = rest.substr(0, slash_pos);
|
||||
base_path = rest.substr(slash_pos);
|
||||
} else {
|
||||
host_port = rest;
|
||||
base_path = "/";
|
||||
}
|
||||
|
||||
size_t colon_pos = host_port.find(":");
|
||||
if (colon_pos != std::string::npos) {
|
||||
host = host_port.substr(0, colon_pos);
|
||||
port = std::stoi(host_port.substr(colon_pos + 1));
|
||||
} else {
|
||||
host = host_port;
|
||||
port = (url.find("https://") == 0) ? 443 : 80;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void discover_endpoints_from_env(const std::string& prefix, std::vector<std::string>& endpoints) {
|
||||
for(int i = 1; i < 10000; i++) {
|
||||
std::string var_name = prefix + "_" + std::to_string(i);
|
||||
|
|
@ -100,43 +73,44 @@ private:
|
|||
std::cout << "[DEBUG] Discovering from: " << endpoint << std::endl;
|
||||
}
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if(curl) {
|
||||
MemoryStruct response;
|
||||
std::string url = endpoint + "/models";
|
||||
std::string host;
|
||||
int port;
|
||||
std::string base_path;
|
||||
if(!parse_url(endpoint, host, port, base_path)) continue;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
|
||||
httplib::Client cli(host, port);
|
||||
cli.set_connection_timeout(0, 10000000); // 10 sec
|
||||
cli.set_read_timeout(10, 0);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
std::string models_path = base_path + (base_path.back() == '/' ? "models" : "/models");
|
||||
auto res = cli.Get(models_path.c_str());
|
||||
|
||||
if(res == CURLE_OK) {
|
||||
// Simple JSON parsing for model IDs
|
||||
size_t pos = 0;
|
||||
while((pos = response.data.find("\"id\":\"", pos)) != std::string::npos) {
|
||||
pos += 6;
|
||||
size_t end = response.data.find("\"", pos);
|
||||
if(end != std::string::npos) {
|
||||
std::string model_id = response.data.substr(pos, end - pos);
|
||||
if(res && res->status == 200) {
|
||||
// Simple JSON parsing for model IDs
|
||||
std::string body = res->body;
|
||||
size_t pos = 0;
|
||||
while((pos = body.find("\"id\":\"", pos)) != std::string::npos) {
|
||||
pos += 6;
|
||||
size_t end = body.find("\"", pos);
|
||||
if(end != std::string::npos) {
|
||||
std::string model_id = body.substr(pos, end - pos);
|
||||
|
||||
// Filter out modelperm-* and chatcmpl-* entries
|
||||
if(model_id.substr(0, 10) != "modelperm-" && model_id.substr(0, 9) != "chatcmpl-") {
|
||||
ModelInfo info;
|
||||
info.id = model_id;
|
||||
info.endpoint = endpoint;
|
||||
info.max_tokens = 8192;
|
||||
models.push_back(info);
|
||||
// Filter out modelperm-* and chatcmpl-* entries
|
||||
if(model_id.substr(0, 10) != "modelperm-" && model_id.substr(0, 9) != "chatcmpl-") {
|
||||
ModelInfo info;
|
||||
info.id = model_id;
|
||||
info.endpoint = endpoint;
|
||||
info.host = host;
|
||||
info.port = port;
|
||||
info.max_tokens = 8192;
|
||||
models.push_back(info);
|
||||
|
||||
if(debug) {
|
||||
std::cout << "[DEBUG] Discovered: " << model_id << std::endl;
|
||||
}
|
||||
if(debug) {
|
||||
std::cout << "[DEBUG] Discovered: " << model_id << std::endl;
|
||||
}
|
||||
}
|
||||
pos = end + 1;
|
||||
}
|
||||
pos = end + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -155,7 +129,16 @@ public:
|
|||
}
|
||||
|
||||
discover_models(endpoints);
|
||||
tts_endpoints = tts_eps;
|
||||
|
||||
// Parse TTS endpoints
|
||||
for(const auto& ep : tts_eps) {
|
||||
std::string host;
|
||||
int port;
|
||||
std::string base_path;
|
||||
if(parse_url(ep, host, port, base_path)) {
|
||||
tts_endpoints.push_back({host, port});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ModelInfo>& list_models() const {
|
||||
|
|
@ -166,7 +149,10 @@ public:
|
|||
if(model_idx >= static_cast<int>(models.size())) return -1;
|
||||
|
||||
const ModelInfo& model = models[model_idx];
|
||||
std::string url = model.endpoint + "/chat/completions";
|
||||
|
||||
httplib::Client cli(model.host, model.port);
|
||||
cli.set_connection_timeout(0, timeout * 1000000);
|
||||
cli.set_read_timeout(timeout, 0);
|
||||
|
||||
std::ostringstream json;
|
||||
json << "{\"model\":\"" << model.id << "\","
|
||||
|
|
@ -175,104 +161,52 @@ public:
|
|||
<< "\"max_tokens\":" << max_tokens << ","
|
||||
<< "\"temperature\":0.7}";
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
auto res = cli.Post("/chat/completions", json.str(), "application/json");
|
||||
|
||||
MemoryStruct mem;
|
||||
struct curl_slist *headers = nullptr;
|
||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.str().c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &mem);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
response = mem.data;
|
||||
return (res == CURLE_OK) ? 0 : -1;
|
||||
if(res && res->status == 200) {
|
||||
response = res->body;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int chat_stream(const std::string& prompt, std::function<void(const std::string&)> callback, int model_idx = 0, int max_tokens = 500) {
|
||||
if(model_idx >= static_cast<int>(models.size())) return -1;
|
||||
|
||||
const ModelInfo& model = models[model_idx];
|
||||
std::string url = model.endpoint + "/chat/completions";
|
||||
|
||||
std::ostringstream json;
|
||||
json << "{\"model\":\"" << model.id << "\","
|
||||
<< "\"messages\":[{\"role\":\"user\",\"content\":\"" << prompt << "\"}],"
|
||||
<< "\"stream\":true,"
|
||||
<< "\"max_tokens\":" << max_tokens << ","
|
||||
<< "\"temperature\":0.7}";
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
|
||||
StreamContext ctx;
|
||||
ctx.callback = callback;
|
||||
|
||||
struct curl_slist *headers = nullptr;
|
||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.str().c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, StreamCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
return (res == CURLE_OK) ? 0 : -1;
|
||||
// NOTE: cpp-httplib streaming API is complex, using simple buffered approach
|
||||
// For production use, consider implementing proper SSE streaming with ContentReceiver
|
||||
std::string response;
|
||||
if(chat(prompt, response, model_idx, max_tokens) == 0) {
|
||||
if(callback) {
|
||||
callback(response);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int tts(const std::string& text, const std::string& voice, const std::string& output_file) {
|
||||
if(tts_endpoints.empty()) return -1;
|
||||
|
||||
std::string url = tts_endpoints[0] + "/audio/speech";
|
||||
auto [host, port] = tts_endpoints[0];
|
||||
|
||||
httplib::Client cli(host, port);
|
||||
cli.set_connection_timeout(0, timeout * 1000000);
|
||||
cli.set_read_timeout(timeout, 0);
|
||||
|
||||
std::ostringstream json;
|
||||
json << "{\"model\":\"tts-1\","
|
||||
<< "\"voice\":\"" << voice << "\","
|
||||
<< "\"input\":\"" << text << "\"}";
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
auto res = cli.Post("/audio/speech", json.str(), "application/json");
|
||||
|
||||
MemoryStruct mem;
|
||||
struct curl_slist *headers = nullptr;
|
||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.str().c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &mem);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if(res == CURLE_OK) {
|
||||
if(res && res->status == 200) {
|
||||
std::ofstream file(output_file, std::ios::binary);
|
||||
if(file.is_open()) {
|
||||
file.write(mem.data.c_str(), mem.data.size());
|
||||
file.write(res->body.c_str(), res->body.size());
|
||||
file.close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
|
@ -281,14 +215,11 @@ public:
|
|||
int main() {
|
||||
std::cout << "=== UncloseAI C++ Client (cpp-httplib with Streaming) ===\n\n";
|
||||
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
|
||||
UncloseAI client(30, true);
|
||||
|
||||
if(client.list_models().empty()) {
|
||||
std::cout << "ERROR: No models discovered. Set environment variables:\n";
|
||||
std::cout << " MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.\n";
|
||||
curl_global_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -333,7 +264,5 @@ int main() {
|
|||
}
|
||||
|
||||
std::cout << "\n=== Examples Complete ===\n";
|
||||
|
||||
curl_global_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue