SDK quickstart: Rust

The async-openai crate, pointed at our endpoint. Tokio runtime, full type safety, streaming via Streams.

Cargo dependencies

# Cargo.toml
[dependencies]
async-openai = "0.27"
tokio = { version = "1", features = ["full"] }
futures-util = "0.3"

Hello world

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

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = OpenAIConfig::new()
        .with_api_base("https://api.greenjoules.cloud/v1")
        .with_api_key(std::env::var("JC_API_KEY")?);
    let client = Client::with_config(config);

    let request = CreateChatCompletionRequestArgs::default()
        .model("auto")
        .messages([ChatCompletionRequestMessage::User(
            ChatCompletionRequestUserMessage {
                content: "hi".into(),
                ..Default::default()
            },
        )])
        .build()?;

    let response = client.chat().create(request).await?;
    println!("{}", response.choices[0].message.content.as_deref().unwrap_or(""));
    Ok(())
}

Streaming

use futures_util::StreamExt;

let mut stream = client.chat().create_stream(request).await?;
while let Some(chunk) = stream.next().await {
    let chunk = chunk?;
    for choice in chunk.choices {
        if let Some(content) = choice.delta.content {
            print!("{content}");
        }
    }
}

Embeddings

use async_openai::types::{CreateEmbeddingRequestArgs, EmbeddingInput};

let req = CreateEmbeddingRequestArgs::default()
    .model("text-embedding-3-small")
    .input(EmbeddingInput::StringArray(vec!["one".into(), "two".into()]))
    .build()?;

let r = client.embeddings().create(req).await?;
for d in r.data {
    println!("dim={} first6={:?}", d.embedding.len(), &d.embedding[..6]);
}

Joule headers

async-openai consumes the headers internally; to surface them, drop down a layer to reqwest directly for that specific call, or open a feature-request issue. The headers are also on the receipt accessible via jc receipt get rcpt_….

Errors

use async_openai::error::OpenAIError;

match client.chat().create(request).await {
    Ok(r) => println!("{}", r.choices[0].message.content.as_deref().unwrap_or("")),
    Err(OpenAIError::ApiError(api_err)) if api_err.r#type == "insufficient_balance" => {
        eprintln!("top up at portal.greenjoules.cloud");
    }
    Err(e) => Err(e)?,
}

Native CLI calls instead

If you're writing CLI tooling in Rust, the jc binary calls our API directly — you can shell out to it instead of pulling async-openai if you only need workloads + object store + receipts. See CLI reference.