modified: CLAUDE.md modified: Makefile new file: languages/awk/Dockerfile new file: languages/awk/uncloseai.awk modified: languages/bash/Dockerfile deleted: languages/bash/examples.sh new file: languages/bash/uncloseai.sh new file: languages/c/curl/Dockerfile new file: languages/c/curl/Makefile new file: languages/c/curl/index.html new file: languages/c/curl/uncloseai.c new file: languages/c/libh2o/Dockerfile new file: languages/c/libh2o/Makefile new file: languages/c/libh2o/uncloseai.c new file: languages/c/nghttp2/Dockerfile new file: languages/c/nghttp2/Makefile new file: languages/c/nghttp2/uncloseai.c new file: languages/clojure/Dockerfile new file: languages/clojure/deps.edn new file: languages/clojure/uncloseai.clj new file: languages/cobol/Dockerfile new file: languages/cobol/discover.sh new file: languages/cobol/hermes.sh new file: languages/cobol/qwen.sh new file: languages/cobol/tts.sh new file: languages/cobol/uncloseai.cob new file: languages/cpp/boost-beast/Dockerfile new file: languages/cpp/boost-beast/Makefile new file: languages/cpp/boost-beast/uncloseai.cpp new file: languages/cpp/cpp-httplib/Dockerfile new file: languages/cpp/cpp-httplib/Makefile new file: languages/cpp/cpp-httplib/uncloseai.cpp new file: languages/cpp/libcurl/Dockerfile new file: languages/cpp/libcurl/Makefile new file: languages/cpp/libcurl/uncloseai.cpp new file: languages/crystal/Dockerfile new file: languages/crystal/uncloseai.cr new file: languages/csharp/Dockerfile new file: languages/csharp/Uncloseai.cs new file: languages/csharp/csharp.csproj new file: languages/dart/Dockerfile new file: languages/dart/bin/uncloseai.dart new file: languages/dart/pubspec.yaml new file: languages/deno/Dockerfile new file: languages/deno/uncloseai.ts new file: languages/elixir/Dockerfile new file: languages/elixir/lib/uncloseai.ex new file: languages/elixir/mix.exs new file: languages/elixir/run.exs new file: languages/erlang/Dockerfile new file: languages/erlang/rebar.config new file: languages/erlang/src/uncloseai.app.src new file: languages/erlang/src/uncloseai.erl new file: languages/fortran/Dockerfile new file: languages/fortran/uncloseai.f90 new file: languages/fsharp/Dockerfile new file: languages/fsharp/Uncloseai.fs new file: languages/fsharp/fsharp.fsproj new file: languages/go/Dockerfile new file: languages/go/README.md new file: languages/go/examples/basic.go new file: languages/go/go.mod new file: languages/go/uncloseai.go new file: languages/go/uncloseai/uncloseai.go new file: languages/haskell/Dockerfile new file: languages/haskell/UncloseAI.hs new file: languages/haskell/uncloseai.cabal new file: languages/java/Dockerfile new file: languages/java/UncloseAI.java new file: languages/javascript/bun/Dockerfile new file: languages/javascript/bun/uncloseai.ts new file: languages/javascript/nodejs/Dockerfile new file: languages/javascript/nodejs/README.md new file: languages/javascript/nodejs/package.json new file: languages/javascript/nodejs/uncloseai.js new file: languages/javascript/typescript/Dockerfile new file: languages/javascript/typescript/package.json new file: languages/javascript/typescript/tsconfig.json new file: languages/javascript/typescript/uncloseai.ts new file: languages/javascript/vanilla/Dockerfile new file: languages/javascript/vanilla/uncloseai.html new file: languages/julia/Dockerfile new file: languages/julia/Project.toml new file: languages/julia/src/uncloseai.jl new file: languages/kotlin/Dockerfile new file: languages/kotlin/build.gradle.kts new file: languages/kotlin/src/main/kotlin/UncloseAI.kt new file: languages/kotlin/uncloseai.kt new file: languages/lua/Dockerfile new file: languages/lua/uncloseai.lua new file: languages/nim/Dockerfile new file: languages/nim/uncloseai.nim new file: languages/ocaml/Dockerfile new file: languages/ocaml/dune new file: languages/ocaml/dune-project new file: languages/ocaml/uncloseai.ml new file: languages/odin/Dockerfile new file: languages/odin/uncloseai.odin new file: languages/perl/Dockerfile new file: languages/perl/uncloseai.pl new file: languages/php/Dockerfile new file: languages/php/uncloseai.php new file: languages/powershell/Dockerfile new file: languages/powershell/uncloseai.ps1 new file: languages/prolog/Dockerfile new file: languages/prolog/uncloseai.pl new file: languages/python/aiohttp/Dockerfile new file: languages/python/aiohttp/requirements.txt new file: languages/python/aiohttp/uncloseai.py modified: languages/python/httpx-async/Dockerfile deleted: languages/python/httpx-async/examples.py new file: languages/python/httpx-async/uncloseai.py modified: languages/python/openai-client/Dockerfile deleted: languages/python/openai-client/examples.py new file: languages/python/openai-client/uncloseai.py modified: languages/python/requests/Dockerfile deleted: languages/python/requests/examples.py new file: languages/python/requests/uncloseai.py new file: languages/r/Dockerfile new file: languages/r/uncloseai.R new file: languages/ruby/Dockerfile new file: languages/ruby/README.md new file: languages/ruby/uncloseai.rb new file: languages/rust/Cargo.toml new file: languages/rust/Dockerfile new file: languages/rust/README.md new file: languages/rust/examples/basic.rs new file: languages/rust/src/lib.rs new file: languages/rust/src/uncloseai.rs new file: languages/scala/Dockerfile new file: languages/scala/build.sbt new file: languages/scala/project/build.properties new file: languages/scala/project/plugins.sbt new file: languages/scala/src/main/scala/UncloseAI.scala new file: languages/tcl/Dockerfile new file: languages/tcl/uncloseai.tcl new file: languages/v/Dockerfile new file: languages/v/uncloseai.v new file: languages/v/v.mod new file: languages/vbnet/Dockerfile new file: languages/vbnet/UncloseAI.vb new file: languages/vbnet/UncloseAI.vbproj modified: languages/zig/Dockerfile modified: languages/zig/build.zig deleted: languages/zig/src/main.zig new file: languages/zig/src/uncloseai.zig
359 lines
7.6 KiB
V
359 lines
7.6 KiB
V
// UncloseAI - V client for OpenAI-compatible APIs with streaming support
|
|
|
|
import net.http
|
|
import json
|
|
import os
|
|
|
|
pub struct ChatMessage {
|
|
pub:
|
|
role string
|
|
content string
|
|
}
|
|
|
|
pub struct ModelInfo {
|
|
pub mut:
|
|
id string
|
|
endpoint string
|
|
max_tokens int
|
|
}
|
|
|
|
struct ChatRequest {
|
|
model string
|
|
messages []ChatMessage
|
|
max_tokens int
|
|
temperature f64
|
|
stream bool
|
|
}
|
|
|
|
struct ResponseMessage {
|
|
content string
|
|
}
|
|
|
|
struct DeltaMessage {
|
|
content string @[json: 'content']
|
|
}
|
|
|
|
struct Choice {
|
|
message ResponseMessage
|
|
}
|
|
|
|
struct StreamChoice {
|
|
delta DeltaMessage
|
|
}
|
|
|
|
struct ChatResponse {
|
|
choices []Choice
|
|
}
|
|
|
|
struct ChatStreamChunk {
|
|
choices []StreamChoice
|
|
}
|
|
|
|
struct TTSRequest {
|
|
model string
|
|
voice string
|
|
input string
|
|
}
|
|
|
|
struct ModelData {
|
|
id string
|
|
max_model_len int @[json: 'max_model_len']
|
|
}
|
|
|
|
struct ModelsResponse {
|
|
data []ModelData
|
|
}
|
|
|
|
pub struct UncloseAI {
|
|
pub mut:
|
|
models []ModelInfo
|
|
tts_endpoints []string
|
|
api_key string
|
|
timeout int
|
|
debug bool
|
|
}
|
|
|
|
pub fn uncloseai_new(endpoints []string, tts_eps []string, api_key string, timeout int, debug bool) !UncloseAI {
|
|
mut client := UncloseAI{
|
|
models: []ModelInfo{}
|
|
tts_endpoints: tts_eps
|
|
api_key: api_key
|
|
timeout: if timeout > 0 { timeout } else { 30 }
|
|
debug: debug
|
|
}
|
|
|
|
// Discover endpoints from environment if not provided
|
|
model_ends := if endpoints.len == 0 {
|
|
discover_env_endpoints('MODEL_ENDPOINT')
|
|
} else {
|
|
endpoints
|
|
}
|
|
|
|
tts_ends := if tts_eps.len == 0 {
|
|
discover_env_endpoints('TTS_ENDPOINT')
|
|
} else {
|
|
tts_eps
|
|
}
|
|
|
|
if debug {
|
|
println('[DEBUG] Initialized with ${model_ends.len} endpoint(s)')
|
|
}
|
|
|
|
client.models = discover_models(model_ends, debug)!
|
|
client.tts_endpoints = tts_ends
|
|
|
|
return client
|
|
}
|
|
|
|
fn discover_env_endpoints(prefix string) []string {
|
|
mut endpoints := []string{}
|
|
for i := 1; i < 10000; i++ {
|
|
endpoint := os.getenv('${prefix}_${i}')
|
|
if endpoint == '' {
|
|
break
|
|
}
|
|
endpoints << endpoint
|
|
}
|
|
return endpoints
|
|
}
|
|
|
|
fn discover_models(endpoints []string, debug bool) ![]ModelInfo {
|
|
mut models := []ModelInfo{}
|
|
|
|
for endpoint in endpoints {
|
|
if debug {
|
|
println('[DEBUG] Discovering from: ${endpoint}')
|
|
}
|
|
|
|
req := http.get('${endpoint}/models') or {
|
|
if debug {
|
|
println('[DEBUG] Error: ${err}')
|
|
}
|
|
continue
|
|
}
|
|
|
|
models_resp := json.decode(ModelsResponse, req.body) or {
|
|
if debug {
|
|
println('[DEBUG] Parse error: ${err}')
|
|
}
|
|
continue
|
|
}
|
|
|
|
for model in models_resp.data {
|
|
// Skip permission entries
|
|
if !model.id.starts_with('modelperm-') && !model.id.starts_with('chatcmpl-') {
|
|
max_tok := if model.max_model_len > 0 { model.max_model_len } else { 8192 }
|
|
models << ModelInfo{
|
|
id: model.id
|
|
endpoint: endpoint
|
|
max_tokens: max_tok
|
|
}
|
|
|
|
if debug {
|
|
println('[DEBUG] Discovered: ${model.id}')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return models
|
|
}
|
|
|
|
pub fn (client &UncloseAI) list_models() []ModelInfo {
|
|
return client.models
|
|
}
|
|
|
|
fn (client &UncloseAI) resolve_model(model_id string) !ModelInfo {
|
|
if client.models.len == 0 {
|
|
return error('No models available')
|
|
}
|
|
|
|
if model_id == '' {
|
|
return client.models[0]
|
|
}
|
|
|
|
for model in client.models {
|
|
if model.id == model_id {
|
|
return model
|
|
}
|
|
}
|
|
|
|
return error('Model ${model_id} not found')
|
|
}
|
|
|
|
pub fn (client &UncloseAI) chat(messages []ChatMessage, model_id string, max_tokens int, temperature f64) !ChatResponse {
|
|
model_info := client.resolve_model(model_id)!
|
|
|
|
request := ChatRequest{
|
|
model: model_info.id
|
|
messages: messages
|
|
max_tokens: if max_tokens > 0 { max_tokens } else { 100 }
|
|
temperature: temperature
|
|
stream: false
|
|
}
|
|
|
|
request_json := json.encode(request)
|
|
|
|
mut req := http.new_request(.post, '${model_info.endpoint}/chat/completions', request_json)
|
|
req.add_header(.content_type, 'application/json')
|
|
|
|
if client.api_key != '' {
|
|
req.add_header(.authorization, 'Bearer ${client.api_key}')
|
|
}
|
|
|
|
resp := req.do()!
|
|
chat_response := json.decode(ChatResponse, resp.body)!
|
|
|
|
return chat_response
|
|
}
|
|
|
|
pub fn (client &UncloseAI) chat_stream(messages []ChatMessage, callback fn (string), model_id string, max_tokens int, temperature f64) ! {
|
|
model_info := client.resolve_model(model_id)!
|
|
|
|
request := ChatRequest{
|
|
model: model_info.id
|
|
messages: messages
|
|
max_tokens: if max_tokens > 0 { max_tokens } else { 500 }
|
|
temperature: temperature
|
|
stream: true
|
|
}
|
|
|
|
request_json := json.encode(request)
|
|
|
|
mut req := http.new_request(.post, '${model_info.endpoint}/chat/completions', request_json)
|
|
req.add_header(.content_type, 'application/json')
|
|
|
|
if client.api_key != '' {
|
|
req.add_header(.authorization, 'Bearer ${client.api_key}')
|
|
}
|
|
|
|
resp := req.do()!
|
|
|
|
// Parse SSE stream line by line
|
|
lines := resp.body.split('\n')
|
|
for line in lines {
|
|
if line.starts_with('data: ') {
|
|
data := line[6..].trim_space()
|
|
if data == '[DONE]' {
|
|
break
|
|
}
|
|
|
|
chunk := json.decode(ChatStreamChunk, data) or { continue }
|
|
|
|
if chunk.choices.len > 0 {
|
|
content := chunk.choices[0].delta.content
|
|
if content != '' {
|
|
callback(content)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (client &UncloseAI) tts(text string, voice string, model string, format string) ![]u8 {
|
|
if client.tts_endpoints.len == 0 {
|
|
return error('No TTS endpoints available')
|
|
}
|
|
|
|
endpoint := client.tts_endpoints[0]
|
|
|
|
request := TTSRequest{
|
|
model: if model != '' { model } else { 'tts-1' }
|
|
voice: if voice != '' { voice } else { 'alloy' }
|
|
input: text
|
|
}
|
|
|
|
request_json := json.encode(request)
|
|
|
|
mut req := http.new_request(.post, '${endpoint}/audio/speech', request_json)
|
|
req.add_header(.content_type, 'application/json')
|
|
|
|
if client.api_key != '' {
|
|
req.add_header(.authorization, 'Bearer ${client.api_key}')
|
|
}
|
|
|
|
resp := req.do()!
|
|
|
|
return resp.body.bytes()
|
|
}
|
|
|
|
// Demo when run as application
|
|
fn main() {
|
|
println('=== UncloseAI V Client (with Streaming) ===\n')
|
|
|
|
client := uncloseai_new([], [], '', 30, true) or {
|
|
println('Error creating client: ${err}')
|
|
return
|
|
}
|
|
|
|
if client.list_models().len == 0 {
|
|
println('ERROR: No models discovered. Set environment variables:')
|
|
println(' MODEL_ENDPOINT_1, MODEL_ENDPOINT_2, etc.')
|
|
return
|
|
}
|
|
|
|
println('\nDiscovered ${client.list_models().len} model(s):')
|
|
for model in client.list_models() {
|
|
println(' - ${model.id} (max_tokens: ${model.max_tokens})')
|
|
}
|
|
println('')
|
|
|
|
// Non-streaming chat
|
|
println('=== Non-Streaming Chat ===')
|
|
chat_resp := client.chat([
|
|
ChatMessage{
|
|
role: 'system'
|
|
content: 'You are a helpful AI assistant.'
|
|
},
|
|
ChatMessage{
|
|
role: 'user'
|
|
content: 'Explain quantum computing in one sentence.'
|
|
},
|
|
], '', 100, 0.7) or {
|
|
println('Error: ${err}\n')
|
|
return
|
|
}
|
|
|
|
println('Response: ${chat_resp.choices[0].message.content}\n')
|
|
|
|
// Streaming chat
|
|
println('=== Streaming Chat ===')
|
|
model_id := if client.list_models().len > 1 { client.list_models()[1].id } else { '' }
|
|
model_name := if model_id != '' { model_id } else { client.list_models()[0].id }
|
|
println('Model: ${model_name}')
|
|
print('Response: ')
|
|
|
|
client.chat_stream([
|
|
ChatMessage{
|
|
role: 'system'
|
|
content: 'You are a coding assistant.'
|
|
},
|
|
ChatMessage{
|
|
role: 'user'
|
|
content: 'Write a V function to check if a number is prime'
|
|
},
|
|
], fn (content string) {
|
|
print(content)
|
|
}, model_id, 200, 0.7) or { println('\nError: ${err}') }
|
|
|
|
println('\n')
|
|
|
|
// TTS
|
|
if client.tts_endpoints.len > 0 {
|
|
println('=== TTS Speech Generation ===')
|
|
audio_data := client.tts('Hello from UncloseAI V client! This demonstrates streaming support.',
|
|
'', '', '') or {
|
|
println('✗ TTS Error: ${err}\n')
|
|
return
|
|
}
|
|
|
|
os.write_file_array('speech.mp3', audio_data) or {
|
|
println('✗ Error writing file: ${err}\n')
|
|
return
|
|
}
|
|
|
|
println('✓ Speech file created: speech.mp3 (${audio_data.len} bytes)\n')
|
|
}
|
|
|
|
println('=== Examples Complete ===')
|
|
}
|