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:
372
tests/v1_agents_api_test.rs
Normal file
372
tests/v1_agents_api_test.rs
Normal file
@@ -0,0 +1,372 @@
|
||||
use mistralai_client::v1::{
|
||||
agents::*,
|
||||
client::Client,
|
||||
};
|
||||
|
||||
mod setup;
|
||||
|
||||
fn make_client() -> Client {
|
||||
Client::new(None, None, None, None).unwrap()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sync tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn test_create_and_delete_agent() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-create-delete".to_string(),
|
||||
description: Some("Integration test agent".to_string()),
|
||||
instructions: Some("You are a test agent. Respond briefly.".to_string()),
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let agent = client.create_agent(&req).unwrap();
|
||||
assert!(!agent.id.is_empty());
|
||||
assert_eq!(agent.name, "test-create-delete");
|
||||
assert_eq!(agent.model, "mistral-medium-latest");
|
||||
assert_eq!(agent.object, "agent");
|
||||
// Version starts at 0 in the API
|
||||
assert!(agent.description.as_deref() == Some("Integration test agent"));
|
||||
assert!(agent.instructions.as_deref() == Some("You are a test agent. Respond briefly."));
|
||||
|
||||
// Cleanup
|
||||
let del = client.delete_agent(&agent.id).unwrap();
|
||||
assert!(del.deleted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_agent_with_tools() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-agent-tools".to_string(),
|
||||
description: None,
|
||||
instructions: Some("You can search.".to_string()),
|
||||
tools: Some(vec![
|
||||
AgentTool::function(
|
||||
"search".to_string(),
|
||||
"Search for things".to_string(),
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"}
|
||||
},
|
||||
"required": ["query"]
|
||||
}),
|
||||
),
|
||||
AgentTool::web_search(),
|
||||
]),
|
||||
handoffs: None,
|
||||
completion_args: Some(CompletionArgs {
|
||||
temperature: Some(0.3),
|
||||
..Default::default()
|
||||
}),
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let agent = client.create_agent(&req).unwrap();
|
||||
assert_eq!(agent.tools.len(), 2);
|
||||
assert!(matches!(&agent.tools[0], AgentTool::Function(_)));
|
||||
assert!(matches!(&agent.tools[1], AgentTool::WebSearch {}));
|
||||
|
||||
// Verify completion_args round-tripped
|
||||
let args = agent.completion_args.as_ref().unwrap();
|
||||
assert!((args.temperature.unwrap() - 0.3).abs() < 0.01);
|
||||
|
||||
client.delete_agent(&agent.id).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_agent() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-get-agent".to_string(),
|
||||
description: Some("Get test".to_string()),
|
||||
instructions: None,
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let created = client.create_agent(&req).unwrap();
|
||||
let fetched = client.get_agent(&created.id).unwrap();
|
||||
|
||||
assert_eq!(fetched.id, created.id);
|
||||
assert_eq!(fetched.name, "test-get-agent");
|
||||
assert_eq!(fetched.model, "mistral-medium-latest");
|
||||
assert_eq!(fetched.description.as_deref(), Some("Get test"));
|
||||
|
||||
client.delete_agent(&created.id).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_agent() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-update-agent".to_string(),
|
||||
description: Some("Before update".to_string()),
|
||||
instructions: Some("Original instructions".to_string()),
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let created = client.create_agent(&req).unwrap();
|
||||
|
||||
let update = UpdateAgentRequest {
|
||||
name: Some("test-update-agent-renamed".to_string()),
|
||||
description: Some("After update".to_string()),
|
||||
instructions: Some("Updated instructions".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let updated = client.update_agent(&created.id, &update).unwrap();
|
||||
assert_eq!(updated.id, created.id);
|
||||
assert_eq!(updated.name, "test-update-agent-renamed");
|
||||
assert_eq!(updated.description.as_deref(), Some("After update"));
|
||||
assert_eq!(updated.instructions.as_deref(), Some("Updated instructions"));
|
||||
// Version should have incremented
|
||||
assert!(updated.version >= created.version);
|
||||
|
||||
client.delete_agent(&created.id).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_agents() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
// Create two agents
|
||||
let req1 = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-list-agent-1".to_string(),
|
||||
description: None,
|
||||
instructions: None,
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
let req2 = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-list-agent-2".to_string(),
|
||||
description: None,
|
||||
instructions: None,
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let a1 = client.create_agent(&req1).unwrap();
|
||||
let a2 = client.create_agent(&req2).unwrap();
|
||||
|
||||
let list = client.list_agents().unwrap();
|
||||
assert!(list.data.len() >= 2);
|
||||
|
||||
// Our two agents should be in the list
|
||||
let ids: Vec<&str> = list.data.iter().map(|a| a.id.as_str()).collect();
|
||||
assert!(ids.contains(&a1.id.as_str()));
|
||||
assert!(ids.contains(&a2.id.as_str()));
|
||||
|
||||
// Cleanup
|
||||
client.delete_agent(&a1.id).unwrap();
|
||||
client.delete_agent(&a2.id).unwrap();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Async tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_and_delete_agent_async() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-async-create-delete".to_string(),
|
||||
description: Some("Async integration test".to_string()),
|
||||
instructions: Some("Respond briefly.".to_string()),
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let agent = client.create_agent_async(&req).await.unwrap();
|
||||
assert!(!agent.id.is_empty());
|
||||
assert_eq!(agent.name, "test-async-create-delete");
|
||||
assert_eq!(agent.object, "agent");
|
||||
|
||||
let del = client.delete_agent_async(&agent.id).await.unwrap();
|
||||
assert!(del.deleted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_agent_async() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-async-get".to_string(),
|
||||
description: None,
|
||||
instructions: None,
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let created = client.create_agent_async(&req).await.unwrap();
|
||||
let fetched = client.get_agent_async(&created.id).await.unwrap();
|
||||
assert_eq!(fetched.id, created.id);
|
||||
assert_eq!(fetched.name, "test-async-get");
|
||||
|
||||
client.delete_agent_async(&created.id).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_agent_async() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-async-update".to_string(),
|
||||
description: Some("Before".to_string()),
|
||||
instructions: None,
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let created = client.create_agent_async(&req).await.unwrap();
|
||||
|
||||
let update = UpdateAgentRequest {
|
||||
description: Some("After".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let updated = client.update_agent_async(&created.id, &update).await.unwrap();
|
||||
assert_eq!(updated.description.as_deref(), Some("After"));
|
||||
|
||||
client.delete_agent_async(&created.id).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_agents_async() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-async-list".to_string(),
|
||||
description: None,
|
||||
instructions: None,
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let agent = client.create_agent_async(&req).await.unwrap();
|
||||
let list = client.list_agents_async().await.unwrap();
|
||||
assert!(list.data.iter().any(|a| a.id == agent.id));
|
||||
|
||||
client.delete_agent_async(&agent.id).await.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_agent_with_handoffs() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
// Create a target agent first
|
||||
let target_req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-handoff-target".to_string(),
|
||||
description: Some("Target agent for handoff".to_string()),
|
||||
instructions: Some("You handle math questions.".to_string()),
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
let target = client.create_agent(&target_req).unwrap();
|
||||
|
||||
// Create orchestrator with handoff to target
|
||||
let orch_req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-handoff-orchestrator".to_string(),
|
||||
description: Some("Orchestrator with handoffs".to_string()),
|
||||
instructions: Some("Delegate math questions.".to_string()),
|
||||
tools: None,
|
||||
handoffs: Some(vec![target.id.clone()]),
|
||||
completion_args: None,
|
||||
metadata: None,
|
||||
};
|
||||
let orch = client.create_agent(&orch_req).unwrap();
|
||||
assert_eq!(orch.handoffs.as_ref().unwrap().len(), 1);
|
||||
assert_eq!(orch.handoffs.as_ref().unwrap()[0], target.id);
|
||||
|
||||
// Cleanup
|
||||
client.delete_agent(&orch.id).unwrap();
|
||||
client.delete_agent(&target.id).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_agent_completion_with_created_agent() {
|
||||
setup::setup();
|
||||
let client = make_client();
|
||||
|
||||
let req = CreateAgentRequest {
|
||||
model: "mistral-medium-latest".to_string(),
|
||||
name: "test-completion-agent".to_string(),
|
||||
description: None,
|
||||
instructions: Some("Always respond with exactly the word 'pong'.".to_string()),
|
||||
tools: None,
|
||||
handoffs: None,
|
||||
completion_args: Some(CompletionArgs {
|
||||
temperature: Some(0.0),
|
||||
..Default::default()
|
||||
}),
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let agent = client.create_agent(&req).unwrap();
|
||||
|
||||
// Use the existing agent_completion method with the created agent
|
||||
use mistralai_client::v1::chat::ChatMessage;
|
||||
let messages = vec![ChatMessage::new_user_message("ping")];
|
||||
let response = client
|
||||
.agent_completion(agent.id.clone(), messages, None)
|
||||
.unwrap();
|
||||
|
||||
assert!(!response.choices.is_empty());
|
||||
let text = response.choices[0].message.content.text().to_lowercase();
|
||||
assert!(text.contains("pong"), "Expected 'pong', got: {text}");
|
||||
assert!(response.usage.total_tokens > 0);
|
||||
|
||||
client.delete_agent(&agent.id).unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user