1mod config;
33mod entry;
34mod error;
35mod traits;
36
37#[cfg(feature = "memory")]
38mod memory;
39
40#[cfg(feature = "redis")]
41mod redis_backend;
42
43pub use config::CacheConfig;
44pub use entry::{CacheEntry, CacheStats};
45pub use error::CacheError;
46pub use traits::{Cache, SemanticCache};
47
48#[cfg(feature = "memory")]
49pub use memory::{MemoryCache, SemanticMemoryCache};
50
51#[cfg(feature = "redis")]
52pub use redis_backend::RedisCache;
53
54pub enum OptionalCache {
56 #[cfg(feature = "memory")]
57 Memory(MemoryCache),
58 #[cfg(feature = "redis")]
59 Redis(RedisCache),
60 Disabled,
61}
62
63impl OptionalCache {
64 #[cfg(feature = "memory")]
66 pub fn memory(config: CacheConfig) -> Self {
67 OptionalCache::Memory(MemoryCache::new(config))
68 }
69
70 #[cfg(feature = "redis")]
72 pub async fn redis(url: &str, config: CacheConfig) -> Self {
73 match RedisCache::new(url, config).await {
74 Ok(cache) => OptionalCache::Redis(cache),
75 Err(e) => {
76 tracing::warn!(
77 "Failed to connect to Redis/Valkey: {}. Using disabled cache.",
78 e
79 );
80 OptionalCache::Disabled
81 }
82 }
83 }
84
85 pub fn disabled() -> Self {
87 OptionalCache::Disabled
88 }
89
90 pub fn is_enabled(&self) -> bool {
92 !matches!(self, OptionalCache::Disabled)
93 }
94
95 pub async fn get(&self, query: &str, context: &str) -> Option<CacheEntry> {
97 match self {
98 #[cfg(feature = "memory")]
99 OptionalCache::Memory(cache) => cache.get(query, context).await.ok().flatten(),
100 #[cfg(feature = "redis")]
101 OptionalCache::Redis(cache) => cache.get(query, context).await.ok().flatten(),
102 OptionalCache::Disabled => None,
103 }
104 }
105
106 pub async fn store(
108 &self,
109 query: &str,
110 context: &str,
111 response: &str,
112 function_calls: Vec<String>,
113 ) {
114 match self {
115 #[cfg(feature = "memory")]
116 OptionalCache::Memory(cache) => {
117 let _ = cache.store(query, context, response, function_calls).await;
118 }
119 #[cfg(feature = "redis")]
120 OptionalCache::Redis(cache) => {
121 let _ = cache.store(query, context, response, function_calls).await;
122 }
123 OptionalCache::Disabled => {}
124 }
125 }
126}
127
128pub struct CacheBuilder {
130 config: CacheConfig,
131 #[cfg(feature = "redis")]
132 redis_url: Option<String>,
133 enable_fallback: bool,
134}
135
136impl CacheBuilder {
137 pub fn new() -> Self {
139 Self {
140 config: CacheConfig::default(),
141 #[cfg(feature = "redis")]
142 redis_url: None,
143 enable_fallback: true,
144 }
145 }
146
147 pub fn config(mut self, config: CacheConfig) -> Self {
149 self.config = config;
150 self
151 }
152
153 #[cfg(feature = "redis")]
155 pub fn redis_url(mut self, url: impl Into<String>) -> Self {
156 self.redis_url = Some(url.into());
157 self
158 }
159
160 pub fn with_fallback(mut self, enable: bool) -> Self {
162 self.enable_fallback = enable;
163 self
164 }
165
166 pub async fn build(self) -> OptionalCache {
168 #[cfg(feature = "redis")]
169 if let Some(url) = self.redis_url {
170 match RedisCache::new(&url, self.config.clone()).await {
171 Ok(cache) => {
172 tracing::info!("Connected to Redis/Valkey cache at {}", url);
173 return OptionalCache::Redis(cache);
174 }
175 Err(e) => {
176 tracing::warn!("Failed to connect to Redis/Valkey: {}", e);
177 if self.enable_fallback {
178 tracing::info!("Falling back to in-memory cache");
179 #[cfg(feature = "memory")]
180 return OptionalCache::Memory(MemoryCache::new(self.config));
181 }
182 return OptionalCache::Disabled;
183 }
184 }
185 }
186
187 #[cfg(feature = "memory")]
188 return OptionalCache::Memory(MemoryCache::new(self.config));
189
190 #[cfg(not(feature = "memory"))]
191 OptionalCache::Disabled
192 }
193}
194
195impl Default for CacheBuilder {
196 fn default() -> Self {
197 Self::new()
198 }
199}