audacity-sdk 0.1.0

Rust SDK for the Audacity Investments AI gateway — Amazon Bedrock Converse-compatible API surface
Documentation

audacity-sdk (Rust)

Rust SDK for the Audacity Investments AI gateway. Exposes an Amazon Bedrock Converse-shaped API so teams migrating off Bedrock can swap the client construction + API key and keep the rest of their code.


Installation

Add to Cargo.toml:

[dependencies]
audacity-sdk = "0.1.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

Quickstart

Converse (non-streaming)

use audacity_sdk::{Client, ContentBlock, ConversationRole, Message};

#[tokio::main]
async fn main() -> Result<(), audacity_sdk::Error> {
    // Reads AUDACITY_API_KEY from the environment.
    let client = Client::from_env()?;

    let response = client.converse()
        .model_id("gpt-5.4-mini")
        .messages(
            Message::builder()
                .role(ConversationRole::User)
                .content(ContentBlock::Text("Hello!".into()))
                .build()?
        )
        .inference_config(
            audacity_sdk::InferenceConfiguration::builder()
                .max_tokens(500)
                .temperature(0.2)
                .build()
        )
        .send()
        .await?;

    let text = response
        .output().unwrap()
        .as_message().unwrap()
        .content().first().unwrap()
        .as_text().unwrap();

    println!("{text}");
    Ok(())
}

ConverseStream (streaming)

use audacity_sdk::{Client, ContentBlock, ConversationRole, ConverseStreamOutput, Message};

#[tokio::main]
async fn main() -> Result<(), audacity_sdk::Error> {
    let client = Client::from_env()?;

    let mut output = client.converse_stream()
        .model_id("gpt-5.4-mini")
        .messages(
            Message::builder()
                .role(ConversationRole::User)
                .content(ContentBlock::Text("Tell me a joke".into()))
                .build()?
        )
        .send()
        .await?;

    while let Some(event) = output.stream.recv().await? {
        if let ConverseStreamOutput::ContentBlockDelta(e) = event {
            if let audacity_sdk::ContentBlockDeltaPayload::Text(t) = e.delta {
                print!("{t}");
            }
        }
    }
    println!();
    Ok(())
}

Images (vision models)

Bedrock-style image content blocks are supported in user messages. Pass raw bytes (base64-encoded for you) or a URL (Audacity extension):

use audacity_sdk::{ContentBlock, ImageBlock, ImageFormat, ImageSource};

let image_bytes = std::fs::read("chart.png")?;

let response = client.converse()
    .model_id("gpt-5.5")
    .messages(
        Message::builder()
            .role(ConversationRole::User)
            .content(ContentBlock::Text("What does this chart show?".into()))
            .content(ContentBlock::Image(ImageBlock {
                format: ImageFormat::Png,
                source: ImageSource::Bytes(image_bytes),
            }))
            .build()?
    )
    .send()
    .await?;

// Or reference a hosted image directly (not available in Bedrock):
// source: ImageSource::Url("https://example.com/photo.jpg".into())

ImageFormat is one of Png, Jpeg, Gif, Webp. Use a vision-capable model.


Migrating from aws-sdk-bedrockruntime

The SDK is designed to be a drop-in. Here is a side-by-side diff:

-use aws_sdk_bedrockruntime::Client;
-use aws_sdk_bedrockruntime::types::{ContentBlock, ConversationRole, Message};
+use audacity_sdk::{Client, ContentBlock, ConversationRole, Message};

-let config = aws_config::load_from_env().await;
-let client = Client::new(&config);
+let client = audacity_sdk::Client::from_env()?;   // reads AUDACITY_API_KEY

 let response = client.converse()
-    .model_id("anthropic.claude-3-5-sonnet-20241022-v2:0")
+    .model_id("gpt-5.4-mini")
     .messages(
         Message::builder()
             .role(ConversationRole::User)
-            .content(ContentBlock::Text("Hello".into()))
+            .content(ContentBlock::Text("Hello".into()))
             .build()?
     )
     .send()
     .await?;

Error handling

use audacity_sdk::{Client, Error};

match client.converse().model_id("m").messages(msg).send().await {
    Ok(resp) => { /* use resp */ }
    Err(Error::Throttling(d)) => {
        eprintln!("Rate limited (retry after {:?}s): {}", d.retry_after_seconds, d.message);
    }
    Err(Error::AccessDenied(d)) => {
        eprintln!("Access denied [{}]: {}", d.error_code.unwrap_or_default(), d.message);
    }
    Err(Error::MissingApiKey) => {
        eprintln!("Set AUDACITY_API_KEY");
    }
    Err(e) => eprintln!("Other error: {e}"),
}

Error variants

Variant Retryable Trigger
Validation no 400 / invalid request
AccessDenied no 401/403 / bad key
ServiceQuotaExceeded no 402 / budget exceeded
ResourceNotFound no 404 / unknown model
ModelTimeout yes 408 / TIMEOUT_ERROR
Throttling yes 429 / rate limited
ModelError no model-side error
ModelStreamError no stream-specific error
ServiceUnavailable yes 502/503/504
InternalServer yes 500
MissingApiKey no key in env/config
Sdk yes (network) network / decode failure

Every server error carries ErrorDetails { message, status_code, error_code, request_id, retry_after_seconds, raw_body }.


Configuration

Source API key Base URL
Explicit Config::builder().api_key("…") .base_url("…")
Environment AUDACITY_API_KEY AUDACITY_BASE_URL
Default https://portal.audacityinvestments.com

Other options:

use std::time::Duration;

let config = audacity_sdk::Config::builder()
    .api_key("audacity_api_…")
    .base_url("https://portal.audacityinvestments.com")
    .timeout(Duration::from_secs(120))
    .max_retries(2)   // 3 total attempts
    .build()?;

let client = audacity_sdk::Client::new(&config);

License

Copyright Audacity Investments. All rights reserved.
See LICENSE.