use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::core::tools::ToolCall;
use super::image::ImageContent;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum MessageType {
System,
Human,
AI,
Tool { tool_call_id: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub content: String,
#[serde(default)]
pub images: Vec<ImageContent>,
#[serde(rename = "type")]
pub message_type: MessageType,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub additional_kwargs: HashMap<String, Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<ToolCall>>,
}
impl Message {
pub fn system(content: impl Into<String>) -> Self {
Self {
content: content.into(),
images: Vec::new(),
message_type: MessageType::System,
name: None,
additional_kwargs: HashMap::new(),
id: None,
tool_calls: None,
}
}
pub fn human(content: impl Into<String>) -> Self {
Self {
content: content.into(),
images: Vec::new(),
message_type: MessageType::Human,
name: None,
additional_kwargs: HashMap::new(),
id: None,
tool_calls: None,
}
}
pub fn human_with_image(
content: impl Into<String>,
image_url: impl Into<String>,
) -> Self {
Self {
content: content.into(),
images: vec![ImageContent::from_url(image_url)],
message_type: MessageType::Human,
name: None,
additional_kwargs: HashMap::new(),
id: None,
tool_calls: None,
}
}
pub fn human_with_images(
content: impl Into<String>,
images: Vec<ImageContent>,
) -> Self {
Self {
content: content.into(),
images,
message_type: MessageType::Human,
name: None,
additional_kwargs: HashMap::new(),
id: None,
tool_calls: None,
}
}
pub fn ai(content: impl Into<String>) -> Self {
Self {
content: content.into(),
images: Vec::new(),
message_type: MessageType::AI,
name: None,
additional_kwargs: HashMap::new(),
id: None,
tool_calls: None,
}
}
pub fn ai_with_tool_calls(content: impl Into<String>, tool_calls: Vec<ToolCall>) -> Self {
Self {
content: content.into(),
images: Vec::new(),
message_type: MessageType::AI,
name: None,
additional_kwargs: HashMap::new(),
id: None,
tool_calls: Some(tool_calls),
}
}
pub fn tool(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
content: content.into(),
images: Vec::new(),
message_type: MessageType::Tool {
tool_call_id: tool_call_id.into(),
},
name: None,
additional_kwargs: HashMap::new(),
id: None,
tool_calls: None,
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn with_additional_kwarg(mut self, key: impl Into<String>, value: Value) -> Self {
self.additional_kwargs.insert(key.into(), value);
self
}
pub fn with_image(mut self, image: ImageContent) -> Self {
self.images.push(image);
self
}
pub fn has_images(&self) -> bool {
!self.images.is_empty()
}
pub fn type_str(&self) -> &str {
match &self.message_type {
MessageType::System => "system",
MessageType::Human => "human",
MessageType::AI => "ai",
MessageType::Tool { .. } => "tool",
}
}
pub fn has_tool_calls(&self) -> bool {
self.tool_calls.is_some() && !self.tool_calls.as_ref().unwrap().is_empty()
}
pub fn get_tool_calls(&self) -> Option<&[ToolCall]> {
self.tool_calls.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_human_with_image() {
let msg = Message::human_with_image("描述这张图", "https://example.com/img.jpg");
assert_eq!(msg.content, "描述这张图");
assert_eq!(msg.images.len(), 1);
assert_eq!(msg.images[0].url, "https://example.com/img.jpg");
assert!(msg.has_images());
}
#[test]
fn test_human_no_images_by_default() {
let msg = Message::human("纯文本");
assert!(msg.images.is_empty());
assert!(!msg.has_images());
}
#[test]
fn test_with_image_builder() {
let msg = Message::human("看图")
.with_image(ImageContent::from_url("https://example.com/a.png"))
.with_image(ImageContent::from_base64("abc"));
assert_eq!(msg.images.len(), 2);
}
#[test]
fn test_message_deserialize_without_images_field() {
let json = r#"{"content":"hi","type":"human"}"#;
let msg: Message = serde_json::from_str(json).unwrap();
assert_eq!(msg.content, "hi");
assert!(msg.images.is_empty());
}
#[test]
fn test_human_with_images_multiple() {
let msg = Message::human_with_images(
"多图",
vec![
ImageContent::from_url("https://example.com/1.jpg"),
ImageContent::from_url("https://example.com/2.jpg"),
],
);
assert_eq!(msg.images.len(), 2);
}
#[test]
fn test_system_ai_no_images() {
assert!(Message::system("s").images.is_empty());
assert!(Message::ai("a").images.is_empty());
assert!(Message::tool("id", "c").images.is_empty());
}
}