Skip to content

Rust

Obstruo exposes an OpenAI-compatible API. The async-openai crate supports custom base URLs and works with Obstruo out of the box.

Prerequisites

  • An ObstruoKey and endpoint URL - see Quick Start
  • A Logical Model configured in your project
  • Rust 1.75 or later

async-openai

Add the dependency to Cargo.toml:

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

Chat completions

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

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = OpenAIConfig::new()
        .with_api_key(std::env::var("OBSTRUO_API_KEY")?)
        .with_api_base(std::env::var("OBSTRUO_BASE_URL")?);

    let client = Client::with_config(config);

    let request = CreateChatCompletionRequestArgs::default()
        .model("obstruo-chat")
        .messages([ChatCompletionRequestUserMessageArgs::default()
            .content("Summarise this contract: ...")
            .build()?
            .into()])
        .build()?;

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

    Ok(())
}

Streaming

use futures::StreamExt;

let request = CreateChatCompletionRequestArgs::default()
    .model("obstruo-chat")
    .messages([ChatCompletionRequestUserMessageArgs::default()
        .content("Tell me a story.")
        .build()?
        .into()])
    .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(e) => eprintln!("Error: {e}"),
    }
}

Embeddings

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

let request = CreateEmbeddingRequestArgs::default()
    .model("obstruo-embedding")
    .input(EmbeddingInput::String("The quick brown fox".to_string()))
    .build()?;

let response = client.embeddings().create(request).await?;
let vector = &response.data[0].embedding;

Environment variables

async-openai reads OPENAI_API_KEY and OPENAI_API_BASE automatically:

export OPENAI_API_KEY="your-obstruo-key"
export OPENAI_API_BASE="https://api.obstruo.ai/your-org/v1"
let client = Client::new(); // reads env vars automatically

Or set them explicitly:

let config = OpenAIConfig::new()
    .with_api_key(std::env::var("OBSTRUO_API_KEY")?)
    .with_api_base(std::env::var("OBSTRUO_BASE_URL")?);

let client = Client::with_config(config);

Reading gateway metadata

async-openai deserialises only known OpenAI fields. To access the obstruo key, use reqwest directly:

[dependencies]
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Routing {
    resolved_model: String,
    provider_type: String,
    duration_ms: u64,
}

#[derive(Debug, Deserialize)]
struct Redaction {
    total_entities: u32,
    by_category: std::collections::HashMap<String, u32>,
    duration_ms: u64,
}

#[derive(Debug, Deserialize)]
struct ObstruoMeta {
    routing: Routing,
    redaction: Redaction,
}

let body = serde_json::json!({
    "model": "obstruo-chat",
    "messages": [{"role": "user", "content": "Hello!"}]
});

let resp = reqwest::Client::new()
    .post(format!("{}/chat/completions", std::env::var("OBSTRUO_BASE_URL")?))
    .header(AUTHORIZATION, format!("Bearer {}", std::env::var("OBSTRUO_API_KEY")?))
    .header(CONTENT_TYPE, "application/json")
    .json(&body)
    .send()
    .await?
    .json::<serde_json::Value>()
    .await?;

if let Some(meta) = resp.get("obstruo") {
    let routing   = &meta["routing"];
    let redaction = &meta["redaction"];
    println!("{}", routing["resolved_model"]);    // "gpt-4o-mini"
    println!("{}", routing["provider_type"]);     // "OpenAI"
    println!("{}", redaction["total_entities"]);  // 0 = no PII found
}

Next steps