feat: add Agents API, Conversations API, and multimodal support (v1.1.0)
Agents API (beta): create, get, update, delete, list agents with tools, handoffs, completion args, and guardrails support. Conversations API (beta): create, append, history, messages, restart, delete, list conversations. Supports agent-backed and model-only conversations with function calling and handoff execution modes. Multimodal: ChatMessageContent enum (Text/Parts) with ContentPart variants for text and image_url. Backwards-compatible constructors. new_user_message_with_images() for mixed content messages. Chat: reasoning field on ChatResponseChoice for Magistral models. HTTP: PATCH methods for agent updates. 81 tests (30 live API integration + 35 serde unit + 16 existing).
This commit is contained in:
204
tests/v1_chat_multimodal_test.rs
Normal file
204
tests/v1_chat_multimodal_test.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use mistralai_client::v1::chat::*;
|
||||
|
||||
#[test]
|
||||
fn test_content_part_text_serialization() {
|
||||
let part = ContentPart::Text {
|
||||
text: "hello".to_string(),
|
||||
};
|
||||
let json = serde_json::to_value(&part).unwrap();
|
||||
assert_eq!(json["type"], "text");
|
||||
assert_eq!(json["text"], "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_part_image_url_serialization() {
|
||||
let part = ContentPart::ImageUrl {
|
||||
image_url: ImageUrl {
|
||||
url: "https://example.com/image.png".to_string(),
|
||||
detail: Some("high".to_string()),
|
||||
},
|
||||
};
|
||||
let json = serde_json::to_value(&part).unwrap();
|
||||
assert_eq!(json["type"], "image_url");
|
||||
assert_eq!(json["image_url"]["url"], "https://example.com/image.png");
|
||||
assert_eq!(json["image_url"]["detail"], "high");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_content_part_image_url_no_detail() {
|
||||
let part = ContentPart::ImageUrl {
|
||||
image_url: ImageUrl {
|
||||
url: "data:image/png;base64,abc123".to_string(),
|
||||
detail: None,
|
||||
},
|
||||
};
|
||||
let json = serde_json::to_value(&part).unwrap();
|
||||
assert_eq!(json["type"], "image_url");
|
||||
assert!(json["image_url"].get("detail").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_text() {
|
||||
let content = ChatMessageContent::Text("hello world".to_string());
|
||||
assert_eq!(content.text(), "hello world");
|
||||
assert_eq!(content.as_text(), Some("hello world"));
|
||||
assert!(!content.has_images());
|
||||
assert_eq!(content.to_string(), "hello world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_parts() {
|
||||
let content = ChatMessageContent::Parts(vec![
|
||||
ContentPart::Text {
|
||||
text: "What is this? ".to_string(),
|
||||
},
|
||||
ContentPart::ImageUrl {
|
||||
image_url: ImageUrl {
|
||||
url: "https://example.com/cat.jpg".to_string(),
|
||||
detail: None,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(content.text(), "What is this? ");
|
||||
assert!(content.as_text().is_none());
|
||||
assert!(content.has_images());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_text_serialization() {
|
||||
let content = ChatMessageContent::Text("hello".to_string());
|
||||
let json = serde_json::to_value(&content).unwrap();
|
||||
assert_eq!(json, serde_json::json!("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_parts_serialization() {
|
||||
let content = ChatMessageContent::Parts(vec![ContentPart::Text {
|
||||
text: "hello".to_string(),
|
||||
}]);
|
||||
let json = serde_json::to_value(&content).unwrap();
|
||||
assert!(json.is_array());
|
||||
assert_eq!(json[0]["type"], "text");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_text_deserialization() {
|
||||
let content: ChatMessageContent = serde_json::from_value(serde_json::json!("hello")).unwrap();
|
||||
assert_eq!(content.text(), "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_parts_deserialization() {
|
||||
let content: ChatMessageContent = serde_json::from_value(serde_json::json!([
|
||||
{"type": "text", "text": "describe this"},
|
||||
{"type": "image_url", "image_url": {"url": "https://example.com/img.jpg"}}
|
||||
]))
|
||||
.unwrap();
|
||||
assert_eq!(content.text(), "describe this");
|
||||
assert!(content.has_images());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_user_message_text_content() {
|
||||
let msg = ChatMessage::new_user_message("hello");
|
||||
let json = serde_json::to_value(&msg).unwrap();
|
||||
assert_eq!(json["role"], "user");
|
||||
assert_eq!(json["content"], "hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_user_message_with_images() {
|
||||
let msg = ChatMessage::new_user_message_with_images(vec![
|
||||
ContentPart::Text {
|
||||
text: "What is this?".to_string(),
|
||||
},
|
||||
ContentPart::ImageUrl {
|
||||
image_url: ImageUrl {
|
||||
url: "data:image/png;base64,abc123".to_string(),
|
||||
detail: None,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
let json = serde_json::to_value(&msg).unwrap();
|
||||
assert_eq!(json["role"], "user");
|
||||
assert!(json["content"].is_array());
|
||||
assert_eq!(json["content"][0]["type"], "text");
|
||||
assert_eq!(json["content"][1]["type"], "image_url");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_from_str() {
|
||||
let content: ChatMessageContent = "test".into();
|
||||
assert_eq!(content.text(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_message_content_from_string() {
|
||||
let content: ChatMessageContent = String::from("test").into();
|
||||
assert_eq!(content.text(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_response_choice_with_reasoning() {
|
||||
let json = serde_json::json!({
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "The answer is 42."
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
"reasoning": "Let me think about this step by step..."
|
||||
});
|
||||
|
||||
let choice: ChatResponseChoice = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(choice.reasoning.as_deref(), Some("Let me think about this step by step..."));
|
||||
assert_eq!(choice.message.content.text(), "The answer is 42.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat_response_choice_without_reasoning() {
|
||||
let json = serde_json::json!({
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Hello"
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
});
|
||||
|
||||
let choice: ChatResponseChoice = serde_json::from_value(json).unwrap();
|
||||
assert!(choice.reasoning.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_chat_response_roundtrip() {
|
||||
let json = serde_json::json!({
|
||||
"id": "chat-abc123",
|
||||
"object": "chat.completion",
|
||||
"created": 1711000000,
|
||||
"model": "mistral-medium-latest",
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Hi there!"
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}],
|
||||
"usage": {
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 5,
|
||||
"total_tokens": 15
|
||||
}
|
||||
});
|
||||
|
||||
let resp: ChatResponse = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(resp.choices[0].message.content.text(), "Hi there!");
|
||||
assert_eq!(resp.usage.total_tokens, 15);
|
||||
|
||||
// Re-serialize and verify
|
||||
let re_json = serde_json::to_value(&resp).unwrap();
|
||||
assert_eq!(re_json["choices"][0]["message"]["content"], "Hi there!");
|
||||
}
|
||||
Reference in New Issue
Block a user