1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct CacheEntry {
8 pub query: String,
10 pub context: String,
12 pub response: String,
14 pub function_calls: Vec<String>,
16 pub created_at: i64,
18 pub hit_count: u32,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub embedding: Option<Vec<f32>>,
23}
24
25impl CacheEntry {
26 pub fn new(
28 query: impl Into<String>,
29 context: impl Into<String>,
30 response: impl Into<String>,
31 function_calls: Vec<String>,
32 ) -> Self {
33 Self {
34 query: query.into(),
35 context: context.into(),
36 response: response.into(),
37 function_calls,
38 created_at: chrono::Utc::now().timestamp(),
39 hit_count: 0,
40 embedding: None,
41 }
42 }
43
44 pub fn with_embedding(mut self, embedding: Vec<f32>) -> Self {
46 self.embedding = Some(embedding);
47 self
48 }
49
50 pub fn record_hit(&mut self) {
52 self.hit_count += 1;
53 }
54
55 pub fn is_expired(&self, ttl_secs: i64) -> bool {
57 let now = chrono::Utc::now().timestamp();
58 now - self.created_at > ttl_secs
59 }
60
61 pub fn age_secs(&self) -> i64 {
63 chrono::Utc::now().timestamp() - self.created_at
64 }
65}
66
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69pub struct CacheStats {
70 pub entries: usize,
72 pub hits: u64,
74 pub misses: u64,
76 pub stores: u64,
78 pub evictions: u64,
80}
81
82impl CacheStats {
83 pub fn hit_rate(&self) -> f64 {
85 let total = self.hits + self.misses;
86 if total == 0 {
87 return 0.0;
88 }
89 self.hits as f64 / total as f64
90 }
91}