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