uncloseai.com/public/languages/c/libsoup/uncloseai.c

349 lines
10 KiB
C

// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
// Copyright 2025 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com
/*
* UncloseAI C Client using libsoup (GNOME HTTP Library)
* OpenAI-compatible API client with streaming support
* Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libsoup/soup.h>
#define MAX_ENDPOINTS 100
#define MAX_MODELS 100
#define MAX_URL_LEN 512
#define MAX_MODEL_LEN 256
// Structure to hold discovered model info
struct ModelInfo {
char id[MAX_MODEL_LEN];
char endpoint[MAX_URL_LEN];
int max_tokens;
};
struct ModelInfo models[MAX_MODELS];
int model_count = 0;
char tts_endpoints[MAX_ENDPOINTS][MAX_URL_LEN];
int tts_count = 0;
// Simple JSON string extractor (finds "id":"value" patterns)
void extract_model_ids(const char *json, const char *endpoint) {
const char *search = json;
const char *id_marker = "\"id\":\"";
while((search = strstr(search, id_marker)) != NULL && model_count < MAX_MODELS) {
search += strlen(id_marker);
const char *end = strchr(search, '"');
if(end) {
size_t len = end - search;
if(len < MAX_MODEL_LEN) {
strncpy(models[model_count].id, search, len);
models[model_count].id[len] = '\0';
// Filter out modelperm-* and chatcmpl-* entries
if(strncmp(models[model_count].id, "modelperm-", 10) == 0 ||
strncmp(models[model_count].id, "chatcmpl-", 9) == 0) {
search = end + 1;
continue;
}
strncpy(models[model_count].endpoint, endpoint, MAX_URL_LEN-1);
models[model_count].max_tokens = 8192; // Default
printf(" - Discovered: %s\n", models[model_count].id);
model_count++;
}
}
search = end + 1;
}
}
// Discover models from an endpoint
void discover_models_from_endpoint(SoupSession *session, const char *endpoint) {
char url[MAX_URL_LEN];
snprintf(url, sizeof(url), "%s/models", endpoint);
printf("Discovering models from: %s\n", endpoint);
SoupMessage *msg = soup_message_new("GET", url);
if(!msg) return;
GError *error = NULL;
GInputStream *stream = soup_session_send(session, msg, NULL, &error);
if(error) {
g_error_free(error);
g_object_unref(msg);
return;
}
// Read response body
GString *body = g_string_new(NULL);
char buffer[4096];
gssize read;
while((read = g_input_stream_read(stream, buffer, sizeof(buffer)-1, NULL, NULL)) > 0) {
buffer[read] = '\0';
g_string_append(body, buffer);
}
extract_model_ids(body->str, endpoint);
g_string_free(body, TRUE);
g_object_unref(stream);
g_object_unref(msg);
}
// Discover all models from environment variables
void discover_all_models(SoupSession *session) {
printf("=== Model Discovery ===\n");
// Discover chat/code models
for(int i = 1; i < 10000; i++) {
char var_name[32];
snprintf(var_name, sizeof(var_name), "MODEL_ENDPOINT_%d", i);
char *endpoint = getenv(var_name);
if(!endpoint) break;
discover_models_from_endpoint(session, endpoint);
}
// Discover TTS endpoints
for(int i = 1; i < 10000; i++) {
char var_name[32];
snprintf(var_name, sizeof(var_name), "TTS_ENDPOINT_%d", i);
char *endpoint = getenv(var_name);
if(!endpoint) break;
printf("Discovering TTS from: %s\n", endpoint);
strncpy(tts_endpoints[tts_count++], endpoint, MAX_URL_LEN-1);
}
printf("\nTotal models discovered: %d\n", model_count);
printf("Total TTS endpoints: %d\n\n", tts_count);
}
// Make a non-streaming chat request
int chat_request(SoupSession *session, int model_idx, const char *prompt) {
char url[MAX_URL_LEN];
char json[2048];
snprintf(url, sizeof(url), "%s/chat/completions", models[model_idx].endpoint);
snprintf(json, sizeof(json),
"{\"model\":\"%s\","
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
"\"stream\":false,"
"\"temperature\":0.7,"
"\"max_tokens\":100}",
models[model_idx].id, prompt);
SoupMessage *msg = soup_message_new("POST", url);
if(!msg) return -1;
soup_message_set_request(msg, "application/json", SOUP_MEMORY_COPY, json, strlen(json));
GError *error = NULL;
GInputStream *stream = soup_session_send(session, msg, NULL, &error);
if(error) {
g_error_free(error);
g_object_unref(msg);
return -1;
}
// Read response
char buffer[4096];
gssize read = g_input_stream_read(stream, buffer, sizeof(buffer)-1, NULL, NULL);
if(read > 0) {
printf("Response received (%ld bytes)\n", read);
printf("(Full response requires JSON parsing library)\n");
}
g_object_unref(stream);
g_object_unref(msg);
return 0;
}
// Streaming context for SSE parsing
typedef struct {
char buffer[4096];
size_t buffer_pos;
} StreamContext;
// Extract content from SSE data line
void extract_sse_content(const char *json_data, char *content, size_t content_size) {
const char *content_marker = "\"content\":\"";
const char *found = strstr(json_data, content_marker);
if(found) {
found += strlen(content_marker);
const char *end = strchr(found, '"');
if(end) {
size_t len = end - found;
if(len < content_size) {
strncpy(content, found, len);
content[len] = '\0';
}
}
}
}
// Process streaming chunk
void process_stream_chunk(const char *data, size_t len, StreamContext *ctx) {
for(size_t i = 0; i < len; i++) {
if(data[i] == '\n') {
ctx->buffer[ctx->buffer_pos] = '\0';
// Process SSE line
if(strncmp(ctx->buffer, "data: ", 6) == 0) {
const char *json_data = ctx->buffer + 6;
if(strcmp(json_data, "[DONE]") == 0) {
return; // Stop streaming
}
char content[1024] = {0};
extract_sse_content(json_data, content, sizeof(content));
if(strlen(content) > 0) {
printf("%s", content);
fflush(stdout);
}
}
ctx->buffer_pos = 0;
} else {
if(ctx->buffer_pos < sizeof(ctx->buffer) - 1) {
ctx->buffer[ctx->buffer_pos++] = data[i];
}
}
}
}
// Streaming chat request
int chat_stream_request(SoupSession *session, int model_idx, const char *prompt) {
char url[MAX_URL_LEN];
char json[2048];
snprintf(url, sizeof(url), "%s/chat/completions", models[model_idx].endpoint);
snprintf(json, sizeof(json),
"{\"model\":\"%s\","
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
"\"stream\":true,"
"\"temperature\":0.7,"
"\"max_tokens\":500}",
models[model_idx].id, prompt);
SoupMessage *msg = soup_message_new("POST", url);
if(!msg) return -1;
soup_message_set_request(msg, "application/json", SOUP_MEMORY_COPY, json, strlen(json));
GError *error = NULL;
GInputStream *stream = soup_session_send(session, msg, NULL, &error);
if(error) {
g_error_free(error);
g_object_unref(msg);
return -1;
}
// Read and process streaming chunks
StreamContext ctx = {{0}, 0};
char buffer[1024];
gssize read;
while((read = g_input_stream_read(stream, buffer, sizeof(buffer), NULL, NULL)) > 0) {
process_stream_chunk(buffer, read, &ctx);
}
g_object_unref(stream);
g_object_unref(msg);
return 0;
}
int main(void) {
printf("=== UncloseAI C Client (libsoup/GNOME) ===\n\n");
// Initialize libsoup session
SoupSession *session = soup_session_new();
discover_all_models(session);
if(model_count == 0) {
printf("ERROR: No models discovered\n");
g_object_unref(session);
return 1;
}
// Non-streaming chat example
printf("=== Non-Streaming Chat ===\n");
printf("Model: %s\n", models[0].id);
if(chat_request(session, 0, "Explain quantum computing in one sentence") != 0) {
printf("Request failed\n");
}
printf("\n");
// Streaming chat example
int model_idx = (model_count >= 2) ? 1 : 0;
printf("=== Streaming Chat ===\n");
printf("Model: %s\n", models[model_idx].id);
printf("Response: ");
if(chat_stream_request(session, model_idx, "Write a hello world program in C") != 0) {
printf("\nStreaming request failed\n");
}
printf("\n\n");
// TTS example
if(tts_count > 0) {
printf("=== TTS Speech Generation ===\n");
printf("Model: tts-1\n");
char tts_url[MAX_URL_LEN];
snprintf(tts_url, sizeof(tts_url), "%s/audio/speech", tts_endpoints[0]);
const char *tts_json = "{"
"\"model\":\"tts-1\","
"\"voice\":\"alloy\","
"\"input\":\"Hello from UncloseAI libsoup client!\""
"}";
SoupMessage *tts_msg = soup_message_new("POST", tts_url);
if(tts_msg) {
soup_message_set_request(tts_msg, "application/json", SOUP_MEMORY_COPY,
tts_json, strlen(tts_json));
GError *error = NULL;
GInputStream *stream = soup_session_send(session, tts_msg, NULL, &error);
if(!error) {
FILE *fp = fopen("/tmp/speech.mp3", "wb");
if(fp) {
char buffer[4096];
gssize read;
while((read = g_input_stream_read(stream, buffer, sizeof(buffer), NULL, NULL)) > 0) {
fwrite(buffer, 1, read, fp);
}
fclose(fp);
printf("Audio saved to /tmp/speech.mp3\n");
} else {
printf("TTS failed: could not write file\n");
}
g_object_unref(stream);
} else {
printf("TTS failed: %s\n", error->message);
g_error_free(error);
}
g_object_unref(tts_msg);
}
}
printf("\n=== Examples Complete ===\n");
g_object_unref(session);
return 0;
}