1use std::pin::Pin;
13
14use async_trait::async_trait;
15use futures::Stream;
16use serde::{Deserialize, Serialize};
17use ts_rs::TS;
18
19use crate::error::AiMuxError;
20use crate::shared::{SharedHeaders, SharedProviderMetadata, SharedProviderOptions, Warning};
21use crate::{AbortSignal, retry, timeout};
22
23#[derive(Debug, Clone, Serialize, Deserialize, TS)]
25#[ts(export)]
26pub enum AudioInput {
27 Binary(Vec<u8>),
29 Base64(String),
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, TS)]
35#[ts(export)]
36pub enum AudioChunk {
37 Binary(Vec<u8>),
39 Base64(String),
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, TS)]
45#[ts(export)]
46pub struct TranscriptionSegment {
47 pub text: String,
49 pub start_second: f64,
51 pub end_second: f64,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, TS)]
59#[ts(export)]
60pub struct TranscriptionCallOptions {
61 pub audio: AudioInput,
63
64 pub media_type: String,
66
67 pub provider_options: Option<SharedProviderOptions>,
69
70 #[serde(skip)]
72 #[ts(skip)]
73 pub abort_signal: Option<AbortSignal>,
74
75 pub max_retries: Option<u32>,
77
78 pub timeout: Option<crate::options::TimeoutConfiguration>,
80
81 pub headers: Option<SharedHeaders>,
83}
84
85impl TranscriptionCallOptions {
86 pub fn new(audio: AudioInput, media_type: impl Into<String>) -> Self {
88 Self {
89 audio,
90 media_type: media_type.into(),
91 provider_options: None,
92 abort_signal: None,
93 max_retries: None,
94 timeout: None,
95 headers: None,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, TS)]
104#[ts(export)]
105pub struct TranscriptionResult {
106 pub text: String,
108
109 pub segments: Vec<TranscriptionSegment>,
111
112 pub language: Option<String>,
114
115 pub duration_in_seconds: Option<f64>,
117
118 pub warnings: Vec<Warning>,
120
121 pub request: Option<TranscriptionRequest>,
123
124 pub response: TranscriptionResponse,
126
127 pub provider_metadata: Option<SharedProviderMetadata>,
129}
130
131#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
133#[ts(export)]
134pub struct TranscriptionRequest {
135 pub body: Option<String>,
137}
138
139#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
141#[ts(export)]
142pub struct TranscriptionResponse {
143 pub timestamp: Option<String>,
145 pub model_id: Option<String>,
147 pub headers: Option<SharedHeaders>,
149 pub body: Option<serde_json::Value>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, TS)]
155#[ts(export)]
156pub struct InputAudioFormat {
157 pub format_type: String,
159 pub rate: Option<u32>,
161}
162
163pub struct TranscriptionStreamOptions {
167 pub audio: Pin<Box<dyn Stream<Item = AudioChunk> + Send>>,
169
170 pub input_audio_format: InputAudioFormat,
172
173 pub provider_options: Option<SharedProviderOptions>,
175
176 pub abort_signal: Option<AbortSignal>,
178
179 pub headers: Option<SharedHeaders>,
181
182 pub include_raw_chunks: bool,
185
186 pub timeout: Option<crate::options::TimeoutConfiguration>,
191}
192
193impl std::fmt::Debug for TranscriptionStreamOptions {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 f.debug_struct("TranscriptionStreamOptions")
196 .field("input_audio_format", &self.input_audio_format)
197 .field("provider_options", &self.provider_options)
198 .field("abort_signal", &self.abort_signal)
199 .field("headers", &self.headers)
200 .field("include_raw_chunks", &self.include_raw_chunks)
201 .field("timeout", &self.timeout)
202 .field("audio", &"<stream>")
203 .finish()
204 }
205}
206
207#[derive(Debug, Serialize, Deserialize, TS)]
211#[ts(export)]
212pub enum TranscriptionStreamPart {
213 StreamStart { warnings: Vec<Warning> },
215 TranscriptDelta {
217 id: Option<String>,
218 delta: String,
219 provider_metadata: Option<SharedProviderMetadata>,
220 },
221 TranscriptPartial {
223 id: Option<String>,
224 text: String,
225 start_second: Option<f64>,
226 duration_in_seconds: Option<f64>,
227 channel_index: Option<u32>,
228 provider_metadata: Option<SharedProviderMetadata>,
229 },
230 TranscriptFinal {
232 id: Option<String>,
233 text: String,
234 start_second: Option<f64>,
235 end_second: Option<f64>,
236 channel_index: Option<u32>,
237 provider_metadata: Option<SharedProviderMetadata>,
238 },
239 ResponseMetadata {
241 timestamp: Option<String>,
242 model_id: Option<String>,
243 headers: Option<SharedHeaders>,
244 body: Option<serde_json::Value>,
245 },
246 Finish {
248 text: String,
249 segments: Vec<TranscriptionSegment>,
250 language: Option<String>,
251 duration_in_seconds: Option<f64>,
252 provider_metadata: Option<SharedProviderMetadata>,
253 },
254 Raw { raw_value: serde_json::Value },
256 Error { error: serde_json::Value },
258}
259
260pub struct TranscriptionStreamResult {
264 pub stream: Pin<Box<dyn Stream<Item = Result<TranscriptionStreamPart, AiMuxError>> + Send>>,
266 pub request: Option<TranscriptionRequest>,
268 pub response: Option<TranscriptionResponse>,
270}
271
272impl std::fmt::Debug for TranscriptionStreamResult {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 f.debug_struct("TranscriptionStreamResult")
275 .field("request", &self.request)
276 .field("response", &self.response)
277 .field("stream", &"<stream>")
278 .finish()
279 }
280}
281
282#[async_trait]
286pub trait TranscriptionModel: Send + Sync {
287 fn specification_version(&self) -> &'static str {
289 "v4"
290 }
291
292 fn provider(&self) -> &str;
294
295 fn model_id(&self) -> &str;
297
298 fn retry_config(&self) -> crate::retry::RetryConfig {
299 crate::retry::RetryConfig::default()
300 }
301
302 async fn do_generate(
306 &self,
307 options: &TranscriptionCallOptions,
308 ) -> Result<TranscriptionResult, AiMuxError>;
309
310 async fn do_stream(
316 &self,
317 _options: TranscriptionStreamOptions,
318 ) -> Result<TranscriptionStreamResult, AiMuxError> {
319 Err(AiMuxError::UnsupportedFunctionality(format!(
320 "transcription streaming is not supported by provider `{}`",
321 self.provider()
322 )))
323 }
324}
325
326pub async fn transcribe(
332 model: &dyn TranscriptionModel,
333 options: TranscriptionCallOptions,
334) -> Result<TranscriptionResult, AiMuxError> {
335 let timeout = timeout::OperationTimeout::new(options.timeout.unwrap_or_default())?;
336 let abort_signal = options.abort_signal.clone();
337 let retries = retry::prepare_retries(
338 options.max_retries,
339 model.retry_config(),
340 abort_signal.clone(),
341 );
342 timeout::run(
343 retries.retry(|| model.do_generate(&options)),
344 abort_signal.as_ref(),
345 timeout,
346 )
347 .await
348}
349
350pub async fn stream_transcribe(
360 model: &dyn TranscriptionModel,
361 options: TranscriptionStreamOptions,
362) -> Result<TranscriptionStreamResult, AiMuxError> {
363 model.do_stream(options).await
364}