1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6
7use lc_shared::tools::ToolCall;
8
9use super::image::ImageContent;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(rename_all = "lowercase")]
14pub enum MessageType {
15 System,
16 Human,
17 AI,
18 Tool { tool_call_id: String },
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct Message {
24 pub content: String,
25
26 #[serde(default)]
28 pub images: Vec<ImageContent>,
29
30 #[serde(rename = "type")]
31 pub message_type: MessageType,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub name: Option<String>,
35
36 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
37 pub additional_kwargs: HashMap<String, Value>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub id: Option<String>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub tool_calls: Option<Vec<ToolCall>>,
44}
45
46impl Message {
47 pub fn system(content: impl Into<String>) -> Self {
49 Self {
50 content: content.into(),
51 images: Vec::new(),
52 message_type: MessageType::System,
53 name: None,
54 additional_kwargs: HashMap::new(),
55 id: None,
56 tool_calls: None,
57 }
58 }
59
60 pub fn human(content: impl Into<String>) -> Self {
62 Self {
63 content: content.into(),
64 images: Vec::new(),
65 message_type: MessageType::Human,
66 name: None,
67 additional_kwargs: HashMap::new(),
68 id: None,
69 tool_calls: None,
70 }
71 }
72
73 pub fn human_with_image(content: impl Into<String>, image_url: impl Into<String>) -> Self {
75 Self {
76 content: content.into(),
77 images: vec![ImageContent::from_url(image_url)],
78 message_type: MessageType::Human,
79 name: None,
80 additional_kwargs: HashMap::new(),
81 id: None,
82 tool_calls: None,
83 }
84 }
85
86 pub fn human_with_images(content: impl Into<String>, images: Vec<ImageContent>) -> Self {
88 Self {
89 content: content.into(),
90 images,
91 message_type: MessageType::Human,
92 name: None,
93 additional_kwargs: HashMap::new(),
94 id: None,
95 tool_calls: None,
96 }
97 }
98
99 pub fn ai(content: impl Into<String>) -> Self {
101 Self {
102 content: content.into(),
103 images: Vec::new(),
104 message_type: MessageType::AI,
105 name: None,
106 additional_kwargs: HashMap::new(),
107 id: None,
108 tool_calls: None,
109 }
110 }
111
112 pub fn ai_with_tool_calls(content: impl Into<String>, tool_calls: Vec<ToolCall>) -> Self {
114 Self {
115 content: content.into(),
116 images: Vec::new(),
117 message_type: MessageType::AI,
118 name: None,
119 additional_kwargs: HashMap::new(),
120 id: None,
121 tool_calls: Some(tool_calls),
122 }
123 }
124
125 pub fn tool(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
127 Self {
128 content: content.into(),
129 images: Vec::new(),
130 message_type: MessageType::Tool {
131 tool_call_id: tool_call_id.into(),
132 },
133 name: None,
134 additional_kwargs: HashMap::new(),
135 id: None,
136 tool_calls: None,
137 }
138 }
139
140 pub fn with_name(mut self, name: impl Into<String>) -> Self {
142 self.name = Some(name.into());
143 self
144 }
145
146 pub fn with_id(mut self, id: impl Into<String>) -> Self {
148 self.id = Some(id.into());
149 self
150 }
151
152 pub fn with_additional_kwarg(mut self, key: impl Into<String>, value: Value) -> Self {
154 self.additional_kwargs.insert(key.into(), value);
155 self
156 }
157
158 pub fn with_image(mut self, image: ImageContent) -> Self {
160 self.images.push(image);
161 self
162 }
163
164 pub fn has_images(&self) -> bool {
166 !self.images.is_empty()
167 }
168
169 pub fn type_str(&self) -> &str {
171 match &self.message_type {
172 MessageType::System => "system",
173 MessageType::Human => "human",
174 MessageType::AI => "ai",
175 MessageType::Tool { .. } => "tool",
176 }
177 }
178
179 pub fn has_tool_calls(&self) -> bool {
181 self.tool_calls.is_some() && !self.tool_calls.as_ref().unwrap().is_empty()
182 }
183
184 pub fn get_tool_calls(&self) -> Option<&[ToolCall]> {
186 self.tool_calls.as_deref()
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_human_with_image() {
196 let msg = Message::human_with_image("描述这张图", "https://example.com/img.jpg");
197 assert_eq!(msg.content, "描述这张图");
198 assert_eq!(msg.images.len(), 1);
199 assert_eq!(msg.images[0].url, "https://example.com/img.jpg");
200 assert!(msg.has_images());
201 }
202
203 #[test]
204 fn test_human_no_images_by_default() {
205 let msg = Message::human("纯文本");
206 assert!(msg.images.is_empty());
207 assert!(!msg.has_images());
208 }
209
210 #[test]
211 fn test_with_image_builder() {
212 let msg = Message::human("看图")
213 .with_image(ImageContent::from_url("https://example.com/a.png"))
214 .with_image(ImageContent::from_base64("abc"));
215 assert_eq!(msg.images.len(), 2);
216 }
217
218 #[test]
219 fn test_message_deserialize_without_images_field() {
220 let json = r#"{"content":"hi","type":"human"}"#;
222 let msg: Message = serde_json::from_str(json).unwrap();
223 assert_eq!(msg.content, "hi");
224 assert!(msg.images.is_empty());
225 }
226
227 #[test]
228 fn test_human_with_images_multiple() {
229 let msg = Message::human_with_images(
230 "多图",
231 vec![
232 ImageContent::from_url("https://example.com/1.jpg"),
233 ImageContent::from_url("https://example.com/2.jpg"),
234 ],
235 );
236 assert_eq!(msg.images.len(), 2);
237 }
238
239 #[test]
240 fn test_system_ai_no_images() {
241 assert!(Message::system("s").images.is_empty());
242 assert!(Message::ai("a").images.is_empty());
243 assert!(Message::tool("id", "c").images.is_empty());
244 }
245}