j_agent/context/
message_compress.rs1use crate::storage::{ChatMessage, MessageRole};
28use std::collections::HashMap;
29
30pub const DEFAULT_OTHER_AGENT_TOOLCALL_THRESHOLD: usize = 5;
32
33fn extract_agent_source(content: &str) -> Option<(String, &str)> {
38 let trimmed = content.trim_start();
39 if !trimmed.starts_with('<') {
40 return None;
41 }
42 let end_bracket = trimmed.find('>')?;
43 let agent_name = trimmed[1..end_bracket].to_string();
44 let remainder = &trimmed[end_bracket + 1..];
45 Some((agent_name, remainder))
46}
47
48fn is_tool_call_broadcast(content: &str) -> Option<(String, String)> {
52 let (agent_name, remainder) = extract_agent_source(content)?;
53 let trimmed = remainder.trim_start();
54 if !trimmed.starts_with("[调用工具 ") {
55 return None;
56 }
57 let end_bracket = trimmed.find(']')?;
58 let tool_name = trimmed["[调用工具 ".len()..end_bracket].to_string();
59 Some((agent_name, tool_name))
60}
61
62pub fn compress_other_agent_toolcalls(
76 messages: &[ChatMessage],
77 self_agent_name: &str,
78 threshold: usize,
79) -> Vec<ChatMessage> {
80 if messages.is_empty() || threshold == 0 {
81 return messages.to_vec();
82 }
83
84 let other_agent_tool_calls: Vec<(usize, String, String)> = messages
86 .iter()
87 .enumerate()
88 .filter_map(|(idx, msg)| {
89 let content = &msg.content;
92 let (agent_name, tool_name) = is_tool_call_broadcast(content)?;
93 if agent_name == self_agent_name {
95 return None;
96 }
97 Some((idx, agent_name, tool_name))
98 })
99 .collect();
100
101 if other_agent_tool_calls.is_empty() {
103 return messages.to_vec();
104 }
105
106 let agent_groups: HashMap<String, Vec<(usize, String)>> =
108 other_agent_tool_calls
109 .iter()
110 .fold(HashMap::new(), |mut acc, (idx, agent, tool)| {
111 acc.entry(agent.clone())
112 .or_default()
113 .push((*idx, tool.clone()));
114 acc
115 });
116
117 let mut indices_to_compress: Vec<usize> = Vec::new(); let mut summary_by_first_idx: HashMap<usize, (String, HashMap<String, usize>)> = HashMap::new();
120
121 for (agent_name, calls) in agent_groups {
122 let total = calls.len();
123 if total <= threshold {
124 continue;
126 }
127
128 let recent_start = total - threshold;
130 let (to_compress, _to_keep) = calls.split_at(recent_start);
131
132 for (idx, _) in to_compress {
134 indices_to_compress.push(*idx);
135 }
136
137 let tool_counts: HashMap<String, usize> =
139 to_compress
140 .iter()
141 .fold(HashMap::new(), |mut acc, (_, tool)| {
142 *acc.entry(tool.clone()).or_default() += 1;
143 acc
144 });
145
146 if let Some((first_idx, _)) = to_compress.first() {
148 summary_by_first_idx.insert(*first_idx, (agent_name.clone(), tool_counts));
149 }
150 }
151
152 let mut result: Vec<ChatMessage> = Vec::with_capacity(messages.len());
154 for (idx, msg) in messages.iter().enumerate() {
155 if let Some((agent_name, tool_counts)) = summary_by_first_idx.get(&idx) {
156 let total_calls: usize = tool_counts.values().sum();
158 let tools_summary: String = tool_counts
159 .iter()
160 .map(|(tool, count)| format!("{}×{}", tool, count))
161 .collect::<Vec<_>>()
162 .join(", ");
163 let summary_content = format!(
164 "<{}> [早期工具调用摘要: {}, 共 {} 次]</{}>",
165 agent_name, tools_summary, total_calls, agent_name
166 );
167 result.push(ChatMessage::text(MessageRole::User, summary_content));
168 }
169
170 if indices_to_compress.contains(&idx) {
171 continue;
173 }
174
175 result.push(msg.clone());
177 }
178
179 result
180}
181
182#[cfg(test)]
183mod tests;