Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions redisvl/extensions/cache/llm/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,10 @@ def check(
if return_fields and not isinstance(return_fields, list):
raise TypeError("Return fields must be a list of values.")

# Use overrides or defaults
distance_threshold = distance_threshold or self._distance_threshold
# Use overrides or defaults. Note: 0 is a valid distance threshold
# (exact match only), so only fall back on None.
if distance_threshold is None:
distance_threshold = self._distance_threshold

# Vectorize prompt if not provided
if vector is None and prompt is not None:
Expand Down Expand Up @@ -491,8 +493,10 @@ async def acheck(
if return_fields and not isinstance(return_fields, list):
raise TypeError("Return fields must be a list of values.")

# Use overrides or defaults
distance_threshold = distance_threshold or self._distance_threshold
# Use overrides or defaults. Note: 0 is a valid distance threshold
# (exact match only), so only fall back on None.
if distance_threshold is None:
distance_threshold = self._distance_threshold

# Vectorize prompt if not provided
if vector is None and prompt is not None:
Expand Down
6 changes: 4 additions & 2 deletions redisvl/extensions/message_history/semantic_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,10 @@ def get_relevant(
# Validate and normalize role parameter
roles_to_filter = self._validate_roles(role)

# override distance threshold
distance_threshold = distance_threshold or self._distance_threshold
# override distance threshold. Note: 0 is a valid distance threshold
# (exact match only), so only fall back on None.
if distance_threshold is None:
distance_threshold = self._distance_threshold
Comment thread
nickleodoen marked this conversation as resolved.

return_fields = [
SESSION_FIELD_NAME,
Expand Down
27 changes: 27 additions & 0 deletions tests/integration/test_llmcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,33 @@ def test_check_no_match(cache, vectorizer):
assert len(check_result) == 0


def test_check_with_zero_distance_threshold(cache):
"""A per-call distance_threshold of 0 must be honored, not treated as unset.

Regression test: `distance_threshold or self._distance_threshold` treated the
valid threshold 0 as falsy and silently fell back to the cache default, so
callers requesting exact-match-only lookups received fuzzy matches.
"""
cache.store("what is the capital of france?", "paris")

# Semantically similar but not identical -> must NOT hit at threshold 0
assert cache.check("what's the capital of france?", distance_threshold=0) == []

# Sanity check: the same query does hit at the cache's default threshold
assert cache.check("what's the capital of france?") != []


@pytest.mark.asyncio
async def test_acheck_with_zero_distance_threshold(cache):
"""Async variant: a per-call distance_threshold of 0 must be honored."""
await cache.astore("what is the capital of france?", "paris")

assert (
await cache.acheck("what's the capital of france?", distance_threshold=0)
) == []
assert (await cache.acheck("what's the capital of france?")) != []


def test_check_invalid_input(cache):
with pytest.raises(ValueError):
cache.check()
Expand Down
24 changes: 24 additions & 0 deletions tests/integration/test_message_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,30 @@ def test_semantic_add_and_get_relevant(semantic_history):
bad_context = semantic_history.get_relevant("test prompt", top_k="3")


@requires_hf
def test_semantic_get_relevant_with_zero_distance_threshold(semantic_history):
"""A per-call distance_threshold of 0 must be honored, not treated as unset.

Regression test: `distance_threshold or self._distance_threshold` treated the
valid threshold 0 as falsy and silently fell back to the configured default,
so callers requesting exact-match-only context received fuzzy matches.
"""
semantic_history.store(
prompt="list of common fruits",
response="apples, oranges, bananas, strawberries",
)
semantic_history.set_distance_threshold(0.5)

# Semantically similar but not identical -> must NOT match at threshold 0
assert (
semantic_history.get_relevant("list of typical fruits", distance_threshold=0)
== []
)

# Sanity check: the same query does match at the configured default
assert semantic_history.get_relevant("list of typical fruits") != []


@requires_hf
def test_semantic_get_raw(semantic_history):
semantic_history.store("first prompt", "first response")
Expand Down
Loading