uncloseai.

Rust Examples - Free LLM & TTS AI Service

Rust Examples

This page demonstrates how to use the uncloseai. API endpoints with Rust using the async-openai community library. All examples use the same OpenAI-compatible API interface, making it easy to switch between different models and endpoints.

Available Endpoints:

Both chat hostnames currently serve the same model, turboderp/Qwen3.8-27B-exl3 (read live from /v1/models). Hostnames are routing labels, not model names: every example below asks the endpoint which model it serves instead of hard-coding an id, so a model swap on the cards never breaks your code. The served model reasons before it answers (the reasoning arrives in a separate reasoning field), so give it room: the samples ask for 600 tokens because at 150 the budget ran out before the answer began.

Many endpoints from environment variables

Our clients (uncloseai-cli, uncloseai.js, opencompletion and the downloadable implementation for every language on this site) share one convention, so an endpoint is configuration, never code:

VariableMeaning
MODEL_ENDPOINT_NBase URL of the N-th OpenAI-compatible endpoint, ending in /v1. The only required variable. Scan N from 0 upward and skip gaps: uncloseai-cli counts from 0, the per-language implementations from 1.
MODEL_API_KEY_NBearer key for endpoint N. Unset means no key, which is all our free relay needs; a cloud endpoint sets one.
MODEL_ID_NModel id to send. Unset means ask /v1/models and take the first id, the rule used by every sample on this page.
MODEL_LABEL_NA display name for endpoint N; defaults to its hostname.
TTS_ENDPOINT_NThe same shape for text-to-speech endpoints.
export MODEL_ENDPOINT_0="https://hermes.ai.unturf.com/v1"
export MODEL_ENDPOINT_1="https://qwen.ai.unturf.com/v1"
# export MODEL_API_KEY_1="sk-..."      # only for an endpoint that needs one
export TTS_ENDPOINT_0="https://speech.ai.unturf.com/v1"

The first endpoint is the default; the rest fail over, in order. The Rust implementation under /languages/ reads these variables and discovers each endpoint's models at startup.

Rust Client Installation

Add the async-openai crate to your Cargo.toml:

[dependencies]
async-openai = "0.24"
tokio = { version = "1", features = ["full"] }

Non-Streaming Examples

Non-streaming mode waits for the complete response before returning. This is simpler to use but provides no intermediate feedback during generation.

Using hermes.ai.unturf.com (RTX 4090)

use async_openai::{
    config::OpenAIConfig,
    types::{ChatCompletionRequestMessage, CreateChatCompletionRequestArgs},
    Client,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = OpenAIConfig::new()
        .with_api_key("choose-any-value")
        .with_api_base("https://hermes.ai.unturf.com/v1");

    let client = Client::with_config(config);

    // Ask the endpoint which model it serves; never hard-code an id.
    let model = client.models().list().await?.data[0].id.clone();

    let request = CreateChatCompletionRequestArgs::default()
        .model(model)
        .messages(vec![ChatCompletionRequestMessage::User(
            "Give a Python Fizzbuzz solution in one line of code?".into()
        )])
        .temperature(0.5)
        .max_tokens(600u32)
        .build()?;

    let response = client.chat().create(request).await?;

    println!("{}", response.choices[0].message.content.as_ref().unwrap());

    Ok(())
}

Using qwen.ai.unturf.com (RTX 3090)

use async_openai::{
    config::OpenAIConfig,
    types::{ChatCompletionRequestMessage, CreateChatCompletionRequestArgs},
    Client,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = OpenAIConfig::new()
        .with_api_key("choose-any-value")
        .with_api_base("https://qwen.ai.unturf.com/v1");

    let client = Client::with_config(config);

    // Ask the endpoint which model it serves; never hard-code an id.
    let model = client.models().list().await?.data[0].id.clone();

    let request = CreateChatCompletionRequestArgs::default()
        .model(model)
        .messages(vec![ChatCompletionRequestMessage::User(
            "Give a Python Fizzbuzz solution in one line of code?".into()
        )])
        .temperature(0.5)
        .max_tokens(600u32)
        .build()?;

    let response = client.chat().create(request).await?;

    println!("{}", response.choices[0].message.content.as_ref().unwrap());

    Ok(())
}

Streaming Examples

Streaming mode returns chunks of the response as they are generated, providing real-time feedback. This is ideal for interactive applications and long responses.

Using hermes.ai.unturf.com (RTX 4090)

use async_openai::{
    config::OpenAIConfig,
    types::{ChatCompletionRequestMessage, CreateChatCompletionRequestArgs},
    Client,
};
use futures::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = OpenAIConfig::new()
        .with_api_key("choose-any-value")
        .with_api_base("https://hermes.ai.unturf.com/v1");

    let client = Client::with_config(config);

    // Ask the endpoint which model it serves; never hard-code an id.
    let model = client.models().list().await?.data[0].id.clone();

    let request = CreateChatCompletionRequestArgs::default()
        .model(model)
        .messages(vec![ChatCompletionRequestMessage::User(
            "Give a Python Fizzbuzz solution in one line of code?".into()
        )])
        .temperature(0.5)
        .max_tokens(600u32)
        .build()?;

    let mut stream = client.chat().create_stream(request).await?;

    while let Some(result) = stream.next().await {
        match result {
            Ok(response) => {
                for choice in response.choices {
                    if let Some(content) = &choice.delta.content {
                        print!("{}", content);
                    }
                }
            }
            Err(err) => eprintln!("Error: {}", err),
        }
    }

    Ok(())
}

Using qwen.ai.unturf.com (RTX 3090)

use async_openai::{
    config::OpenAIConfig,
    types::{ChatCompletionRequestMessage, CreateChatCompletionRequestArgs},
    Client,
};
use futures::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = OpenAIConfig::new()
        .with_api_key("choose-any-value")
        .with_api_base("https://qwen.ai.unturf.com/v1");

    let client = Client::with_config(config);

    // Ask the endpoint which model it serves; never hard-code an id.
    let model = client.models().list().await?.data[0].id.clone();

    let request = CreateChatCompletionRequestArgs::default()
        .model(model)
        .messages(vec![ChatCompletionRequestMessage::User(
            "Give a Python Fizzbuzz solution in one line of code?".into()
        )])
        .temperature(0.5)
        .max_tokens(600u32)
        .build()?;

    let mut stream = client.chat().create_stream(request).await?;

    while let Some(result) = stream.next().await {
        match result {
            Ok(response) => {
                for choice in response.choices {
                    if let Some(content) = &choice.delta.content {
                        print!("{}", content);
                    }
                }
            }
            Err(err) => eprintln!("Error: {}", err),
        }
    }

    Ok(())
}

Text-to-Speech Example

Generate audio speech from text using the TTS endpoint. The audio is saved as an MP3 file.

This sample adds reqwest = { version = "0.12", features = ["json"] } and serde_json = "1" to the dependencies above.

use std::fs::File;
use std::io::Write;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // async-openai 0.24's Voice enum is closed to OpenAI's voice names, so call
    // the speech endpoint directly: it is one JSON POST.
    let bytes = reqwest::Client::new()
        .post("https://speech.ai.unturf.com/v1/audio/speech")
        .bearer_auth("YOLO")
        .json(&serde_json::json!({
            "model": "tts-1-f5",
            "voice": "foxhop",
            "speed": 0.9,
            "input": "I think so therefore, Today is a wonderful day to grow something people love!"
        }))
        .send()
        .await?
        .error_for_status()?
        .bytes()
        .await?;

    File::create("speech.mp3")?.write_all(&bytes)?;
    println!("Audio saved to speech.mp3");
    Ok(())
}