55"""
66
77from typing import Any , Dict , List , Literal , Optional
8+ from urllib .parse import quote , unquote
89
910from redisvl .extensions .cache .llm .base import BaseLLMCache
1011from redisvl .extensions .cache .llm .schema import CacheHit
1516logger = get_logger (__name__ )
1617
1718
18- _LANGCACHE_ATTR_ENCODE_TRANS = str .maketrans (
19- {
20- "," : "," , # U+FF0C FULLWIDTH COMMA
21- "/" : "∕" , # U+2215 DIVISION SLASH
22- "\\ " : "\" , # U+FF3C FULLWIDTH REVERSE SOLIDUS (backslash)
23- "?" : "?" , # U+FF1F FULLWIDTH QUESTION MARK
24- }
25- )
26-
27-
28- _LANGCACHE_ATTR_DECODE_TRANS = str .maketrans (
29- {v : k for k , v in _LANGCACHE_ATTR_ENCODE_TRANS .items ()}
30- )
31-
32-
33- def _encode_attribute_value_for_langcache (value : str ) -> str :
34- """Encode a string attribute value for use with the LangCache service.
35-
36- LangCache applies validation and matching rules to attribute values. In
37- particular, the managed service can reject values containing commas (",")
38- and may not reliably match filters on values containing slashes ("/").
39-
40- To keep attribute values round-trippable *and* usable for attribute
41- filtering, we replace these characters with visually similar Unicode
42- variants that the service accepts. A precomputed ``str.translate`` table is
43- used so values are scanned only once.
44- """
45-
46- return value .translate (_LANGCACHE_ATTR_ENCODE_TRANS )
47-
48-
4919def _encode_attributes_for_langcache (attributes : Dict [str , Any ]) -> Dict [str , Any ]:
5020 """Return a copy of *attributes* with string values safely encoded.
5121
@@ -61,25 +31,17 @@ def _encode_attributes_for_langcache(attributes: Dict[str, Any]) -> Dict[str, An
6131 safe_attributes : Dict [str , Any ] = dict (attributes )
6232 for key , value in attributes .items ():
6333 if isinstance (value , str ):
64- encoded = _encode_attribute_value_for_langcache (value )
34+ # Percent-encode all characters (no ``safe`` set) so punctuation and
35+ # other special characters cannot interfere with LangCache's
36+ # underlying query/tokenization rules.
37+ encoded = quote (value , safe = "" )
6538 if encoded != value :
6639 safe_attributes [key ] = encoded
6740 changed = True
6841
6942 return safe_attributes if changed else attributes
7043
7144
72- def _decode_attribute_value_from_langcache (value : str ) -> str :
73- """Decode a string attribute value returned from the LangCache service.
74-
75- This reverses :func:`_encode_attribute_value_for_langcache`, translating the
76- fullwidth comma and division slash characters back to their ASCII
77- counterparts so callers see the original values they stored.
78- """
79-
80- return value .translate (_LANGCACHE_ATTR_DECODE_TRANS )
81-
82-
8345def _decode_attributes_from_langcache (attributes : Dict [str , Any ]) -> Dict [str , Any ]:
8446 """Return a copy of *attributes* with string values safely decoded.
8547
@@ -95,7 +57,7 @@ def _decode_attributes_from_langcache(attributes: Dict[str, Any]) -> Dict[str, A
9557 decoded_attributes : Dict [str , Any ] = dict (attributes )
9658 for key , value in attributes .items ():
9759 if isinstance (value , str ):
98- decoded = _decode_attribute_value_from_langcache (value )
60+ decoded = unquote (value )
9961 if decoded != value :
10062 decoded_attributes [key ] = decoded
10163 changed = True
@@ -472,7 +434,7 @@ def store(
472434 vector (Optional[List[float]]): Not supported by LangCache API.
473435 metadata (Optional[Dict[str, Any]]): Optional metadata (stored as attributes).
474436 filters (Optional[Dict[str, Any]]): Not supported.
475- ttl (Optional[int]): Optional TTL override (not supported by LangCache) .
437+ ttl (Optional[int]): Optional TTL override in seconds .
476438
477439 Returns:
478440 str: The entry ID for the cached entry.
@@ -491,18 +453,22 @@ def store(
491453 if filters is not None :
492454 logger .warning ("LangCache does not support filters" )
493455
494- if ttl is not None :
495- logger .warning ("LangCache does not support per-entry TTL" )
496-
497- # Store using the LangCache client; only send attributes if provided (non-empty)
498456 try :
457+ ttl_millis = round (ttl * 1000 ) if ttl is not None else None
499458 if metadata :
500459 safe_metadata = _encode_attributes_for_langcache (metadata )
501460 result = self ._client .set (
502- prompt = prompt , response = response , attributes = safe_metadata
461+ prompt = prompt ,
462+ response = response ,
463+ attributes = safe_metadata ,
464+ ttl_millis = ttl_millis ,
503465 )
504466 else :
505- result = self ._client .set (prompt = prompt , response = response )
467+ result = self ._client .set (
468+ prompt = prompt ,
469+ response = response ,
470+ ttl_millis = ttl_millis ,
471+ )
506472 except Exception as e : # narrow for known SDK error when possible
507473 try :
508474 from langcache .errors import BadRequestErrorResponseContent
@@ -541,7 +507,7 @@ async def astore(
541507 vector (Optional[List[float]]): Not supported by LangCache API.
542508 metadata (Optional[Dict[str, Any]]): Optional metadata (stored as attributes).
543509 filters (Optional[Dict[str, Any]]): Not supported.
544- ttl (Optional[int]): Optional TTL override (not supported by LangCache) .
510+ ttl (Optional[int]): Optional TTL override in seconds .
545511
546512 Returns:
547513 str: The entry ID for the cached entry.
@@ -560,18 +526,22 @@ async def astore(
560526 if filters is not None :
561527 logger .warning ("LangCache does not support filters" )
562528
563- if ttl is not None :
564- logger .warning ("LangCache does not support per-entry TTL" )
565-
566- # Store using the LangCache client (async); only send attributes if provided (non-empty)
567529 try :
530+ ttl_millis = round (ttl * 1000 ) if ttl is not None else None
568531 if metadata :
569532 safe_metadata = _encode_attributes_for_langcache (metadata )
570533 result = await self ._client .set_async (
571- prompt = prompt , response = response , attributes = safe_metadata
534+ prompt = prompt ,
535+ response = response ,
536+ attributes = safe_metadata ,
537+ ttl_millis = ttl_millis ,
572538 )
573539 else :
574- result = await self ._client .set_async (prompt = prompt , response = response )
540+ result = await self ._client .set_async (
541+ prompt = prompt ,
542+ response = response ,
543+ ttl_millis = ttl_millis ,
544+ )
575545 except Exception as e :
576546 try :
577547 from langcache .errors import BadRequestErrorResponseContent
0 commit comments