#[cfg(feature = "redis")]
use redis::aio::ConnectionManager;
#[cfg(feature = "redis")]
use super::CacheEntry;
#[cfg(feature = "redis")]
pub struct RedisCache {
conn: ConnectionManager,
prefix: String,
}
#[cfg(feature = "redis")]
impl RedisCache {
pub fn new(conn: ConnectionManager) -> Self {
Self {
conn,
prefix: "anyllm:cache:".to_string(),
}
}
pub async fn connect(redis_url: &str) -> Result<Self, redis::RedisError> {
let client = redis::Client::open(redis_url)?;
let conn = ConnectionManager::new(client).await?;
Ok(Self::new(conn))
}
fn redis_key(&self, key: &str) -> String {
format!("{}{}", self.prefix, key)
}
pub async fn get(&self, key: &str) -> Option<CacheEntry> {
let redis_key = self.redis_key(key);
let mut conn = self.conn.clone();
let result: Result<Option<String>, redis::RedisError> = redis::cmd("GET")
.arg(&redis_key)
.query_async(&mut conn)
.await;
match result {
Ok(Some(json)) => serde_json::from_str::<RedisCacheValue>(&json)
.ok()
.map(|v| CacheEntry {
response_body: bytes::Bytes::from(v.response_body),
model: v.model,
created_at: std::time::Instant::now(),
ttl_secs: None, }),
Ok(None) => None,
Err(e) => {
tracing::warn!(error = %e, "Redis cache GET failed");
None
}
}
}
pub async fn put(&self, key: &str, entry: &CacheEntry, ttl_secs: u64) {
let redis_key = self.redis_key(key);
let value = RedisCacheValue {
response_body: String::from_utf8_lossy(&entry.response_body).to_string(),
model: entry.model.clone(),
};
let json = match serde_json::to_string(&value) {
Ok(j) => j,
Err(_) => return,
};
let mut conn = self.conn.clone();
let result: Result<(), redis::RedisError> = redis::cmd("SETEX")
.arg(&redis_key)
.arg(ttl_secs)
.arg(&json)
.query_async(&mut conn)
.await;
if let Err(e) = result {
tracing::warn!(error = %e, "Redis cache SETEX failed");
}
}
}
#[cfg(feature = "redis")]
#[derive(serde::Serialize, serde::Deserialize)]
struct RedisCacheValue {
response_body: String,
model: String,
}