mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +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>
95 lines
3.1 KiB
Rust
95 lines
3.1 KiB
Rust
//! The shared cache contracts, run against the in-process fake connection and, when
|
|
//! `LITELLM_TEST_REDIS_CLUSTER_NODES` is set, against a live Redis Cluster.
|
|
|
|
mod support;
|
|
|
|
use std::time::Duration;
|
|
|
|
use litellm_cache::{ExactCacheContext, JsonCodec};
|
|
use litellm_cache_testing as contract;
|
|
use rstest::rstest;
|
|
use serde_json::json;
|
|
use support::{JsonCache, cluster_cache, fake_cache};
|
|
|
|
const PREFIX: &str = "contract:";
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum Contract {
|
|
HitAndMiss,
|
|
SyncAsyncEquivalence,
|
|
OverwriteReplaces,
|
|
PipelineWritesEveryEntry,
|
|
BatchPreservesOrder,
|
|
DeleteRemovesKey,
|
|
FlushClears,
|
|
CounterAccumulates,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum Server {
|
|
Fake,
|
|
Cluster,
|
|
}
|
|
|
|
async fn check<C>(contract: Contract, cache: &JsonCache<C>)
|
|
where
|
|
C: redis::ConnectionLike + Send + 'static,
|
|
{
|
|
let context = ExactCacheContext::default();
|
|
match contract {
|
|
Contract::HitAndMiss => {
|
|
contract::hit_and_miss(cache, context, PREFIX, json!({"answer": 42})).await
|
|
}
|
|
Contract::SyncAsyncEquivalence => {
|
|
contract::sync_async_equivalence(cache, context, PREFIX, json!("first"), json!([2]))
|
|
.await
|
|
}
|
|
Contract::OverwriteReplaces => {
|
|
contract::overwrite_replaces(cache, context, PREFIX, json!(1), json!({"b": 2})).await
|
|
}
|
|
Contract::PipelineWritesEveryEntry => {
|
|
contract::pipeline_writes_every_entry(
|
|
cache,
|
|
context,
|
|
PREFIX,
|
|
vec![json!("a"), json!(2), json!({"c": true})],
|
|
)
|
|
.await
|
|
}
|
|
Contract::BatchPreservesOrder => {
|
|
contract::batch_preserves_order(cache, context, PREFIX, json!("first"), json!(2)).await
|
|
}
|
|
Contract::DeleteRemovesKey => {
|
|
contract::delete_removes_key(cache, context, PREFIX, json!("value")).await
|
|
}
|
|
Contract::FlushClears => {
|
|
contract::flush_clears(cache, context, PREFIX, json!("value")).await
|
|
}
|
|
Contract::CounterAccumulates => contract::counter_accumulates(cache, context, PREFIX).await,
|
|
}
|
|
}
|
|
|
|
#[rstest]
|
|
#[case::hit_and_miss(Contract::HitAndMiss)]
|
|
#[case::sync_async_equivalence(Contract::SyncAsyncEquivalence)]
|
|
#[case::overwrite_replaces(Contract::OverwriteReplaces)]
|
|
#[case::pipeline_writes_every_entry(Contract::PipelineWritesEveryEntry)]
|
|
#[case::batch_preserves_order(Contract::BatchPreservesOrder)]
|
|
#[case::delete_removes_key(Contract::DeleteRemovesKey)]
|
|
#[case::flush_clears(Contract::FlushClears)]
|
|
#[case::counter_accumulates(Contract::CounterAccumulates)]
|
|
#[tokio::test]
|
|
async fn redis_satisfies_the_cache_contract(
|
|
#[case] contract: Contract,
|
|
#[values(Server::Fake, Server::Cluster)] server: Server,
|
|
) {
|
|
match server {
|
|
Server::Fake => check(contract, &fake_cache("contract")).await,
|
|
Server::Cluster => {
|
|
let label = format!("{contract:?}");
|
|
if let Some(cache) = cluster_cache(&label, Duration::from_secs(120), JsonCodec::new()) {
|
|
check(contract, &cache).await;
|
|
}
|
|
}
|
|
}
|
|
}
|