@@ -37,6 +37,7 @@ impl_client_trait!(
3737 ) ,
3838 ( prepare_embeddings, openai_embeddings) ,
3939 ( noop_prepare_rerank, noop_rerank) ,
40+ ( openai_audio_transcriptions) ,
4041) ;
4142
4243fn prepare_chat_completions (
@@ -406,3 +407,90 @@ fn normalize_function_id(value: &str) -> Option<String> {
406407 Some ( value. to_string ( ) )
407408 }
408409}
410+
411+ pub async fn openai_audio_transcriptions (
412+ self_ : & OpenAIClient ,
413+ client : & reqwest:: Client ,
414+ data : TranscriptionData ,
415+ ) -> Result < String > {
416+ let api_key = self_. get_api_key ( ) ?;
417+ let api_base = self_
418+ . get_api_base ( )
419+ . unwrap_or_else ( |_| API_BASE . to_string ( ) ) ;
420+ let model_name = self_. model . real_name ( ) . to_string ( ) ;
421+ openai_compatible_audio_transcriptions ( client, & api_base, Some ( & api_key) , & model_name, data)
422+ . await
423+ }
424+
425+ pub async fn openai_compatible_audio_transcriptions (
426+ client : & reqwest:: Client ,
427+ api_base : & str ,
428+ api_key : Option < & str > ,
429+ model_name : & str ,
430+ data : TranscriptionData ,
431+ ) -> Result < String > {
432+ let url = format ! ( "{}/audio/transcriptions" , api_base. trim_end_matches( '/' ) ) ;
433+
434+ let file_bytes = tokio:: fs:: read ( & data. path )
435+ . await
436+ . with_context ( || format ! ( "Failed to read audio file '{}'" , data. path. display( ) ) ) ?;
437+
438+ let file_name = data
439+ . path
440+ . file_name ( )
441+ . and_then ( |n| n. to_str ( ) )
442+ . unwrap_or ( "audio" )
443+ . to_string ( ) ;
444+
445+ let mime_type = audio_mime_type ( & data. path ) ;
446+
447+ let file_part = reqwest:: multipart:: Part :: bytes ( file_bytes)
448+ . file_name ( file_name)
449+ . mime_str ( & mime_type)
450+ . context ( "Invalid MIME type for audio file" ) ?;
451+
452+ let mut form = reqwest:: multipart:: Form :: new ( )
453+ . part ( "file" , file_part)
454+ . text ( "model" , model_name. to_string ( ) ) ;
455+
456+ if let Some ( prompt) = data. prompt {
457+ form = form. text ( "prompt" , prompt) ;
458+ }
459+
460+ let mut request = client. post ( & url) . multipart ( form) ;
461+ if let Some ( key) = api_key {
462+ request = request. bearer_auth ( key) ;
463+ }
464+
465+ let res = request. send ( ) . await ?;
466+ let status = res. status ( ) ;
467+ let body: serde_json:: Value = res. json ( ) . await ?;
468+
469+ if !status. is_success ( ) {
470+ catch_error ( & body, status. as_u16 ( ) ) ?;
471+ }
472+
473+ body[ "text" ]
474+ . as_str ( )
475+ . map ( |s| s. to_string ( ) )
476+ . ok_or_else ( || anyhow:: anyhow!( "Invalid transcription response: {body}" ) )
477+ }
478+
479+ fn audio_mime_type ( path : & std:: path:: Path ) -> String {
480+ let ext = path
481+ . extension ( )
482+ . and_then ( |e| e. to_str ( ) )
483+ . unwrap_or ( "" )
484+ . to_lowercase ( ) ;
485+ match ext. as_str ( ) {
486+ "mp3" | "mpeg" | "mpga" => "audio/mpeg" ,
487+ "mp4" => "audio/mp4" ,
488+ "m4a" => "audio/m4a" ,
489+ "ogg" | "oga" => "audio/ogg" ,
490+ "wav" => "audio/wav" ,
491+ "webm" => "audio/webm" ,
492+ "flac" => "audio/flac" ,
493+ _ => "application/octet-stream" ,
494+ }
495+ . to_string ( )
496+ }
0 commit comments