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
275 lines
10 KiB
FSharp
275 lines
10 KiB
FSharp
// UncloseAI F# Library
|
|
// OpenAI-compatible API client with streaming support
|
|
// Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
|
|
|
|
open System
|
|
open System.Net.Http
|
|
open System.Text
|
|
open System.Text.Json
|
|
open System.IO
|
|
open System.Collections.Generic
|
|
open System.Threading.Tasks
|
|
|
|
type ModelInfo = {
|
|
Id: string
|
|
Endpoint: string
|
|
MaxTokens: int
|
|
}
|
|
|
|
type Message = {
|
|
Role: string
|
|
Content: string
|
|
}
|
|
|
|
/// UncloseAI Client class
|
|
type UncloseAIClient(timeout: int) =
|
|
let httpClient = new HttpClient(Timeout = TimeSpan.FromSeconds(float timeout))
|
|
let models = List<ModelInfo>()
|
|
let ttsEndpoints = List<string>()
|
|
|
|
/// Initialize client with auto-discovery
|
|
member this.Init() =
|
|
task {
|
|
printfn "Initializing UncloseAI client..."
|
|
|
|
// Discover chat/code models
|
|
let mutable i = 1
|
|
let mutable continueLoop = true
|
|
while continueLoop && i <= 9999 do
|
|
let endpoint = Environment.GetEnvironmentVariable($"MODEL_ENDPOINT_{i}")
|
|
if String.IsNullOrEmpty(endpoint) then
|
|
continueLoop <- false
|
|
else
|
|
printfn $"Endpoint {i}: {endpoint}"
|
|
do! this.DiscoverModelsFromEndpoint(endpoint)
|
|
i <- i + 1
|
|
|
|
// Discover TTS endpoints
|
|
i <- 1
|
|
continueLoop <- true
|
|
while continueLoop && i <= 9999 do
|
|
let endpoint = Environment.GetEnvironmentVariable($"TTS_ENDPOINT_{i}")
|
|
if String.IsNullOrEmpty(endpoint) then
|
|
continueLoop <- false
|
|
else
|
|
ttsEndpoints.Add(endpoint)
|
|
i <- i + 1
|
|
|
|
printfn $"Discovered {models.Count} models, {ttsEndpoints.Count} TTS endpoints\n"
|
|
}
|
|
|
|
member private this.DiscoverModelsFromEndpoint(endpoint: string) =
|
|
task {
|
|
try
|
|
let! response = httpClient.GetAsync($"{endpoint}/models")
|
|
let! body = response.Content.ReadAsStringAsync()
|
|
|
|
use doc = JsonDocument.Parse(body)
|
|
let root = doc.RootElement
|
|
|
|
match root.TryGetProperty("data") with
|
|
| (true, data) ->
|
|
for model in data.EnumerateArray() do
|
|
let modelId = model.GetProperty("id").GetString()
|
|
|
|
// Skip modelperm-* entries
|
|
if not (modelId.StartsWith("modelperm-")) then
|
|
let maxTokens =
|
|
match model.TryGetProperty("max_model_len") with
|
|
| (true, prop) -> prop.GetInt32()
|
|
| (false, _) -> 8192
|
|
|
|
models.Add({ Id = modelId; Endpoint = endpoint; MaxTokens = maxTokens })
|
|
| (false, _) -> ()
|
|
with ex ->
|
|
() // Silently skip failed endpoints
|
|
}
|
|
|
|
member this.Models = models :> IReadOnlyList<ModelInfo>
|
|
member this.TtsEndpoints = ttsEndpoints :> IReadOnlyList<string>
|
|
|
|
/// Non-streaming chat completion
|
|
member this.Chat(messages: Message[], ?modelIdx: int, ?maxTokens: int, ?temperature: float) =
|
|
task {
|
|
let idx = defaultArg modelIdx 0
|
|
let tokens = defaultArg maxTokens 100
|
|
let temp = defaultArg temperature 0.7
|
|
|
|
if idx >= models.Count then
|
|
return Error "Invalid model index"
|
|
else
|
|
let model = models.[idx]
|
|
let url = $"{model.Endpoint}/chat/completions"
|
|
|
|
let payload = {|
|
|
model = model.Id
|
|
messages = messages |> Array.map (fun m -> {| role = m.Role; content = m.Content |})
|
|
stream = false
|
|
max_tokens = tokens
|
|
temperature = temp
|
|
|}
|
|
|
|
let json = JsonSerializer.Serialize(payload)
|
|
let content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
|
|
try
|
|
let! response = httpClient.PostAsync(url, content)
|
|
let! body = response.Content.ReadAsStringAsync()
|
|
|
|
use doc = JsonDocument.Parse(body)
|
|
let root = doc.RootElement
|
|
let responseContent = root.GetProperty("choices").[0].GetProperty("message").GetProperty("content").GetString()
|
|
return Ok responseContent
|
|
with ex ->
|
|
return Error ex.Message
|
|
}
|
|
|
|
/// Streaming chat completion - yields content chunks
|
|
member this.ChatStream(messages: Message[], ?modelIdx: int, ?maxTokens: int, ?temperature: float) =
|
|
seq {
|
|
let idx = defaultArg modelIdx 0
|
|
let tokens = defaultArg maxTokens 500
|
|
let temp = defaultArg temperature 0.7
|
|
|
|
if idx >= models.Count then
|
|
yield Error "Invalid model index"
|
|
else
|
|
let model = models.[idx]
|
|
let url = $"{model.Endpoint}/chat/completions"
|
|
|
|
let payload = {|
|
|
model = model.Id
|
|
messages = messages |> Array.map (fun m -> {| role = m.Role; content = m.Content |})
|
|
stream = true
|
|
max_tokens = tokens
|
|
temperature = temp
|
|
|}
|
|
|
|
let json = JsonSerializer.Serialize(payload)
|
|
let content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
|
|
try
|
|
use request = new HttpRequestMessage(HttpMethod.Post, url, Content = content)
|
|
let response = httpClient.Send(request, HttpCompletionOption.ResponseHeadersRead)
|
|
use stream = response.Content.ReadAsStream()
|
|
use reader = new StreamReader(stream)
|
|
|
|
let mutable line = reader.ReadLine()
|
|
while not (isNull line) do
|
|
if line.StartsWith("data: ") then
|
|
let data = line.Substring(6)
|
|
if data = "[DONE]" then
|
|
line <- null
|
|
else
|
|
try
|
|
use doc = JsonDocument.Parse(data)
|
|
let root = doc.RootElement
|
|
match root.TryGetProperty("choices") with
|
|
| (true, choices) ->
|
|
let choice = choices.[0]
|
|
match choice.TryGetProperty("delta") with
|
|
| (true, delta) ->
|
|
match delta.TryGetProperty("content") with
|
|
| (true, contentProp) ->
|
|
let content = contentProp.GetString()
|
|
if not (String.IsNullOrEmpty(content)) then
|
|
yield Ok content
|
|
| (false, _) -> ()
|
|
| (false, _) -> ()
|
|
| (false, _) -> ()
|
|
with _ ->
|
|
() // Skip malformed JSON
|
|
|
|
line <- reader.ReadLine()
|
|
else
|
|
line <- reader.ReadLine()
|
|
with ex ->
|
|
yield Error ex.Message
|
|
}
|
|
|
|
/// Text-to-speech generation
|
|
member this.Tts(text: string, ?voice: string, ?outputFile: string) =
|
|
task {
|
|
let voiceStr = defaultArg voice "alloy"
|
|
let fileStr = defaultArg outputFile "/tmp/speech.mp3"
|
|
|
|
if ttsEndpoints.Count = 0 then
|
|
return Error "No TTS endpoints available"
|
|
else
|
|
let endpoint = ttsEndpoints.[0]
|
|
let url = $"{endpoint}/audio/speech"
|
|
|
|
let payload = {|
|
|
model = "tts-1"
|
|
voice = voiceStr
|
|
input = text
|
|
|}
|
|
|
|
let json = JsonSerializer.Serialize(payload)
|
|
let content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
|
|
try
|
|
let! response = httpClient.PostAsync(url, content)
|
|
let! audioData = response.Content.ReadAsByteArrayAsync()
|
|
|
|
File.WriteAllBytes(fileStr, audioData)
|
|
return Ok fileStr
|
|
with ex ->
|
|
return Error ex.Message
|
|
}
|
|
|
|
interface IDisposable with
|
|
member this.Dispose() =
|
|
httpClient.Dispose()
|
|
|
|
/// Demo program showing library usage
|
|
[<EntryPoint>]
|
|
let main argv =
|
|
printfn "=== UncloseAI F# Client (with Streaming) ===\n"
|
|
|
|
use client = new UncloseAIClient(30)
|
|
|
|
task {
|
|
// Initialize client
|
|
do! client.Init()
|
|
|
|
if client.Models.Count = 0 then
|
|
printfn "ERROR: No models discovered"
|
|
Environment.Exit(1)
|
|
|
|
// Non-streaming chat example
|
|
printfn "=== Non-Streaming Chat ==="
|
|
printfn $"Model: {client.Models.[0].Id}"
|
|
|
|
let! result = client.Chat([| { Role = "user"; Content = "Explain quantum computing in one sentence" } |])
|
|
match result with
|
|
| Ok response -> printfn $"Response: {response}\n"
|
|
| Error err -> printfn $"Error: {err}\n"
|
|
|
|
// Streaming chat example
|
|
let modelIdx = if client.Models.Count >= 2 then 1 else 0
|
|
printfn "=== Streaming Chat ==="
|
|
printfn $"Model: {client.Models.[modelIdx].Id}"
|
|
printf "Response: "
|
|
|
|
let messages = [| { Role = "user"; Content = "Write a hello world program in F#" } |]
|
|
for chunk in client.ChatStream(messages, modelIdx) do
|
|
match chunk with
|
|
| Ok content -> printf $"{content}"
|
|
| Error _ -> ()
|
|
printfn "\n"
|
|
|
|
// TTS example
|
|
if client.TtsEndpoints.Count > 0 then
|
|
printfn "=== TTS Speech Generation ==="
|
|
printfn "Model: tts-1"
|
|
|
|
let! ttsResult = client.Tts("Hello from UncloseAI F# client!", "alloy", "/tmp/speech.mp3")
|
|
match ttsResult with
|
|
| Ok file -> printfn $"Audio saved to {file}"
|
|
| Error err -> printfn $"TTS failed: {err}"
|
|
|
|
printfn "\n=== Examples Complete ==="
|
|
} |> Async.AwaitTask |> Async.RunSynchronously
|
|
|
|
0
|