fix Docker builds: go-http missing files, go-openai go.sum, csharp-openai SDK API, D language JSONValue syntax

This commit is contained in:
Russell Ballestrini 2025-10-15 16:53:15 -04:00
parent 381bd5f739
commit 6318db3d1f
65 changed files with 2934 additions and 2306 deletions

103
Makefile
View file

@ -1,7 +1,29 @@
# Makefile for ai.unturf.com project
# All commands used for testing, validation, and development
.PHONY: help format check test validate-exports validate-all clean install dev build deploy languages-list languages-build-% languages-test-% languages-clean
# All phony targets
.PHONY: help format check test validate-exports validate-all clean install dev build deploy
.PHONY: languages-list languages-build-% languages-test-% languages-build-all languages-test-all languages-clean
.PHONY: languages-build-c-curl languages-build-c-libsoup languages-build-c-nghttp2
.PHONY: languages-build-cpp-boost-beast languages-build-cpp-cpp-httplib languages-build-cpp-libcurl
.PHONY: languages-build-csharp-http languages-build-csharp-openai
.PHONY: languages-build-elixir-httpoison languages-build-elixir-openai
.PHONY: languages-build-go-http languages-build-go-openai
.PHONY: languages-build-groovy-openai-client
.PHONY: languages-build-java-http languages-build-java-openai
.PHONY: languages-build-javascript-bun languages-build-javascript-nodejs languages-build-javascript-openai-nodejs languages-build-javascript-typescript languages-build-javascript-vanilla
.PHONY: languages-build-python-aiohttp languages-build-python-httpx-async languages-build-python-openai languages-build-python-requests
.PHONY: languages-build-ruby-http languages-build-ruby-openai
.PHONY: languages-test-c-curl languages-test-c-libsoup languages-test-c-nghttp2
.PHONY: languages-test-cpp-boost-beast languages-test-cpp-cpp-httplib languages-test-cpp-libcurl
.PHONY: languages-test-csharp-http languages-test-csharp-openai
.PHONY: languages-test-elixir-httpoison languages-test-elixir-openai
.PHONY: languages-test-go-http languages-test-go-openai
.PHONY: languages-test-groovy-openai-client
.PHONY: languages-test-java-http languages-test-java-openai
.PHONY: languages-test-javascript-bun languages-test-javascript-nodejs languages-test-javascript-openai-nodejs languages-test-javascript-typescript languages-test-javascript-vanilla
.PHONY: languages-test-python-aiohttp languages-test-python-httpx-async languages-test-python-openai languages-test-python-requests
.PHONY: languages-test-ruby-http languages-test-ruby-openai
# Environment variables for testing language implementations
MODEL_ENDPOINT_1 ?= https://hermes.ai.unturf.com/v1
@ -34,8 +56,10 @@ help:
@echo ""
@echo "Language Examples:"
@echo " make languages-list - List all language directories"
@echo " make languages-build-<lang> - Build Docker image for language"
@echo " make languages-build-<lang> - Build Docker image for language (use - for /: python-openai)"
@echo " make languages-build-all - Build all language Docker images"
@echo " make languages-test-<lang> - Test language implementation with endpoints"
@echo " make languages-test-all - Test all language implementations"
@echo " make languages-clean - Stop and remove language test containers"
# Code formatting and linting
@ -177,14 +201,20 @@ ci: clean format-check validate-all validate-structure validate-translations che
# Language Examples Commands
languages-list:
@echo "Available language implementations:"
@ls -d languages/*/ 2>/dev/null | sed 's|languages/||g' | sed 's|/||g' || echo "No language directories found"
@find languages -name "Dockerfile" -type f | sed 's|/Dockerfile||g' | sed 's|languages/||g' | sort || echo "No language implementations found"
languages-build-%:
@echo "Building Docker image for $*..."
@if [ -d "languages/$*" ]; then \
@target_path=$$(echo "$*" | tr '-' '/'); \
if [ -f "languages/$$target_path/Dockerfile" ]; then \
echo "Found Dockerfile at languages/$$target_path/"; \
docker build -t ai-unturf-$* languages/$$target_path/; \
elif [ -f "languages/$*/Dockerfile" ]; then \
echo "Found Dockerfile at languages/$*/"; \
docker build -t ai-unturf-$* languages/$*/; \
else \
echo "❌ Language directory languages/$* not found"; \
echo "❌ No Dockerfile found for $*"; \
echo "Tried: languages/$$target_path/ and languages/$*/"; \
exit 1; \
fi
@ -193,7 +223,8 @@ languages-test-%:
@echo "Environment: MODEL_ENDPOINT_1=$(MODEL_ENDPOINT_1)"
@echo "Environment: MODEL_ENDPOINT_2=$(MODEL_ENDPOINT_2)"
@echo "Environment: TTS_ENDPOINT_1=$(TTS_ENDPOINT_1)"
@if [ -d "languages/$*" ]; then \
@target_path=$$(echo "$*" | tr '-' '/'); \
if [ -f "languages/$$target_path/Dockerfile" ] || [ -f "languages/$*/Dockerfile" ]; then \
echo "Starting container..."; \
docker run -d --name test-$* $(DOCKER_ENV_VARS) ai-unturf-$* && \
sleep 3 && \
@ -203,10 +234,68 @@ languages-test-%:
echo "✅ Container started - check logs above for model discovery" && \
echo "To stop: docker stop test-$* && docker rm test-$*"; \
else \
echo "❌ Language directory languages/$* not found"; \
echo "❌ No Docker image found for $*"; \
echo "Build it first: make languages-build-$*"; \
exit 1; \
fi
languages-build-all:
@echo "Building all language implementations..."
@failed=0; \
total=0; \
for dockerfile in $$(find languages -name "Dockerfile" -type f | sort); do \
dir=$$(dirname $$dockerfile); \
name=$$(echo $$dir | sed 's|languages/||g' | tr '/' '-'); \
total=$$((total + 1)); \
echo ""; \
echo "========================================"; \
echo "[$$total] Building $$name ($$dir)"; \
echo "========================================"; \
if docker build -t ai-unturf-$$name $$dir/ 2>&1 | tail -20; then \
echo "$$name built successfully"; \
else \
echo "$$name build failed"; \
failed=$$((failed + 1)); \
fi; \
done; \
echo ""; \
echo "========================================"; \
echo "Build Summary: $$((total - failed))/$$total successful"; \
if [ $$failed -gt 0 ]; then \
echo "$$failed build(s) failed"; \
exit 1; \
else \
echo "✅ All builds successful!"; \
fi
languages-test-all:
@echo "Testing all language implementations..."
@failed=0; \
total=0; \
for dockerfile in $$(find languages -name "Dockerfile" -type f | sort); do \
dir=$$(dirname $$dockerfile); \
name=$$(echo $$dir | sed 's|languages/||g' | tr '/' '-'); \
total=$$((total + 1)); \
echo ""; \
echo "========================================"; \
echo "[$$total] Testing $$name"; \
echo "========================================"; \
if docker run --rm $(DOCKER_ENV_VARS) ai-unturf-$$name 2>&1 | head -15; then \
echo "$$name ran successfully"; \
else \
echo "$$name test failed"; \
failed=$$((failed + 1)); \
fi; \
done; \
echo ""; \
echo "========================================"; \
echo "Test Summary: $$((total - failed))/$$total successful"; \
if [ $$failed -gt 0 ]; then \
echo "$$failed test(s) failed"; \
else \
echo "✅ All tests successful!"; \
fi
languages-clean:
@echo "Stopping and removing language test containers..."
@docker ps -a | grep test- | awk '{print $$1}' | xargs -r docker stop 2>/dev/null || true

1355
build-all.log Normal file

File diff suppressed because it is too large Load diff

View file

@ -125,15 +125,16 @@
<li><a href="/nodejs-examples.html"><strong>Node.js Examples</strong></a> - Complete examples using the OpenAI Node.js client library</li>
</ul>
<h3>37 Programming Languages:</h3>
<p>We provide SDK implementations with streaming support for 37 programming languages! Browse all language examples:</p>
<p>🔗 <a href="https://git.unturf.com/engineering/unturf/uncloseai.com/-/tree/master/languages" target="_blank"><strong>View All 37 Language Examples on Git →</strong></a></p>
<h3>42 Programming Languages:</h3>
<p>We provide SDK implementations with streaming support for 42 programming languages! Browse all language examples:</p>
<p>🔗 <a href="https://git.unturf.com/engineering/unturf/uncloseai.com/-/tree/master/languages" target="_blank"><strong>View All 42 Language Examples on Git →</strong></a></p>
<p>Includes: AWK, Bash, C, C++, C#, Clojure, COBOL, Crystal, Dart, Deno, Elixir, Erlang, F#, Fortran, Go, Haskell, Java, JavaScript, Julia, Kotlin, Lua, Nim, OCaml, Odin, Perl, PHP, PowerShell, Prolog, Python, R, Ruby, Rust, Scala, Tcl, V, VB.NET, and Zig!</p>
<p>Includes: AWK, Bash, C, C++, C#, Clojure, COBOL, Common Lisp, Crystal, D, Dart, Deno, Elixir, Erlang, F#, Fortran, Go, Groovy, Haskell, Java, JavaScript, Julia, Kotlin, Lua, Mojo*, Nim, OCaml, Odin, Perl, PHP, PowerShell, Prolog, Python, R, Ruby, Rust, Scala, Scheme, Tcl, V, VB.NET, and Zig!</p>
<p><small>* Mojo implementation is non-streaming only (Lightbug HTTP library doesn't yet support SSE streaming as of 2025-10-15)</small></p>
<h3>uncloseai. book - Machine Learning Reference Guide:</h3>
<p>📚 <a href="https://shop.unturf.com/p/8486f492-a93e-11f0-b477-02dfe05770ee/uncloseai-machine-learning-reference-guide-to-inference-clients" target="_blank"><strong>Purchase the uncloseai. book on unturf.com Shop →</strong></a></p>
<p>Comprehensive reference guide covering inference clients, streaming implementations, and best practices for all 37 programming languages.</p>
<p>Comprehensive reference guide covering inference clients, streaming implementations, and best practices for all 42 programming languages.</p>
<p>All examples use the OpenAI-compatible API interface, making it easy to integrate with existing code. Simply change the <code>base_url</code> to point to our endpoints:</p>

View file

@ -0,0 +1,25 @@
# Pin to specific SBCL version (checked 2025-10-15: clfoundation/sbcl:latest is stable)
FROM clfoundation/sbcl:latest
# Install Quicklisp (Common Lisp package manager)
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp && \
sbcl --load quicklisp.lisp --eval '(quicklisp-quickstart:install)' --quit && \
echo '(load "~/quicklisp/setup.lisp")' >> ~/.sbclrc
# Install Dexador HTTP client and JSON parser
RUN sbcl --eval '(ql:quickload :dexador)' \
--eval '(ql:quickload :jonathan)' \
--quit
# Set working directory
WORKDIR /app
# Copy application files
COPY hermes-nonstreaming.lisp .
COPY hermes-streaming.lisp .
COPY qwen-nonstreaming.lisp .
COPY qwen-streaming.lisp .
COPY tts.lisp .
# Default command shows available examples
CMD ["sbcl", "--eval", "(format t \"Available examples:~% sbcl --script hermes-nonstreaming.lisp~% sbcl --script hermes-streaming.lisp~% sbcl --script qwen-nonstreaming.lisp~% sbcl --script qwen-streaming.lisp~% sbcl --script tts.lisp~%\")", "--quit"]

View file

@ -0,0 +1,37 @@
#!/usr/bin/env sbcl --script
;;; Hermes AI Non-Streaming Example
;;; Uses Dexador HTTP client and Jonathan JSON library
(load "~/quicklisp/setup.lisp")
(ql:quickload '(:dexador :jonathan) :silent t)
(defparameter *base-url* "https://hermes.ai.unturf.com/v1/chat/completions")
(defparameter *model* "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic")
(defun make-chat-request (prompt)
"Make a non-streaming chat completion request"
(let* ((payload (jonathan:to-json
(list :|model| *model*
:|messages| (vector (list :|role| "user"
:|content| prompt))
:|temperature| 0.5
:|max_tokens| 150
:|stream| :false)))
(response (dex:post *base-url*
:headers '(("Content-Type" . "application/json"))
:content payload)))
(let* ((parsed (jonathan:parse response))
(choice (aref (getf parsed :|choices|) 0))
(message (getf choice :|message|))
(content (getf message :|content|)))
content)))
;; Main execution
(handler-case
(progn
(format t "Requesting from Hermes AI...~%~%")
(let ((result (make-chat-request "Give a Python Fizzbuzz solution in one line of code?")))
(format t "Response: ~A~%" result)))
(error (e)
(format t "Error: ~A~%" e)))

View file

@ -0,0 +1,55 @@
#!/usr/bin/env sbcl --script
;;; Hermes AI Streaming Example
;;; Uses Dexador with :want-stream for SSE streaming
(load "~/quicklisp/setup.lisp")
(ql:quickload '(:dexador :jonathan) :silent t)
(defparameter *base-url* "https://hermes.ai.unturf.com/v1/chat/completions")
(defparameter *model* "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic")
(defun process-sse-line (line)
"Process a single Server-Sent Event line"
(when (and line (> (length line) 6) (string= (subseq line 0 6) "data: "))
(let ((data (subseq line 6)))
(unless (string= data "[DONE]")
(handler-case
(let* ((parsed (jonathan:parse data))
(choices (getf parsed :|choices|))
(delta (when (> (length choices) 0)
(getf (aref choices 0) :|delta|)))
(content (when delta (getf delta :|content|))))
(when content
(format t "~A" content)
(force-output)))
(error (e) nil))))))
(defun make-streaming-request (prompt)
"Make a streaming chat completion request"
(let ((payload (jonathan:to-json
(list :|model| *model*
:|messages| (vector (list :|role| "user"
:|content| prompt))
:|temperature| 0.5
:|max_tokens| 150
:|stream| t))))
(dex:request *base-url*
:method :post
:headers '(("Content-Type" . "application/json"))
:content payload
:want-stream t
:stream-callback
(lambda (stream)
(loop for line = (read-line stream nil nil)
while line
do (process-sse-line line))))))
;; Main execution
(handler-case
(progn
(format t "Streaming from Hermes AI...~%~%")
(make-streaming-request "Give a Python Fizzbuzz solution in one line of code?")
(format t "~%~%Done!~%"))
(error (e)
(format t "~%Error: ~A~%" e)))

View file

@ -0,0 +1,37 @@
#!/usr/bin/env sbcl --script
;;; Qwen 3 Coder Non-Streaming Example
;;; Uses Dexador HTTP client and Jonathan JSON library
(load "~/quicklisp/setup.lisp")
(ql:quickload '(:dexador :jonathan) :silent t)
(defparameter *base-url* "https://qwen.ai.unturf.com/v1/chat/completions")
(defparameter *model* "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M")
(defun make-chat-request (prompt)
"Make a non-streaming chat completion request"
(let* ((payload (jonathan:to-json
(list :|model| *model*
:|messages| (vector (list :|role| "user"
:|content| prompt))
:|temperature| 0.5
:|max_tokens| 150
:|stream| :false)))
(response (dex:post *base-url*
:headers '(("Content-Type" . "application/json"))
:content payload)))
(let* ((parsed (jonathan:parse response))
(choice (aref (getf parsed :|choices|) 0))
(message (getf choice :|message|))
(content (getf message :|content|)))
content)))
;; Main execution
(handler-case
(progn
(format t "Requesting from Qwen 3 Coder...~%~%")
(let ((result (make-chat-request "Give a Python Fizzbuzz solution in one line of code?")))
(format t "Response: ~A~%" result)))
(error (e)
(format t "Error: ~A~%" e)))

View file

@ -0,0 +1,55 @@
#!/usr/bin/env sbcl --script
;;; Qwen 3 Coder Streaming Example
;;; Uses Dexador with :want-stream for SSE streaming
(load "~/quicklisp/setup.lisp")
(ql:quickload '(:dexador :jonathan) :silent t)
(defparameter *base-url* "https://qwen.ai.unturf.com/v1/chat/completions")
(defparameter *model* "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M")
(defun process-sse-line (line)
"Process a single Server-Sent Event line"
(when (and line (> (length line) 6) (string= (subseq line 0 6) "data: "))
(let ((data (subseq line 6)))
(unless (string= data "[DONE]")
(handler-case
(let* ((parsed (jonathan:parse data))
(choices (getf parsed :|choices|))
(delta (when (> (length choices) 0)
(getf (aref choices 0) :|delta|)))
(content (when delta (getf delta :|content|))))
(when content
(format t "~A" content)
(force-output)))
(error (e) nil))))))
(defun make-streaming-request (prompt)
"Make a streaming chat completion request"
(let ((payload (jonathan:to-json
(list :|model| *model*
:|messages| (vector (list :|role| "user"
:|content| prompt))
:|temperature| 0.5
:|max_tokens| 150
:|stream| t))))
(dex:request *base-url*
:method :post
:headers '(("Content-Type" . "application/json"))
:content payload
:want-stream t
:stream-callback
(lambda (stream)
(loop for line = (read-line stream nil nil)
while line
do (process-sse-line line))))))
;; Main execution
(handler-case
(progn
(format t "Streaming from Qwen 3 Coder...~%~%")
(make-streaming-request "Give a Python Fizzbuzz solution in one line of code?")
(format t "~%~%Done!~%"))
(error (e)
(format t "~%Error: ~A~%" e)))

View file

@ -1,18 +0,0 @@
# .NET 9.0 C# (checked 2025-10-13: mcr.microsoft.com/dotnet/sdk:9.0 is latest stable)
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS builder
WORKDIR /app
COPY csharp.csproj .
COPY Uncloseai.cs .
# Build the application
RUN dotnet build -c Release -o out
FROM mcr.microsoft.com/dotnet/runtime:9.0-alpine
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=builder /app/out .
CMD ["dotnet", "csharp.dll"]

View file

@ -1,306 +0,0 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
using System.Collections.Generic;
class ModelInfo
{
public string Id { get; set; } = "";
public string Endpoint { get; set; } = "";
public int MaxTokens { get; set; } = 8192;
}
class Uncloseai
{
static readonly HttpClient client = new HttpClient();
static readonly List<ModelInfo> models = new List<ModelInfo>();
static readonly List<string> ttsEndpoints = new List<string>();
static async Task DiscoverModelsFromEndpoint(string endpoint)
{
Console.WriteLine($"Discovering models from: {endpoint}");
try
{
var response = await client.GetAsync($"{endpoint}/models");
var body = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var root = doc.RootElement;
if (root.TryGetProperty("data", out var data))
{
foreach (var model in data.EnumerateArray())
{
var modelId = model.GetProperty("id").GetString() ?? "";
var maxTokens = 8192;
if (model.TryGetProperty("max_model_len", out var maxModelLen))
{
maxTokens = maxModelLen.GetInt32();
}
models.Add(new ModelInfo
{
Id = modelId,
Endpoint = endpoint,
MaxTokens = maxTokens
});
Console.WriteLine($" - Discovered: {modelId}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($" Error: {ex.Message}");
}
}
static async Task DiscoverAllModels()
{
Console.WriteLine("=== Model Discovery ===");
// Discover chat/code models
for (int i = 1; i <= 9999; i++)
{
var endpoint = Environment.GetEnvironmentVariable($"MODEL_ENDPOINT_{i}");
if (string.IsNullOrEmpty(endpoint)) break;
await DiscoverModelsFromEndpoint(endpoint);
}
// Discover TTS endpoints
for (int i = 1; i <= 9999; i++)
{
var endpoint = Environment.GetEnvironmentVariable($"TTS_ENDPOINT_{i}");
if (string.IsNullOrEmpty(endpoint)) break;
Console.WriteLine($"Discovering TTS from: {endpoint}");
ttsEndpoints.Add(endpoint);
}
Console.WriteLine($"\n{models.Count} model(s) discovered");
Console.WriteLine($"{ttsEndpoints.Count} TTS endpoint(s) discovered\n");
}
static async Task<string> MakeChatRequest(int modelIdx, string systemMsg, string userMsg, int maxTokens)
{
var model = models[modelIdx];
var url = $"{model.Endpoint}/chat/completions";
var payload = new
{
model = model.Id,
messages = new[]
{
new { role = "system", content = systemMsg },
new { role = "user", content = userMsg }
},
max_tokens = maxTokens
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
var body = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var root = doc.RootElement;
return root.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString() ?? "";
}
static async Task MakeChatStreamRequest(int modelIdx, string systemMsg, string userMsg, int maxTokens)
{
var model = models[modelIdx];
var url = $"{model.Endpoint}/chat/completions";
var payload = new
{
model = model.Id,
messages = new[]
{
new { role = "system", content = systemMsg },
new { role = "user", content = userMsg }
},
max_tokens = maxTokens,
stream = true
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = content;
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
Console.Write("Response: ");
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
if (string.IsNullOrEmpty(line)) continue;
if (line.StartsWith("data: "))
{
var data = line.Substring(6);
if (data == "[DONE]") break;
try
{
using var doc = JsonDocument.Parse(data);
var root = doc.RootElement;
if (root.TryGetProperty("choices", out var choices) && choices.GetArrayLength() > 0)
{
var choice = choices[0];
if (choice.TryGetProperty("delta", out var delta))
{
if (delta.TryGetProperty("content", out var contentProp))
{
var contentStr = contentProp.GetString();
if (!string.IsNullOrEmpty(contentStr))
{
Console.Write(contentStr);
}
}
}
}
}
catch
{
// Ignore parse errors
}
}
}
Console.WriteLine();
}
static async Task<string> MakeTtsRequest(string text)
{
if (ttsEndpoints.Count == 0)
return "ERROR: No TTS endpoints available";
var endpoint = ttsEndpoints[0];
var url = $"{endpoint}/audio/speech";
var payload = new
{
model = "tts-1",
voice = "alloy",
input = text
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
var audioData = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("output.mp3", audioData);
return $"Audio saved to output.mp3 ({audioData.Length} bytes)";
}
static async Task HermesExample()
{
Console.WriteLine("\n=== Non-Streaming Chat (using first discovered model) ===");
if (models.Count == 0)
{
Console.WriteLine("ERROR: No models available");
return;
}
var model = models[0];
Console.WriteLine($"Model: {model.Id}");
Console.WriteLine($"Endpoint: {model.Endpoint}\n");
try
{
var response = await MakeChatRequest(
0,
"You are Hermes, a helpful AI assistant from Nous Research.",
"Explain quantum computing in one sentence.",
100
);
Console.WriteLine($"Response: {response}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task QwenStreamExample()
{
Console.WriteLine("\n=== Streaming Chat (using second or first model) ===");
if (models.Count == 0)
{
Console.WriteLine("ERROR: No models available");
return;
}
var modelIdx = models.Count >= 2 ? 1 : 0;
var model = models[modelIdx];
Console.WriteLine($"Model: {model.Id}");
Console.WriteLine($"Endpoint: {model.Endpoint}\n");
try
{
await MakeChatStreamRequest(
modelIdx,
"You are Qwen, a coding assistant specialized in software development.",
"Write a hello world function in C#.",
200
);
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task TtsExample()
{
Console.WriteLine("\n=== TTS Speech Generation Example ===");
if (ttsEndpoints.Count == 0)
{
Console.WriteLine("ERROR: No TTS endpoints available");
return;
}
Console.WriteLine($"Endpoint: {ttsEndpoints[0]}\n");
try
{
var result = await MakeTtsRequest("Hello from C#! This is a text to speech example.");
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static async Task Main(string[] args)
{
Console.WriteLine("C# AI API Examples (Dynamic Model Discovery)");
Console.WriteLine("=============================================");
Console.WriteLine();
await DiscoverAllModels();
if (models.Count == 0)
{
Console.WriteLine("ERROR: No models discovered. Check environment variables:");
Console.WriteLine(" MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.");
Environment.Exit(1);
}
await HermesExample();
await QwenStreamExample();
await TtsExample();
}
}

View file

@ -1,7 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View file

@ -44,8 +44,8 @@ class uncloseai
Console.WriteLine("=== Non-Streaming Chat (Model 1) ===");
var client1 = new ChatClient(
model: model1Id,
apiKey: "dummy-key",
new OpenAIClientOptions
credential: "dummy-key",
options: new OpenAIClientOptions
{
Endpoint = new Uri(modelEndpoint1)
}
@ -91,8 +91,8 @@ class uncloseai
Console.WriteLine("=== Non-Streaming Chat (Model 2) ===");
var client2 = new ChatClient(
model: model2Id,
apiKey: "dummy-key",
new OpenAIClientOptions
credential: "dummy-key",
options: new OpenAIClientOptions
{
Endpoint = new Uri(modelEndpoint2)
}
@ -138,8 +138,8 @@ class uncloseai
Console.WriteLine("=== TTS Speech Generation ===");
var ttsClient = new AudioClient(
model: "tts-1",
apiKey: "dummy-key",
new OpenAIClientOptions
credential: "dummy-key",
options: new OpenAIClientOptions
{
Endpoint = new Uri(ttsEndpoint1)
}
@ -150,7 +150,7 @@ class uncloseai
GeneratedSpeechVoice.Alloy,
new SpeechGenerationOptions
{
Speed = 0.9f
SpeedRatio = 0.9f
}
);

26
languages/d/Dockerfile Normal file
View file

@ -0,0 +1,26 @@
# Use official D language Docker image (checked 2025-10-15: dlang2/dmd-ubuntu:latest is stable)
FROM dlang2/dmd-ubuntu:latest
# Install curl development libraries (required for std.net.curl)
RUN apt-get update && \
apt-get install -y libcurl4-openssl-dev && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy D source files
COPY hermes_nonstreaming.d .
COPY hermes_streaming.d .
COPY qwen_nonstreaming.d .
COPY qwen_streaming.d .
COPY tts.d .
# Compile all examples
RUN dmd -of=hermes_nonstreaming hermes_nonstreaming.d && \
dmd -of=hermes_streaming hermes_streaming.d && \
dmd -of=qwen_nonstreaming qwen_nonstreaming.d && \
dmd -of=qwen_streaming qwen_streaming.d && \
dmd -of=tts tts.d
# Default command shows available examples
CMD ["sh", "-c", "echo 'Available examples:' && echo ' ./hermes_nonstreaming' && echo ' ./hermes_streaming' && echo ' ./qwen_nonstreaming' && echo ' ./qwen_streaming' && echo ' ./tts'"]

View file

@ -0,0 +1,49 @@
#!/usr/bin/env rdmd
// Hermes AI Non-Streaming Example in D
// Uses std.net.curl for HTTP requests and std.json for JSON parsing
import std.stdio;
import std.net.curl;
import std.json;
import std.conv;
void main()
{
immutable baseUrl = "https://hermes.ai.unturf.com/v1/chat/completions";
immutable model = "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic";
try
{
writeln("Requesting from Hermes AI...\n");
// Create JSON payload
JSONValue payload;
payload["model"] = model;
payload["messages"] = [
JSONValue([
"role": JSONValue("user"),
"content": JSONValue("Give a Python Fizzbuzz solution in one line of code?")
])
];
payload["temperature"] = 0.5;
payload["max_tokens"] = 150;
payload["stream"] = false;
// Make POST request
auto http = HTTP();
http.addRequestHeader("Content-Type", "application/json");
auto response = post(baseUrl, payload.toString(), http);
// Parse response
auto jsonResponse = parseJSON(response);
auto content = jsonResponse["choices"][0]["message"]["content"].str;
writeln("Response: ", content);
}
catch (Exception e)
{
writeln("Error: ", e.msg);
}
}

View file

@ -0,0 +1,94 @@
#!/usr/bin/env rdmd
// Hermes AI Streaming Example in D
// Uses std.net.curl with byChunk for streaming SSE responses
import std.stdio;
import std.net.curl;
import std.json;
import std.string;
import std.algorithm;
import std.array;
void main()
{
immutable baseUrl = "https://hermes.ai.unturf.com/v1/chat/completions";
immutable model = "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic";
try
{
writeln("Streaming from Hermes AI...\n");
// Create JSON payload
JSONValue payload;
payload.object = [
"model": model,
"messages": [
[
"role": "user",
"content": "Give a Python Fizzbuzz solution in one line of code?"
]
],
"temperature": 0.5,
"max_tokens": 150,
"stream": true
];
// Make streaming POST request
auto http = HTTP();
http.addRequestHeader("Content-Type", "application/json");
http.method = HTTP.Method.post;
http.url = baseUrl;
http.postData = payload.toString();
string buffer = "";
// Process chunks as they arrive
http.onReceive = (ubyte[] data)
{
buffer ~= cast(string)data;
// Process complete lines
while (true)
{
auto idx = buffer.indexOf("\n");
if (idx == -1) break;
auto line = buffer[0..idx].strip();
buffer = buffer[idx+1..$];
// Process SSE data lines
if (line.startsWith("data: "))
{
auto jsonData = line[6..$];
if (jsonData == "[DONE]") continue;
try
{
auto parsed = parseJSON(jsonData);
if ("choices" in parsed && parsed["choices"].array.length > 0)
{
auto delta = parsed["choices"][0]["delta"];
if ("content" in delta)
{
write(delta["content"].str);
stdout.flush();
}
}
}
catch (Exception) {}
}
}
return data.length;
};
http.perform();
writeln("\n\nDone!");
}
catch (Exception e)
{
writeln("\nError: ", e.msg);
}
}

View file

@ -0,0 +1,50 @@
#!/usr/bin/env rdmd
// Qwen 3 Coder Non-Streaming Example in D
import std.stdio;
import std.net.curl;
import std.json;
import std.conv;
void main()
{
immutable baseUrl = "https://qwen.ai.unturf.com/v1/chat/completions";
immutable model = "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M";
try
{
writeln("Requesting from Qwen 3 Coder...\n");
// Create JSON payload
JSONValue payload;
payload.object = [
"model": model,
"messages": [
[
"role": "user",
"content": "Give a Python Fizzbuzz solution in one line of code?"
]
],
"temperature": 0.5,
"max_tokens": 150,
"stream": false
];
// Make POST request
auto http = HTTP();
http.addRequestHeader("Content-Type", "application/json");
auto response = post(baseUrl, payload.toString(), http);
// Parse response
auto jsonResponse = parseJSON(response);
auto content = jsonResponse["choices"][0]["message"]["content"].str;
writeln("Response: ", content);
}
catch (Exception e)
{
writeln("Error: ", e.msg);
}
}

View file

@ -0,0 +1,93 @@
#!/usr/bin/env rdmd
// Qwen 3 Coder Streaming Example in D
import std.stdio;
import std.net.curl;
import std.json;
import std.string;
import std.algorithm;
import std.array;
void main()
{
immutable baseUrl = "https://qwen.ai.unturf.com/v1/chat/completions";
immutable model = "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M";
try
{
writeln("Streaming from Qwen 3 Coder...\n");
// Create JSON payload
JSONValue payload;
payload.object = [
"model": model,
"messages": [
[
"role": "user",
"content": "Give a Python Fizzbuzz solution in one line of code?"
]
],
"temperature": 0.5,
"max_tokens": 150,
"stream": true
];
// Make streaming POST request
auto http = HTTP();
http.addRequestHeader("Content-Type", "application/json");
http.method = HTTP.Method.post;
http.url = baseUrl;
http.postData = payload.toString();
string buffer = "";
// Process chunks as they arrive
http.onReceive = (ubyte[] data)
{
buffer ~= cast(string)data;
// Process complete lines
while (true)
{
auto idx = buffer.indexOf("\n");
if (idx == -1) break;
auto line = buffer[0..idx].strip();
buffer = buffer[idx+1..$];
// Process SSE data lines
if (line.startsWith("data: "))
{
auto jsonData = line[6..$];
if (jsonData == "[DONE]") continue;
try
{
auto parsed = parseJSON(jsonData);
if ("choices" in parsed && parsed["choices"].array.length > 0)
{
auto delta = parsed["choices"][0]["delta"];
if ("content" in delta)
{
write(delta["content"].str);
stdout.flush();
}
}
}
catch (Exception) {}
}
}
return data.length;
};
http.perform();
writeln("\n\nDone!");
}
catch (Exception e)
{
writeln("\nError: ", e.msg);
}
}

43
languages/d/tts.d Normal file
View file

@ -0,0 +1,43 @@
#!/usr/bin/env rdmd
// Text-to-Speech Example in D
import std.stdio;
import std.net.curl;
import std.json;
import std.file;
void main()
{
immutable baseUrl = "https://speech.ai.unturf.com/v1/audio/speech";
immutable outputFile = "speech.mp3";
try
{
writeln("Generating speech from TTS...\n");
// Create JSON payload
JSONValue payload;
payload.object = [
"model": "tts-1",
"voice": "alloy",
"speed": 0.9,
"input": "I think so therefore, Today is a wonderful day to grow something people love!"
];
// Make POST request and get binary response
auto http = HTTP();
http.addRequestHeader("Content-Type", "application/json");
auto response = post(baseUrl, payload.toString(), http);
// Write binary data to file
std.file.write(outputFile, response);
writeln("Speech saved to: ", outputFile);
}
catch (Exception e)
{
writeln("Error: ", e.msg);
}
}

View file

@ -1,37 +0,0 @@
FROM elixir:1.17-alpine AS build
WORKDIR /app
# Install hex and rebar
RUN mix local.hex --force && \
mix local.rebar --force
# Copy mix files for dependency resolution
COPY mix.exs mix.lock* ./
RUN mix deps.get --only prod
# Copy source code
COPY lib ./lib
COPY run.exs ./
# Compile the project (don't run yet)
RUN MIX_ENV=prod mix compile
# Runtime stage
FROM elixir:1.17-alpine
WORKDIR /app
# Install hex and rebar in runtime
RUN mix local.hex --force && \
mix local.rebar --force
# Copy built application from build stage
COPY --from=build /app/_build /app/_build
COPY --from=build /app/deps /app/deps
COPY --from=build /app/lib /app/lib
COPY --from=build /app/run.exs /app/run.exs
COPY --from=build /app/mix.exs /app/mix.exs
# Run the application
CMD ["elixir", "run.exs"]

View file

@ -1,252 +0,0 @@
defmodule UncloseAI do
@moduledoc """
UncloseAI Elixir Library
OpenAI-compatible API client with streaming support
Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
"""
defmodule ModelInfo do
@moduledoc "Struct to hold model information"
defstruct [:id, :endpoint, :max_tokens]
end
defmodule Client do
@moduledoc "Client struct to hold discovered models and endpoints"
defstruct models: [], tts_endpoints: [], timeout: 30_000
@doc """
Initialize a new UncloseAI client with auto-discovery
"""
def new(opts \\ []) do
timeout = Keyword.get(opts, :timeout, 30_000)
IO.puts("Initializing UncloseAI client...")
# Discover chat/code models
models =
Stream.iterate(1, &(&1 + 1))
|> Stream.take_while(fn i ->
System.get_env("MODEL_ENDPOINT_#{i}") != nil
end)
|> Stream.map(fn i ->
endpoint = System.get_env("MODEL_ENDPOINT_#{i}")
IO.puts("Endpoint #{i}: #{endpoint}")
discover_models_from_endpoint(endpoint)
end)
|> Enum.to_list()
|> List.flatten()
# Discover TTS endpoints
tts_endpoints =
Stream.iterate(1, &(&1 + 1))
|> Stream.take_while(fn i ->
System.get_env("TTS_ENDPOINT_#{i}") != nil
end)
|> Stream.map(fn i ->
System.get_env("TTS_ENDPOINT_#{i}")
end)
|> Enum.to_list()
IO.puts("Discovered #{length(models)} models, #{length(tts_endpoints)} TTS endpoints\n")
%Client{models: models, tts_endpoints: tts_endpoints, timeout: timeout}
end
defp discover_models_from_endpoint(endpoint) do
case HTTPoison.get("#{endpoint}/models", [], recv_timeout: 10_000) do
{:ok, %HTTPoison.Response{body: body}} ->
data = Jason.decode!(body)
case data["data"] do
nil ->
[]
models ->
Enum.filter(models, fn model ->
# Skip modelperm-* entries
not String.starts_with?(model["id"], "modelperm-")
end)
|> Enum.map(fn model ->
model_id = model["id"]
max_tokens = model["max_model_len"] || 8192
%ModelInfo{id: model_id, endpoint: endpoint, max_tokens: max_tokens}
end)
end
{:error, _reason} ->
# Silently skip failed endpoints
[]
end
end
@doc """
Non-streaming chat completion
"""
def chat(client, messages, opts \\ []) do
model_idx = Keyword.get(opts, :model_idx, 0)
max_tokens = Keyword.get(opts, :max_tokens, 100)
temperature = Keyword.get(opts, :temperature, 0.7)
model = Enum.at(client.models, model_idx)
if model == nil do
{:error, "Invalid model index"}
else
url = "#{model.endpoint}/chat/completions"
request = %{
model: model.id,
messages: messages,
stream: false,
max_tokens: max_tokens,
temperature: temperature
}
case HTTPoison.post(
url,
Jason.encode!(request),
[{"Content-Type", "application/json"}],
recv_timeout: client.timeout
) do
{:ok, %HTTPoison.Response{body: body}} ->
data = Jason.decode!(body)
{:ok, get_in(data, ["choices", Access.at(0), "message", "content"])}
{:error, reason} ->
{:error, reason}
end
end
end
@doc """
Streaming chat completion - returns a Stream that yields content chunks
"""
def chat_stream(client, messages, opts \\ []) do
model_idx = Keyword.get(opts, :model_idx, 0)
max_tokens = Keyword.get(opts, :max_tokens, 500)
temperature = Keyword.get(opts, :temperature, 0.7)
model = Enum.at(client.models, model_idx)
if model == nil do
raise "Invalid model index"
end
url = "#{model.endpoint}/chat/completions"
request = %{
model: model.id,
messages: messages,
stream: true,
max_tokens: max_tokens,
temperature: temperature
}
# Use HTTPoison stream with async response handling
Stream.resource(
fn ->
{:ok, response} =
HTTPoison.post(
url,
Jason.encode!(request),
[{"Content-Type", "application/json"}],
stream_to: self(),
async: :once,
recv_timeout: client.timeout
)
{response, ""}
end,
fn {response, buffer} ->
receive do
%HTTPoison.AsyncStatus{} ->
HTTPoison.stream_next(response)
{[], {response, buffer}}
%HTTPoison.AsyncHeaders{} ->
HTTPoison.stream_next(response)
{[], {response, buffer}}
%HTTPoison.AsyncChunk{chunk: chunk} ->
# Append chunk to buffer and process lines
new_buffer = buffer <> chunk
{lines, remaining} = extract_lines(new_buffer)
contents =
lines
|> Enum.filter(&String.starts_with?(&1, "data: "))
|> Enum.map(&String.slice(&1, 6..-1))
|> Enum.reject(&(&1 == "[DONE]"))
|> Enum.map(&extract_content/1)
|> Enum.reject(&is_nil/1)
HTTPoison.stream_next(response)
{contents, {response, remaining}}
%HTTPoison.AsyncEnd{} ->
{:halt, {response, buffer}}
after
client.timeout ->
{:halt, {response, buffer}}
end
end,
fn {response, _buffer} ->
:hackney.close(response.id)
end
)
end
defp extract_lines(buffer) do
lines = String.split(buffer, "\n")
case List.last(lines) do
"" -> {Enum.drop(lines, -1), ""}
partial -> {Enum.drop(lines, -1), partial}
end
end
defp extract_content(data) do
case Jason.decode(data) do
{:ok, json} ->
get_in(json, ["choices", Access.at(0), "delta", "content"])
{:error, _} ->
nil
end
end
@doc """
Text-to-speech generation
"""
def tts(client, text, opts \\ []) do
voice = Keyword.get(opts, :voice, "alloy")
output_file = Keyword.get(opts, :output_file, "/tmp/speech.mp3")
if Enum.empty?(client.tts_endpoints) do
{:error, "No TTS endpoints available"}
else
endpoint = List.first(client.tts_endpoints)
url = "#{endpoint}/audio/speech"
request = %{
model: "tts-1",
voice: voice,
input: text
}
case HTTPoison.post(
url,
Jason.encode!(request),
[{"Content-Type", "application/json"}],
recv_timeout: client.timeout
) do
{:ok, %HTTPoison.Response{body: body}} ->
File.write!(output_file, body)
{:ok, output_file}
{:error, reason} ->
{:error, reason}
end
end
end
end
end

View file

@ -1,26 +0,0 @@
defmodule AIExamples.MixProject do
use Mix.Project
def project do
[
app: :ai_examples,
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger]
]
end
defp deps do
[
{:httpoison, "~> 2.2"},
{:jason, "~> 1.4"}
]
end
end

View file

@ -1,62 +0,0 @@
#!/usr/bin/env elixir
# Load dependencies
Mix.install([
{:httpoison, "~> 2.2"},
{:jason, "~> 1.4"}
])
# Load the module
Code.require_file("lib/uncloseai.ex", __DIR__)
# Demo program showing library usage
alias UncloseAI.Client
IO.puts("=== UncloseAI Elixir Client (with Streaming) ===\n")
# Initialize client
client = Client.new()
if Enum.empty?(client.models) do
IO.puts("ERROR: No models discovered")
System.halt(1)
end
# Non-streaming chat example
IO.puts("=== Non-Streaming Chat ===")
IO.puts("Model: #{Enum.at(client.models, 0).id}")
messages = [%{role: "user", content: "Explain quantum computing in one sentence"}]
case Client.chat(client, messages) do
{:ok, response} -> IO.puts("Response: #{response}\n")
{:error, reason} -> IO.puts("Error: #{inspect(reason)}\n")
end
# Streaming chat example
model_idx = if length(client.models) >= 2, do: 1, else: 0
IO.puts("=== Streaming Chat ===")
IO.puts("Model: #{Enum.at(client.models, model_idx).id}")
IO.write("Response: ")
messages = [%{role: "user", content: "Write a hello world program in Elixir"}]
client
|> Client.chat_stream(messages, model_idx: model_idx)
|> Enum.each(&IO.write/1)
IO.puts("\n")
# TTS example
if !Enum.empty?(client.tts_endpoints) do
IO.puts("=== TTS Speech Generation ===")
IO.puts("Model: tts-1")
case Client.tts(client, "Hello from UncloseAI Elixir client!",
output_file: "/tmp/speech.mp3") do
{:ok, file} -> IO.puts("Audio saved to #{file}")
{:error, reason} -> IO.puts("TTS failed: #{inspect(reason)}")
end
end
IO.puts("\n=== Examples Complete ===")

View file

@ -1,28 +0,0 @@
# Pin to specific Go version (checked 2025-10-13: golang:1.23-alpine is latest stable)
FROM golang:1.23-alpine AS builder
# Install ca-certificates for HTTPS requests
RUN apk --no-cache add ca-certificates
WORKDIR /app
# Copy module files
COPY go.mod .
COPY uncloseai/ ./uncloseai/
COPY examples/ ./examples/
# Build the examples
RUN go build -o basic examples/basic.go
# Use minimal alpine image for runtime
FROM alpine:3.21
# Install ca-certificates for HTTPS
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/basic .
# Default: run basic example
CMD ["./basic"]

View file

@ -1,4 +1,4 @@
# UncloseAI Go Client
# uncloseai. Go Client
A Go client library for interacting with vLLM, Ollama, and OpenAI-compatible APIs.
@ -118,7 +118,7 @@ Main client struct for interacting with AI APIs.
#### `func New(config *Config) (*Client, error)`
Create a new UncloseAI client.
Create a new uncloseai. client.
**Parameters:**
- `config` - Configuration options. If nil, uses defaults and auto-discovers from environment
@ -271,7 +271,7 @@ Information about a discovered model.
#### `Config`
Configuration for the UncloseAI client.
Configuration for the uncloseai. client.
**Fields:**
- `Endpoints []string` - Model endpoints (nil = auto-discover)
@ -384,7 +384,7 @@ response2, _ := client.Chat(ctx, messages, nil)
```go
import "os"
audio, err := client.TTS(ctx, "Hello from UncloseAI!", "alloy", "tts-1")
audio, err := client.TTS(ctx, "Hello from uncloseai.!", "alloy", "tts-1")
if err != nil {
log.Fatal(err)
}

View file

@ -1,3 +0,0 @@
module uncloseai.com
go 1.23

View file

@ -6,13 +6,12 @@ RUN apk --no-cache add ca-certificates
WORKDIR /app
# Copy module files
# Copy module files and source
COPY go.mod .
COPY uncloseai/ ./uncloseai/
COPY examples/ ./examples/
COPY uncloseai.go .
# Build the examples
RUN go build -o basic examples/basic.go
# Build the application
RUN go build -o uncloseai uncloseai.go
# Use minimal alpine image for runtime
FROM alpine:3.21
@ -22,7 +21,6 @@ RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/basic .
COPY --from=builder /app/uncloseai .
# Default: run basic example
CMD ["./basic"]
CMD ["./uncloseai"]

View file

@ -4,7 +4,7 @@ FROM golang:1.22-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod ./
COPY go.mod go.sum ./
RUN go mod download
# Copy source code

View file

@ -0,0 +1,2 @@
github.com/openai/openai-go v0.1.0-alpha.39 h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=
github.com/openai/openai-go v0.1.0-alpha.39/go.mod h1:3SdE6BffOX9HPEQv8IL/fi3LYZ5TUpRYaqGQZbyk11A=

View file

@ -1,465 +0,0 @@
package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
// ModelInfo contains metadata about a discovered model
type ModelInfo struct {
ID string `json:"id"`
Endpoint string `json:"endpoint"`
MaxTokens int `json:"max_tokens"`
}
// Message represents a chat message
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
// ChatResponse is the response from a non-streaming chat completion
type ChatResponse struct {
Model string `json:"model"`
Choices []struct {
Message Message `json:"message"`
} `json:"choices"`
}
// StreamChunk represents a chunk from streaming chat
type StreamChunk struct {
Model string `json:"model"`
Choices []struct {
Delta struct {
Content string `json:"content"`
} `json:"delta"`
} `json:"choices"`
}
// UncloseAI is the main client for OpenAI-compatible APIs
type UncloseAI struct {
models []ModelInfo
ttsEndpoints []string
apiKey string
timeout time.Duration
httpClient *http.Client
}
// NewUncloseAI creates a new client with auto-discovery from environment variables
func NewUncloseAI() (*UncloseAI, error) {
return NewUncloseAIWithOptions(nil, nil, "")
}
// NewUncloseAIWithOptions creates a new client with custom endpoints
func NewUncloseAIWithOptions(modelEndpoints, ttsEndpoints []string, apiKey string) (*UncloseAI, error) {
client := &UncloseAI{
models: make([]ModelInfo, 0),
ttsEndpoints: make([]string, 0),
apiKey: apiKey,
timeout: 30 * time.Second,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
// Discover endpoints from environment if not provided
if modelEndpoints == nil {
modelEndpoints = discoverEnvEndpoints("MODEL_ENDPOINT")
}
if ttsEndpoints == nil {
ttsEndpoints = discoverEnvEndpoints("TTS_ENDPOINT")
}
// Discover models from each endpoint
for _, endpoint := range modelEndpoints {
if err := client.discoverModelsFromEndpoint(endpoint); err != nil {
fmt.Printf("Warning: Failed to discover models from %s: %v\n", endpoint, err)
}
}
client.ttsEndpoints = ttsEndpoints
return client, nil
}
// discoverEnvEndpoints finds endpoints from environment variables like PREFIX_1, PREFIX_2, ...
func discoverEnvEndpoints(prefix string) []string {
endpoints := make([]string, 0)
for i := 1; i <= 9999; i++ {
endpoint := os.Getenv(fmt.Sprintf("%s_%d", prefix, i))
if endpoint == "" {
break
}
endpoints = append(endpoints, endpoint)
}
return endpoints
}
// discoverModelsFromEndpoint discovers available models from an endpoint
func (c *UncloseAI) discoverModelsFromEndpoint(endpoint string) error {
req, err := http.NewRequest("GET", endpoint+"/models", nil)
if err != nil {
return err
}
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
var result struct {
Data []struct {
ID string `json:"id"`
MaxModelLen int `json:"max_model_len"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
for _, model := range result.Data {
maxTokens := model.MaxModelLen
if maxTokens == 0 {
maxTokens = 8192
}
c.models = append(c.models, ModelInfo{
ID: model.ID,
Endpoint: endpoint,
MaxTokens: maxTokens,
})
}
return nil
}
// ListModels returns all discovered models
func (c *UncloseAI) ListModels() []ModelInfo {
return c.models
}
// Chat performs non-streaming chat completion
func (c *UncloseAI) Chat(ctx context.Context, messages []Message, model string, maxTokens int, temperature float64) (*ChatResponse, error) {
modelInfo, err := c.getModelInfo(model)
if err != nil {
return nil, err
}
payload := map[string]interface{}{
"model": modelInfo.ID,
"messages": messages,
"max_tokens": maxTokens,
"temperature": temperature,
"stream": false,
}
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, "POST", modelInfo.Endpoint+"/chat/completions", bytes.NewBuffer(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
}
var chatResp ChatResponse
if err := json.NewDecoder(resp.Body).Decode(&chatResp); err != nil {
return nil, err
}
return &chatResp, nil
}
// ChatStream performs streaming chat completion, returning a channel of chunks
func (c *UncloseAI) ChatStream(ctx context.Context, messages []Message, model string, maxTokens int, temperature float64) (<-chan StreamChunk, <-chan error) {
chunkChan := make(chan StreamChunk)
errChan := make(chan error, 1)
go func() {
defer close(chunkChan)
defer close(errChan)
modelInfo, err := c.getModelInfo(model)
if err != nil {
errChan <- err
return
}
payload := map[string]interface{}{
"model": modelInfo.ID,
"messages": messages,
"max_tokens": maxTokens,
"temperature": temperature,
"stream": true,
}
body, err := json.Marshal(payload)
if err != nil {
errChan <- err
return
}
req, err := http.NewRequestWithContext(ctx, "POST", modelInfo.Endpoint+"/chat/completions", bytes.NewBuffer(body))
if err != nil {
errChan <- err
return
}
req.Header.Set("Content-Type", "application/json")
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
resp, err := c.httpClient.Do(req)
if err != nil {
errChan <- err
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
errChan <- fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
return
}
// Parse SSE stream
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
// SSE format: "data: {...}"
if strings.HasPrefix(line, "data: ") {
data := strings.TrimPrefix(line, "data: ")
// Check for stream termination
if strings.TrimSpace(data) == "[DONE]" {
break
}
var chunk StreamChunk
if err := json.Unmarshal([]byte(data), &chunk); err != nil {
continue // Skip malformed chunks
}
select {
case chunkChan <- chunk:
case <-ctx.Done():
return
}
}
}
if err := scanner.Err(); err != nil {
errChan <- err
}
}()
return chunkChan, errChan
}
// TTS generates speech from text
func (c *UncloseAI) TTS(text, voice, model string) ([]byte, error) {
if len(c.ttsEndpoints) == 0 {
return nil, fmt.Errorf("no TTS endpoints available")
}
endpoint := c.ttsEndpoints[0]
payload := map[string]interface{}{
"model": model,
"voice": voice,
"input": text,
}
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", endpoint+"/audio/speech", bytes.NewBuffer(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
if c.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+c.apiKey)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
// getModelInfo retrieves model info by ID or returns first available
func (c *UncloseAI) getModelInfo(modelID string) (*ModelInfo, error) {
if len(c.models) == 0 {
return nil, fmt.Errorf("no models available")
}
if modelID == "" {
return &c.models[0], nil
}
for i := range c.models {
if c.models[i].ID == modelID {
return &c.models[i], nil
}
}
return nil, fmt.Errorf("model '%s' not found", modelID)
}
// Demo usage
func main() {
fmt.Println("=== UncloseAI Go Client (with Streaming) ===\n")
// Initialize client with auto-discovery
client, err := NewUncloseAI()
if err != nil {
fmt.Printf("Error initializing client: %v\n", err)
os.Exit(1)
}
models := client.ListModels()
if len(models) == 0 {
fmt.Println("ERROR: No models discovered. Set environment variables:")
fmt.Println(" MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.")
os.Exit(1)
}
fmt.Printf("Discovered %d model(s)\n", len(models))
for _, m := range models {
fmt.Printf(" - %s (max_tokens: %d)\n", m.ID, m.MaxTokens)
}
fmt.Println()
ctx := context.Background()
// Non-streaming chat example
fmt.Println("=== Non-Streaming Chat ===")
response, err := client.Chat(
ctx,
[]Message{
{Role: "system", Content: "You are a helpful AI assistant."},
{Role: "user", Content: "Explain quantum computing in one sentence."},
},
"", // Use first available model
100,
0.7,
)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Model: %s\n", response.Model)
fmt.Printf("Response: %s\n\n", response.Choices[0].Message.Content)
}
// Streaming chat example
fmt.Println("=== Streaming Chat ===")
modelID := ""
if len(models) > 1 {
modelID = models[1].ID
}
fmt.Printf("Model: %s\n", modelID)
fmt.Print("Response: ")
chunkChan, errChan := client.ChatStream(
ctx,
[]Message{
{Role: "system", Content: "You are a coding assistant."},
{Role: "user", Content: "Write a Go function to check if a number is prime"},
},
modelID,
200,
0.7,
)
for {
select {
case chunk, ok := <-chunkChan:
if !ok {
goto done
}
if len(chunk.Choices) > 0 {
content := chunk.Choices[0].Delta.Content
if content != "" {
fmt.Print(content)
}
}
case err := <-errChan:
if err != nil {
fmt.Printf("\nError: %v\n", err)
}
goto done
}
}
done:
fmt.Println("\n")
// TTS example
if len(client.ttsEndpoints) > 0 {
fmt.Println("=== TTS Speech Generation ===")
audioData, err := client.TTS(
"Hello from UncloseAI Go client! This demonstrates text to speech with streaming support.",
"alloy",
"tts-1",
)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
if err := os.WriteFile("speech.mp3", audioData, 0644); err != nil {
fmt.Printf("[ERROR] Failed to write speech file: %v\n", err)
} else {
fmt.Printf("[OK] Speech file created: speech.mp3 (%d bytes)\n\n", len(audioData))
}
}
}
fmt.Println("=== Examples Complete ===")
}

View file

@ -0,0 +1,17 @@
# Pin to specific Groovy version (checked 2025-10-15: groovy:4.0.25-jdk17 is latest stable)
FROM groovy:4.0.25-jdk17
# Install Grape (Groovy dependency manager) dependencies
# The dependencies will be downloaded at runtime via @Grab annotations
WORKDIR /app
# Copy Groovy scripts
COPY HermesNonStreaming.groovy .
COPY HermesStreaming.groovy .
COPY QwenNonStreaming.groovy .
COPY QwenStreaming.groovy .
COPY TTS.groovy .
# Default command shows available examples
CMD ["sh", "-c", "echo 'Available examples:' && echo ' groovy HermesNonStreaming.groovy' && echo ' groovy HermesStreaming.groovy' && echo ' groovy QwenNonStreaming.groovy' && echo ' groovy QwenStreaming.groovy' && echo ' groovy TTS.groovy'"]

View file

@ -0,0 +1,48 @@
#!/usr/bin/env groovy
// Hermes AI Non-Streaming Example using OpenAI Java Client
// Groovy leverages Java libraries seamlessly
@Grapes([
@Grab(group='com.openai', module='openai-java', version='4.2.0')
])
import com.openai.client.OpenAIClient
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.ChatCompletion
import com.openai.models.ChatCompletionCreateParams
import com.openai.models.ChatCompletionMessageParam
import com.openai.models.ChatCompletionUserMessageParam
def baseUrl = "https://hermes.ai.unturf.com/v1"
def model = "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic"
try {
println "Requesting from Hermes AI...\n"
// Create OpenAI client with custom base URL
def client = OpenAIOkHttpClient.builder()
.baseUrl(baseUrl)
.apiKey("dummy-api-key")
.build()
// Create chat completion request
def params = ChatCompletionCreateParams.builder()
.model(model)
.addMessage(ChatCompletionUserMessageParam.builder()
.content("Give a Python Fizzbuzz solution in one line of code?")
.build())
.temperature(0.5)
.maxTokens(150)
.build()
// Make request and get response
def completion = client.chat().completions().create(params)
def response = completion.choices()[0].message().content().get()
println "Response: ${response}"
} catch (Exception e) {
println "Error: ${e.message}"
e.printStackTrace()
}

View file

@ -0,0 +1,55 @@
#!/usr/bin/env groovy
// Hermes AI Streaming Example using OpenAI Java Client
// Demonstrates real-time streaming responses
@Grapes([
@Grab(group='com.openai', module='openai-java', version='4.2.0')
])
import com.openai.client.OpenAIClient
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.ChatCompletionChunk
import com.openai.models.ChatCompletionCreateParams
import com.openai.models.ChatCompletionUserMessageParam
def baseUrl = "https://hermes.ai.unturf.com/v1"
def model = "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic"
try {
println "Streaming from Hermes AI...\n"
// Create OpenAI client with custom base URL
def client = OpenAIOkHttpClient.builder()
.baseUrl(baseUrl)
.apiKey("dummy-api-key")
.build()
// Create streaming chat completion request
def params = ChatCompletionCreateParams.builder()
.model(model)
.addMessage(ChatCompletionUserMessageParam.builder()
.content("Give a Python Fizzbuzz solution in one line of code?")
.build())
.temperature(0.5)
.maxTokens(150)
.stream(true)
.build()
// Stream the response
def stream = client.chat().completions().createStreaming(params)
stream.forEach { chunk ->
def delta = chunk.choices()[0].delta()
if (delta.content().isPresent()) {
print delta.content().get()
System.out.flush()
}
}
println "\n\nDone!"
} catch (Exception e) {
println "\nError: ${e.message}"
e.printStackTrace()
}

View file

@ -0,0 +1,47 @@
#!/usr/bin/env groovy
// Qwen 3 Coder Non-Streaming Example using OpenAI Java Client
@Grapes([
@Grab(group='com.openai', module='openai-java', version='4.2.0')
])
import com.openai.client.OpenAIClient
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.ChatCompletion
import com.openai.models.ChatCompletionCreateParams
import com.openai.models.ChatCompletionMessageParam
import com.openai.models.ChatCompletionUserMessageParam
def baseUrl = "https://qwen.ai.unturf.com/v1"
def model = "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M"
try {
println "Requesting from Qwen 3 Coder...\n"
// Create OpenAI client with custom base URL
def client = OpenAIOkHttpClient.builder()
.baseUrl(baseUrl)
.apiKey("dummy-api-key")
.build()
// Create chat completion request
def params = ChatCompletionCreateParams.builder()
.model(model)
.addMessage(ChatCompletionUserMessageParam.builder()
.content("Give a Python Fizzbuzz solution in one line of code?")
.build())
.temperature(0.5)
.maxTokens(150)
.build()
// Make request and get response
def completion = client.chat().completions().create(params)
def response = completion.choices()[0].message().content().get()
println "Response: ${response}"
} catch (Exception e) {
println "Error: ${e.message}"
e.printStackTrace()
}

View file

@ -0,0 +1,54 @@
#!/usr/bin/env groovy
// Qwen 3 Coder Streaming Example using OpenAI Java Client
@Grapes([
@Grab(group='com.openai', module='openai-java', version='4.2.0')
])
import com.openai.client.OpenAIClient
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.ChatCompletionChunk
import com.openai.models.ChatCompletionCreateParams
import com.openai.models.ChatCompletionUserMessageParam
def baseUrl = "https://qwen.ai.unturf.com/v1"
def model = "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M"
try {
println "Streaming from Qwen 3 Coder...\n"
// Create OpenAI client with custom base URL
def client = OpenAIOkHttpClient.builder()
.baseUrl(baseUrl)
.apiKey("dummy-api-key")
.build()
// Create streaming chat completion request
def params = ChatCompletionCreateParams.builder()
.model(model)
.addMessage(ChatCompletionUserMessageParam.builder()
.content("Give a Python Fizzbuzz solution in one line of code?")
.build())
.temperature(0.5)
.maxTokens(150)
.stream(true)
.build()
// Stream the response
def stream = client.chat().completions().createStreaming(params)
stream.forEach { chunk ->
def delta = chunk.choices()[0].delta()
if (delta.content().isPresent()) {
print delta.content().get()
System.out.flush()
}
}
println "\n\nDone!"
} catch (Exception e) {
println "\nError: ${e.message}"
e.printStackTrace()
}

View file

@ -0,0 +1,45 @@
#!/usr/bin/env groovy
// Text-to-Speech Example using OpenAI Java Client
@Grapes([
@Grab(group='com.openai', module='openai-java', version='4.2.0')
])
import com.openai.client.okhttp.OpenAIOkHttpClient
import com.openai.models.SpeechCreateParams
import java.nio.file.Files
import java.nio.file.Paths
def baseUrl = "https://speech.ai.unturf.com/v1"
def outputFile = "speech.mp3"
try {
println "Generating speech from TTS...\n"
// Create OpenAI client with custom base URL
def client = OpenAIOkHttpClient.builder()
.baseUrl(baseUrl)
.apiKey("YOLO")
.build()
// Create TTS request
def params = SpeechCreateParams.builder()
.model(SpeechCreateParams.Model.TTS_1)
.voice(SpeechCreateParams.Voice.ALLOY)
.speed(0.9)
.input("I think so therefore, Today is a wonderful day to grow something people love!")
.build()
// Generate speech and get response as bytes
def response = client.audio().speech().create(params)
// Write to file
Files.write(Paths.get(outputFile), response.readAllBytes())
println "Speech saved to: ${outputFile}"
} catch (Exception e) {
println "Error: ${e.message}"
e.printStackTrace()
}

View file

@ -1,9 +0,0 @@
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY UncloseAI.java .
RUN javac UncloseAI.java
CMD ["java", "UncloseAI"]

View file

@ -1,366 +0,0 @@
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.function.Consumer;
class ModelInfo {
String id;
String endpoint;
int maxTokens;
ModelInfo(String id, String endpoint, int maxTokens) {
this.id = id;
this.endpoint = endpoint;
this.maxTokens = maxTokens;
}
}
public class UncloseAI {
private List<ModelInfo> models = new ArrayList<>();
private List<String> ttsEndpoints = new ArrayList<>();
private String apiKey;
private int timeout = 30000;
private boolean debug = false;
public UncloseAI() {
this(null, null, null, 30000, false);
}
public UncloseAI(List<String> endpoints, List<String> ttsEndpoints, String apiKey, int timeout, boolean debug) {
this.apiKey = apiKey;
this.timeout = timeout;
this.debug = debug;
if (endpoints == null) {
endpoints = discoverEndpointsFromEnv("MODEL_ENDPOINT");
}
if (ttsEndpoints == null) {
ttsEndpoints = discoverEndpointsFromEnv("TTS_ENDPOINT");
}
if (debug) {
System.out.println("[DEBUG] Initialized with " + endpoints.size() + " endpoint(s)");
}
discoverModels(endpoints);
this.ttsEndpoints = ttsEndpoints;
}
public List<ModelInfo> listModels() {
return new ArrayList<>(models);
}
public String chat(List<Map<String, String>> messages, String model, int maxTokens) throws IOException {
ModelInfo modelInfo = resolveModel(model);
String jsonRequest = buildChatRequest(modelInfo.id, messages, maxTokens, false);
String response = postJSON(modelInfo.endpoint + "/chat/completions", jsonRequest);
return extractContent(response);
}
public void chatStream(List<Map<String, String>> messages, String model, int maxTokens, Consumer<String> callback) throws IOException {
ModelInfo modelInfo = resolveModel(model);
String jsonRequest = buildChatRequest(modelInfo.id, messages, maxTokens, true);
URL url = new URL(modelInfo.endpoint + "/chat/completions");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(timeout);
conn.setReadTimeout(timeout);
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
}
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("data: ")) {
String data = line.substring(6).trim();
if ("[DONE]".equals(data)) {
break;
}
String content = extractStreamContent(data);
if (content != null && !content.isEmpty()) {
callback.accept(content);
}
}
}
}
}
public byte[] tts(String text, String voice, String model) throws IOException {
if (ttsEndpoints.isEmpty()) {
throw new IOException("No TTS endpoints available");
}
String jsonRequest = String.format(
"{\"model\":\"%s\",\"voice\":\"%s\",\"input\":\"%s\"}",
model, voice, text.replace("\"", "\\\"")
);
return postJSONBinary(ttsEndpoints.get(0) + "/audio/speech", jsonRequest);
}
private List<String> discoverEndpointsFromEnv(String prefix) {
List<String> endpoints = new ArrayList<>();
for (int i = 1; i < 10000; i++) {
String endpoint = System.getenv(prefix + "_" + i);
if (endpoint == null || endpoint.isEmpty()) {
break;
}
endpoints.add(endpoint);
}
return endpoints;
}
private void discoverModels(List<String> endpoints) {
for (String endpoint : endpoints) {
if (debug) {
System.out.println("[DEBUG] Discovering from: " + endpoint);
}
try {
String response = getJSON(endpoint + "/models");
parseModels(response, endpoint);
} catch (Exception e) {
if (debug) {
System.out.println("[DEBUG] Error: " + e.getMessage());
}
}
}
}
private void parseModels(String jsonResponse, String endpoint) {
int dataIndex = jsonResponse.indexOf("\"data\":[");
if (dataIndex == -1) return;
String dataSection = jsonResponse.substring(dataIndex + 8);
int pos = 0;
while (pos < dataSection.length()) {
int idIndex = dataSection.indexOf("\"id\":\"", pos);
if (idIndex == -1) break;
int idStart = idIndex + 6;
int idEnd = dataSection.indexOf("\"", idStart);
String modelId = dataSection.substring(idStart, idEnd);
if (modelId.startsWith("modelperm-")) {
pos = idEnd + 1;
continue;
}
int maxTokens = 8192;
int maxLenIndex = dataSection.indexOf("\"max_model_len\":", idEnd);
if (maxLenIndex != -1 && maxLenIndex < dataSection.indexOf("}", idEnd)) {
int maxLenStart = maxLenIndex + 16;
int maxLenEnd = dataSection.indexOf(",", maxLenStart);
if (maxLenEnd == -1) maxLenEnd = dataSection.indexOf("}", maxLenStart);
if (maxLenEnd != -1) {
try {
maxTokens = Integer.parseInt(dataSection.substring(maxLenStart, maxLenEnd).trim());
} catch (NumberFormatException ignored) {}
}
}
models.add(new ModelInfo(modelId, endpoint, maxTokens));
if (debug) {
System.out.println("[DEBUG] Discovered: " + modelId);
}
pos = idEnd + 1;
}
}
private ModelInfo resolveModel(String model) throws IOException {
if (models.isEmpty()) {
throw new IOException("No models available");
}
if (model == null || model.isEmpty()) {
return models.get(0);
}
for (ModelInfo m : models) {
if (m.id.equals(model)) {
return m;
}
}
throw new IOException("Model '" + model + "' not found");
}
private String buildChatRequest(String modelId, List<Map<String, String>> messages, int maxTokens, boolean stream) {
StringBuilder sb = new StringBuilder();
sb.append("{\"model\":\"").append(modelId).append("\",");
sb.append("\"messages\":[");
for (int i = 0; i < messages.size(); i++) {
if (i > 0) sb.append(",");
Map<String, String> msg = messages.get(i);
sb.append("{\"role\":\"").append(msg.get("role")).append("\",");
sb.append("\"content\":\"").append(msg.get("content").replace("\"", "\\\"")).append("\"}");
}
sb.append("],\"max_tokens\":").append(maxTokens);
if (stream) {
sb.append(",\"stream\":true");
}
sb.append("}");
return sb.toString();
}
private String getJSON(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line.trim());
}
return response.toString();
}
}
private String postJSON(String urlString, String jsonRequest) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(timeout);
conn.setReadTimeout(timeout);
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
}
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line.trim());
}
return response.toString();
}
}
private byte[] postJSONBinary(String urlString, String jsonRequest) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(timeout);
conn.setReadTimeout(timeout);
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
}
try (InputStream is = conn.getInputStream()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int nRead;
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
}
}
private String extractContent(String jsonResponse) {
int contentIndex = jsonResponse.indexOf("\"content\":\"");
if (contentIndex == -1) return jsonResponse;
int startIndex = contentIndex + 11;
int endIndex = jsonResponse.indexOf("\"", startIndex);
while (endIndex > 0 && jsonResponse.charAt(endIndex - 1) == '\\') {
endIndex = jsonResponse.indexOf("\"", endIndex + 1);
}
if (endIndex == -1) return jsonResponse.substring(startIndex);
String content = jsonResponse.substring(startIndex, endIndex);
return content.replace("\\n", "\n").replace("\\\"", "\"").replace("\\\\", "\\");
}
private String extractStreamContent(String jsonChunk) {
int contentIndex = jsonChunk.indexOf("\"content\":\"");
if (contentIndex == -1) return null;
int startIndex = contentIndex + 11;
int endIndex = jsonChunk.indexOf("\"", startIndex);
if (endIndex == -1) return null;
return jsonChunk.substring(startIndex, endIndex)
.replace("\\n", "\n").replace("\\\"", "\"").replace("\\\\", "\\");
}
// Demo when run as application
public static void main(String[] args) {
System.out.println("=== UncloseAI Java Client (with Streaming) ===\n");
UncloseAI client = new UncloseAI(null, null, null, 30000, true);
if (client.models.isEmpty()) {
System.out.println("ERROR: No models discovered. Set environment variables:");
System.out.println(" MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.");
System.exit(1);
}
System.out.println("\nDiscovered " + client.models.size() + " model(s):");
for (ModelInfo m : client.models) {
System.out.println(" - " + m.id + " (max_tokens: " + m.maxTokens + ")");
}
System.out.println();
// Non-streaming chat
System.out.println("=== Non-Streaming Chat ===");
try {
List<Map<String, String>> messages = Arrays.asList(
new HashMap<String, String>() {{ put("role", "system"); put("content", "You are a helpful AI assistant."); }},
new HashMap<String, String>() {{ put("role", "user"); put("content", "Explain quantum computing in one sentence."); }}
);
String response = client.chat(messages, null, 100);
System.out.println("Response: " + response + "\n");
} catch (IOException e) {
System.out.println("Error: " + e.getMessage() + "\n");
}
// Streaming chat
System.out.println("=== Streaming Chat ===");
String modelId = client.models.size() > 1 ? client.models.get(1).id : null;
System.out.println("Model: " + (modelId != null ? modelId : client.models.get(0).id));
System.out.print("Response: ");
try {
List<Map<String, String>> messages = Arrays.asList(
new HashMap<String, String>() {{ put("role", "system"); put("content", "You are a coding assistant."); }},
new HashMap<String, String>() {{ put("role", "user"); put("content", "Write a Java function to check if a number is prime"); }}
);
client.chatStream(messages, modelId, 200, content -> System.out.print(content));
System.out.println("\n");
} catch (IOException e) {
System.out.println("\nError: " + e.getMessage() + "\n");
}
// TTS
if (!client.ttsEndpoints.isEmpty()) {
System.out.println("=== TTS Speech Generation ===");
try {
byte[] audio = client.tts("Hello from UncloseAI Java client! This demonstrates streaming support.", "alloy", "tts-1");
Files.write(Paths.get("speech.mp3"), audio);
System.out.println("[OK] Speech file created: speech.mp3 (" + audio.length + " bytes)\n");
} catch (IOException e) {
System.out.println("[ERROR] TTS Error: " + e.getMessage() + "\n");
}
}
System.out.println("=== Examples Complete ===");
}
}

View file

@ -1,4 +1,4 @@
# UncloseAI Node.js Client
# uncloseai. Node.js Client
A Node.js client library for interacting with vLLM, Ollama, and OpenAI-compatible APIs.
@ -26,10 +26,10 @@ node examples.js
## Quick Start
```javascript
const { UncloseAI } = require('./uncloseai_lib');
const { uncloseai } = require('./uncloseai_lib');
// Initialize client (auto-discovers from environment variables)
const client = new UncloseAI();
const client = new uncloseai();
// Non-streaming chat
const response = await client.chat({
@ -64,7 +64,7 @@ export TTS_ENDPOINT_1="https://speech.ai.unturf.com/v1"
### Programmatic Configuration
```javascript
const client = new UncloseAI({
const client = new uncloseai({
endpoints: ['https://api.example.com/v1'],
ttsEndpoints: ['https://tts.example.com/v1'],
apiKey: 'your-api-key', // Optional
@ -75,7 +75,7 @@ const client = new UncloseAI({
## API Reference
### UncloseAI
### uncloseai. Client
Main client class for interacting with AI APIs.
@ -178,12 +178,12 @@ Generate speech from text.
**Throws:**
- `ConnectionError`: If request fails
- `UncloseAIError`: If no TTS endpoints available
- `uncloseaiError`: If no TTS endpoints available
**Example:**
```javascript
const audioData = await client.tts({
text: 'Hello from UncloseAI!',
text: 'Hello from uncloseai.!',
voice: 'alloy',
model: 'tts-1'
});
@ -196,9 +196,9 @@ fs.writeFileSync('output.mp3', audioData);
### Basic Chat
```javascript
const { UncloseAI } = require('./uncloseai_lib');
const { uncloseai } = require('./uncloseai_lib');
const client = new UncloseAI();
const client = new uncloseai();
const response = await client.chat({
model: 'auto',
@ -252,7 +252,7 @@ const response2 = await client.chat({ model: 'auto', messages });
const fs = require('fs');
const audioData = await client.tts({
text: 'Hello from UncloseAI!',
text: 'Hello from uncloseai.!',
voice: 'alloy',
model: 'tts-1'
});
@ -279,9 +279,9 @@ const response = await client.chat({
### Error Handling
```javascript
const { UncloseAI, UncloseAIError, ModelNotFoundError } = require('./uncloseai_lib');
const { uncloseai, uncloseaiError, ModelNotFoundError } = require('./uncloseai_lib');
const client = new UncloseAI();
const client = new uncloseai();
try {
const response = await client.chat({
@ -291,7 +291,7 @@ try {
} catch (error) {
if (error instanceof ModelNotFoundError) {
console.log(`Model error: ${error.message}`);
} else if (error instanceof UncloseAIError) {
} else if (error instanceof uncloseaiError) {
console.log(`API error: ${error.message}`);
} else {
throw error;
@ -330,7 +330,7 @@ Tested with:
## Error Types
- `UncloseAIError` - Base error class for all library errors
- `uncloseaiError` - Base error class for all library errors
- `ConnectionError` - Network connection errors
- `ModelNotFoundError` - Requested model not available
- `StreamingError` - Errors during streaming requests

View file

@ -1,17 +1,43 @@
import OpenAI from 'openai';
import fs from 'fs';
console.log('=== UncloseAI Node.js Client (Official OpenAI SDK) ===\n');
console.log('=== uncloseai. Node.js Client (Official OpenAI SDK) ===\n');
// Non-streaming chat with Hermes
console.log('=== Non-Streaming Chat (Hermes) ===');
const hermesClient = new OpenAI({
// Discover endpoints from environment variables
const modelEndpoint1 = process.env.MODEL_ENDPOINT_1;
const modelEndpoint2 = process.env.MODEL_ENDPOINT_2;
const ttsEndpoint1 = process.env.TTS_ENDPOINT_1;
if (!modelEndpoint1 || !modelEndpoint2 || !ttsEndpoint1) {
console.error('ERROR: No models discovered. Set environment variables:');
console.error(' MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, TTS_ENDPOINT_1');
process.exit(1);
}
// Discover models from endpoint 1
console.log(`Discovering models from ${modelEndpoint1}...`);
const client1 = new OpenAI({
apiKey: 'dummy-key',
baseURL: 'https://hermes.ai.unturf.com/v1'
baseURL: modelEndpoint1
});
const models1 = await client1.models.list();
const model1Id = models1.data[0].id;
console.log(`Model 1: ${model1Id}\n`);
const hermesResponse = await hermesClient.chat.completions.create({
model: 'adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic',
// Discover models from endpoint 2
console.log(`Discovering models from ${modelEndpoint2}...`);
const client2 = new OpenAI({
apiKey: 'dummy-key',
baseURL: modelEndpoint2
});
const models2 = await client2.models.list();
const model2Id = models2.data[0].id;
console.log(`Model 2: ${model2Id}\n`);
// Non-streaming chat with Model 1
console.log('=== Non-Streaming Chat (Model 1) ===');
const response1 = await client1.chat.completions.create({
model: model1Id,
messages: [
{ role: 'user', content: 'Give a Python Fizzbuzz solution in one line of code?' }
],
@ -19,12 +45,12 @@ const hermesResponse = await hermesClient.chat.completions.create({
max_tokens: 150
});
console.log(`Response: ${hermesResponse.choices[0].message.content}\n`);
console.log(`Response: ${response1.choices[0].message.content}\n`);
// Streaming chat with Hermes
console.log('=== Streaming Chat (Hermes) ===');
const hermesStream = await hermesClient.chat.completions.create({
model: 'adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic',
// Streaming chat with Model 1
console.log('=== Streaming Chat (Model 1) ===');
const stream1 = await client1.chat.completions.create({
model: model1Id,
messages: [
{ role: 'user', content: 'Explain quantum entanglement in one sentence.' }
],
@ -34,20 +60,15 @@ const hermesStream = await hermesClient.chat.completions.create({
});
process.stdout.write('Response: ');
for await (const chunk of hermesStream) {
for await (const chunk of stream1) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
console.log('\n');
// Non-streaming chat with Qwen
console.log('=== Non-Streaming Chat (Qwen) ===');
const qwenClient = new OpenAI({
apiKey: 'dummy-key',
baseURL: 'https://qwen.ai.unturf.com/v1'
});
const qwenResponse = await qwenClient.chat.completions.create({
model: 'hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M',
// Non-streaming chat with Model 2
console.log('=== Non-Streaming Chat (Model 2) ===');
const response2 = await client2.chat.completions.create({
model: model2Id,
messages: [
{ role: 'user', content: 'Write a JavaScript function to check if a number is prime' }
],
@ -55,12 +76,12 @@ const qwenResponse = await qwenClient.chat.completions.create({
max_tokens: 150
});
console.log(`Response: ${qwenResponse.choices[0].message.content}\n`);
console.log(`Response: ${response2.choices[0].message.content}\n`);
// Streaming chat with Qwen
console.log('=== Streaming Chat (Qwen) ===');
const qwenStream = await qwenClient.chat.completions.create({
model: 'hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M',
// Streaming chat with Model 2
console.log('=== Streaming Chat (Model 2) ===');
const stream2 = await client2.chat.completions.create({
model: model2Id,
messages: [
{ role: 'user', content: 'Give a Python Fizzbuzz solution in one line of code?' }
],
@ -70,7 +91,7 @@ const qwenStream = await qwenClient.chat.completions.create({
});
process.stdout.write('Response: ');
for await (const chunk of qwenStream) {
for await (const chunk of stream2) {
process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
console.log('\n');
@ -78,8 +99,8 @@ console.log('\n');
// TTS example
console.log('=== TTS Speech Generation ===');
const ttsClient = new OpenAI({
apiKey: 'YOLO',
baseURL: 'https://speech.ai.unturf.com/v1'
apiKey: 'dummy-key',
baseURL: ttsEndpoint1
});
const mp3 = await ttsClient.audio.speech.create({

16
languages/mojo/Dockerfile Normal file
View file

@ -0,0 +1,16 @@
# Use Modular MAX container (checked 2025-10-15: latest stable Mojo runtime)
# Note: This requires Modular license acceptance
FROM modular/max:latest
WORKDIR /app
# Install Lightbug HTTP client
RUN magic add lightbug_http
# Copy Mojo source files
COPY hermes_nonstreaming.mojo .
COPY qwen_nonstreaming.mojo .
COPY README.md .
# Default command shows available examples
CMD ["sh", "-c", "cat README.md"]

44
languages/mojo/README.md Normal file
View file

@ -0,0 +1,44 @@
# Mojo Language Examples
⚠️ **STREAMING NOT YET IMPLEMENTED**
The Mojo programming language is an AI-focused systems language from Modular.
While Lightbug HTTP client exists, streaming support for Server-Sent Events (SSE)
is not yet available in the Lightbug library as of 2025-10-15.
## Current Status
**Non-streaming examples**: Implemented for Hermes and Qwen
**Streaming examples**: NOT IMPLEMENTED (waiting for Lightbug SSE support)
**TTS example**: NOT IMPLEMENTED (binary response handling not ready)
## Available Examples
```bash
# Run Hermes non-streaming example
magic run mojo hermes_nonstreaming.mojo
# Run Qwen non-streaming example
magic run mojo qwen_nonstreaming.mojo
```
## Why Mojo?
Mojo is a new language designed for AI/ML workloads with:
- Performance comparable to C/C++
- Python-like syntax
- Zero-cost abstractions
- Built for AI infrastructure
## Future Work
Once Lightbug HTTP adds streaming support, we will implement:
- Hermes streaming example
- Qwen streaming example
- TTS speech generation example
## Links
- Mojo Language: https://www.modular.com/mojo
- Lightbug HTTP: https://github.com/Lightbug-HQ/lightbug_http
- Modular Docs: https://docs.modular.com/mojo/

View file

@ -0,0 +1,58 @@
from lightbug_http import *
from lightbug_http.client import Client
fn main() raises:
"""
Hermes AI Non-Streaming Example in Mojo
NOTE: This is a basic implementation. Streaming is NOT yet supported
by Lightbug HTTP as of 2025-10-15.
"""
print("Requesting from Hermes AI...\n")
# Create JSON payload
var payload = String("""
{
"model": "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
"messages": [
{
"role": "user",
"content": "Give a Python Fizzbuzz solution in one line of code?"
}
],
"temperature": 0.5,
"max_tokens": 150,
"stream": false
}
""")
# Create HTTP client
var client = Client()
# Parse URI
var uri = URI.parse_raises("https://hermes.ai.unturf.com/v1/chat/completions")
# Create headers
var headers = Header("Host", "hermes.ai.unturf.com")
headers.append("Content-Type", "application/json")
headers.append("Content-Length", String(len(payload)))
# Create request
var request = HTTPRequest(uri, headers)
request.body = payload.as_bytes()
try:
# Make request
var response = client.do(request^)
# Print response
print("Status Code:", response.status_code)
print("\nResponse Body:")
print(to_string(response.body_raw))
print("\nNOTE: Streaming is not yet implemented in Lightbug HTTP.")
print("This is a non-streaming example only.")
except e:
print("Error:", e)

View file

@ -0,0 +1,58 @@
from lightbug_http import *
from lightbug_http.client import Client
fn main() raises:
"""
Qwen 3 Coder Non-Streaming Example in Mojo
NOTE: This is a basic implementation. Streaming is NOT yet supported
by Lightbug HTTP as of 2025-10-15.
"""
print("Requesting from Qwen 3 Coder...\n")
# Create JSON payload
var payload = String("""
{
"model": "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M",
"messages": [
{
"role": "user",
"content": "Give a Python Fizzbuzz solution in one line of code?"
}
],
"temperature": 0.5,
"max_tokens": 150,
"stream": false
}
""")
# Create HTTP client
var client = Client()
# Parse URI
var uri = URI.parse_raises("https://qwen.ai.unturf.com/v1/chat/completions")
# Create headers
var headers = Header("Host", "qwen.ai.unturf.com")
headers.append("Content-Type", "application/json")
headers.append("Content-Length", String(len(payload)))
# Create request
var request = HTTPRequest(uri, headers)
request.body = payload.as_bytes()
try:
# Make request
var response = client.do(request^)
# Print response
print("Status Code:", response.status_code)
print("\nResponse Body:")
print(to_string(response.body_raw))
print("\nNOTE: Streaming is not yet implemented in Lightbug HTTP.")
print("This is a non-streaming example only.")
except e:
print("Error:", e)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
UncloseAI - Async Python Client (aiohttp)
uncloseai. - Async Python Client (aiohttp)
A Python async client library for OpenAI-compatible APIs with streaming support
Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
"""
@ -12,7 +12,7 @@ import os
from typing import List, Dict, Optional, AsyncIterator
class UncloseAI:
class uncloseai:
"""Async client for OpenAI-compatible API endpoints with streaming support"""
def __init__(
@ -23,7 +23,7 @@ class UncloseAI:
timeout: float = 30.0
):
"""
Initialize UncloseAI async client
Initialize uncloseai. async client
Args:
model_endpoints: List of model endpoint URLs (defaults to MODEL_ENDPOINT_* env vars)
@ -273,10 +273,10 @@ class UncloseAI:
# Demo usage when run as script
async def main():
print("=== UncloseAI Python Async Client (aiohttp) ===\n")
print("=== uncloseai. Python Async Client (aiohttp) ===\n")
# Initialize client (auto-discovers from environment)
client = UncloseAI()
client = uncloseai()
models = await client.list_models()
if not models:
@ -327,7 +327,7 @@ async def main():
if client.tts_endpoints:
print("=== TTS Speech Generation ===")
audio_data = await client.tts(
text="Hello from UncloseAI Python async client with aiohttp! This demonstrates text to speech with streaming support.",
text="Hello from uncloseai. Python async client with aiohttp! This demonstrates text to speech with streaming support.",
voice="alloy"
)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
UncloseAI - Async Python Client (httpx)
uncloseai. - Async Python Client (httpx)
A Python async client for OpenAI-compatible APIs with streaming support
"""
@ -11,7 +11,7 @@ import asyncio
from typing import List, Dict, Optional, AsyncIterator
class UncloseAI:
class uncloseai:
"""Async client for OpenAI-compatible API endpoints with streaming support"""
def __init__(
@ -203,9 +203,9 @@ class UncloseAI:
async def main():
print("=== UncloseAI Python Async Client (httpx) ===\n")
print("=== uncloseai. Python Async Client (httpx) ===\n")
client = UncloseAI()
client = uncloseai()
models = await client.list_models()
if not models:
@ -256,7 +256,7 @@ async def main():
if client.tts_endpoints:
print("=== TTS Speech Generation ===")
audio_data = await client.tts(
text="Hello from UncloseAI Python async client! This demonstrates text to speech with streaming support.",
text="Hello from uncloseai. Python async client! This demonstrates text to speech with streaming support.",
voice="alloy"
)

View file

@ -1,13 +0,0 @@
# Pin to specific Python version (checked 2025-10-12: python:3.13-alpine is latest stable)
FROM python:3.13-alpine
WORKDIR /app
COPY requirements.txt /app/requirements.txt
COPY uncloseai.py /app/uncloseai.py
RUN chmod +x /app/uncloseai.py
RUN pip3 install --no-cache-dir -r /app/requirements.txt
CMD ["python3", "/app/uncloseai.py"]

View file

@ -1 +0,0 @@
openai==2.3.0

View file

@ -1,305 +0,0 @@
#!/usr/bin/env python3
"""
UncloseAI - Python Client using OpenAI SDK
A Python client library for OpenAI-compatible APIs with streaming support
Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
"""
from openai import OpenAI
import os
import requests
from typing import List, Dict, Optional, Iterator
class UncloseAI:
"""Client for OpenAI-compatible API endpoints using OpenAI SDK"""
def __init__(
self,
model_endpoints: Optional[List[str]] = None,
tts_endpoints: Optional[List[str]] = None,
api_key: str = "dummy-key",
timeout: int = 30
):
"""
Initialize UncloseAI client with automatic model discovery
Args:
model_endpoints: List of model endpoint URLs (defaults to MODEL_ENDPOINT_* env vars)
tts_endpoints: List of TTS endpoint URLs (defaults to TTS_ENDPOINT_* env vars)
api_key: API key for authentication (default: "dummy-key")
timeout: Request timeout in seconds
"""
self.timeout = timeout
self.api_key = api_key
self.models: List[Dict] = []
self.tts_endpoints: List[str] = []
# Discover endpoints from environment or use provided
if model_endpoints is None:
model_endpoints = self._discover_env_endpoints("MODEL_ENDPOINT")
if tts_endpoints is None:
tts_endpoints = self._discover_env_endpoints("TTS_ENDPOINT")
# Discover models from each endpoint
for endpoint in model_endpoints:
self._discover_models_from_endpoint(endpoint)
self.tts_endpoints = tts_endpoints
def _discover_env_endpoints(self, prefix: str) -> List[str]:
"""Discover endpoints from environment variables like PREFIX_1, PREFIX_2, ..."""
endpoints = []
for i in range(1, 10000):
endpoint = os.getenv(f"{prefix}_{i}")
if not endpoint:
break
endpoints.append(endpoint)
return endpoints
def _discover_models_from_endpoint(self, endpoint: str) -> None:
"""Discover available models from an endpoint"""
try:
response = requests.get(f"{endpoint}/models", timeout=10)
if response.status_code == 200:
data = response.json()
for model in data.get("data", []):
model_id = model["id"]
# Filter out modelperm-* and chatcmpl-* entries
if model_id.startswith("modelperm-") or model_id.startswith("chatcmpl-"):
continue
self.models.append({
"id": model_id,
"endpoint": endpoint,
"max_tokens": model.get("max_model_len", 8192)
})
except Exception:
# Silently skip failed endpoints
pass
def list_models(self) -> List[Dict]:
"""Return list of discovered models with their metadata"""
return self.models.copy()
def chat(
self,
messages: List[Dict[str, str]],
model: Optional[str] = None,
max_tokens: int = 100,
temperature: float = 0.7,
**kwargs
) -> Dict:
"""
Non-streaming chat completion
Args:
messages: List of message dicts with 'role' and 'content'
model: Model ID (defaults to first available model)
max_tokens: Maximum tokens in response
temperature: Sampling temperature
**kwargs: Additional parameters to pass to the API
Returns:
Response dict with 'choices' containing the completion
"""
model_info = self._get_model_info(model)
client = OpenAI(
base_url=f"{model_info['endpoint']}/v1",
api_key=self.api_key,
timeout=self.timeout
)
response = client.chat.completions.create(
model=model_info["id"],
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
**kwargs
)
# Convert OpenAI response to dict format
return {
"id": response.id,
"model": response.model,
"choices": [
{
"index": choice.index,
"message": {
"role": choice.message.role,
"content": choice.message.content
},
"finish_reason": choice.finish_reason
}
for choice in response.choices
],
"usage": {
"prompt_tokens": response.usage.prompt_tokens,
"completion_tokens": response.usage.completion_tokens,
"total_tokens": response.usage.total_tokens
}
}
def chat_stream(
self,
messages: List[Dict[str, str]],
model: Optional[str] = None,
max_tokens: int = 500,
temperature: float = 0.7,
**kwargs
) -> Iterator[str]:
"""
Streaming chat completion using OpenAI SDK
Args:
messages: List of message dicts with 'role' and 'content'
model: Model ID (defaults to first available model)
max_tokens: Maximum tokens in response
temperature: Sampling temperature
**kwargs: Additional parameters to pass to the API
Yields:
Content strings as they arrive
"""
model_info = self._get_model_info(model)
client = OpenAI(
base_url=f"{model_info['endpoint']}/v1",
api_key=self.api_key,
timeout=self.timeout
)
stream = client.chat.completions.create(
model=model_info["id"],
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
stream=True,
**kwargs
)
for chunk in stream:
if chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
def tts(
self,
text: str,
voice: str = "alloy",
model: str = "tts-1",
output_file: str = "speech.mp3"
) -> str:
"""
Generate speech from text
Args:
text: Input text to convert to speech
voice: Voice name (alloy, echo, fable, onyx, nova, shimmer)
model: TTS model (tts-1 or tts-1-hd)
output_file: Path to save the audio file
Returns:
Path to the saved audio file
"""
if not self.tts_endpoints:
raise ValueError("No TTS endpoints available")
endpoint = self.tts_endpoints[0]
client = OpenAI(
base_url=f"{endpoint}/v1",
api_key=self.api_key,
timeout=self.timeout
)
with client.audio.speech.with_streaming_response.create(
model=model,
voice=voice,
input=text
) as response:
response.stream_to_file(output_file)
return output_file
def _get_model_info(self, model: Optional[str] = None) -> Dict:
"""Get model info by ID or return first available model"""
if not self.models:
raise ValueError("No models available. Check endpoint configuration.")
if model is None:
return self.models[0]
for m in self.models:
if m["id"] == model:
return m
raise ValueError(f"Model '{model}' not found in discovered models")
# Demo usage when run as script
if __name__ == "__main__":
print("=== UncloseAI Python Client (OpenAI SDK) ===\n")
# Initialize client (auto-discovers from environment)
client = UncloseAI()
if not client.models:
print("ERROR: No models discovered. Set environment variables:")
print(" MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.")
exit(1)
print(f"Discovered {len(client.models)} model(s)")
for model in client.models:
print(f" - {model['id']} (max_tokens: {model['max_tokens']})")
print()
# Non-streaming chat example
print("=== Non-Streaming Chat ===")
response = client.chat(
messages=[
{"role": "system", "content": "You are a helpful AI assistant."},
{"role": "user", "content": "Explain quantum computing in one sentence."}
],
max_tokens=100
)
print(f"Model: {response['model']}")
print(f"Response: {response['choices'][0]['message']['content']}\n")
# Streaming chat example
print("=== Streaming Chat ===")
if len(client.models) > 1:
model_id = client.models[1]["id"]
else:
model_id = None
print(f"Model: {model_id or client.models[0]['id']}")
print("Response: ", end="", flush=True)
for content in client.chat_stream(
messages=[
{"role": "system", "content": "You are a coding assistant."},
{"role": "user", "content": "Write a Python function to check if a number is prime"}
],
model=model_id,
max_tokens=200
):
print(content, end="", flush=True)
print("\n")
# TTS example
if client.tts_endpoints:
print("=== TTS Speech Generation ===")
output_path = client.tts(
text="Hello from UncloseAI Python client with OpenAI SDK! This demonstrates text to speech with streaming support.",
voice="alloy",
output_file="speech.mp3"
)
if os.path.exists(output_path):
file_size = os.path.getsize(output_path)
print(f"[OK] Speech file created: {output_path} ({file_size} bytes)\n")
print("=== Examples Complete ===")

View file

@ -1 +1,2 @@
openai==2.3.0
requests==2.32.5

View file

@ -11,7 +11,7 @@ import os
from typing import List, Dict, Optional, Iterator, Union
class UncloseAI:
class uncloseai:
"""Client for OpenAI-compatible API endpoints with streaming support"""
def __init__(
@ -22,7 +22,7 @@ class UncloseAI:
timeout: int = 30
):
"""
Initialize UncloseAI client with automatic model discovery
Initialize uncloseai. client with automatic model discovery
Args:
model_endpoints: List of model endpoint URLs (defaults to MODEL_ENDPOINT_* env vars)
@ -259,10 +259,10 @@ class UncloseAI:
# Demo usage when run as script
if __name__ == "__main__":
print("=== UncloseAI Python Client (with Streaming) ===\n")
print("=== uncloseai. Python Client (with Streaming) ===\n")
# Initialize client (auto-discovers from environment)
client = UncloseAI()
client = uncloseai()
if not client.models:
print("ERROR: No models discovered. Set environment variables:")
@ -316,7 +316,7 @@ if __name__ == "__main__":
if client.tts_endpoints:
print("=== TTS Speech Generation ===")
audio_data = client.tts(
text="Hello from UncloseAI Python client! This demonstrates text to speech with streaming support.",
text="Hello from uncloseai. Python client! This demonstrates text to speech with streaming support.",
voice="alloy"
)

View file

@ -1,10 +0,0 @@
# Ruby 3.3 (checked 2025-10-13: ruby:3.3-alpine is latest stable)
FROM ruby:3.3-alpine
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY uncloseai.rb .
RUN chmod +x uncloseai.rb
CMD ["ruby", "uncloseai.rb"]

View file

@ -1,4 +1,4 @@
# UncloseAI Ruby Client
# uncloseai. Ruby Client
A Ruby client library for interacting with vLLM, Ollama, and OpenAI-compatible APIs.
@ -27,7 +27,7 @@ require_relative 'uncloseai_lib'
require_relative 'uncloseai_lib'
# Initialize client (auto-discovers from environment variables)
client = UncloseAI::Client.new
client = uncloseai::Client.new
# Non-streaming chat
response = client.chat(
@ -57,7 +57,7 @@ export TTS_ENDPOINT_1="https://speech.ai.unturf.com/v1"
### Programmatic Configuration
```ruby
client = UncloseAI::Client.new(
client = uncloseai::Client.new(
endpoints: ['https://api.example.com/v1'],
tts_endpoints: ['https://tts.example.com/v1'],
api_key: 'your-api-key',
@ -68,7 +68,7 @@ client = UncloseAI::Client.new(
## API Reference
### UncloseAI::Client
### uncloseai::Client
#### `new(endpoints: nil, tts_endpoints: nil, api_key: nil, timeout: 30, debug: false)`
@ -95,7 +95,7 @@ Generate speech from text. Returns binary MP3 data.
### Basic Chat
```ruby
client = UncloseAI::Client.new
client = uncloseai::Client.new
response = client.chat(
[
@ -122,7 +122,7 @@ end
### Text-to-Speech
```ruby
audio = client.tts('Hello from UncloseAI!', voice: 'alloy')
audio = client.tts('Hello from uncloseai.!', voice: 'alloy')
File.binwrite('speech.mp3', audio)
```

View file

@ -6,7 +6,8 @@ require 'uri'
require 'json'
# uncloseai. - Ruby client for OpenAI-compatible APIs with streaming support
class uncloseai
# Ruby requires class names to be constants, so we use a class constant name
class Uncloseai
attr_reader :models, :tts_endpoints
def initialize(endpoints: nil, tts_endpoints: nil, api_key: nil, timeout: 30, debug: false)
@ -175,7 +176,7 @@ end
if __FILE__ == $0
puts "=== uncloseai. Ruby Client (with Streaming) ===\n"
client = uncloseai.new(debug: true)
client = Uncloseai.new(debug: true)
if client.models.empty?
puts "ERROR: No models discovered. Set environment variables:"

View file

@ -1,3 +1,3 @@
source 'https://rubygems.org'
gem 'openai', '~> 0.30.0'
gem 'openai', '~> 0.31.0'

View file

@ -1,73 +1,101 @@
#!/usr/bin/env ruby
# UncloseAI - Ruby Client using Official OpenAI SDK
# uncloseai. - Ruby Client using Official OpenAI SDK
# A Ruby client for OpenAI-compatible APIs with streaming support
require 'openai'
require 'net/http'
require 'json'
puts "=== UncloseAI Ruby Client (Official OpenAI SDK) ===\n\n"
puts "=== uncloseai. Ruby Client (Official OpenAI SDK) ===\n\n"
# Non-streaming chat with Hermes
puts "=== Non-Streaming Chat (Hermes) ==="
hermes_client = OpenAI::Client.new(
access_token: "dummy-key",
uri_base: "https://hermes.ai.unturf.com/v1"
# Discover endpoints from environment variables
model_endpoint_1 = ENV['MODEL_ENDPOINT_1']
model_endpoint_2 = ENV['MODEL_ENDPOINT_2']
tts_endpoint_1 = ENV['TTS_ENDPOINT_1']
if !model_endpoint_1 || !model_endpoint_2 || !tts_endpoint_1
puts "ERROR: No models discovered. Set environment variables:"
puts " MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, TTS_ENDPOINT_1"
exit 1
end
# Discover models from endpoint 1
puts "Discovering models from #{model_endpoint_1}..."
uri = URI("#{model_endpoint_1}/models")
response = Net::HTTP.get_response(uri)
models_1 = JSON.parse(response.body)['data']
model_1_id = models_1.first['id']
puts "Model 1: #{model_1_id}\n\n"
# Discover models from endpoint 2
puts "Discovering models from #{model_endpoint_2}..."
uri = URI("#{model_endpoint_2}/models")
response = Net::HTTP.get_response(uri)
models_2 = JSON.parse(response.body)['data']
model_2_id = models_2.first['id']
puts "Model 2: #{model_2_id}\n\n"
# Non-streaming chat with Model 1
puts "=== Non-Streaming Chat (Model 1) ==="
client_1 = OpenAI::Client.new(
api_key: "dummy-key",
base_url: model_endpoint_1
)
response = hermes_client.chat(
parameters: {
model: "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
messages: [
{ role: "user", content: "Give a Python Fizzbuzz solution in one line of code?" }
],
temperature: 0.5,
max_tokens: 150
}
response = client_1.chat.completions.create(
model: model_1_id,
messages: [
{ role: "user", content: "Give a Python Fizzbuzz solution in one line of code?" }
],
temperature: 0.5,
max_tokens: 150
)
puts "Response: #{response.dig('choices', 0, 'message', 'content')}\n\n"
puts "Response: #{response.choices[0].message.content}\n\n"
# Streaming chat with Qwen
puts "=== Streaming Chat (Qwen) ==="
qwen_client = OpenAI::Client.new(
access_token: "dummy-key",
uri_base: "https://qwen.ai.unturf.com/v1"
# Streaming chat with Model 2
puts "=== Streaming Chat (Model 2) ==="
client_2 = OpenAI::Client.new(
api_key: "dummy-key",
base_url: model_endpoint_2
)
print "Response: "
qwen_client.chat(
parameters: {
model: "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M",
messages: [
{ role: "user", content: "Give a Python Fizzbuzz solution in one line of code?" }
],
temperature: 0.5,
max_tokens: 150,
stream: proc do |chunk, _bytesize|
content = chunk.dig('choices', 0, 'delta', 'content')
print content if content
end
}
stream = client_2.chat.completions.stream_raw(
model: model_2_id,
messages: [
{ role: "user", content: "Give a Python Fizzbuzz solution in one line of code?" }
],
temperature: 0.5,
max_tokens: 150
)
stream.each do |chunk|
content = chunk.choices&.first&.delta&.content
print content if content
end
puts "\n\n"
# TTS example
puts "=== TTS Speech Generation ==="
tts_client = OpenAI::Client.new(
access_token: "YOLO",
uri_base: "https://speech.ai.unturf.com/v1"
api_key: "dummy-key",
base_url: tts_endpoint_1
)
response = tts_client.audio.speech(
parameters: {
begin
response = tts_client.audio.speech.create(
model: "tts-1",
voice: "alloy",
input: "I think so therefore, Today is a wonderful day to grow something people love!",
speed: 0.9
}
)
)
File.binwrite("speech.mp3", response)
file_size = File.size("speech.mp3")
puts "[OK] Speech file created: speech.mp3 (#{file_size} bytes)\n\n"
# Response is a StringIO object, need to read its content
audio_content = response.read
File.binwrite("speech.mp3", audio_content)
puts "[OK] Speech file created: speech.mp3 (#{audio_content.bytesize} bytes)\n\n"
rescue => e
puts "[ERROR] TTS failed: #{e.class} - #{e.message}\n\n"
end
puts "=== Examples Complete ==="

View file

@ -1,225 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'net/http'
require 'uri'
require 'json'
# UncloseAI - Ruby client for OpenAI-compatible APIs with streaming support
class UncloseAI
attr_reader :models, :tts_endpoints
def initialize(endpoints: nil, tts_endpoints: nil, api_key: nil, timeout: 30, debug: false)
@api_key = api_key
@timeout = timeout
@debug = debug
@models = []
@tts_endpoints = []
endpoints ||= discover_endpoints_from_env('MODEL_ENDPOINT')
tts_endpoints ||= discover_endpoints_from_env('TTS_ENDPOINT')
puts "[DEBUG] Initialized with #{endpoints.length} endpoint(s)" if @debug
discover_models(endpoints)
@tts_endpoints = tts_endpoints
end
def list_models
@models
end
def chat(messages, model: nil, max_tokens: 100, temperature: 0.7, **kwargs)
model_info = resolve_model(model)
payload = {
model: model_info[:id],
messages: messages,
max_tokens: max_tokens,
temperature: temperature,
**kwargs
}
response = http_request("#{model_info[:endpoint]}/chat/completions", :post, payload)
JSON.parse(response)
end
def chat_stream(messages, model: nil, max_tokens: 500, temperature: 0.7, **kwargs)
model_info = resolve_model(model)
payload = {
model: model_info[:id],
messages: messages,
max_tokens: max_tokens,
temperature: temperature,
stream: true,
**kwargs
}
uri = URI.parse("#{model_info[:endpoint]}/chat/completions")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.read_timeout = @timeout
request = Net::HTTP::Post.new(uri.request_uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{@api_key}" if @api_key
request.body = payload.to_json
buffer = ''
http.request(request) do |response|
raise "HTTP #{response.code}" unless response.code.to_i == 200
response.read_body do |chunk|
buffer += chunk
lines = buffer.split("\n")
buffer = lines.pop || ''
lines.each do |line|
next unless line.start_with?('data: ')
data = line[6..-1].strip
break if data == '[DONE]'
begin
parsed = JSON.parse(data)
yield parsed
rescue JSON::ParserError => e
puts "[DEBUG] Parse error: #{e.message}" if @debug
end
end
end
end
end
def tts(text, voice: 'alloy', model: 'tts-1')
raise 'No TTS endpoints available' if @tts_endpoints.empty?
payload = {
model: model,
voice: voice,
input: text
}
http_request("#{@tts_endpoints[0]}/audio/speech", :post, payload)
end
private
def discover_endpoints_from_env(prefix)
endpoints = []
(1..9999).each do |i|
endpoint = ENV["#{prefix}_#{i}"]
break unless endpoint
endpoints << endpoint
end
endpoints
end
def discover_models(endpoints)
endpoints.each do |endpoint|
puts "[DEBUG] Discovering from: #{endpoint}" if @debug
begin
response = http_request("#{endpoint}/models", :get)
data = JSON.parse(response)
data['data'].each do |model|
@models << {
id: model['id'],
endpoint: endpoint,
max_tokens: model['max_model_len'] || 8192
}
puts "[DEBUG] Discovered: #{model['id']}" if @debug
end
rescue => e
puts "[DEBUG] Error: #{e.message}" if @debug
end
end
end
def resolve_model(model)
raise 'No models available' if @models.empty?
return @models[0] if model.nil?
found = @models.find { |m| m[:id] == model }
raise "Model '#{model}' not found" unless found
found
end
def http_request(url, method, payload = nil)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.read_timeout = @timeout
request = case method
when :get
Net::HTTP::Get.new(uri.request_uri)
when :post
req = Net::HTTP::Post.new(uri.request_uri)
req['Content-Type'] = 'application/json'
req.body = payload.to_json if payload
req
end
request['Authorization'] = "Bearer #{@api_key}" if @api_key
response = http.request(request)
raise "HTTP #{response.code}" unless response.code.to_i == 200
response.body
end
end
# Demo when run as script
if __FILE__ == $0
puts "=== UncloseAI Ruby Client (with Streaming) ===\n"
client = UncloseAI.new(debug: true)
if client.models.empty?
puts "ERROR: No models discovered. Set environment variables:"
puts " MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc."
exit 1
end
puts "\nDiscovered #{client.models.length} model(s):"
client.models.each do |m|
puts " - #{m[:id]} (max_tokens: #{m[:max_tokens]})"
end
puts
# Non-streaming chat
puts "=== Non-Streaming Chat ==="
response = client.chat([
{ role: 'system', content: 'You are a helpful AI assistant.' },
{ role: 'user', content: 'Explain quantum computing in one sentence.' }
])
puts "Response: #{response['choices'][0]['message']['content']}\n\n"
# Streaming chat
puts "=== Streaming Chat ==="
model_id = client.models.length > 1 ? client.models[1][:id] : nil
puts "Model: #{model_id || client.models[0][:id]}"
print "Response: "
client.chat_stream([
{ role: 'system', content: 'You are a coding assistant.' },
{ role: 'user', content: 'Write a Ruby function to check if a number is prime' }
], model: model_id, max_tokens: 200) do |chunk|
content = chunk.dig('choices', 0, 'delta', 'content')
print content if content
end
puts "\n\n"
# TTS
if client.tts_endpoints.any?
puts "=== TTS Speech Generation ==="
audio_data = client.tts('Hello from UncloseAI Ruby client! This demonstrates streaming support.')
File.binwrite('speech.mp3', audio_data)
puts "[OK] Speech file created: speech.mp3 (#{audio_data.bytesize} bytes)\n\n"
end
puts "=== Examples Complete ==="
end

View file

@ -1,4 +1,4 @@
# UncloseAI Rust Client
# uncloseai. Rust Client
A Rust client library for interacting with vLLM, Ollama, and OpenAI-compatible APIs.
@ -33,12 +33,12 @@ uncloseai = { path = "../path/to/uncloseai" }
## Quick Start
```rust
use uncloseai::{UncloseAI, ChatMessage};
use uncloseai::{uncloseai, ChatMessage};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize client (auto-discovers from environment variables)
let client = UncloseAI::new(None).await?;
let client = uncloseai::new(None).await?;
// Non-streaming chat
let response = client.chat(
@ -83,9 +83,9 @@ export TTS_ENDPOINT_1="https://speech.ai.unturf.com/v1"
### Programmatic Configuration
```rust
use uncloseai::{UncloseAI, UncloseAIConfig};
use uncloseai::{uncloseai, uncloseaiConfig};
let config = UncloseAIConfig {
let config = uncloseaiConfig {
endpoints: Some(vec!["https://api.example.com/v1".to_string()]),
tts_endpoints: Some(vec!["https://tts.example.com/v1".to_string()]),
api_key: Some("your-api-key".to_string()),
@ -93,16 +93,16 @@ let config = UncloseAIConfig {
debug: true,
};
let client = UncloseAI::new(Some(config)).await?;
let client = uncloseai::new(Some(config)).await?;
```
## API Reference
### UncloseAI
### uncloseai
Main client struct for interacting with AI APIs.
#### `async fn new(config: Option<UncloseAIConfig>) -> Result<UncloseAI, UncloseAIError>`
#### `async fn new(config: Option<uncloseaiConfig>) -> Result<uncloseai, uncloseaiError>`
Initialize the client.
@ -110,7 +110,7 @@ Initialize the client.
- `config` - Optional configuration. If None, uses defaults and auto-discovers from environment
**Returns:**
- `Result<UncloseAI, UncloseAIError>` - Initialized client or error
- `Result<uncloseai, uncloseaiError>` - Initialized client or error
**Example:**
```rust
@ -118,11 +118,11 @@ Initialize the client.
let client = UncloseAI::new(None).await?;
// Explicit configuration
let config = UncloseAIConfig {
let config = uncloseaiConfig {
endpoints: Some(vec!["https://api.example.com/v1".to_string()]),
..Default::default()
};
let client = UncloseAI::new(Some(config)).await?;
let client = uncloseai::new(Some(config)).await?;
```
#### `fn list_models(&self) -> &[ModelInfo]`
@ -140,7 +140,7 @@ for model in models {
}
```
#### `async fn chat(&self, model: &str, messages: Vec<ChatMessage>, options: Option<ChatOptions>) -> Result<ChatResponse, UncloseAIError>`
#### `async fn chat(&self, model: &str, messages: Vec<ChatMessage>, options: Option<ChatOptions>) -> Result<ChatResponse, uncloseaiError>`
Send a non-streaming chat completion request.
@ -150,7 +150,7 @@ Send a non-streaming chat completion request.
- `options` - Optional `ChatOptions` for max_tokens, temperature, etc.
**Returns:**
- `Result<ChatResponse, UncloseAIError>` - Chat completion response or error
- `Result<ChatResponse, uncloseaiError>` - Chat completion response or error
**Example:**
```rust
@ -170,7 +170,7 @@ let response = client.chat(
println!("{}", response.choices[0].message.content);
```
#### `async fn chat_stream(&self, model: &str, messages: Vec<ChatMessage>, options: Option<ChatOptions>) -> Result<impl Stream<Item = Result<ChatChunk, UncloseAIError>>, UncloseAIError>`
#### `async fn chat_stream(&self, model: &str, messages: Vec<ChatMessage>, options: Option<ChatOptions>) -> Result<impl Stream<Item = Result<ChatChunk, uncloseaiError>>, uncloseaiError>`
Send a streaming chat completion request.
@ -178,7 +178,7 @@ Send a streaming chat completion request.
- Same as `chat()`
**Returns:**
- `Result<Stream<...>, UncloseAIError>` - Stream of chat chunks or error
- `Result<Stream<...>, uncloseaiError>` - Stream of chat chunks or error
**Example:**
```rust
@ -199,7 +199,7 @@ while let Some(chunk) = stream.next().await {
}
```
#### `async fn tts(&self, text: &str, voice: &str, model: &str) -> Result<Vec<u8>, UncloseAIError>`
#### `async fn tts(&self, text: &str, voice: &str, model: &str) -> Result<Vec<u8>, uncloseaiError>`
Generate speech from text.
@ -209,7 +209,7 @@ Generate speech from text.
- `model` - TTS model (tts-1 or tts-1-hd)
**Returns:**
- `Result<Vec<u8>, UncloseAIError>` - Audio data (MP3 format) or error
- `Result<Vec<u8>, uncloseaiError>` - Audio data (MP3 format) or error
**Example:**
```rust
@ -254,7 +254,7 @@ Information about a discovered model.
- `endpoint: String` - Endpoint URL
- `max_tokens: u32` - Maximum context length
#### `UncloseAIError`
#### `uncloseaiError`
Error types for the library.
@ -269,11 +269,11 @@ Error types for the library.
### Basic Chat
```rust
use uncloseai::{UncloseAI, ChatMessage, ChatOptions};
use uncloseai::{uncloseai, ChatMessage, ChatOptions};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = UncloseAI::new(None).await?;
let client = uncloseai::new(None).await?;
let response = client.chat(
"auto",
@ -295,12 +295,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
### Streaming Chat
```rust
use uncloseai::{UncloseAI, ChatMessage};
use uncloseai::{uncloseai, ChatMessage};
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = UncloseAI::new(None).await?;
let client = uncloseai::new(None).await?;
let mut stream = client.chat_stream(
"auto",
@ -346,7 +346,7 @@ let response2 = client.chat("auto", messages, None).await?;
use std::fs::File;
use std::io::Write;
let audio = client.tts("Hello from UncloseAI!", "alloy", "tts-1").await?;
let audio = client.tts("Hello from uncloseai.!", "alloy", "tts-1").await?;
let mut file = File::create("output.mp3")?;
file.write_all(&audio)?;
```
@ -371,12 +371,12 @@ let response = client.chat(
### Error Handling
```rust
use uncloseai::{UncloseAI, UncloseAIError, ChatMessage};
use uncloseai::{uncloseai, uncloseaiError, ChatMessage};
match client.chat("non-existent-model", vec![ChatMessage::user("Hello")], None).await {
Ok(response) => println!("{}", response.choices[0].message.content),
Err(UncloseAIError::ModelNotFoundError(msg)) => println!("Model error: {}", msg),
Err(UncloseAIError::ConnectionError(msg)) => println!("Connection error: {}", msg),
Err(uncloseaiError::ModelNotFoundError(msg)) => println!("Model error: {}", msg),
Err(uncloseaiError::ConnectionError(msg)) => println!("Connection error: {}", msg),
Err(e) => println!("Other error: {}", e),
}
```

View file

@ -0,0 +1,26 @@
# Build GNU Guile 3.0.10 (checked 2025-10-15: latest stable)
FROM debian:bookworm-slim
# Install build dependencies and runtime libraries
RUN apt-get update && \
apt-get install -y \
guile-3.0 \
guile-3.0-dev \
curl \
ca-certificates && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy Scheme scripts
COPY hermes-nonstreaming.scm .
COPY hermes-streaming.scm .
COPY qwen-nonstreaming.scm .
COPY qwen-streaming.scm .
COPY tts.scm .
# Make scripts executable
RUN chmod +x *.scm
# Default command shows available examples
CMD ["sh", "-c", "echo 'Available examples:' && echo ' guile hermes-nonstreaming.scm' && echo ' guile hermes-streaming.scm' && echo ' guile qwen-nonstreaming.scm' && echo ' guile qwen-streaming.scm' && echo ' guile tts.scm'"]

View file

@ -0,0 +1,43 @@
#!/usr/bin/env guile
!#
;;; Hermes AI Non-Streaming Example in GNU Guile
;;; Uses (web client) and (json) modules
(use-modules (web client)
(web response)
(ice-9 textual-ports)
(json))
(define base-url "https://hermes.ai.unturf.com/v1/chat/completions")
(define model "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic")
;; Create JSON payload
(define payload
(scm->json-string
`((model . ,model)
(messages . #(((role . "user")
(content . "Give a Python Fizzbuzz solution in one line of code?"))))
(temperature . 0.5)
(max_tokens . 150)
(stream . #f))))
(display "Requesting from Hermes AI...\n\n")
(catch #t
(lambda ()
;; Make POST request
(call-with-values
(lambda ()
(http-post base-url
#:body payload
#:headers '((Content-Type . "application/json"))))
(lambda (response body)
;; Parse JSON response
(let* ((json-response (json-string->scm (utf8->string body)))
(choices (assoc-ref json-response "choices"))
(message (assoc-ref (vector-ref choices 0) "message"))
(content (assoc-ref message "content")))
(format #t "Response: ~a\n" content)))))
(lambda (key . args)
(format #t "Error: ~a ~a\n" key args)))

View file

@ -0,0 +1,59 @@
#!/usr/bin/env guile
!#
;;; Hermes AI Streaming Example in GNU Guile
;;; Uses (web client) for streaming SSE responses
(use-modules (web client)
(web response)
(ice-9 textual-ports)
(ice-9 rdelim)
(json)
(srfi srfi-1))
(define base-url "https://hermes.ai.unturf.com/v1/chat/completions")
(define model "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic")
;; Create JSON payload for streaming
(define payload
(scm->json-string
`((model . ,model)
(messages . #(((role . "user")
(content . "Give a Python Fizzbuzz solution in one line of code?"))))
(temperature . 0.5)
(max_tokens . 150)
(stream . #t))))
(display "Streaming from Hermes AI...\n\n")
(catch #t
(lambda ()
;; Make streaming POST request
(call-with-values
(lambda ()
(http-post base-url
#:body payload
#:headers '((Content-Type . "application/json"))
#:streaming? #t))
(lambda (response port)
;; Read and process SSE stream line by line
(let loop ((line (read-line port)))
(unless (eof-object? line)
(when (string-prefix? "data: " line)
(let ((json-data (substring line 6)))
(unless (string=? json-data "[DONE]")
(catch #t
(lambda ()
(let* ((parsed (json-string->scm json-data))
(choices (assoc-ref parsed "choices")))
(when (and choices (> (vector-length choices) 0))
(let* ((delta (assoc-ref (vector-ref choices 0) "delta"))
(content (assoc-ref delta "content")))
(when content
(display content)
(force-output))))))
(lambda (key . args) #f)))))
(loop (read-line port)))))
(display "\n\nDone!\n"))))
(lambda (key . args)
(format #t "\nError: ~a ~a\n" key args)))

View file

@ -0,0 +1,39 @@
#!/usr/bin/env guile
!#
;;; Qwen 3 Coder Non-Streaming Example in GNU Guile
(use-modules (web client)
(web response)
(ice-9 textual-ports)
(json))
(define base-url "https://qwen.ai.unturf.com/v1/chat/completions")
(define model "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M")
(define payload
(scm->json-string
`((model . ,model)
(messages . #(((role . "user")
(content . "Give a Python Fizzbuzz solution in one line of code?"))))
(temperature . 0.5)
(max_tokens . 150)
(stream . #f))))
(display "Requesting from Qwen 3 Coder...\n\n")
(catch #t
(lambda ()
(call-with-values
(lambda ()
(http-post base-url
#:body payload
#:headers '((Content-Type . "application/json"))))
(lambda (response body)
(let* ((json-response (json-string->scm (utf8->string body)))
(choices (assoc-ref json-response "choices"))
(message (assoc-ref (vector-ref choices 0) "message"))
(content (assoc-ref message "content")))
(format #t "Response: ~a\n" content)))))
(lambda (key . args)
(format #t "Error: ~a ~a\n" key args)))

View file

@ -0,0 +1,55 @@
#!/usr/bin/env guile
!#
;;; Qwen 3 Coder Streaming Example in GNU Guile
(use-modules (web client)
(web response)
(ice-9 textual-ports)
(ice-9 rdelim)
(json)
(srfi srfi-1))
(define base-url "https://qwen.ai.unturf.com/v1/chat/completions")
(define model "hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M")
(define payload
(scm->json-string
`((model . ,model)
(messages . #(((role . "user")
(content . "Give a Python Fizzbuzz solution in one line of code?"))))
(temperature . 0.5)
(max_tokens . 150)
(stream . #t))))
(display "Streaming from Qwen 3 Coder...\n\n")
(catch #t
(lambda ()
(call-with-values
(lambda ()
(http-post base-url
#:body payload
#:headers '((Content-Type . "application/json"))
#:streaming? #t))
(lambda (response port)
(let loop ((line (read-line port)))
(unless (eof-object? line)
(when (string-prefix? "data: " line)
(let ((json-data (substring line 6)))
(unless (string=? json-data "[DONE]")
(catch #t
(lambda ()
(let* ((parsed (json-string->scm json-data))
(choices (assoc-ref parsed "choices")))
(when (and choices (> (vector-length choices) 0))
(let* ((delta (assoc-ref (vector-ref choices 0) "delta"))
(content (assoc-ref delta "content")))
(when content
(display content)
(force-output))))))
(lambda (key . args) #f)))))
(loop (read-line port)))))
(display "\n\nDone!\n"))))
(lambda (key . args)
(format #t "\nError: ~a ~a\n" key args)))

37
languages/scheme/tts.scm Normal file
View file

@ -0,0 +1,37 @@
#!/usr/bin/env guile
!#
;;; Text-to-Speech Example in GNU Guile
(use-modules (web client)
(web response)
(ice-9 binary-ports)
(json))
(define base-url "https://speech.ai.unturf.com/v1/audio/speech")
(define output-file "speech.mp3")
(define payload
(scm->json-string
'((model . "tts-1")
(voice . "alloy")
(speed . 0.9)
(input . "I think so therefore, Today is a wonderful day to grow something people love!"))))
(display "Generating speech from TTS...\n\n")
(catch #t
(lambda ()
(call-with-values
(lambda ()
(http-post base-url
#:body payload
#:headers '((Content-Type . "application/json"))))
(lambda (response body)
;; Write binary response to file
(call-with-output-file output-file
(lambda (port)
(put-bytevector port body)))
(format #t "Speech saved to: ~a\n" output-file))))
(lambda (key . args)
(format #t "Error: ~a ~a\n" key args)))

View file

@ -1,4 +1,4 @@
/* UncloseAI Modal - Built-in Styles (for blog versions without PicoCSS) */
/* uncloseai. Modal - Built-in Styles (for blog versions without PicoCSS) */
/* Main modal dialog */
dialog#uncloseai-embedded-modal {

View file

@ -1,4 +1,4 @@
/* UncloseAI Modal - PicoCSS Override Styles */
/* uncloseai. Modal - PicoCSS Override Styles */
/* Remove PicoCSS backdrop for main modal */
dialog#uncloseai-embedded-modal::backdrop {