1use std::time::{Duration, SystemTime};
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct CacheKey {
11 pub tool_name: String,
12 pub arguments: String,
13}
14
15impl CacheKey {
16 pub fn new(tool_name: String, arguments: Value) -> Self {
17 let normalized = normalize_json(&arguments);
18 Self {
19 tool_name,
20 arguments: normalized,
21 }
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct CacheEntry {
28 pub result: Value,
29 pub timestamp: SystemTime,
30 pub ttl: Duration,
31 pub hit_count: u64,
32}
33
34impl CacheEntry {
35 pub fn new(result: Value, ttl: Duration) -> Self {
36 Self {
37 result,
38 timestamp: SystemTime::now(),
39 ttl,
40 hit_count: 0,
41 }
42 }
43
44 pub fn is_expired(&self) -> bool {
45 match self.timestamp.elapsed() {
46 Ok(elapsed) => elapsed > self.ttl,
47 Err(_) => true,
48 }
49 }
50
51 pub fn hit(&mut self) {
52 self.hit_count += 1;
53 }
54}
55
56#[derive(Clone)]
58pub struct ToolCallCache {
59 entries: dashmap::DashMap<CacheKey, CacheEntry>,
60 default_ttl: Duration,
61 max_size: usize,
62 enable_cache: bool,
63}
64
65impl ToolCallCache {
66 pub fn new() -> Self {
67 Self {
68 entries: dashmap::DashMap::new(),
69 default_ttl: Duration::from_secs(300),
70 max_size: 1000,
71 enable_cache: true,
72 }
73 }
74
75 pub fn with_ttl(mut self, ttl: Duration) -> Self {
76 self.default_ttl = ttl;
77 self
78 }
79
80 pub fn with_max_size(mut self, size: usize) -> Self {
81 self.max_size = size;
82 self
83 }
84
85 pub fn with_enabled(mut self, enabled: bool) -> Self {
86 self.enable_cache = enabled;
87 self
88 }
89
90 pub fn get(&self, key: &CacheKey) -> Option<Value> {
91 if !self.enable_cache {
92 return None;
93 }
94
95 let expired = self.entries.remove_if(key, |_k, v| v.is_expired());
100
101 if expired.is_some() {
102 return None;
104 }
105
106 let mut entry = self.entries.get_mut(key)?;
108 entry.hit();
109 Some(entry.result.clone())
110 }
111
112 pub fn insert(&self, key: CacheKey, result: Value, ttl: Option<Duration>) {
113 if !self.enable_cache {
114 return;
115 }
116
117 if self.entries.len() >= self.max_size {
118 self.evict_lru();
119 }
120
121 let entry = CacheEntry::new(result, ttl.unwrap_or(self.default_ttl));
122 self.entries.insert(key, entry);
123 }
124
125 pub fn insert_with_key(&self, tool_name: String, arguments: Value, result: Value) {
126 let key = CacheKey::new(tool_name, arguments);
127 self.insert(key, result, None);
128 }
129
130 pub fn clear(&self) {
131 self.entries.clear();
132 }
133
134 pub fn invalidate_tool(&self, tool_name: &str) {
135 self.entries.retain(|key, _| key.tool_name != tool_name);
136 }
137
138 pub fn stats(&self) -> CacheStats {
139 let mut total_hits = 0u64;
140 let mut expired_count = 0u64;
141
142 for entry in self.entries.iter() {
143 total_hits += entry.hit_count;
144 if entry.is_expired() {
145 expired_count += 1;
146 }
147 }
148
149 CacheStats {
150 total_entries: self.entries.len(),
151 total_hits,
152 expired_count,
153 hit_rate: if self.entries.is_empty() {
154 0.0
155 } else {
156 total_hits as f64 / self.entries.len() as f64
157 },
158 }
159 }
160
161 fn evict_lru(&self) {
162 let mut entries: Vec<_> = self
163 .entries
164 .iter()
165 .map(|entry| (entry.key().clone(), entry.value().timestamp))
166 .collect();
167
168 entries.sort_by_key(|a| a.1);
169
170 let remove_count = (self.max_size / 10).max(1);
171 for (key, _) in entries.into_iter().take(remove_count) {
172 self.entries.remove(&key);
173 }
174 }
175}
176
177impl Default for ToolCallCache {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct CacheStats {
186 pub total_entries: usize,
187 pub total_hits: u64,
188 pub expired_count: u64,
189 pub hit_rate: f64,
190}
191
192fn normalize_json(value: &Value) -> String {
193 match value {
194 Value::Object(obj) => {
195 let mut normalized = serde_json::Map::new();
196 for (k, v) in obj {
197 let normalized_key = k.trim().to_string();
198 let normalized_value = normalize_json_value(v);
199 normalized.insert(normalized_key, normalized_value);
200 }
201 serde_json::to_string(&normalized).unwrap_or_default()
202 },
203 Value::Array(arr) => {
204 let normalized: Vec<_> = arr.iter().map(normalize_json_value).collect();
205 serde_json::to_string(&normalized).unwrap_or_default()
206 },
207 Value::String(s) => s.clone(),
208 _ => serde_json::to_string(value).unwrap_or_default(),
209 }
210}
211
212fn normalize_json_value(value: &Value) -> Value {
213 match value {
214 Value::Object(obj) => {
215 let mut normalized = serde_json::Map::new();
216 for (k, v) in obj {
217 let normalized_key = k.trim().to_string();
218 normalized.insert(normalized_key, normalize_json_value(v));
219 }
220 Value::Object(normalized)
221 },
222 Value::Array(arr) => {
223 let normalized: Vec<_> = arr.iter().map(normalize_json_value).collect();
224 Value::Array(normalized)
225 },
226 _ => value.clone(),
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_cache_key_new() {
236 let args = serde_json::json!({"city": "Shenzhen", "count": 5});
237 let key = CacheKey::new("test_tool".to_string(), args);
238 assert_eq!(key.tool_name, "test_tool");
239 assert!(key.arguments.contains("city"));
240 }
241
242 #[test]
243 fn test_cache_entry_expired() {
244 let entry = CacheEntry::new(
245 serde_json::json!({"result": "success"}),
246 Duration::from_secs(1),
247 );
248 assert!(!entry.is_expired());
249
250 let mut entry_mut = entry.clone();
251 entry_mut.timestamp = SystemTime::now() - Duration::from_secs(2);
252 assert!(entry_mut.is_expired());
253 }
254
255 #[test]
256 fn test_cache_hit() {
257 let mut entry = CacheEntry::new(
258 serde_json::json!({"result": "success"}),
259 Duration::from_secs(60),
260 );
261 entry.hit();
262 entry.hit();
263 assert_eq!(entry.hit_count, 2);
264 }
265
266 #[test]
267 fn test_cache_insert_get() {
268 let cache = ToolCallCache::new();
269 let args = serde_json::json!({"input": "test"});
270 let result = serde_json::json!({"output": "success"});
271
272 cache.insert_with_key("test_tool".to_string(), args.clone(), result.clone());
273
274 let key = CacheKey::new("test_tool".to_string(), args);
275 let cached = cache.get(&key);
276 assert!(cached.is_some());
277 assert_eq!(cached.unwrap(), result);
278 }
279
280 #[test]
281 fn test_cache_expiration() {
282 let cache = ToolCallCache::new().with_ttl(Duration::from_millis(10));
284 let args = serde_json::json!({"input": "test"});
285 let result = serde_json::json!({"output": "success"});
286
287 cache.insert_with_key("test_tool".to_string(), args.clone(), result.clone());
288
289 let key = CacheKey::new("test_tool".to_string(), args.clone());
290
291 assert!(cache.get(&key).is_some());
293
294 std::thread::sleep(Duration::from_millis(20));
296
297 assert!(cache.get(&key).is_none());
299 }
300
301 #[test]
302 fn test_cache_stats() {
303 let cache = ToolCallCache::new();
304 let args = serde_json::json!({"input": "test"});
305
306 cache.insert_with_key("tool_a".to_string(), args.clone(), serde_json::json!({}));
307 cache.insert_with_key("tool_b".to_string(), args.clone(), serde_json::json!({}));
308
309 let key = CacheKey::new("tool_a".to_string(), args.clone());
310 let _ = cache.get(&key);
311 let _ = cache.get(&key);
312
313 let stats = cache.stats();
314 assert_eq!(stats.total_entries, 2);
315 assert_eq!(stats.total_hits, 2);
316 }
317
318 #[test]
319 fn test_normalize_json() {
320 let obj = serde_json::json!({
321 "CITY": "Shenzhen",
322 "count": 5,
323 "Data": {"NAME": "test"}
324 });
325
326 let normalized = normalize_json(&obj);
327 let parsed: Value = serde_json::from_str(&normalized).unwrap();
328
329 if let Some(parsed_obj) = parsed.as_object() {
331 assert!(parsed_obj.contains_key("CITY"));
332 assert!(parsed_obj.contains_key("count"));
333 assert!(parsed_obj.contains_key("Data"));
334 assert_eq!(parsed_obj.get("CITY"), Some(&serde_json::json!("Shenzhen")));
335 assert_eq!(parsed_obj.get("count"), Some(&serde_json::json!(5)));
336 }
337 }
338
339 #[test]
340 fn test_normalize_json_consistency_with_llm() {
341 let obj = serde_json::json!({"CityName": "Shenzhen", " UserID ": 42});
344 let normalized = normalize_json(&obj);
345 let parsed: Value = serde_json::from_str(&normalized).unwrap();
346 assert!(parsed.as_object().unwrap().contains_key("CityName"));
347 assert!(parsed.as_object().unwrap().contains_key("UserID"));
348 }
349
350 #[test]
351 fn test_cache_concurrent_insert_and_get() {
352 use std::{sync::Arc, thread};
353
354 let cache = Arc::new(ToolCallCache::new().with_max_size(1000));
355 let mut handles = Vec::new();
356
357 for i in 0..10 {
358 let cache_clone = Arc::clone(&cache);
359 handles.push(thread::spawn(move || {
360 let key_name = format!("tool_{}", i);
361 let args = serde_json::json!({"input": i});
362 let result = serde_json::json!({"output": format!("result_{}", i)});
363 cache_clone.insert_with_key(key_name.clone(), args.clone(), result);
364
365 let key = CacheKey::new(key_name, args);
367 cache_clone.get(&key)
368 }));
369 }
370
371 let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
372 let successful_gets = results.iter().filter(|r| r.is_some()).count();
373 assert_eq!(successful_gets, 10);
374
375 let stats = cache.stats();
376 assert_eq!(stats.total_entries, 10);
377 }
378
379 #[test]
380 fn test_cache_evict_lru() {
381 let cache = ToolCallCache::new()
383 .with_max_size(5)
384 .with_ttl(Duration::from_secs(300));
385
386 for i in 0..5 {
388 let args = serde_json::json!({"input": i});
389 cache.insert_with_key(
390 format!("tool_{}", i),
391 args,
392 serde_json::json!({"result": i}),
393 );
394 }
395
396 assert_eq!(cache.stats().total_entries, 5);
397
398 let args = serde_json::json!({"input": "new"});
400 cache.insert_with_key(
401 "tool_new".to_string(),
402 args,
403 serde_json::json!({"result": "new"}),
404 );
405
406 let stats = cache.stats();
407 assert!(stats.total_entries <= 5);
409 let key = CacheKey::new("tool_new".to_string(), serde_json::json!({"input": "new"}));
411 assert!(cache.get(&key).is_some());
412 }
413}