use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Message {
Human(HumanMessage),
Assistant(AssistantMessage),
System(SystemMessage),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HumanMessage {
pub content: MessageContent,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AssistantMessage {
pub content: MessageContent,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SystemMessage {
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MessageContent {
Text(String),
Mixed {
text: Option<String>,
tool_calls: Vec<ToolCall>,
},
}
impl MessageContent {
pub fn text<S: Into<String>>(text: S) -> Self {
Self::Text(text.into())
}
pub fn mixed<S: Into<String>>(text: Option<S>, tool_calls: Vec<ToolCall>) -> Self {
Self::Mixed {
text: text.map(|t| t.into()),
tool_calls,
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(text) => Some(text),
Self::Mixed { text, .. } => text.as_deref(),
}
}
pub fn to_text(&self) -> String {
match self {
Self::Text(text) => text.clone(),
Self::Mixed { text, .. } => text.clone().unwrap_or_default(),
}
}
pub fn has_tool_calls(&self) -> bool {
matches!(self, Self::Mixed { tool_calls, .. } if !tool_calls.is_empty())
}
pub fn tool_calls(&self) -> &[ToolCall] {
match self {
Self::Text(_) => &[],
Self::Mixed { tool_calls, .. } => tool_calls,
}
}
}
impl From<String> for MessageContent {
fn from(text: String) -> Self {
Self::Text(text)
}
}
impl From<&str> for MessageContent {
fn from(text: &str) -> Self {
Self::Text(text.to_string())
}
}
impl Message {
pub fn human<S: Into<String>>(content: S) -> Self {
Self::Human(HumanMessage {
content: MessageContent::text(content),
})
}
pub fn assistant<S: Into<String>>(content: S) -> Self {
Self::Assistant(AssistantMessage {
content: MessageContent::text(content),
})
}
pub fn system<S: Into<String>>(content: S) -> Self {
Self::System(SystemMessage {
content: content.into(),
})
}
pub fn role(&self) -> &'static str {
match self {
Self::Human(_) => "user",
Self::Assistant(_) => "assistant",
Self::System(_) => "system",
}
}
pub fn content_text(&self) -> String {
match self {
Self::Human(msg) => msg.content.to_text(),
Self::Assistant(msg) => msg.content.to_text(),
Self::System(msg) => msg.content.clone(),
}
}
}
impl HumanMessage {
pub fn new<S: Into<String>>(content: S) -> Self {
Self {
content: MessageContent::text(content),
}
}
pub fn with_tool_calls<S: Into<String>>(text: Option<S>, tool_calls: Vec<ToolCall>) -> Self {
Self {
content: MessageContent::mixed(text, tool_calls),
}
}
}
impl AssistantMessage {
pub fn new<S: Into<String>>(content: S) -> Self {
Self {
content: MessageContent::text(content),
}
}
pub fn with_tool_calls<S: Into<String>>(text: Option<S>, tool_calls: Vec<ToolCall>) -> Self {
Self {
content: MessageContent::mixed(text, tool_calls),
}
}
}
impl SystemMessage {
pub fn new<S: Into<String>>(content: S) -> Self {
Self {
content: content.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_content_text() {
let content = MessageContent::text("Hello world");
assert_eq!(content.as_text(), Some("Hello world"));
assert_eq!(content.to_text(), "Hello world");
assert!(!content.has_tool_calls());
assert!(content.tool_calls().is_empty());
}
#[test]
fn test_message_content_mixed() {
let tool_call = ToolCall {
id: "call_1".to_string(),
call_id: Some("call_1".to_string()),
function: ToolFunction {
name: "test_function".to_string(),
arguments: serde_json::json!({"param": "value"}),
},
};
let content = MessageContent::mixed(Some("Hello"), vec![tool_call.clone()]);
assert_eq!(content.as_text(), Some("Hello"));
assert_eq!(content.to_text(), "Hello");
assert!(content.has_tool_calls());
assert_eq!(content.tool_calls().len(), 1);
assert_eq!(content.tool_calls()[0].id, "call_1");
}
#[test]
fn test_message_content_from_string() {
let content: MessageContent = "Test message".into();
assert_eq!(content.as_text(), Some("Test message"));
}
#[test]
fn test_message_constructors() {
let human_msg = Message::human("Hello");
assert_eq!(human_msg.role(), "user");
assert_eq!(human_msg.content_text(), "Hello");
let assistant_msg = Message::assistant("Hi there");
assert_eq!(assistant_msg.role(), "assistant");
assert_eq!(assistant_msg.content_text(), "Hi there");
let system_msg = Message::system("You are helpful");
assert_eq!(system_msg.role(), "system");
assert_eq!(system_msg.content_text(), "You are helpful");
}
#[test]
fn test_human_message_constructors() {
let msg = HumanMessage::new("Hello");
assert_eq!(msg.content.to_text(), "Hello");
let tool_call = ToolCall {
id: "call_1".to_string(),
call_id: None,
function: ToolFunction {
name: "test".to_string(),
arguments: serde_json::json!({}),
},
};
let msg_with_tools = HumanMessage::with_tool_calls(Some("Text"), vec![tool_call]);
assert_eq!(msg_with_tools.content.to_text(), "Text");
assert!(msg_with_tools.content.has_tool_calls());
}
#[test]
fn test_assistant_message_constructors() {
let msg = AssistantMessage::new("Response");
assert_eq!(msg.content.to_text(), "Response");
let tool_call = ToolCall {
id: "call_1".to_string(),
call_id: None,
function: ToolFunction {
name: "test".to_string(),
arguments: serde_json::json!({}),
},
};
let msg_with_tools = AssistantMessage::with_tool_calls(Some("Response"), vec![tool_call]);
assert_eq!(msg_with_tools.content.to_text(), "Response");
assert!(msg_with_tools.content.has_tool_calls());
}
#[test]
fn test_message_serialization() {
let msg = Message::human("Test message");
let serialized = serde_json::to_string(&msg).unwrap();
let deserialized: Message = serde_json::from_str(&serialized).unwrap();
match deserialized {
Message::Human(human_msg) => {
assert_eq!(human_msg.content.to_text(), "Test message");
}
_ => panic!("Expected human message"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum HumanContent {
Text(Text),
ToolCall(ToolCall),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Text {
pub text: String,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ToolCall {
pub id: String,
pub call_id: Option<String>,
pub function: ToolFunction,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ToolFunction {
pub name: String,
pub arguments: serde_json::Value,
}