audacity-sdk 0.2.0

Rust SDK for the Audacity Investments AI gateway — Amazon Bedrock Converse-compatible API surface
Documentation
//! Live smoke test: `AUDACITY_API_KEY=… cargo run --example smoke`
use audacity_sdk::types::{ContentBlock, ConversationRole, InferenceConfiguration, Message};
use audacity_sdk::{Client, ConverseStreamOutput, Error};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::from_env()?;

    println!("--- non-streaming ---");
    let response = client
        .converse()
        .model_id("gpt-5.4-mini")
        .messages(
            Message::builder()
                .role(ConversationRole::User)
                .content(ContentBlock::Text("Reply with exactly: OK".into()))
                .build()?,
        )
        .inference_config(
            InferenceConfiguration::builder()
                .max_tokens(20)
                .temperature(0.0)
                .build(),
        )
        .send()
        .await?;
    let text = response
        .output()
        .ok_or("missing output")?
        .as_message()
        .map_err(|_| "not a message")?
        .content()
        .first()
        .ok_or("missing content")?
        .as_text()
        .map_err(|_| "not text")?;
    println!("text: {text:?}");
    println!(
        "stopReason: {:?} | usage: {:?} | latencyMs: {}",
        response.stop_reason(),
        response.usage(),
        response.metrics().latency_ms
    );

    println!("--- streaming ---");
    let mut output = client
        .converse_stream()
        .model_id("gpt-5.4-mini")
        .messages(
            Message::builder()
                .role(ConversationRole::User)
                .content(ContentBlock::Text(
                    "Count from 1 to 5, digits separated by spaces.".into(),
                ))
                .build()?,
        )
        .inference_config(InferenceConfiguration::builder().max_tokens(30).build())
        .send()
        .await?;
    let mut kinds: Vec<&'static str> = Vec::new();
    let mut text = String::new();
    while let Some(event) = output.stream.recv().await? {
        let kind = match &event {
            ConverseStreamOutput::MessageStart(_) => "messageStart",
            ConverseStreamOutput::ContentBlockStart(_) => "contentBlockStart",
            ConverseStreamOutput::ContentBlockDelta(e) => {
                if let audacity_sdk::ContentBlockDeltaPayload::Text(t) = &e.delta {
                    text.push_str(t);
                }
                "contentBlockDelta"
            }
            ConverseStreamOutput::ContentBlockStop(_) => "contentBlockStop",
            ConverseStreamOutput::MessageStop(_) => "messageStop",
            ConverseStreamOutput::Metadata(e) => {
                println!("metadata usage: {:?}", e.usage);
                "metadata"
            }
            _ => "other",
        };
        if kinds.last() != Some(&kind) {
            kinds.push(kind);
        }
    }
    println!("event kinds in order: {kinds:?}");
    println!("streamed text: {text:?}");

    println!("--- error: bad api key ---");
    let bad = Client::new(
        &audacity_sdk::Config::builder()
            .api_key("audacity_api_bogus")
            .build()?,
    )?;
    match bad
        .converse()
        .model_id("gpt-5.4-mini")
        .messages(
            Message::builder()
                .role(ConversationRole::User)
                .content(ContentBlock::Text("hi".into()))
                .build()?,
        )
        .send()
        .await
    {
        Err(Error::AccessDenied(d)) => {
            println!(
                "AccessDenied: {:?} {:?} {}",
                d.status_code, d.error_code, d.message
            )
        }
        other => println!("FAIL: expected AccessDenied, got {other:?}"),
    }

    println!("--- error: bad model ---");
    match client
        .converse()
        .model_id("not-a-real-model-xyz")
        .messages(
            Message::builder()
                .role(ConversationRole::User)
                .content(ContentBlock::Text("hi".into()))
                .build()?,
        )
        .send()
        .await
    {
        Err(e) => println!("{e:?}"),
        Ok(_) => println!("FAIL: no error"),
    }

    Ok(())
}