Skip to main content

mimo_api/types/
chat.rs

1//! Chat request types.
2
3use super::*;
4use serde::{Deserialize, Serialize};
5
6/// Available MiMo models.
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
8#[serde(rename_all = "kebab-case")]
9pub enum Model {
10    /// MiMo V2.5 Pro - Latest flagship model
11    MiMoV25Pro,
12    /// MiMo V2.5 - Balanced performance model
13    MiMoV25,
14    /// MiMo V2.5 TTS - Text-to-speech with preset voices
15    MiMoV25Tts,
16    /// MiMo V2.5 TTS VoiceDesign - Voice design via text description
17    MiMoV25TtsVoiceDesign,
18    /// MiMo V2.5 TTS VoiceClone - Voice cloning via audio sample
19    MiMoV25TtsVoiceClone,
20    /// MiMo V2 Pro - Agent-oriented flagship model
21    MiMoV2Pro,
22    /// MiMo V2 Omni - Multi-modal agent model
23    MiMoV2Omni,
24    /// MiMo V2 TTS - Text-to-speech model (legacy)
25    MiMoV2Tts,
26    /// MiMo V2 Flash - Fast and efficient model
27    MiMoV2Flash,
28}
29
30impl Model {
31    /// Get the model identifier string.
32    pub fn as_str(&self) -> &'static str {
33        match self {
34            Model::MiMoV25Pro => "mimo-v2.5-pro",
35            Model::MiMoV25 => "mimo-v2.5",
36            Model::MiMoV25Tts => "mimo-v2.5-tts",
37            Model::MiMoV25TtsVoiceDesign => "mimo-v2.5-tts-voicedesign",
38            Model::MiMoV25TtsVoiceClone => "mimo-v2.5-tts-voiceclone",
39            Model::MiMoV2Pro => "mimo-v2-pro",
40            Model::MiMoV2Omni => "mimo-v2-omni",
41            Model::MiMoV2Tts => "mimo-v2-tts",
42            Model::MiMoV2Flash => "mimo-v2-flash",
43        }
44    }
45}
46
47impl std::fmt::Display for Model {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "{}", self.as_str())
50    }
51}
52
53impl From<&str> for Model {
54    fn from(s: &str) -> Self {
55        match s {
56            "mimo-v2.5-pro" => Model::MiMoV25Pro,
57            "mimo-v2.5" => Model::MiMoV25,
58            "mimo-v2.5-tts" => Model::MiMoV25Tts,
59            "mimo-v2.5-tts-voicedesign" => Model::MiMoV25TtsVoiceDesign,
60            "mimo-v2.5-tts-voiceclone" => Model::MiMoV25TtsVoiceClone,
61            "mimo-v2-pro" => Model::MiMoV2Pro,
62            "mimo-v2-omni" => Model::MiMoV2Omni,
63            "mimo-v2-tts" => Model::MiMoV2Tts,
64            "mimo-v2-flash" => Model::MiMoV2Flash,
65            _ => Model::MiMoV2Flash,
66        }
67    }
68}
69
70/// Thinking mode configuration.
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
72#[serde(rename_all = "lowercase")]
73pub enum ThinkingType {
74    /// Enable thinking mode (deep reasoning)
75    Enabled,
76    /// Disable thinking mode
77    Disabled,
78}
79
80/// Thinking configuration.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Thinking {
83    /// Whether to enable thinking mode
84    #[serde(rename = "type")]
85    pub thinking_type: ThinkingType,
86}
87
88impl Thinking {
89    /// Create a new thinking configuration.
90    pub fn new(thinking_type: ThinkingType) -> Self {
91        Self { thinking_type }
92    }
93
94    /// Enable thinking mode.
95    pub fn enabled() -> Self {
96        Self::new(ThinkingType::Enabled)
97    }
98
99    /// Disable thinking mode.
100    pub fn disabled() -> Self {
101        Self::new(ThinkingType::Disabled)
102    }
103}
104
105/// Tool choice configuration.
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "lowercase")]
108pub enum ToolChoice {
109    /// Let the model decide whether to call tools
110    Auto,
111}
112
113/// Response format type.
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
115#[serde(rename_all = "lowercase")]
116pub enum ResponseFormatType {
117    /// Plain text response
118    Text,
119    /// JSON object response
120    JsonObject,
121}
122
123/// Response format configuration.
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ResponseFormat {
126    /// The format type
127    #[serde(rename = "type")]
128    pub format_type: ResponseFormatType,
129}
130
131impl ResponseFormat {
132    /// Create a new response format.
133    pub fn new(format_type: ResponseFormatType) -> Self {
134        Self { format_type }
135    }
136
137    /// Create a text response format.
138    pub fn text() -> Self {
139        Self::new(ResponseFormatType::Text)
140    }
141
142    /// Create a JSON object response format.
143    pub fn json_object() -> Self {
144        Self::new(ResponseFormatType::JsonObject)
145    }
146}
147
148/// Stop sequence configuration.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(untagged)]
151pub enum Stop {
152    /// Single stop sequence
153    Single(String),
154    /// Multiple stop sequences (max 4)
155    Multiple(Vec<String>),
156}
157
158/// Chat completion request.
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ChatRequest {
161    /// The model to use for generation
162    pub model: String,
163    /// List of messages in the conversation
164    pub messages: Vec<Message>,
165    /// Audio output parameters (for TTS)
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub audio: Option<Audio>,
168    /// Frequency penalty (-2.0 to 2.0)
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub frequency_penalty: Option<f32>,
171    /// Maximum completion tokens
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub max_completion_tokens: Option<u32>,
174    /// Presence penalty (-2.0 to 2.0)
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub presence_penalty: Option<f32>,
177    /// Response format
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub response_format: Option<ResponseFormat>,
180    /// Stop sequences
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub stop: Option<Stop>,
183    /// Enable streaming response
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub stream: Option<bool>,
186    /// Thinking mode configuration
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub thinking: Option<Thinking>,
189    /// Sampling temperature (0 to 1.5)
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub temperature: Option<f32>,
192    /// Tool choice
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub tool_choice: Option<ToolChoice>,
195    /// List of tools
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub tools: Option<Vec<Tool>>,
198    /// Top-p sampling (0.01 to 1.0)
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub top_p: Option<f32>,
201    /// Enable web search capability.
202    ///
203    /// **Note:** You must first enable the "联网服务插件" (Web Search Plugin)
204    /// in the MiMo console before using this feature. If the plugin is not
205    /// enabled, setting this to `true` will result in a 400 error.
206    #[serde(skip_serializing_if = "Option::is_none", rename = "webSearchEnabled")]
207    pub web_search_enabled: Option<bool>,
208}
209
210impl Default for ChatRequest {
211    fn default() -> Self {
212        Self {
213            model: "mimo-v2-flash".to_string(),
214            messages: Vec::new(),
215            audio: None,
216            frequency_penalty: None,
217            max_completion_tokens: None,
218            presence_penalty: None,
219            response_format: None,
220            stop: None,
221            stream: None,
222            thinking: None,
223            temperature: None,
224            tool_choice: None,
225            tools: None,
226            top_p: None,
227            web_search_enabled: None,
228        }
229    }
230}
231
232impl ChatRequest {
233    /// Create a new chat request with the specified model.
234    pub fn new(model: impl Into<String>) -> Self {
235        Self {
236            model: model.into(),
237            messages: Vec::new(),
238            audio: None,
239            frequency_penalty: None,
240            max_completion_tokens: None,
241            presence_penalty: None,
242            response_format: None,
243            stop: None,
244            stream: None,
245            thinking: None,
246            temperature: None,
247            tool_choice: None,
248            tools: None,
249            top_p: None,
250            web_search_enabled: None,
251        }
252    }
253
254    /// Create a chat request with the MiMo V2 Flash model.
255    pub fn flash() -> Self {
256        Self::new(Model::MiMoV2Flash.as_str())
257    }
258
259    /// Create a chat request with the MiMo V2 Pro model.
260    pub fn pro() -> Self {
261        Self::new(Model::MiMoV2Pro.as_str())
262    }
263
264    /// Create a chat request with the MiMo V2.5 Pro model.
265    pub fn v25_pro() -> Self {
266        Self::new(Model::MiMoV25Pro.as_str())
267    }
268
269    /// Create a chat request with the MiMo V2.5 model.
270    pub fn v25() -> Self {
271        Self::new(Model::MiMoV25.as_str())
272    }
273
274    /// Create a chat request with the MiMo V2 Omni model.
275    pub fn omni() -> Self {
276        Self::new(Model::MiMoV2Omni.as_str())
277    }
278
279    /// Create a chat request with the MiMo V2.5 TTS model (preset voices).
280    pub fn v25_tts() -> Self {
281        Self::new(Model::MiMoV25Tts.as_str())
282    }
283
284    /// Create a chat request with the MiMo V2.5 TTS VoiceDesign model.
285    pub fn v25_tts_voicedesign() -> Self {
286        Self::new(Model::MiMoV25TtsVoiceDesign.as_str())
287    }
288
289    /// Create a chat request with the MiMo V2.5 TTS VoiceClone model.
290    pub fn v25_tts_voiceclone() -> Self {
291        Self::new(Model::MiMoV25TtsVoiceClone.as_str())
292    }
293
294    /// Create a chat request with the MiMo V2 TTS model (legacy).
295    pub fn tts() -> Self {
296        Self::new(Model::MiMoV2Tts.as_str())
297    }
298
299    /// Set the model.
300    pub fn model(mut self, model: impl Into<String>) -> Self {
301        self.model = model.into();
302        self
303    }
304
305    /// Add a message to the conversation.
306    pub fn message(mut self, message: Message) -> Self {
307        self.messages.push(message);
308        self
309    }
310
311    /// Add multiple messages to the conversation.
312    pub fn messages(mut self, messages: Vec<Message>) -> Self {
313        self.messages = messages;
314        self
315    }
316
317    /// Add a system message.
318    pub fn system(mut self, content: impl Into<String>) -> Self {
319        self.messages
320            .push(Message::system(MessageContent::Text(content.into())));
321        self
322    }
323
324    /// Add a user message.
325    pub fn user(mut self, content: impl Into<String>) -> Self {
326        self.messages
327            .push(Message::user(MessageContent::Text(content.into())));
328        self
329    }
330
331    /// Add an assistant message.
332    pub fn assistant(mut self, content: impl Into<String>) -> Self {
333        self.messages
334            .push(Message::assistant(MessageContent::Text(content.into())));
335        self
336    }
337
338    /// Set audio output parameters (for TTS).
339    pub fn audio(mut self, audio: Audio) -> Self {
340        self.audio = Some(audio);
341        self
342    }
343
344    /// Set the frequency penalty.
345    pub fn frequency_penalty(mut self, penalty: f32) -> Self {
346        self.frequency_penalty = Some(penalty);
347        self
348    }
349
350    /// Set the maximum completion tokens.
351    pub fn max_completion_tokens(mut self, tokens: u32) -> Self {
352        self.max_completion_tokens = Some(tokens);
353        self
354    }
355
356    /// Set the presence penalty.
357    pub fn presence_penalty(mut self, penalty: f32) -> Self {
358        self.presence_penalty = Some(penalty);
359        self
360    }
361
362    /// Set the response format.
363    pub fn response_format(mut self, format: ResponseFormat) -> Self {
364        self.response_format = Some(format);
365        self
366    }
367
368    /// Set the stop sequences.
369    pub fn stop(mut self, stop: Stop) -> Self {
370        self.stop = Some(stop);
371        self
372    }
373
374    /// Enable or disable streaming.
375    pub fn stream(mut self, stream: bool) -> Self {
376        self.stream = Some(stream);
377        self
378    }
379
380    /// Set the thinking mode.
381    pub fn thinking(mut self, thinking: Thinking) -> Self {
382        self.thinking = Some(thinking);
383        self
384    }
385
386    /// Enable thinking mode.
387    pub fn enable_thinking(mut self) -> Self {
388        self.thinking = Some(Thinking::enabled());
389        self
390    }
391
392    /// Disable thinking mode.
393    pub fn disable_thinking(mut self) -> Self {
394        self.thinking = Some(Thinking::disabled());
395        self
396    }
397
398    /// Set the temperature.
399    pub fn temperature(mut self, temperature: f32) -> Self {
400        self.temperature = Some(temperature);
401        self
402    }
403
404    /// Set the tool choice.
405    pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
406        self.tool_choice = Some(choice);
407        self
408    }
409
410    /// Add a tool.
411    pub fn tool(mut self, tool: Tool) -> Self {
412        if self.tools.is_none() {
413            self.tools = Some(Vec::new());
414        }
415        self.tools.as_mut().unwrap().push(tool);
416        self
417    }
418
419    /// Set the tools.
420    pub fn tools(mut self, tools: Vec<Tool>) -> Self {
421        self.tools = Some(tools);
422        self
423    }
424
425    /// Set the top-p.
426    pub fn top_p(mut self, top_p: f32) -> Self {
427        self.top_p = Some(top_p);
428        self
429    }
430
431    /// Enable or disable web search.
432    ///
433    /// **Note:** You must first enable the "联网服务插件" (Web Search Plugin)
434    /// in the MiMo console before using this feature. If the plugin is not
435    /// enabled, setting this to `true` will result in a 400 error.
436    pub fn web_search_enabled(mut self, enabled: bool) -> Self {
437        self.web_search_enabled = Some(enabled);
438        self
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::*;
445
446    #[test]
447    fn test_model_as_str() {
448        assert_eq!(Model::MiMoV25Pro.as_str(), "mimo-v2.5-pro");
449        assert_eq!(Model::MiMoV25.as_str(), "mimo-v2.5");
450        assert_eq!(Model::MiMoV25Tts.as_str(), "mimo-v2.5-tts");
451        assert_eq!(
452            Model::MiMoV25TtsVoiceDesign.as_str(),
453            "mimo-v2.5-tts-voicedesign"
454        );
455        assert_eq!(
456            Model::MiMoV25TtsVoiceClone.as_str(),
457            "mimo-v2.5-tts-voiceclone"
458        );
459        assert_eq!(Model::MiMoV2Pro.as_str(), "mimo-v2-pro");
460        assert_eq!(Model::MiMoV2Omni.as_str(), "mimo-v2-omni");
461        assert_eq!(Model::MiMoV2Tts.as_str(), "mimo-v2-tts");
462        assert_eq!(Model::MiMoV2Flash.as_str(), "mimo-v2-flash");
463    }
464
465    #[test]
466    fn test_model_from_str() {
467        assert_eq!(Model::from("mimo-v2.5-pro"), Model::MiMoV25Pro);
468        assert_eq!(Model::from("mimo-v2.5-tts"), Model::MiMoV25Tts);
469        assert_eq!(Model::from("mimo-v2-pro"), Model::MiMoV2Pro);
470        assert_eq!(Model::from("mimo-v2-flash"), Model::MiMoV2Flash);
471        assert_eq!(Model::from("unknown"), Model::MiMoV2Flash);
472    }
473
474    #[test]
475    fn test_model_display() {
476        assert_eq!(format!("{}", Model::MiMoV25Pro), "mimo-v2.5-pro");
477    }
478
479    #[test]
480    fn test_thinking() {
481        let enabled = Thinking::enabled();
482        assert_eq!(enabled.thinking_type, ThinkingType::Enabled);
483
484        let disabled = Thinking::disabled();
485        assert_eq!(disabled.thinking_type, ThinkingType::Disabled);
486    }
487
488    #[test]
489    fn test_response_format() {
490        let text = ResponseFormat::text();
491        assert_eq!(text.format_type, ResponseFormatType::Text);
492
493        let json = ResponseFormat::json_object();
494        assert_eq!(json.format_type, ResponseFormatType::JsonObject);
495    }
496
497    #[test]
498    fn test_chat_request_builder() {
499        let request = ChatRequest::flash()
500            .system("You are a helpful assistant.")
501            .user("Hello!")
502            .temperature(0.7)
503            .max_completion_tokens(1024);
504
505        assert_eq!(request.model, "mimo-v2-flash");
506        assert_eq!(request.messages.len(), 2);
507        assert_eq!(request.temperature, Some(0.7));
508        assert_eq!(request.max_completion_tokens, Some(1024));
509    }
510
511    #[test]
512    fn test_chat_request_serialization() {
513        let request = ChatRequest::new("mimo-v2-flash")
514            .user("Hello!")
515            .temperature(0.5);
516
517        let json = serde_json::to_string(&request).unwrap();
518        assert!(json.contains("\"model\":\"mimo-v2-flash\""));
519        assert!(json.contains("\"temperature\":0.5"));
520    }
521}