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.push(Message::system(MessageContent::Text(content.into())));
320        self
321    }
322
323    /// Add a user message.
324    pub fn user(mut self, content: impl Into<String>) -> Self {
325        self.messages.push(Message::user(MessageContent::Text(content.into())));
326        self
327    }
328
329    /// Add an assistant message.
330    pub fn assistant(mut self, content: impl Into<String>) -> Self {
331        self.messages.push(Message::assistant(MessageContent::Text(content.into())));
332        self
333    }
334
335    /// Set audio output parameters (for TTS).
336    pub fn audio(mut self, audio: Audio) -> Self {
337        self.audio = Some(audio);
338        self
339    }
340
341    /// Set the frequency penalty.
342    pub fn frequency_penalty(mut self, penalty: f32) -> Self {
343        self.frequency_penalty = Some(penalty);
344        self
345    }
346
347    /// Set the maximum completion tokens.
348    pub fn max_completion_tokens(mut self, tokens: u32) -> Self {
349        self.max_completion_tokens = Some(tokens);
350        self
351    }
352
353    /// Set the presence penalty.
354    pub fn presence_penalty(mut self, penalty: f32) -> Self {
355        self.presence_penalty = Some(penalty);
356        self
357    }
358
359    /// Set the response format.
360    pub fn response_format(mut self, format: ResponseFormat) -> Self {
361        self.response_format = Some(format);
362        self
363    }
364
365    /// Set the stop sequences.
366    pub fn stop(mut self, stop: Stop) -> Self {
367        self.stop = Some(stop);
368        self
369    }
370
371    /// Enable or disable streaming.
372    pub fn stream(mut self, stream: bool) -> Self {
373        self.stream = Some(stream);
374        self
375    }
376
377    /// Set the thinking mode.
378    pub fn thinking(mut self, thinking: Thinking) -> Self {
379        self.thinking = Some(thinking);
380        self
381    }
382
383    /// Enable thinking mode.
384    pub fn enable_thinking(mut self) -> Self {
385        self.thinking = Some(Thinking::enabled());
386        self
387    }
388
389    /// Disable thinking mode.
390    pub fn disable_thinking(mut self) -> Self {
391        self.thinking = Some(Thinking::disabled());
392        self
393    }
394
395    /// Set the temperature.
396    pub fn temperature(mut self, temperature: f32) -> Self {
397        self.temperature = Some(temperature);
398        self
399    }
400
401    /// Set the tool choice.
402    pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
403        self.tool_choice = Some(choice);
404        self
405    }
406
407    /// Add a tool.
408    pub fn tool(mut self, tool: Tool) -> Self {
409        if self.tools.is_none() {
410            self.tools = Some(Vec::new());
411        }
412        self.tools.as_mut().unwrap().push(tool);
413        self
414    }
415
416    /// Set the tools.
417    pub fn tools(mut self, tools: Vec<Tool>) -> Self {
418        self.tools = Some(tools);
419        self
420    }
421
422    /// Set the top-p.
423    pub fn top_p(mut self, top_p: f32) -> Self {
424        self.top_p = Some(top_p);
425        self
426    }
427
428    /// Enable or disable web search.
429    ///
430    /// **Note:** You must first enable the "联网服务插件" (Web Search Plugin)
431    /// in the MiMo console before using this feature. If the plugin is not
432    /// enabled, setting this to `true` will result in a 400 error.
433    pub fn web_search_enabled(mut self, enabled: bool) -> Self {
434        self.web_search_enabled = Some(enabled);
435        self
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn test_model_as_str() {
445        assert_eq!(Model::MiMoV25Pro.as_str(), "mimo-v2.5-pro");
446        assert_eq!(Model::MiMoV25.as_str(), "mimo-v2.5");
447        assert_eq!(Model::MiMoV25Tts.as_str(), "mimo-v2.5-tts");
448        assert_eq!(Model::MiMoV25TtsVoiceDesign.as_str(), "mimo-v2.5-tts-voicedesign");
449        assert_eq!(Model::MiMoV25TtsVoiceClone.as_str(), "mimo-v2.5-tts-voiceclone");
450        assert_eq!(Model::MiMoV2Pro.as_str(), "mimo-v2-pro");
451        assert_eq!(Model::MiMoV2Omni.as_str(), "mimo-v2-omni");
452        assert_eq!(Model::MiMoV2Tts.as_str(), "mimo-v2-tts");
453        assert_eq!(Model::MiMoV2Flash.as_str(), "mimo-v2-flash");
454    }
455
456    #[test]
457    fn test_model_from_str() {
458        assert_eq!(Model::from("mimo-v2.5-pro"), Model::MiMoV25Pro);
459        assert_eq!(Model::from("mimo-v2.5-tts"), Model::MiMoV25Tts);
460        assert_eq!(Model::from("mimo-v2-pro"), Model::MiMoV2Pro);
461        assert_eq!(Model::from("mimo-v2-flash"), Model::MiMoV2Flash);
462        assert_eq!(Model::from("unknown"), Model::MiMoV2Flash);
463    }
464
465    #[test]
466    fn test_model_display() {
467        assert_eq!(format!("{}", Model::MiMoV25Pro), "mimo-v2.5-pro");
468    }
469
470    #[test]
471    fn test_thinking() {
472        let enabled = Thinking::enabled();
473        assert_eq!(enabled.thinking_type, ThinkingType::Enabled);
474
475        let disabled = Thinking::disabled();
476        assert_eq!(disabled.thinking_type, ThinkingType::Disabled);
477    }
478
479    #[test]
480    fn test_response_format() {
481        let text = ResponseFormat::text();
482        assert_eq!(text.format_type, ResponseFormatType::Text);
483
484        let json = ResponseFormat::json_object();
485        assert_eq!(json.format_type, ResponseFormatType::JsonObject);
486    }
487
488    #[test]
489    fn test_chat_request_builder() {
490        let request = ChatRequest::flash()
491            .system("You are a helpful assistant.")
492            .user("Hello!")
493            .temperature(0.7)
494            .max_completion_tokens(1024);
495
496        assert_eq!(request.model, "mimo-v2-flash");
497        assert_eq!(request.messages.len(), 2);
498        assert_eq!(request.temperature, Some(0.7));
499        assert_eq!(request.max_completion_tokens, Some(1024));
500    }
501
502    #[test]
503    fn test_chat_request_serialization() {
504        let request = ChatRequest::new("mimo-v2-flash")
505            .user("Hello!")
506            .temperature(0.5);
507
508        let json = serde_json::to_string(&request).unwrap();
509        assert!(json.contains("\"model\":\"mimo-v2-flash\""));
510        assert!(json.contains("\"temperature\":0.5"));
511    }
512}