Skip to main content

cortexai_cache/
lib.rs

1//! Semantic Cache for cortex
2//!
3//! Provides caching with optional semantic similarity matching for LLM responses.
4//! Supports multiple backends:
5//!
6//! - **In-Memory**: Fast LRU cache with TTL support (default)
7//! - **Redis/Valkey**: Distributed cache for multi-instance deployments
8//!
9//! # Features
10//!
11//! - `memory` (default): Enable in-memory LRU cache
12//! - `redis`: Enable Redis/Valkey backend
13//! - `full`: Enable all backends
14//!
15//! # Example
16//!
17//! ```rust,ignore
18//! use cortexai_cache::{Cache, MemoryCache, CacheConfig};
19//!
20//! // In-memory cache
21//! let cache = MemoryCache::new(CacheConfig::default());
22//!
23//! // Store a response
24//! cache.store("What is Rust?", "context", "Rust is a systems programming language", vec![]).await?;
25//!
26//! // Retrieve (exact match)
27//! if let Some(entry) = cache.get("What is Rust?", "context").await? {
28//!     println!("Cached response: {}", entry.response);
29//! }
30//! ```
31
32mod 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
54/// Optional cache wrapper for graceful degradation
55pub enum OptionalCache {
56    #[cfg(feature = "memory")]
57    Memory(MemoryCache),
58    #[cfg(feature = "redis")]
59    Redis(RedisCache),
60    Disabled,
61}
62
63impl OptionalCache {
64    /// Create a memory cache
65    #[cfg(feature = "memory")]
66    pub fn memory(config: CacheConfig) -> Self {
67        OptionalCache::Memory(MemoryCache::new(config))
68    }
69
70    /// Create a Redis/Valkey cache
71    #[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    /// Create disabled cache
86    pub fn disabled() -> Self {
87        OptionalCache::Disabled
88    }
89
90    /// Check if cache is enabled
91    pub fn is_enabled(&self) -> bool {
92        !matches!(self, OptionalCache::Disabled)
93    }
94
95    /// Get from cache
96    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    /// Store in cache
107    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
128/// Builder for creating caches with fallback
129pub struct CacheBuilder {
130    config: CacheConfig,
131    #[cfg(feature = "redis")]
132    redis_url: Option<String>,
133    enable_fallback: bool,
134}
135
136impl CacheBuilder {
137    /// Create a new cache builder
138    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    /// Set cache configuration
148    pub fn config(mut self, config: CacheConfig) -> Self {
149        self.config = config;
150        self
151    }
152
153    /// Set Redis/Valkey URL
154    #[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    /// Enable/disable fallback to memory cache
161    pub fn with_fallback(mut self, enable: bool) -> Self {
162        self.enable_fallback = enable;
163        self
164    }
165
166    /// Build the cache
167    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}