use mistralai_client::v1::{ chat::{ ChatMessage, ChatParams, ChatResponseChoiceFinishReason, ContentPart, ImageUrl, }, client::Client, constants::Model, }; mod setup; fn make_client() -> Client { Client::new(None, None, None, None).unwrap() } #[test] fn test_multimodal_chat_with_image_url() { setup::setup(); let client = make_client(); // Use a small, publicly accessible image let msg = ChatMessage::new_user_message_with_images(vec![ ContentPart::Text { text: "Describe this image in one sentence.".to_string(), }, ContentPart::ImageUrl { image_url: ImageUrl { url: "https://picsum.photos/id/237/200/300".to_string(), detail: None, }, }, ]); let model = Model::new("pixtral-large-latest".to_string()); let options = ChatParams { max_tokens: Some(100), temperature: Some(0.0), ..Default::default() }; let response = client.chat(model, vec![msg], Some(options)).unwrap(); assert_eq!( response.choices[0].finish_reason, ChatResponseChoiceFinishReason::Stop ); let text = response.choices[0].message.content.text(); assert!(!text.is_empty(), "Expected non-empty description"); assert!(response.usage.total_tokens > 0); } #[tokio::test] async fn test_multimodal_chat_with_image_url_async() { setup::setup(); let client = make_client(); let msg = ChatMessage::new_user_message_with_images(vec![ ContentPart::Text { text: "What colors do you see in this image? Reply in one sentence.".to_string(), }, ContentPart::ImageUrl { image_url: ImageUrl { url: "https://picsum.photos/id/237/200/300".to_string(), detail: None, }, }, ]); let model = Model::new("pixtral-large-latest".to_string()); let options = ChatParams { max_tokens: Some(100), temperature: Some(0.0), ..Default::default() }; let response = client .chat_async(model, vec![msg], Some(options)) .await .unwrap(); let text = response.choices[0].message.content.text(); assert!(!text.is_empty(), "Expected non-empty description"); assert!(response.usage.total_tokens > 0); } #[test] fn test_mixed_text_and_image_messages() { setup::setup(); let client = make_client(); // First message: just text let msg1 = ChatMessage::new_user_message("I'm going to show you an image next."); // Second message: text + image let msg2 = ChatMessage::new_user_message_with_images(vec![ ContentPart::Text { text: "Here it is. What do you see?".to_string(), }, ContentPart::ImageUrl { image_url: ImageUrl { url: "https://picsum.photos/id/237/200/300".to_string(), detail: None, }, }, ]); let model = Model::new("pixtral-large-latest".to_string()); let options = ChatParams { max_tokens: Some(100), temperature: Some(0.0), ..Default::default() }; let response = client.chat(model, vec![msg1, msg2], Some(options)).unwrap(); let text = response.choices[0].message.content.text(); assert!(!text.is_empty()); } #[test] fn test_text_only_message_still_works() { setup::setup(); let client = make_client(); // Verify that text-only messages (the common case) still work fine // with the new ChatMessageContent type let msg = ChatMessage::new_user_message("What is 7 + 8?"); let model = Model::mistral_small_latest(); let options = ChatParams { temperature: Some(0.0), max_tokens: Some(50), ..Default::default() }; let response = client.chat(model, vec![msg], Some(options)).unwrap(); let text = response.choices[0].message.content.text(); assert!(text.contains("15"), "Expected '15', got: {text}"); } #[test] fn test_reasoning_field_presence() { setup::setup(); let client = make_client(); // Normal model should not have reasoning let msg = ChatMessage::new_user_message("What is 2 + 2?"); let model = Model::mistral_small_latest(); let options = ChatParams { temperature: Some(0.0), max_tokens: Some(50), ..Default::default() }; let response = client.chat(model, vec![msg], Some(options)).unwrap(); // reasoning is None for non-Magistral models (or it might just be absent) // This test verifies the field deserializes correctly either way let _ = response.choices[0].reasoning.as_ref(); }