qai-sdk 0.1.6

Universal Rust SDK for AI Providers
Documentation
use crate::core::types::{SpeechOptions, SpeechResult};
use anyhow::anyhow;
use async_trait::async_trait;
use reqwest::Client;
use serde::Serialize;

/// `OpenAI` speech (text-to-speech) model.
pub struct OpenAISpeechModel {
    pub api_key: String,
    pub base_url: String,
    pub client: Client,
}

impl OpenAISpeechModel {
    #[must_use]
    pub fn new(api_key: String) -> Self {
        Self {
            api_key,
            base_url: "https://api.openai.com/v1".to_string(),
            client: Client::new(),
        }
    }
}

#[derive(Serialize)]
struct OpenAISpeechRequest {
    model: String,
    input: String,
    voice: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    response_format: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    speed: Option<f32>,
}

#[async_trait]
impl crate::core::SpeechModel for OpenAISpeechModel {
    async fn synthesize(&self, options: SpeechOptions) -> crate::core::Result<SpeechResult> {
        let request = OpenAISpeechRequest {
            model: options.model_id,
            input: options.input,
            voice: options.voice,
            response_format: options.response_format,
            speed: options.speed,
        };

        let resp = self
            .client
            .post(format!("{}/audio/speech", self.base_url))
            .header("Authorization", &format!("Bearer {}", self.api_key))
            .json(&request)
            .send()
            .await?;

        if !resp.status().is_success() {
            let error_text = resp.text().await?;
            return Err(anyhow!("OpenAI Speech API error: {error_text}").into());
        }

        let audio = resp.bytes().await?.to_vec();

        Ok(SpeechResult { audio })
    }
}