fix: address review feedback - local variable for getattr, sync path embed_api_base, test production code

- Store getattr result in inner_cache local variable (caching_handler.py)
- Apply embed_api_base fix to sync set_cache and get_cache (qdrant_semantic_cache.py)
- Rewrite test to call LLMCachingHandler.__init__() directly instead of replaying getattr
This commit is contained in:
Vedaant Singh 2026-04-11 12:22:35 +01:00
parent 12bd05990e
commit fb7cf50148
3 changed files with 31 additions and 63 deletions

View file

@ -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:

View file

@ -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

View file

@ -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