deepinfra_client_rs/
audio_transcription.rs1use crate::client::DeepinfraClient;
2use bon::Builder;
3use reqwest::multipart;
4use serde::Deserialize;
5use std::path::Path;
6use tracing::instrument;
7
8const AUDIO_TRANSCRIPTION_API_URL: &str =
9 "https://api.deepinfra.com/v1/openai/audio/transcriptions";
10
11#[derive(Debug, Deserialize)]
12pub struct AudioTranscriptionResponse {
13 pub text: String,
14}
15
16#[derive(Debug, Deserialize)]
17pub struct ErrorDetail {
18 loc: Vec<String>,
19 msg: String,
20 #[serde(rename = "type")]
21 error_type: String,
22}
23
24#[derive(Debug, Deserialize)]
25#[serde(untagged)]
26pub enum ErrorResponse {
27 Simple { detail: String },
28 Detailed { detail: Vec<ErrorDetail> },
29}
30
31#[derive(Debug, Deserialize)]
32#[serde(untagged)]
33enum AudioTranscriptionApiResponse {
34 TranscriptionResponse(AudioTranscriptionResponse),
35 ErrorResponse(ErrorResponse),
36}
37
38#[derive(Debug, thiserror::Error)]
39pub enum AudioTranscriptionError {
40 #[error("Request error: {0}")]
41 ReqwestError(#[from] reqwest::Error),
42 #[error("File not found: {0}")]
43 FileNotFoundError(String),
44 #[error("IO error: {0}")]
45 IoError(#[from] std::io::Error),
46 #[error("Error response: {0}")]
47 ErrorResponse(String),
48}
49
50#[derive(Debug)]
51pub enum FileSource {
52 Filepath(Box<Path>),
53 Bytes { buffer: Vec<u8>, file_name: String },
54}
55
56#[derive(Builder)]
57pub struct AudioTranscriptionRequest {
68 #[builder(into)]
70 language: Option<String>,
71 #[builder(default = "openai/whisper-large-v3-turbo".to_string(), into)]
73 model: String,
74 #[builder(into)]
76 prompt: Option<String>,
77 #[builder(default = "json".to_string())]
79 response_format: String,
80 #[builder(into)]
82 source: FileSource,
83 #[builder(into)]
85 temperature: Option<f32>,
86 #[builder(into)]
88 timestamp_granularities: Option<Vec<String>>,
89}
90
91impl DeepinfraClient {
92 #[instrument(skip(self, request))]
108 pub async fn audio_transcription(
109 &self,
110 request: AudioTranscriptionRequest,
111 ) -> Result<AudioTranscriptionResponse, AudioTranscriptionError> {
112 let mut form = multipart::Form::new()
113 .text("model", request.model.to_string())
114 .text("response_format", request.response_format.to_string());
115
116 match request.source {
117 FileSource::Filepath(file_path) => {
118 let file_path = file_path.as_ref();
119
120 if !file_path.exists() {
121 return Err(AudioTranscriptionError::FileNotFoundError(
122 file_path.to_string_lossy().into_owned(),
123 ));
124 }
125
126 form = form.file("file", file_path).await?;
127 }
128 FileSource::Bytes { buffer, file_name } => {
129 let part = multipart::Part::bytes(buffer).file_name(file_name);
130 form = form.part("file", part);
131 }
132 }
133
134 if let Some(language) = request.language {
135 form = form.text("language", language.to_string());
136 }
137 if let Some(prompt) = request.prompt {
138 form = form.text("prompt", prompt.to_string());
139 }
140 if let Some(temperature) = request.temperature {
141 form = form.text("temperature", temperature.to_string());
142 }
143 if let Some(timestamp_granularities) = request.timestamp_granularities {
144 for granularity in timestamp_granularities {
145 form = form.text("timestamp_granularities[]", granularity.to_string());
146 }
147 }
148
149 let response = self
150 .client
151 .post(AUDIO_TRANSCRIPTION_API_URL)
152 .multipart(form)
153 .send()
154 .await?
155 .json::<AudioTranscriptionApiResponse>()
156 .await?;
157
158 match response {
159 AudioTranscriptionApiResponse::TranscriptionResponse(response) => Ok(response),
160 AudioTranscriptionApiResponse::ErrorResponse(error) => match error {
161 ErrorResponse::Simple { detail } => {
162 Err(AudioTranscriptionError::ErrorResponse(detail))
163 }
164 ErrorResponse::Detailed { detail } => {
165 let error_details: Vec<String> = detail
166 .iter()
167 .map(|d| format!("{}: {}", d.loc.join("."), d.msg))
168 .collect();
169 Err(AudioTranscriptionError::ErrorResponse(
170 error_details.join(", "),
171 ))
172 }
173 },
174 }
175 }
176}