mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-14 04:09:09 +00:00
* refactor(rust): align the cache crates with Python and activate every backend The cache port had drifted: lifecycle and Redis-only operations sat on `BaseCache`, counters were pinned to `f64`, each semantic backend defined its own embedder and prompt handling, and only the in-memory backend could be selected natively. - Split `disconnect` and `test_connection` out of `BaseCache` into optional capabilities, implemented only where the Python class defines them, and give every Redis-only operation its own capability trait. - Decouple counters from the stored value type, so one backend can serve both responses and counters as Python's `RedisCache` does. - Share one `Embedder` and prompt contract in `litellm_cache::semantic`, and make the Redis and Valkey semantic backends generic over their codec. - Port the Python operations that were missing: `async_refresh_ttl`, `async_rpush_and_trim`, `async_set_cache_pipeline_with_ttls`, the DualCache pipeline, sadd, bulk delete and TTL reads, and the semantic-similarity write-back. - Take the HTTP client from the host pool in the GCS, S3 and Azure backends. - Activate all nine backends through the Rust catalog, whose rules all stay `PYTHON_ONLY`, and route the `Cache` facade's storage calls to the native runtime when one is selected. - Give every crate the same layout, move all tests to `tests/` on rstest, and add the shared `litellm-cache-testing` contract suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: freeze native cache request kwargs and batch entries for type discipline Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: declare semantic lookup methods in the native stub Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(rust): align the cache crates with Python and activate every backend The cache port had drifted: lifecycle and Redis-only operations sat on `BaseCache`, counters were pinned to `f64`, each semantic backend defined its own embedder and prompt handling, and only the in-memory backend could be selected natively. - Split `disconnect` and `test_connection` out of `BaseCache` into optional capabilities, implemented only where the Python class defines them, and give every Redis-only operation its own capability trait. - Decouple counters from the stored value type, so one backend can serve both responses and counters as Python's `RedisCache` does. - Share one `Embedder` and prompt contract in `litellm_cache::semantic`, and make the Redis and Valkey semantic backends generic over their codec. - Port the Python operations that were missing: `async_refresh_ttl`, `async_rpush_and_trim`, `async_set_cache_pipeline_with_ttls`, the DualCache pipeline, sadd, bulk delete and TTL reads, and the semantic-similarity write-back. - Take the HTTP client from the host pool in the GCS, S3 and Azure backends. - Activate all nine backends through the Rust catalog, whose rules all stay `PYTHON_ONLY`, and route the `Cache` facade's storage calls to the native runtime when one is selected. - Give every crate the same layout, move all tests to `tests/` on rstest, and add the shared `litellm-cache-testing` contract suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: freeze native cache request kwargs and batch entries for type discipline Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: declare semantic lookup methods in the native stub Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(rust): opt the native Messages and tokenizer suites into Rust explicitly #42517 made the Messages, token counter and tokenizer routes Python-only, so tests/test_litellm_rust silently exercised the Python path or failed outright. Each suite now prepends a RUST_OPT_IN rule for its route, keeping native coverage without changing the shipped default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(rust): pop one at a time in the Redis 6 lpop pipeline and drop explanatory comments Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Yujong Lee <yujong@berri.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
125 lines
3 KiB
Rust
125 lines
3 KiB
Rust
use std::{future::Future, time::Duration};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::Error;
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum BatchEntry<V> {
|
|
Hit(V),
|
|
Miss,
|
|
Invalid,
|
|
}
|
|
|
|
pub trait CacheContext: Clone + Send + Sync + 'static {
|
|
fn ttl(&self) -> Option<Duration>;
|
|
|
|
fn with_ttl(&self, ttl: Option<Duration>) -> Self;
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
pub struct ExactCacheContext {
|
|
pub ttl: Option<Duration>,
|
|
}
|
|
|
|
impl CacheContext for ExactCacheContext {
|
|
fn ttl(&self) -> Option<Duration> {
|
|
self.ttl
|
|
}
|
|
|
|
fn with_ttl(&self, ttl: Option<Duration>) -> Self {
|
|
Self { ttl }
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
pub struct SemanticCacheContext {
|
|
pub input: Option<serde_json::Value>,
|
|
pub messages: Option<serde_json::Value>,
|
|
pub metadata: Option<serde_json::Value>,
|
|
pub scope: Option<String>,
|
|
pub ttl: Option<Duration>,
|
|
}
|
|
|
|
impl CacheContext for SemanticCacheContext {
|
|
fn ttl(&self) -> Option<Duration> {
|
|
self.ttl
|
|
}
|
|
|
|
fn with_ttl(&self, ttl: Option<Duration>) -> Self {
|
|
Self {
|
|
ttl,
|
|
..self.clone()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum CacheConnectionStatus {
|
|
Success,
|
|
Failed,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
pub struct CacheConnectionResult {
|
|
pub status: CacheConnectionStatus,
|
|
pub message: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
pub trait BaseCache: Send + Sync {
|
|
type Value: Clone + Send + Sync + 'static;
|
|
type Context: CacheContext;
|
|
|
|
fn get_ttl(&self, context: &Self::Context) -> Option<Duration>;
|
|
|
|
fn set_cache(
|
|
&self,
|
|
key: &str,
|
|
value: Self::Value,
|
|
context: &Self::Context,
|
|
) -> Result<(), Error>;
|
|
|
|
fn get_cache(&self, key: &str, context: &Self::Context) -> Result<Option<Self::Value>, Error>;
|
|
|
|
fn async_set_cache(
|
|
&self,
|
|
key: &str,
|
|
value: Self::Value,
|
|
context: Self::Context,
|
|
) -> impl Future<Output = Result<(), Error>> + Send {
|
|
async move { self.set_cache(key, value, &context) }
|
|
}
|
|
|
|
fn async_get_cache(
|
|
&self,
|
|
key: &str,
|
|
context: &Self::Context,
|
|
) -> impl Future<Output = Result<Option<Self::Value>, Error>> + Send {
|
|
async move { self.get_cache(key, context) }
|
|
}
|
|
|
|
fn async_set_cache_pipeline(
|
|
&self,
|
|
entries: Vec<(String, Self::Value)>,
|
|
context: Self::Context,
|
|
) -> impl Future<Output = Result<(), Error>> + Send {
|
|
async move {
|
|
for (key, value) in entries {
|
|
self.async_set_cache(&key, value, context.clone()).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn batch_cache_write(
|
|
&self,
|
|
key: &str,
|
|
value: Self::Value,
|
|
context: Self::Context,
|
|
) -> impl Future<Output = Result<(), Error>> + Send {
|
|
self.async_set_cache(key, value, context)
|
|
}
|
|
}
|