Skip to main content

openai_tools/realtime/
session.rs

1//! Session configuration types for the Realtime API.
2
3use crate::common::parameters::{Name, ParameterProperty, Parameters};
4use crate::common::tool::Tool;
5use serde::{Deserialize, Serialize};
6
7use super::audio::{AudioFormat, InputAudioNoiseReduction, InputAudioTranscription, Voice};
8use super::vad::TurnDetection;
9
10/// Tool definition for the Realtime API.
11///
12/// The Realtime API uses a flattened tool format, unlike the Chat Completions API
13/// which nests the function details under a `function` key.
14///
15/// # Example
16///
17/// ```rust
18/// use openai_tools::realtime::RealtimeTool;
19/// use openai_tools::common::parameters::ParameterProperty;
20///
21/// let tool = RealtimeTool::function(
22///     "get_weather",
23///     "Get the current weather for a location",
24///     vec![("location", ParameterProperty::from_string("The city name"))],
25/// );
26/// ```
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct RealtimeTool {
29    /// The type of tool (always "function" for function calling).
30    #[serde(rename = "type")]
31    pub type_name: String,
32
33    /// The name of the function.
34    pub name: String,
35
36    /// A description of what the function does.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub description: Option<String>,
39
40    /// The parameters the function accepts.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub parameters: Option<Parameters>,
43}
44
45impl RealtimeTool {
46    /// Create a new function tool.
47    pub fn function<T, U, V>(name: T, description: U, parameters: Vec<(V, ParameterProperty)>) -> Self
48    where
49        T: Into<String>,
50        U: Into<String>,
51        V: AsRef<str>,
52    {
53        let params: Vec<(Name, ParameterProperty)> = parameters.into_iter().map(|(k, v)| (k.as_ref().to_string(), v)).collect();
54
55        Self {
56            type_name: "function".to_string(),
57            name: name.into(),
58            description: Some(description.into()),
59            parameters: Some(Parameters::new(params, None)),
60        }
61    }
62}
63
64impl From<Tool> for RealtimeTool {
65    /// Convert a Chat API tool to a Realtime API tool.
66    fn from(tool: Tool) -> Self {
67        if let Some(func) = tool.function {
68            Self { type_name: "function".to_string(), name: func.name, description: func.description, parameters: func.parameters }
69        } else {
70            // Fallback for tools without function definition
71            Self { type_name: tool.type_name, name: tool.name.unwrap_or_default(), description: None, parameters: tool.parameters }
72        }
73    }
74}
75
76/// Session modality - what types of input/output are supported.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "lowercase")]
79#[non_exhaustive]
80pub enum Modality {
81    /// Text input/output
82    Text,
83    /// Audio input/output
84    Audio,
85}
86
87/// Session configuration sent in session.update events.
88#[derive(Debug, Clone, Default, Serialize, Deserialize)]
89pub struct SessionConfig {
90    /// Supported modalities for this session.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub modalities: Option<Vec<Modality>>,
93
94    /// System instructions for the model.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub instructions: Option<String>,
97
98    /// Voice for audio output.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub voice: Option<Voice>,
101
102    /// Format for input audio.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub input_audio_format: Option<AudioFormat>,
105
106    /// Format for output audio.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub output_audio_format: Option<AudioFormat>,
109
110    /// Configuration for input audio transcription.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub input_audio_transcription: Option<InputAudioTranscription>,
113
114    /// Noise reduction configuration.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub input_audio_noise_reduction: Option<InputAudioNoiseReduction>,
117
118    /// Turn detection configuration.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub turn_detection: Option<TurnDetection>,
121
122    /// Available tools for function calling.
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub tools: Option<Vec<RealtimeTool>>,
125
126    /// How to select tools.
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub tool_choice: Option<ToolChoice>,
129
130    /// Sampling temperature (0.6 to 1.2).
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub temperature: Option<f32>,
133
134    /// Maximum tokens in a response.
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub max_response_output_tokens: Option<MaxTokens>,
137}
138
139impl SessionConfig {
140    /// Create a new empty session configuration.
141    pub fn new() -> Self {
142        Self::default()
143    }
144
145    /// Set the modalities.
146    pub fn with_modalities(mut self, modalities: Vec<Modality>) -> Self {
147        self.modalities = Some(modalities);
148        self
149    }
150
151    /// Set the instructions.
152    pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
153        self.instructions = Some(instructions.into());
154        self
155    }
156
157    /// Set the voice.
158    pub fn with_voice(mut self, voice: Voice) -> Self {
159        self.voice = Some(voice);
160        self
161    }
162
163    /// Set the input audio format.
164    pub fn with_input_audio_format(mut self, format: AudioFormat) -> Self {
165        self.input_audio_format = Some(format);
166        self
167    }
168
169    /// Set the output audio format.
170    pub fn with_output_audio_format(mut self, format: AudioFormat) -> Self {
171        self.output_audio_format = Some(format);
172        self
173    }
174
175    /// Set the transcription configuration.
176    pub fn with_transcription(mut self, config: InputAudioTranscription) -> Self {
177        self.input_audio_transcription = Some(config);
178        self
179    }
180
181    /// Set the turn detection configuration.
182    pub fn with_turn_detection(mut self, config: TurnDetection) -> Self {
183        self.turn_detection = Some(config);
184        self
185    }
186
187    /// Set the available tools.
188    ///
189    /// Accepts `Tool` from the common module and converts to `RealtimeTool`.
190    pub fn with_tools(mut self, tools: Vec<Tool>) -> Self {
191        self.tools = Some(tools.into_iter().map(RealtimeTool::from).collect());
192        self
193    }
194
195    /// Set the available realtime tools directly.
196    pub fn with_realtime_tools(mut self, tools: Vec<RealtimeTool>) -> Self {
197        self.tools = Some(tools);
198        self
199    }
200
201    /// Set the tool choice.
202    pub fn with_tool_choice(mut self, choice: ToolChoice) -> Self {
203        self.tool_choice = Some(choice);
204        self
205    }
206
207    /// Set the temperature.
208    pub fn with_temperature(mut self, temp: f32) -> Self {
209        self.temperature = Some(temp);
210        self
211    }
212
213    /// Set the maximum response tokens.
214    pub fn with_max_tokens(mut self, max: MaxTokens) -> Self {
215        self.max_response_output_tokens = Some(max);
216        self
217    }
218}
219
220/// Maximum tokens configuration.
221#[derive(Debug, Clone)]
222pub enum MaxTokens {
223    /// Specific token count limit.
224    Count(u32),
225    /// No limit (infinite).
226    Infinite,
227}
228
229impl serde::Serialize for MaxTokens {
230    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
231    where
232        S: serde::Serializer,
233    {
234        match self {
235            MaxTokens::Count(n) => serializer.serialize_u32(*n),
236            MaxTokens::Infinite => serializer.serialize_str("inf"),
237        }
238    }
239}
240
241impl<'de> serde::Deserialize<'de> for MaxTokens {
242    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
243    where
244        D: serde::Deserializer<'de>,
245    {
246        use serde::de::{self, Visitor};
247
248        struct MaxTokensVisitor;
249
250        impl<'de> Visitor<'de> for MaxTokensVisitor {
251            type Value = MaxTokens;
252
253            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
254                formatter.write_str("a positive integer or \"inf\"")
255            }
256
257            fn visit_u64<E>(self, value: u64) -> std::result::Result<MaxTokens, E>
258            where
259                E: de::Error,
260            {
261                Ok(MaxTokens::Count(value as u32))
262            }
263
264            fn visit_str<E>(self, value: &str) -> std::result::Result<MaxTokens, E>
265            where
266                E: de::Error,
267            {
268                if value == "inf" {
269                    Ok(MaxTokens::Infinite)
270                } else {
271                    Err(de::Error::custom(format!("unknown value: {}", value)))
272                }
273            }
274        }
275
276        deserializer.deserialize_any(MaxTokensVisitor)
277    }
278}
279
280impl From<u32> for MaxTokens {
281    fn from(count: u32) -> Self {
282        Self::Count(count)
283    }
284}
285
286/// How to select tools for function calling.
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(untagged)]
289pub enum ToolChoice {
290    /// Simple string-based choices: "auto", "none", "required"
291    Simple(SimpleToolChoice),
292    /// Force a specific function by name
293    Function(NamedToolChoice),
294}
295
296/// Simple tool choice options.
297#[derive(Debug, Clone, Serialize, Deserialize)]
298#[serde(rename_all = "lowercase")]
299pub enum SimpleToolChoice {
300    /// Model decides whether to use tools.
301    Auto,
302    /// Never use tools.
303    None,
304    /// Must use a tool.
305    Required,
306}
307
308impl Default for ToolChoice {
309    fn default() -> Self {
310        Self::Simple(SimpleToolChoice::Auto)
311    }
312}
313
314impl ToolChoice {
315    /// Model decides whether to use tools.
316    pub fn auto() -> Self {
317        Self::Simple(SimpleToolChoice::Auto)
318    }
319
320    /// Never use tools.
321    pub fn none() -> Self {
322        Self::Simple(SimpleToolChoice::None)
323    }
324
325    /// Must use a tool.
326    pub fn required() -> Self {
327        Self::Simple(SimpleToolChoice::Required)
328    }
329
330    /// Force a specific function by name.
331    pub fn function(name: impl Into<String>) -> Self {
332        Self::Function(NamedToolChoice { type_name: "function".to_string(), function: NamedFunction { name: name.into() } })
333    }
334}
335
336/// Named tool choice for forcing a specific function.
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct NamedToolChoice {
339    #[serde(rename = "type")]
340    pub type_name: String,
341    pub function: NamedFunction,
342}
343
344/// Function name for named tool choice.
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct NamedFunction {
347    pub name: String,
348}
349
350/// Response creation configuration.
351#[derive(Debug, Clone, Default, Serialize, Deserialize)]
352pub struct ResponseCreateConfig {
353    /// Modalities for this response.
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub modalities: Option<Vec<Modality>>,
356
357    /// Instructions for this response.
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub instructions: Option<String>,
360
361    /// Voice for this response.
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub voice: Option<Voice>,
364
365    /// Output audio format.
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub output_audio_format: Option<AudioFormat>,
368
369    /// Tools available for this response.
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub tools: Option<Vec<RealtimeTool>>,
372
373    /// Tool choice for this response.
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub tool_choice: Option<ToolChoice>,
376
377    /// Temperature for this response.
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub temperature: Option<f32>,
380
381    /// Maximum output tokens.
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub max_output_tokens: Option<MaxTokens>,
384
385    /// Whether to include in conversation history.
386    /// Set to "none" to exclude.
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub conversation: Option<String>,
389
390    /// Metadata for this response.
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub metadata: Option<serde_json::Value>,
393}
394
395impl ResponseCreateConfig {
396    /// Create a new empty response configuration.
397    pub fn new() -> Self {
398        Self::default()
399    }
400
401    /// Set the modalities.
402    pub fn with_modalities(mut self, modalities: Vec<Modality>) -> Self {
403        self.modalities = Some(modalities);
404        self
405    }
406
407    /// Set the instructions.
408    pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
409        self.instructions = Some(instructions.into());
410        self
411    }
412
413    /// Set the voice.
414    pub fn with_voice(mut self, voice: Voice) -> Self {
415        self.voice = Some(voice);
416        self
417    }
418
419    /// Exclude this response from conversation history.
420    pub fn out_of_band(mut self) -> Self {
421        self.conversation = Some("none".to_string());
422        self
423    }
424}