11 KiB
uncloseai. Go Client
A Go client library for interacting with vLLM, Ollama, and OpenAI-compatible APIs.
Features
- 🔍 Automatic Model Discovery - Discovers available models from configured endpoints
- 💬 Chat Completions - Both streaming and non-streaming modes
- 🎙️ Text-to-Speech - Generate audio from text with multiple voice options
- 🔄 Multiple Endpoints - Support for multiple model and TTS endpoints
- 🛡️ Error Handling - Comprehensive error handling with typed errors
- 🚀 Concurrency - Built with Go's channels and goroutines for efficient streaming
- 📦 Zero Dependencies - Uses only Go standard library
Installation
go get uncloseai.com/uncloseai
Or use as a local module:
# In your go.mod
replace uncloseai.com => ./path/to/uncloseai
Quick Start
package main
import (
"context"
"fmt"
"log"
"uncloseai.com/uncloseai"
)
func main() {
ctx := context.Background()
// Initialize client (auto-discovers from environment variables)
client, err := uncloseai.New(nil)
if err != nil {
log.Fatal(err)
}
// Non-streaming chat
response, err := client.Chat(ctx, []uncloseai.Message{
{Role: "user", Content: "Hello!"},
}, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Choices[0].Message.Content)
// Streaming chat
chunkChan, errChan := client.ChatStream(ctx, []uncloseai.Message{
{Role: "user", Content: "Write a story"},
}, nil)
for {
select {
case chunk, ok := <-chunkChan:
if !ok {
return
}
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
case err := <-errChan:
if err != nil {
log.Fatal(err)
}
return
}
}
}
Configuration
Environment Variables
# Model endpoints (numbered 1-9999)
export MODEL_ENDPOINT_1="https://hermes.ai.unturf.com/v1"
export MODEL_ENDPOINT_2="https://qwen.ai.unturf.com/v1"
# TTS endpoints (numbered 1-9999)
export TTS_ENDPOINT_1="https://speech.ai.unturf.com/v1"
Programmatic Configuration
import "time"
config := &uncloseai.Config{
Endpoints: []string{"https://api.example.com/v1"},
TTSEndpoints: []string{"https://tts.example.com/v1"},
APIKey: "your-api-key",
Timeout: 30 * time.Second,
Debug: true,
}
client, err := uncloseai.New(config)
API Reference
Client
Main client struct for interacting with AI APIs.
func New(config *Config) (*Client, error)
Create a new uncloseai. client.
Parameters:
config- Configuration options. If nil, uses defaults and auto-discovers from environment
Returns:
*Client- Initialized clienterror- Error if initialization fails
Example:
// Auto-discover from environment
client, err := uncloseai.New(nil)
// Explicit configuration
config := &uncloseai.Config{
Endpoints: []string{"https://api.example.com/v1"},
Debug: true,
}
client, err := uncloseai.New(config)
func (c *Client) ListModels() []ModelInfo
List all discovered models with their metadata.
Returns:
- Slice of
ModelInfostructs with ID, Endpoint, and MaxTokens
Example:
models := client.ListModels()
for _, model := range models {
fmt.Printf("%s - %d tokens\n", model.ID, model.MaxTokens)
}
func (c *Client) Chat(ctx context.Context, messages []Message, options *ChatOptions) (*ChatResponse, error)
Send a non-streaming chat completion request.
Parameters:
ctx- Context for request cancellationmessages- Slice of Message structs with Role and Contentoptions- Optional ChatOptions (can be nil for defaults)
Returns:
*ChatResponse- Chat completion responseerror- Error if request fails
Example:
response, err := client.Chat(ctx, []uncloseai.Message{
{Role: "system", Content: "You are a helpful assistant."},
{Role: "user", Content: "What is AI?"},
}, &uncloseai.ChatOptions{
MaxTokens: 100,
Temperature: 0.7,
})
fmt.Println(response.Choices[0].Message.Content)
func (c *Client) ChatStream(ctx context.Context, messages []Message, options *ChatOptions) (<-chan StreamChunk, <-chan error)
Send a streaming chat completion request.
Parameters:
- Same as
Chat()
Returns:
<-chan StreamChunk- Channel receiving streaming chunks<-chan error- Channel receiving errors (buffered, capacity 1)
Example:
chunkChan, errChan := client.ChatStream(ctx, []uncloseai.Message{
{Role: "user", Content: "Write a haiku"},
}, nil)
for {
select {
case chunk, ok := <-chunkChan:
if !ok {
return
}
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
case err := <-errChan:
if err != nil {
log.Fatal(err)
}
return
}
}
func (c *Client) TTS(ctx context.Context, text, voice, model string) ([]byte, error)
Generate speech from text.
Parameters:
ctx- Context for request cancellationtext- Text to convert to speechvoice- Voice to use (alloy, echo, fable, onyx, nova, shimmer)model- TTS model (tts-1 or tts-1-hd)
Returns:
[]byte- Audio data (MP3 format)error- Error if request fails
Example:
audio, err := client.TTS(ctx, "Hello!", "alloy", "tts-1")
if err != nil {
log.Fatal(err)
}
err = os.WriteFile("speech.mp3", audio, 0644)
Types
Message
Message in a chat conversation.
Fields:
Role string- Message role (system, user, assistant)Content string- Message content
ChatOptions
Options for chat completions.
Fields:
Model string- Model ID (empty = auto-select first available)MaxTokens int- Maximum tokens to generate (0 = no limit)Temperature float64- Sampling temperature (0.0 - 2.0)TopP float64- Nucleus sampling parameter (0.0 - 1.0)
ModelInfo
Information about a discovered model.
Fields:
ID string- Model IDEndpoint string- Endpoint URLMaxTokens int- Maximum context length
Config
Configuration for the uncloseai. client.
Fields:
Endpoints []string- Model endpoints (nil = auto-discover)TTSEndpoints []string- TTS endpoints (nil = auto-discover)APIKey string- API key for authenticationTimeout time.Duration- HTTP client timeout (0 = 30s default)Debug bool- Enable debug logging
Error Types
Custom error constants:
ErrConnection- Network connection errorsErrModelNotFound- Requested model not availableErrStreaming- Errors during streamingErrNoModels- No models availableErrNoTTSEndpoints- No TTS endpoints availableErrInvalidResponse- Invalid API response
Usage Examples
Basic Chat
package main
import (
"context"
"fmt"
"log"
"uncloseai.com/uncloseai"
)
func main() {
ctx := context.Background()
client, _ := uncloseai.New(nil)
response, err := client.Chat(ctx, []uncloseai.Message{
{Role: "system", Content: "You are a helpful assistant."},
{Role: "user", Content: "What is Go?"},
}, &uncloseai.ChatOptions{
MaxTokens: 100,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Choices[0].Message.Content)
}
Streaming Chat
ctx := context.Background()
client, _ := uncloseai.New(nil)
chunkChan, errChan := client.ChatStream(ctx, []uncloseai.Message{
{Role: "user", Content: "Write a haiku about code"},
}, nil)
for {
select {
case chunk, ok := <-chunkChan:
if !ok {
goto done
}
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
case err := <-errChan:
if err != nil {
log.Fatal(err)
}
goto done
}
}
done:
fmt.Println() // newline
Multi-Turn Conversation
messages := []uncloseai.Message{
{Role: "system", Content: "You are a helpful assistant."},
{Role: "user", Content: "What is AI?"},
}
// First response
response1, _ := client.Chat(ctx, messages, nil)
assistantMsg := response1.Choices[0].Message.Content
messages = append(messages, uncloseai.Message{
Role: "assistant",
Content: assistantMsg,
})
// Follow-up question
messages = append(messages, uncloseai.Message{
Role: "user",
Content: "Can you explain more?",
})
response2, _ := client.Chat(ctx, messages, nil)
Text-to-Speech
import "os"
audio, err := client.TTS(ctx, "Hello from uncloseai.!", "alloy", "tts-1")
if err != nil {
log.Fatal(err)
}
err = os.WriteFile("output.mp3", audio, 0644)
Using Specific Models
// List available models
models := client.ListModels()
for _, model := range models {
fmt.Printf("%s - %d tokens\n", model.ID, model.MaxTokens)
}
// Use specific model
response, _ := client.Chat(ctx, []uncloseai.Message{
{Role: "user", Content: "Hello"},
}, &uncloseai.ChatOptions{
Model: models[0].ID,
})
Error Handling
import "errors"
_, err := client.Chat(ctx, []uncloseai.Message{
{Role: "user", Content: "Hello"},
}, &uncloseai.ChatOptions{
Model: "non-existent-model",
})
if err != nil {
if errors.Is(err, uncloseai.ErrModelNotFound) {
fmt.Println("Model not found")
} else if errors.Is(err, uncloseai.ErrConnection) {
fmt.Println("Connection error")
} else {
fmt.Printf("Other error: %v\n", err)
}
}
Running Examples
# Set environment variables
export MODEL_ENDPOINT_1="https://hermes.ai.unturf.com/v1"
export MODEL_ENDPOINT_2="https://qwen.ai.unturf.com/v1"
export TTS_ENDPOINT_1="https://speech.ai.unturf.com/v1"
# Run example
go run examples/basic.go
# Or build and run
go build -o uncloseai-examples examples/basic.go
./uncloseai-examples
Docker Usage
# Build
docker build -t uncloseai-go .
# Run examples
docker run -e MODEL_ENDPOINT_1="https://..." uncloseai-go
Compatibility
Tested with:
- ✅ vLLM (v0.5.0+)
- ✅ Ollama (v0.1.0+)
- ✅ OpenAI API (compatible endpoints)
Dependencies
Zero external dependencies - uses only Go standard library.
License
MIT License - See LICENSE file for details
Contributing
Contributions welcome! Please submit pull requests or open issues.
Support
For issues, questions, or contributions, please visit: https://github.com/yourusername/uncloseai
Changelog
v1.0.0 (2025-10-13)
- Initial release
- Streaming and non-streaming chat support
- Text-to-speech generation
- Automatic model discovery
- Type-safe API with comprehensive error handling
- Zero external dependencies