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:
Stefano Lottini
2024-05-16 19:22:24 +02:00
committed by GitHub
parent 8db4a14648
commit 040597e832
4 changed files with 272 additions and 96 deletions

View File

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

View File

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