Skip to content

Commit c7bf8c0

Browse files
authored
LangCache integration cleanup (#418)
Changes/fixes to the LangCache integration: - The name "LangCacheWrapper" just doesn't hit as deep. Also, it doesn't follow the same pattern as "SemanticCache," the Redis-based cache in this library. Seeing "Cache" twice feels better than "Wrapper." - Adjust how we send attributes - Use the correct import for the LangCache client
1 parent 69407f6 commit c7bf8c0

File tree

3 files changed

+183
-58
lines changed

3 files changed

+183
-58
lines changed

redisvl/extensions/cache/llm/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
This module provides LLM cache implementations for RedisVL.
55
"""
66

7-
from redisvl.extensions.cache.llm.langcache import LangCacheWrapper
7+
from redisvl.extensions.cache.llm.langcache import LangCacheSemanticCache
88
from redisvl.extensions.cache.llm.schema import (
99
CacheEntry,
1010
CacheHit,
@@ -14,7 +14,7 @@
1414

1515
__all__ = [
1616
"SemanticCache",
17-
"LangCacheWrapper",
17+
"LangCacheSemanticCache",
1818
"CacheEntry",
1919
"CacheHit",
2020
"SemanticCacheIndexSchema",

redisvl/extensions/cache/llm/langcache.py

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
logger = get_logger(__name__)
1616

1717

18-
class LangCacheWrapper(BaseLLMCache):
18+
class LangCacheSemanticCache(BaseLLMCache):
1919
"""LLM Cache implementation using the LangCache managed service.
2020
2121
This cache uses the LangCache API service for semantic caching of LLM
@@ -24,9 +24,9 @@ class LangCacheWrapper(BaseLLMCache):
2424
Example:
2525
.. code-block:: python
2626
27-
from redisvl.extensions.cache.llm import LangCacheWrapper
27+
from redisvl.extensions.cache.llm import LangCacheSemanticCache
2828
29-
cache = LangCacheWrapper(
29+
cache = LangCacheSemanticCache(
3030
name="my_cache",
3131
server_url="https://api.langcache.com",
3232
cache_id="your-cache-id",
@@ -47,7 +47,7 @@ class LangCacheWrapper(BaseLLMCache):
4747
def __init__(
4848
self,
4949
name: str = "langcache",
50-
server_url: str = "https://api.langcache.com",
50+
server_url: str = "https://aws-us-east-1.langcache.redis.io",
5151
cache_id: str = "",
5252
api_key: str = "",
5353
ttl: Optional[int] = None,
@@ -56,7 +56,7 @@ def __init__(
5656
distance_scale: Literal["normalized", "redis"] = "normalized",
5757
**kwargs,
5858
):
59-
"""Initialize a LangCache wrapper.
59+
"""Initialize a LangCache semantic cache.
6060
6161
Args:
6262
name (str): The name of the cache. Defaults to "langcache".
@@ -79,9 +79,9 @@ def __init__(
7979
self._distance_scale = distance_scale
8080

8181
if not cache_id:
82-
raise ValueError("cache_id is required for LangCacheWrapper")
82+
raise ValueError("cache_id is required for LangCacheSemanticCache")
8383
if not api_key:
84-
raise ValueError("api_key is required for LangCacheWrapper")
84+
raise ValueError("api_key is required for LangCacheSemanticCache")
8585

8686
super().__init__(name=name, ttl=ttl, **kwargs)
8787

@@ -108,20 +108,20 @@ def _create_client(self):
108108
Initialize the LangCache client.
109109
110110
Returns:
111-
LangCacheClient: The LangCache client.
111+
LangCache: The LangCache client.
112112
113113
Raises:
114114
ImportError: If the langcache package is not installed.
115115
"""
116116
try:
117-
from langcache import LangCacheClient
117+
from langcache import LangCache
118118
except ImportError as e:
119119
raise ImportError(
120-
"The langcache package is required to use LangCacheWrapper. "
120+
"The langcache package is required to use LangCacheSemanticCache. "
121121
"Install it with: pip install langcache"
122122
) from e
123123

124-
return LangCacheClient(
124+
return LangCache(
125125
server_url=self._server_url,
126126
cache_id=self._cache_id,
127127
api_key=self._api_key,
@@ -155,6 +155,8 @@ def _build_search_kwargs(
155155
SearchStrategy.EXACT if "exact" in self._search_strategies else None,
156156
SearchStrategy.SEMANTIC if "semantic" in self._search_strategies else None,
157157
]
158+
# Filter out Nones to avoid sending invalid enum values
159+
search_strategies = [s for s in search_strategies if s is not None]
158160
kwargs: Dict[str, Any] = {
159161
"prompt": prompt,
160162
"search_strategies": search_strategies,
@@ -253,15 +255,31 @@ def check(
253255
if distance_threshold is not None:
254256
similarity_threshold = self._similarity_threshold(distance_threshold)
255257

256-
# Search using the LangCache client
257-
# The client itself is the context manager
258+
# Build kwargs
258259
search_kwargs = self._build_search_kwargs(
259260
prompt=prompt,
260261
similarity_threshold=similarity_threshold,
261262
attributes=attributes,
262263
)
263264

264-
response = self._client.search(**search_kwargs)
265+
try:
266+
response = self._client.search(**search_kwargs)
267+
except Exception as e:
268+
try:
269+
from langcache.errors import BadRequestErrorResponseContent
270+
except Exception:
271+
raise
272+
if (
273+
isinstance(e, BadRequestErrorResponseContent)
274+
and "no attributes are configured" in str(e).lower()
275+
and attributes
276+
):
277+
raise RuntimeError(
278+
"LangCache reported attributes are not configured for this cache, "
279+
"but attributes were provided to check(). Remove attributes or configure them on the cache."
280+
) from e
281+
else:
282+
raise
265283

266284
# Convert results to cache hits
267285
return self._hits_from_response(response, num_results)
@@ -321,7 +339,24 @@ async def acheck(
321339

322340
# Add attributes if provided (already handled by builder)
323341

324-
response = await self._client.search_async(**search_kwargs)
342+
try:
343+
response = await self._client.search_async(**search_kwargs)
344+
except Exception as e:
345+
try:
346+
from langcache.errors import BadRequestErrorResponseContent
347+
except Exception:
348+
raise
349+
if (
350+
isinstance(e, BadRequestErrorResponseContent)
351+
and "no attributes are configured" in str(e).lower()
352+
and attributes
353+
):
354+
raise RuntimeError(
355+
"LangCache reported attributes are not configured for this cache, "
356+
"but attributes were provided to acheck(). Remove attributes or configure them on the cache."
357+
) from e
358+
else:
359+
raise
325360

326361
# Convert results to cache hits
327362
return self._hits_from_response(response, num_results)
@@ -365,16 +400,30 @@ def store(
365400
if ttl is not None:
366401
logger.warning("LangCache does not support per-entry TTL")
367402

368-
# Store using the LangCache client
369-
# The client itself is the context manager
370-
# Only pass attributes if metadata is provided
371-
# Some caches may not have attributes configured
372-
if metadata:
373-
result = self._client.set(
374-
prompt=prompt, response=response, attributes=metadata
375-
)
376-
else:
377-
result = self._client.set(prompt=prompt, response=response)
403+
# Store using the LangCache client; only send attributes if provided (non-empty)
404+
try:
405+
if metadata:
406+
result = self._client.set(
407+
prompt=prompt, response=response, attributes=metadata
408+
)
409+
else:
410+
result = self._client.set(prompt=prompt, response=response)
411+
except Exception as e: # narrow for known SDK error when possible
412+
try:
413+
from langcache.errors import BadRequestErrorResponseContent
414+
except Exception:
415+
raise
416+
if (
417+
isinstance(e, BadRequestErrorResponseContent)
418+
and "no attributes are configured" in str(e).lower()
419+
and metadata
420+
):
421+
raise RuntimeError(
422+
"LangCache reported attributes are not configured for this cache, "
423+
"but metadata was provided to store(). Remove metadata or configure attributes on the cache."
424+
) from e
425+
else:
426+
raise
378427

379428
# Return the entry ID
380429
# Result is a SetResponse Pydantic model with entry_id attribute
@@ -419,16 +468,30 @@ async def astore(
419468
if ttl is not None:
420469
logger.warning("LangCache does not support per-entry TTL")
421470

422-
# Store using the LangCache client (async)
423-
# The client itself is the context manager
424-
# Only pass attributes if metadata is provided
425-
# Some caches may not have attributes configured
426-
if metadata:
427-
result = await self._client.set_async(
428-
prompt=prompt, response=response, attributes=metadata
429-
)
430-
else:
431-
result = await self._client.set_async(prompt=prompt, response=response)
471+
# Store using the LangCache client (async); only send attributes if provided (non-empty)
472+
try:
473+
if metadata:
474+
result = await self._client.set_async(
475+
prompt=prompt, response=response, attributes=metadata
476+
)
477+
else:
478+
result = await self._client.set_async(prompt=prompt, response=response)
479+
except Exception as e:
480+
try:
481+
from langcache.errors import BadRequestErrorResponseContent
482+
except Exception:
483+
raise
484+
if (
485+
isinstance(e, BadRequestErrorResponseContent)
486+
and "no attributes are configured" in str(e).lower()
487+
and metadata
488+
):
489+
raise RuntimeError(
490+
"LangCache reported attributes are not configured for this cache, "
491+
"but metadata was provided to astore(). Remove metadata or configure attributes on the cache."
492+
) from e
493+
else:
494+
raise
432495

433496
# Return the entry ID
434497
# Result is a SetResponse Pydantic model with entry_id attribute

0 commit comments

Comments
 (0)