Skip to main content

rig_core/
transcription.rs

1//! Audio transcription requests, normalized responses, and model interfaces.
2//!
3//! ```no_run
4//! use rig_core::DynModel;
5//! use rig_core::operation::Transcription;
6//! use rig_core::transcription::TranscriptionRequestBuilder;
7//!
8//! # async fn example(model: DynModel<Transcription>) -> Result<(), Box<dyn std::error::Error>> {
9//! let request = TranscriptionRequestBuilder::from_file("audio.wav")?.build();
10//! let response = model.call(request).await?;
11//! # let _ = response;
12//! # Ok(())
13//! # }
14//! ```
15use crate::completion::Usage;
16use crate::json_utils;
17use serde::{Deserialize, Serialize};
18use std::io;
19use std::{fs, path::Path};
20
21/// Transcript and normalized provider metadata, with provider-specific data
22/// available through [`Self::raw`].
23///
24/// Timings and speaker labels are filled only when the provider returned
25/// them, which usually takes a provider option passed through
26/// [`TranscriptionRequestBuilder::additional_params`], such as
27/// `{"response_format": "verbose_json", "timestamp_granularities": ["segment", "word"]}`
28/// on OpenAI-compatible endpoints.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct TranscriptionResponse {
31    /// The transcribed text.
32    pub text: String,
33    /// Provider-reported token usage. Unreported counters remain `None`;
34    /// this field does not contain audio duration.
35    #[serde(default)]
36    pub usage: Usage,
37    /// Stable descriptor name of the provider that produced this response,
38    /// for example `"openai"`. Always populated.
39    pub provider: String,
40    /// Provider-reported model identifier, when the wire response named one.
41    /// This is the model the provider says answered, not the model requested.
42    #[serde(default)]
43    pub model: Option<String>,
44    /// Provider-assigned response-scoped identifier, when reported.
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub response_id: Option<String>,
47    /// Transport request ID from HTTP headers, or `None` when unreported.
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub provider_request_id: Option<String>,
50    /// Language of the audio as the provider reported it, either a code
51    /// such as `"en"` or a name such as `"english"`.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub language: Option<String>,
54    /// Length of the audio in seconds, when reported.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub duration: Option<f64>,
57    /// The provider's confidence in the whole transcript, from 0 to 1.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub confidence: Option<f64>,
60    /// Timed stretches of the transcript, in the provider's order. Empty when
61    /// the provider returned none.
62    #[serde(default, skip_serializing_if = "Vec::is_empty")]
63    pub segments: Vec<TranscriptSegment>,
64    /// Timed words, in the provider's order. Empty when the provider returned
65    /// none.
66    #[serde(default, skip_serializing_if = "Vec::is_empty")]
67    pub words: Vec<TranscriptWord>,
68    /// Provider response document. Defaults to null until populated.
69    #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
70    pub raw: serde_json::Value,
71}
72
73impl TranscriptionResponse {
74    /// A response carrying `text`. The driver writes the provider, the
75    /// transport request id and the reply document; decoders set what the
76    /// provider reported.
77    pub fn new(text: impl Into<String>) -> Self {
78        Self {
79            text: text.into(),
80            usage: Usage::default(),
81            provider: String::new(),
82            model: None,
83            response_id: None,
84            provider_request_id: None,
85            language: None,
86            duration: None,
87            confidence: None,
88            segments: Vec::new(),
89            words: Vec::new(),
90            raw: serde_json::Value::Null,
91        }
92    }
93}
94
95/// A timed stretch of a transcript. Times are seconds from the start of the
96/// audio.
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub struct TranscriptSegment {
99    /// When the segment starts.
100    pub start: f64,
101    /// When the segment ends.
102    pub end: f64,
103    /// The segment's text, with the provider's spacing.
104    pub text: String,
105    /// Who spoke it, when the provider diarized the audio. Labels are the
106    /// provider's own, such as `"0"`, `"A"` or `"speaker_1"`, and are stable
107    /// within one response only.
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub speaker: Option<String>,
110}
111
112/// A timed word of a transcript. Times are seconds from the start of the
113/// audio.
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct TranscriptWord {
116    /// The word, with the provider's spacing and punctuation.
117    pub word: String,
118    /// When the word starts.
119    pub start: f64,
120    /// When the word ends.
121    pub end: f64,
122    /// Who spoke it, labeled as on [`TranscriptSegment::speaker`].
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub speaker: Option<String>,
125    /// The provider's confidence in the word, from 0 to 1.
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub confidence: Option<f64>,
128}
129
130/// Struct representing a general transcription request that can be sent to a transcription model provider.
131pub struct TranscriptionRequest {
132    /// The file data to be sent to the transcription model provider
133    pub data: Vec<u8>,
134    /// The file name to be used in the request
135    pub filename: String,
136    /// The language used in the response from the transcription model provider
137    pub language: Option<String>,
138    /// The prompt to be sent to the transcription model provider
139    pub prompt: Option<String>,
140    /// The temperature sent to the transcription model provider
141    pub temperature: Option<f64>,
142    /// Additional parameters to be sent to the transcription model provider
143    pub additional_params: Option<serde_json::Value>,
144}
145
146/// The filename a request carries until the caller or its file names it.
147const DEFAULT_FILENAME: &str = "file";
148
149/// Builds a transcription request over the supplied audio. The audio is not
150/// validated for format or nonemptiness.
151pub struct TranscriptionRequestBuilder {
152    request: TranscriptionRequest,
153}
154
155impl TranscriptionRequestBuilder {
156    /// A request over `data`, named `"file"` until [`Self::filename`] names it.
157    pub fn new(data: Vec<u8>) -> Self {
158        Self {
159            request: TranscriptionRequest {
160                data,
161                filename: DEFAULT_FILENAME.to_owned(),
162                language: None,
163                prompt: None,
164                temperature: None,
165                additional_params: None,
166            },
167        }
168    }
169
170    /// A request over the file at `path`, named after its base name. Reads the
171    /// file synchronously and returns I/O errors unchanged.
172    pub fn from_file(path: impl AsRef<Path>) -> io::Result<Self> {
173        let path = path.as_ref();
174        let filename = path
175            .file_name()
176            .map(|name| name.to_string_lossy().into_owned());
177        Ok(Self::new(fs::read(path)?).filename(filename))
178    }
179
180    /// Names the audio file; `None` restores the default name.
181    pub fn filename(mut self, filename: impl Into<Option<String>>) -> Self {
182        self.request.filename = filename
183            .into()
184            .unwrap_or_else(|| DEFAULT_FILENAME.to_owned());
185        self
186    }
187
188    /// Sets the output language.
189    pub fn language(mut self, language: String) -> Self {
190        self.request.language = Some(language);
191        self
192    }
193
194    /// Sets the prompt sent with the audio.
195    pub fn prompt(mut self, prompt: String) -> Self {
196        self.request.prompt = Some(prompt);
197        self
198    }
199
200    /// Sets the sampling temperature.
201    pub fn temperature(mut self, temperature: f64) -> Self {
202        self.request.temperature = Some(temperature);
203        self
204    }
205
206    /// Merges provider-specific parameters over earlier ones, key by key for
207    /// JSON objects; `None` clears existing parameters.
208    pub fn additional_params(mut self, params: impl Into<Option<serde_json::Value>>) -> Self {
209        self.request.additional_params =
210            json_utils::merge_params(self.request.additional_params.take(), params.into());
211        self
212    }
213
214    /// Builds the transcription request.
215    pub fn build(self) -> TranscriptionRequest {
216        self.request
217    }
218}
219
220#[cfg(test)]
221mod builder_tests;