agent_chain_core/messages/
utils.rs1use super::ai::AIMessage;
7use super::base::{BaseMessage, BaseMessageChunk};
8use super::chat::ChatMessage;
9use super::function::FunctionMessage;
10use super::human::HumanMessage;
11use super::modifier::RemoveMessage;
12use super::system::SystemMessage;
13use super::tool::ToolMessage;
14
15pub type AnyMessage = BaseMessage;
18
19pub type MessageLikeRepresentation = serde_json::Value;
23
24pub fn get_buffer_string(messages: &[BaseMessage], human_prefix: &str, ai_prefix: &str) -> String {
38 messages
39 .iter()
40 .map(|m| {
41 let role = match m {
42 BaseMessage::Human(_) => human_prefix,
43 BaseMessage::System(_) => "System",
44 BaseMessage::AI(_) => ai_prefix,
45 BaseMessage::Tool(_) => "Tool",
46 BaseMessage::Chat(c) => c.role(),
47 BaseMessage::Function(_) => "Function",
48 BaseMessage::Remove(_) => "Remove",
49 };
50 format!("{}: {}", role, m.content())
51 })
52 .collect::<Vec<_>>()
53 .join("\n")
54}
55
56pub fn message_to_dict(message: &BaseMessage) -> serde_json::Value {
60 serde_json::json!({
61 "type": message.message_type(),
62 "data": {
63 "content": message.content(),
64 "id": message.id(),
65 "name": message.name(),
66 }
67 })
68}
69
70pub fn messages_to_dict(messages: &[BaseMessage]) -> Vec<serde_json::Value> {
74 messages.iter().map(message_to_dict).collect()
75}
76
77pub fn message_from_dict(message: &serde_json::Value) -> Result<BaseMessage, String> {
81 let msg_type = message
82 .get("type")
83 .and_then(|t| t.as_str())
84 .ok_or_else(|| "Message dict must contain 'type' key".to_string())?;
85
86 let data = message
87 .get("data")
88 .ok_or_else(|| "Message dict must contain 'data' key".to_string())?;
89
90 let content = data.get("content").and_then(|c| c.as_str()).unwrap_or("");
91
92 let id = data.get("id").and_then(|i| i.as_str());
93
94 match msg_type {
95 "human" => {
96 let msg = match id {
97 Some(id) => HumanMessage::with_id(id, content),
98 None => HumanMessage::new(content),
99 };
100 Ok(BaseMessage::Human(msg))
101 }
102 "ai" => {
103 let msg = match id {
104 Some(id) => AIMessage::with_id(id, content),
105 None => AIMessage::new(content),
106 };
107 Ok(BaseMessage::AI(msg))
108 }
109 "system" => {
110 let msg = match id {
111 Some(id) => SystemMessage::with_id(id, content),
112 None => SystemMessage::new(content),
113 };
114 Ok(BaseMessage::System(msg))
115 }
116 "tool" => {
117 let tool_call_id = data
118 .get("tool_call_id")
119 .and_then(|t| t.as_str())
120 .unwrap_or("");
121 let msg = match id {
122 Some(id) => ToolMessage::with_id(id, content, tool_call_id),
123 None => ToolMessage::new(content, tool_call_id),
124 };
125 Ok(BaseMessage::Tool(msg))
126 }
127 "chat" => {
128 let role = data.get("role").and_then(|r| r.as_str()).unwrap_or("chat");
129 let msg = match id {
130 Some(id) => ChatMessage::with_id(id, role, content),
131 None => ChatMessage::new(role, content),
132 };
133 Ok(BaseMessage::Chat(msg))
134 }
135 "function" => {
136 let name = data.get("name").and_then(|n| n.as_str()).unwrap_or("");
137 let msg = match id {
138 Some(id) => FunctionMessage::with_id(id, name, content),
139 None => FunctionMessage::new(name, content),
140 };
141 Ok(BaseMessage::Function(msg))
142 }
143 "remove" => {
144 let id = id.ok_or_else(|| "RemoveMessage requires an id".to_string())?;
145 Ok(BaseMessage::Remove(RemoveMessage::new(id)))
146 }
147 _ => Err(format!("Unknown message type: {}", msg_type)),
148 }
149}
150
151pub fn messages_from_dict(messages: &[serde_json::Value]) -> Result<Vec<BaseMessage>, String> {
155 messages.iter().map(message_from_dict).collect()
156}
157
158pub fn convert_to_messages(messages: &[serde_json::Value]) -> Result<Vec<BaseMessage>, String> {
168 let mut result = Vec::new();
169
170 for message in messages {
171 if let Some(_msg_type) = message.get("type").and_then(|t| t.as_str()) {
172 result.push(message_from_dict(message)?);
174 } else if let Some(role) = message.get("role").and_then(|r| r.as_str()) {
175 let content = message
177 .get("content")
178 .and_then(|c| c.as_str())
179 .unwrap_or("");
180 let msg = create_message_from_role(role, content)?;
181 result.push(msg);
182 } else if let Some(s) = message.as_str() {
183 result.push(BaseMessage::Human(HumanMessage::new(s)));
185 } else if let Some(arr) = message.as_array() {
186 if arr.len() == 2 {
188 let role = arr[0].as_str().ok_or("First element must be role string")?;
189 let content = arr[1]
190 .as_str()
191 .ok_or("Second element must be content string")?;
192 let msg = create_message_from_role(role, content)?;
193 result.push(msg);
194 } else {
195 return Err(
196 "Array message must have exactly 2 elements [role, content]".to_string()
197 );
198 }
199 } else {
200 return Err(format!("Cannot convert to message: {:?}", message));
201 }
202 }
203
204 Ok(result)
205}
206
207fn create_message_from_role(role: &str, content: &str) -> Result<BaseMessage, String> {
209 match role {
210 "human" | "user" => Ok(BaseMessage::Human(HumanMessage::new(content))),
211 "ai" | "assistant" => Ok(BaseMessage::AI(AIMessage::new(content))),
212 "system" | "developer" => Ok(BaseMessage::System(SystemMessage::new(content))),
213 "function" => Err("Function messages require a name".to_string()),
214 "tool" => Err("Tool messages require a tool_call_id".to_string()),
215 _ => Ok(BaseMessage::Chat(ChatMessage::new(role, content))),
216 }
217}
218
219pub fn filter_messages(
223 messages: &[BaseMessage],
224 include_names: Option<&[&str]>,
225 exclude_names: Option<&[&str]>,
226 include_types: Option<&[&str]>,
227 exclude_types: Option<&[&str]>,
228 include_ids: Option<&[&str]>,
229 exclude_ids: Option<&[&str]>,
230) -> Vec<BaseMessage> {
231 messages
232 .iter()
233 .filter(|msg| {
234 if let Some(exclude_names) = exclude_names
236 && let Some(name) = msg.name()
237 && exclude_names.contains(&name)
238 {
239 return false;
240 }
241
242 if let Some(exclude_types) = exclude_types
243 && exclude_types.contains(&msg.message_type())
244 {
245 return false;
246 }
247
248 if let Some(exclude_ids) = exclude_ids
249 && let Some(id) = msg.id()
250 && exclude_ids.contains(&id)
251 {
252 return false;
253 }
254
255 let include_by_name = include_names
257 .is_none_or(|names| msg.name().is_some_and(|name| names.contains(&name)));
258
259 let include_by_type =
260 include_types.is_none_or(|types| types.contains(&msg.message_type()));
261
262 let include_by_id =
263 include_ids.is_none_or(|ids| msg.id().is_some_and(|id| ids.contains(&id)));
264
265 let any_include_specified =
267 include_names.is_some() || include_types.is_some() || include_ids.is_some();
268
269 if any_include_specified {
270 include_by_name || include_by_type || include_by_id
271 } else {
272 true
273 }
274 })
275 .cloned()
276 .collect()
277}
278
279pub fn merge_message_runs(messages: &[BaseMessage], chunk_separator: &str) -> Vec<BaseMessage> {
285 if messages.is_empty() {
286 return Vec::new();
287 }
288
289 let mut merged: Vec<BaseMessage> = Vec::new();
290
291 for msg in messages {
292 if merged.is_empty() {
293 merged.push(msg.clone());
294 continue;
295 }
296
297 let last = merged.last().expect("merged is not empty");
298
299 if matches!(msg, BaseMessage::Tool(_))
301 || std::mem::discriminant(last) != std::mem::discriminant(msg)
302 {
303 merged.push(msg.clone());
304 } else {
305 let last = merged.pop().expect("merged is not empty");
307 let merged_content = format!("{}{}{}", last.content(), chunk_separator, msg.content());
308
309 let new_msg = match (last, msg) {
310 (BaseMessage::Human(_), BaseMessage::Human(_)) => {
311 BaseMessage::Human(HumanMessage::new(&merged_content))
312 }
313 (BaseMessage::AI(_), BaseMessage::AI(_)) => {
314 BaseMessage::AI(AIMessage::new(&merged_content))
315 }
316 (BaseMessage::System(_), BaseMessage::System(_)) => {
317 BaseMessage::System(SystemMessage::new(&merged_content))
318 }
319 (BaseMessage::Chat(c), BaseMessage::Chat(_)) => {
320 BaseMessage::Chat(ChatMessage::new(c.role(), &merged_content))
321 }
322 (BaseMessage::Function(f), BaseMessage::Function(_)) => {
323 BaseMessage::Function(FunctionMessage::new(f.name(), &merged_content))
324 }
325 _ => {
326 merged.push(msg.clone());
328 continue;
329 }
330 };
331
332 merged.push(new_msg);
333 }
334 }
335
336 merged
337}
338
339pub fn message_chunk_to_message(chunk: &BaseMessageChunk) -> BaseMessage {
343 chunk.to_message()
344}