diff --git a/litellm/caching/caching_handler.py b/litellm/caching/caching_handler.py index 7cc45fbd5b1..f34f72dc393 100644 --- a/litellm/caching/caching_handler.py +++ b/litellm/caching/caching_handler.py @@ -98,9 +98,10 @@ class LLMCachingHandler: self.request_kwargs = request_kwargs self.original_function = original_function self.start_time = start_time - if litellm.cache is not None and isinstance(getattr(litellm.cache, "cache", None), RedisCache): + inner_cache = getattr(litellm.cache, "cache", None) + if litellm.cache is not None and isinstance(inner_cache, RedisCache): self.dual_cache: Optional[DualCache] = DualCache( - redis_cache=getattr(litellm.cache, "cache", None), + redis_cache=inner_cache, in_memory_cache=in_memory_cache_obj, ) else: diff --git a/litellm/caching/qdrant_semantic_cache.py b/litellm/caching/qdrant_semantic_cache.py index 138be85511c..35a7f3c2345 100644 --- a/litellm/caching/qdrant_semantic_cache.py +++ b/litellm/caching/qdrant_semantic_cache.py @@ -183,13 +183,16 @@ class QdrantSemanticCache(BaseCache): prompt += message["content"] # create an embedding for prompt + embedding_kwargs: dict[str, Any] = { + "model": self.embedding_model, + "input": prompt, + "cache": {"no-store": True, "no-cache": True}, + } + if getattr(self, "embed_api_base", None) is not None: + embedding_kwargs["api_base"] = self.embed_api_base embedding_response = cast( EmbeddingResponse, - litellm.embedding( - model=self.embedding_model, - input=prompt, - cache={"no-store": True, "no-cache": True}, - ), + litellm.embedding(**embedding_kwargs), ) # get the embedding @@ -227,13 +230,16 @@ class QdrantSemanticCache(BaseCache): prompt += message["content"] # convert to embedding + embedding_kwargs: dict[str, Any] = { + "model": self.embedding_model, + "input": prompt, + "cache": {"no-store": True, "no-cache": True}, + } + if getattr(self, "embed_api_base", None) is not None: + embedding_kwargs["api_base"] = self.embed_api_base embedding_response = cast( EmbeddingResponse, - litellm.embedding( - model=self.embedding_model, - input=prompt, - cache={"no-store": True, "no-cache": True}, - ), + litellm.embedding(**embedding_kwargs), ) # get the embedding diff --git a/tests/test_litellm/caching/test_qdrant_semantic_cache.py b/tests/test_litellm/caching/test_qdrant_semantic_cache.py index 5107654a5a6..d4c435e0c3b 100644 --- a/tests/test_litellm/caching/test_qdrant_semantic_cache.py +++ b/tests/test_litellm/caching/test_qdrant_semantic_cache.py @@ -9,57 +9,15 @@ sys.path.insert( ) # Adds the parent directory to the system path -def test_proxy_init_cache_does_not_crash_on_non_redis_cache(): +def test_caching_handler_init_does_not_crash_on_non_redis_cache(): """ - Test that proxy cache initialization does not crash for non-Redis backends. - Verifies that accessing litellm.cache.cache via getattr prevents AttributeError - when the cache type is qdrant-semantic. + Test that LLMCachingHandler.__init__ does not crash for non-Redis backends. + Verifies that the production code path handles Qdrant semantic cache without + raising AttributeError on litellm.cache.cache. """ import litellm from litellm.caching.caching import Cache - - with patch("litellm.llms.custom_httpx.http_handler._get_httpx_client") as mock_sync_client, \ - patch("litellm.llms.custom_httpx.http_handler.get_async_httpx_client"): - - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.json.return_value = {"result": {"exists": True}} - - mock_sync_client_instance = MagicMock() - mock_sync_client_instance.get.return_value = mock_response - mock_sync_client.return_value = mock_sync_client_instance - - # Create a qdrant-semantic cache (has no inner .cache attribute like Redis does) - cache = Cache( - type="qdrant-semantic", - qdrant_api_base="http://test.qdrant.local", - qdrant_api_key="test_key", - qdrant_collection_name="test_collection", - similarity_threshold=0.8, - ) - litellm.cache = cache - - # This should not raise AttributeError - from litellm.caching.redis_cache import RedisCache - from litellm.caching.redis_cluster_cache import RedisClusterCache - - inner = getattr(litellm.cache, "cache", None) - result = isinstance(inner, (RedisCache, RedisClusterCache)) - assert result is False - - # Cleanup - litellm.cache = None - - -def test_caching_handler_does_not_crash_on_non_redis_cache(): - """ - Test that CachingHandlerResponse isinstance checks do not crash for non-Redis backends. - Verifies that getattr prevents AttributeError when cache type is qdrant-semantic. - """ - import litellm - from litellm.caching.caching import Cache - from litellm.caching.redis_cache import RedisCache - from litellm.caching.s3_cache import S3Cache + from litellm.caching.caching_handler import LLMCachingHandler with patch("litellm.llms.custom_httpx.http_handler._get_httpx_client") as mock_sync_client, \ patch("litellm.llms.custom_httpx.http_handler.get_async_httpx_client"): @@ -81,10 +39,13 @@ def test_caching_handler_does_not_crash_on_non_redis_cache(): ) litellm.cache = cache - # These isinstance checks should not raise AttributeError - inner = getattr(litellm.cache, "cache", None) - assert not isinstance(inner, RedisCache) - assert not isinstance(inner, S3Cache) + # This should not raise AttributeError — exercises the actual production code + handler = LLMCachingHandler( + original_function=MagicMock(), + request_kwargs={"messages": [{"content": "test"}]}, + start_time=None, + ) + assert handler.dual_cache is None # Cleanup litellm.cache = None