mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-02 03:26:17 +00:00
community: init signature revision for Cassandra LLM cache classes + small maintenance (#17765)
This PR improves on the `CassandraCache` and `CassandraSemanticCache` classes, mainly in the constructor signature, and also introduces several minor improvements around these classes. ### Init signature A (sigh) breaking change is tentatively introduced to the constructor. To me, the advantages outweigh the possible discomfort: the new syntax places the DB-connection objects `session` and `keyspace` later in the param list, so that they can be given a default value. This is what enables the pattern of _not_ specifying them, provided one has previously initialized the Cassandra connection through the versatile utility method `cassio.init(...)`. In this way, a much less unwieldy instantiation can be done, such as `CassandraCache()` and `CassandraSemanticCache(embedding=xyz)`, everything else falling back to defaults. A downside is that, compared to the earlier signature, this might turn out to be breaking for those doing positional instantiation. As a way to mitigate this problem, this PR typechecks its first argument trying to detect the legacy usage. (And to make this point less tricky in the future, most arguments are left to be keyword-only). If this is considered too harsh, I'd like guidance on how to further smoothen this transition. **Our plan is to make the pattern of optional session/keyspace a standard across all Cassandra classes**, so that a repeatable strategy would be ideal. A possibility would be to keep positional arguments for legacy reasons but issue a deprecation warning if any of them is actually used, to later remove them with 0.2 - please advise on this point. ### Other changes - class docstrings: enriched, completely moved to class level, added note on `cassio.init(...)` pattern, added tiny sample usage code. - semantic cache: revised terminology to never mention "distance" (it is in fact a similarity!). Kept the legacy constructor param with a deprecation warning if used. - `llm_caching` notebook: uniform flow with the Cassandra and Astra DB separate cases; better and Cassandra-first description; all imports made explicit and from community where appropriate. - cache integration tests moved to community (incl. the imported tools), env var bugfix for `CASSANDRA_CONTACT_POINTS`. --------- Co-authored-by: Erick Friis <erick@langchain.dev>
This commit is contained in:
@@ -1045,11 +1045,43 @@ class CassandraCache(BaseCache):
|
||||
"""
|
||||
Cache that uses Cassandra / Astra DB as a backend.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cassio
|
||||
|
||||
from langchain_community.cache import CassandraCache
|
||||
from langchain_core.globals import set_llm_cache
|
||||
|
||||
cassio.init(auto=True) # Requires env. variables, see CassIO docs
|
||||
|
||||
set_llm_cache(CassandraCache())
|
||||
|
||||
It uses a single Cassandra table.
|
||||
The lookup keys (which get to form the primary key) are:
|
||||
- prompt, a string
|
||||
- llm_string, a deterministic str representation of the model parameters.
|
||||
(needed to prevent collisions same-prompt-different-model collisions)
|
||||
(needed to prevent same-prompt-different-model collisions)
|
||||
|
||||
Args:
|
||||
session: an open Cassandra session.
|
||||
Leave unspecified to use the global cassio init (see below)
|
||||
keyspace: the keyspace to use for storing the cache.
|
||||
Leave unspecified to use the global cassio init (see below)
|
||||
table_name: name of the Cassandra table to use as cache
|
||||
ttl_seconds: time-to-live for cache entries
|
||||
(default: None, i.e. forever)
|
||||
setup_mode: a value in langchain_community.utilities.cassandra.SetupMode.
|
||||
Choose between SYNC, ASYNC and OFF - the latter if the Cassandra
|
||||
table is guaranteed to exist already, for a faster initialization.
|
||||
|
||||
Note:
|
||||
The session and keyspace parameters, when left out (or passed as None),
|
||||
fall back to the globally-available cassio settings if any are available.
|
||||
In other words, if a previously-run 'cassio.init(...)' has been
|
||||
executed previously anywhere in the code, Cassandra-based objects
|
||||
need not specify the connection parameters at all.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -1061,25 +1093,21 @@ class CassandraCache(BaseCache):
|
||||
skip_provisioning: bool = False,
|
||||
setup_mode: CassandraSetupMode = CassandraSetupMode.SYNC,
|
||||
):
|
||||
"""
|
||||
Initialize with a ready session and a keyspace name.
|
||||
Args:
|
||||
session (cassandra.cluster.Session): an open Cassandra session
|
||||
keyspace (str): the keyspace to use for storing the cache
|
||||
table_name (str): name of the Cassandra table to use as cache
|
||||
ttl_seconds (optional int): time-to-live for cache entries
|
||||
(default: None, i.e. forever)
|
||||
"""
|
||||
if skip_provisioning:
|
||||
warn_deprecated(
|
||||
"0.0.33", alternative="Use setup_mode=CassandraSetupMode.OFF instead."
|
||||
"0.0.33",
|
||||
name="skip_provisioning",
|
||||
alternative=(
|
||||
"setup_mode=langchain_community.utilities.cassandra.SetupMode.OFF"
|
||||
),
|
||||
pending=True,
|
||||
)
|
||||
try:
|
||||
from cassio.table import ElasticCassandraTable
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
raise ImportError(
|
||||
"Could not import cassio python package. "
|
||||
"Please install it with `pip install cassio`."
|
||||
"Please install it with `pip install -U cassio`."
|
||||
)
|
||||
|
||||
self.session = session
|
||||
@@ -1170,6 +1198,7 @@ class CassandraCache(BaseCache):
|
||||
await self.kv_cache.aclear()
|
||||
|
||||
|
||||
# This constant is in fact a similarity - the 'distance' name is kept for compatibility:
|
||||
CASSANDRA_SEMANTIC_CACHE_DEFAULT_DISTANCE_METRIC = "dot"
|
||||
CASSANDRA_SEMANTIC_CACHE_DEFAULT_SCORE_THRESHOLD = 0.85
|
||||
CASSANDRA_SEMANTIC_CACHE_DEFAULT_TABLE_NAME = "langchain_llm_semantic_cache"
|
||||
@@ -1182,60 +1211,117 @@ class CassandraSemanticCache(BaseCache):
|
||||
Cache that uses Cassandra as a vector-store backend for semantic
|
||||
(i.e. similarity-based) lookup.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cassio
|
||||
|
||||
from langchain_community.cache import CassandraSemanticCache
|
||||
from langchain_core.globals import set_llm_cache
|
||||
|
||||
cassio.init(auto=True) # Requires env. variables, see CassIO docs
|
||||
|
||||
my_embedding = ...
|
||||
|
||||
set_llm_cache(CassandraSemanticCache(
|
||||
embedding=my_embedding,
|
||||
table_name="my_semantic_cache",
|
||||
))
|
||||
|
||||
It uses a single (vector) Cassandra table and stores, in principle,
|
||||
cached values from several LLMs, so the LLM's llm_string is part
|
||||
of the rows' primary keys.
|
||||
|
||||
The similarity is based on one of several distance metrics (default: "dot").
|
||||
If choosing another metric, the default threshold is to be re-tuned accordingly.
|
||||
One can choose a similarity measure (default: "dot" for dot-product).
|
||||
Choosing another one ("cos", "l2") almost certainly requires threshold tuning.
|
||||
(which may be in order nevertheless, even if sticking to "dot").
|
||||
|
||||
Args:
|
||||
session: an open Cassandra session.
|
||||
Leave unspecified to use the global cassio init (see below)
|
||||
keyspace: the keyspace to use for storing the cache.
|
||||
Leave unspecified to use the global cassio init (see below)
|
||||
embedding: Embedding provider for semantic
|
||||
encoding and search.
|
||||
table_name: name of the Cassandra (vector) table
|
||||
to use as cache. There is a default for "simple" usage, but
|
||||
remember to explicitly specify different tables if several embedding
|
||||
models coexist in your app (they cannot share one cache table).
|
||||
distance_metric: an alias for the 'similarity_measure' parameter (see below).
|
||||
As the "distance" terminology is misleading, please prefer
|
||||
'similarity_measure' for clarity.
|
||||
score_threshold: numeric value to use as
|
||||
cutoff for the similarity searches
|
||||
ttl_seconds: time-to-live for cache entries
|
||||
(default: None, i.e. forever)
|
||||
similarity_measure: which measure to adopt for similarity searches.
|
||||
Note: this parameter is aliased by 'distance_metric' - however,
|
||||
it is suggested to use the "similarity" terminology since this value
|
||||
is in fact a similarity (i.e. higher means closer).
|
||||
Note that at most one of the two parameters 'distance_metric'
|
||||
and 'similarity_measure' can be provided.
|
||||
setup_mode: a value in langchain_community.utilities.cassandra.SetupMode.
|
||||
Choose between SYNC, ASYNC and OFF - the latter if the Cassandra
|
||||
table is guaranteed to exist already, for a faster initialization.
|
||||
|
||||
Note:
|
||||
The session and keyspace parameters, when left out (or passed as None),
|
||||
fall back to the globally-available cassio settings if any are available.
|
||||
In other words, if a previously-run 'cassio.init(...)' has been
|
||||
executed previously anywhere in the code, Cassandra-based objects
|
||||
need not specify the connection parameters at all.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
session: Optional[CassandraSession],
|
||||
keyspace: Optional[str],
|
||||
embedding: Embeddings,
|
||||
session: Optional[CassandraSession] = None,
|
||||
keyspace: Optional[str] = None,
|
||||
embedding: Optional[Embeddings] = None,
|
||||
table_name: str = CASSANDRA_SEMANTIC_CACHE_DEFAULT_TABLE_NAME,
|
||||
distance_metric: str = CASSANDRA_SEMANTIC_CACHE_DEFAULT_DISTANCE_METRIC,
|
||||
distance_metric: Optional[str] = None,
|
||||
score_threshold: float = CASSANDRA_SEMANTIC_CACHE_DEFAULT_SCORE_THRESHOLD,
|
||||
ttl_seconds: Optional[int] = CASSANDRA_SEMANTIC_CACHE_DEFAULT_TTL_SECONDS,
|
||||
skip_provisioning: bool = False,
|
||||
similarity_measure: str = CASSANDRA_SEMANTIC_CACHE_DEFAULT_DISTANCE_METRIC,
|
||||
setup_mode: CassandraSetupMode = CassandraSetupMode.SYNC,
|
||||
):
|
||||
"""
|
||||
Initialize the cache with all relevant parameters.
|
||||
Args:
|
||||
session (cassandra.cluster.Session): an open Cassandra session
|
||||
keyspace (str): the keyspace to use for storing the cache
|
||||
embedding (Embedding): Embedding provider for semantic
|
||||
encoding and search.
|
||||
table_name (str): name of the Cassandra (vector) table
|
||||
to use as cache
|
||||
distance_metric (str, 'dot'): which measure to adopt for
|
||||
similarity searches
|
||||
score_threshold (optional float): numeric value to use as
|
||||
cutoff for the similarity searches
|
||||
ttl_seconds (optional int): time-to-live for cache entries
|
||||
(default: None, i.e. forever)
|
||||
The default score threshold is tuned to the default metric.
|
||||
Tune it carefully yourself if switching to another distance metric.
|
||||
"""
|
||||
if skip_provisioning:
|
||||
warn_deprecated(
|
||||
"0.0.33", alternative="Use setup_mode=CassandraSetupMode.OFF instead."
|
||||
"0.0.33",
|
||||
name="skip_provisioning",
|
||||
alternative=(
|
||||
"setup_mode=langchain_community.utilities.cassandra.SetupMode.OFF"
|
||||
),
|
||||
pending=True,
|
||||
)
|
||||
try:
|
||||
from cassio.table import MetadataVectorCassandraTable
|
||||
except (ImportError, ModuleNotFoundError):
|
||||
raise ImportError(
|
||||
"Could not import cassio python package. "
|
||||
"Please install it with `pip install cassio`."
|
||||
"Please install it with `pip install -U cassio`."
|
||||
)
|
||||
|
||||
if not embedding:
|
||||
raise ValueError("Missing required parameter 'embedding'.")
|
||||
|
||||
# detect if legacy 'distance_metric' parameter used
|
||||
if distance_metric is not None:
|
||||
# if passed, takes precedence over 'similarity_measure', but we warn:
|
||||
warn_deprecated(
|
||||
"0.0.33",
|
||||
name="distance_metric",
|
||||
alternative="similarity_measure",
|
||||
pending=True,
|
||||
)
|
||||
similarity_measure = distance_metric
|
||||
|
||||
self.session = session
|
||||
self.keyspace = keyspace
|
||||
self.embedding = embedding
|
||||
self.table_name = table_name
|
||||
self.distance_metric = distance_metric
|
||||
self.similarity_measure = similarity_measure
|
||||
self.score_threshold = score_threshold
|
||||
self.ttl_seconds = ttl_seconds
|
||||
|
||||
@@ -1347,7 +1433,7 @@ class CassandraSemanticCache(BaseCache):
|
||||
vector=prompt_embedding,
|
||||
metadata={"_llm_string_hash": _hash(llm_string)},
|
||||
n=1,
|
||||
metric=self.distance_metric,
|
||||
metric=self.similarity_measure,
|
||||
metric_threshold=self.score_threshold,
|
||||
)
|
||||
)
|
||||
@@ -1378,7 +1464,7 @@ class CassandraSemanticCache(BaseCache):
|
||||
vector=prompt_embedding,
|
||||
metadata={"_llm_string_hash": _hash(llm_string)},
|
||||
n=1,
|
||||
metric=self.distance_metric,
|
||||
metric=self.similarity_measure,
|
||||
metric_threshold=self.score_threshold,
|
||||
)
|
||||
)
|
||||
|
@@ -21,7 +21,7 @@ def cassandra_connection() -> Iterator[Tuple[Any, str]]:
|
||||
keyspace = "langchain_cache_test_keyspace"
|
||||
# get db connection
|
||||
if "CASSANDRA_CONTACT_POINTS" in os.environ:
|
||||
contact_points = os.environ["CONTACT_POINTS"].split(",")
|
||||
contact_points = os.environ["CASSANDRA_CONTACT_POINTS"].split(",")
|
||||
cluster = Cluster(contact_points)
|
||||
else:
|
||||
cluster = Cluster()
|
||||
|
Reference in New Issue
Block a user