Skip to content

fix: honor distance_threshold=0 in semantic cache and message history#650

Merged
vishal-bala merged 2 commits into
redis:mainfrom
nickleodoen:fix/zero-distance-threshold
Jul 14, 2026
Merged

fix: honor distance_threshold=0 in semantic cache and message history#650
vishal-bala merged 2 commits into
redis:mainfrom
nickleodoen:fix/zero-distance-threshold

Conversation

@nickleodoen

@nickleodoen nickleodoen commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #649

Problem

The per-call distance_threshold override was resolved with a truthiness check:

distance_threshold = distance_threshold or self._distance_threshold

0.0 is falsy, so a caller passing distance_threshold=0 (exact-match-only) had it silently replaced by the instance default and received fuzzy cache hits instead. 0 is a valid threshold everywhere else: set_threshold() accepts 0 <= x <= 2 and VectorRangeQuery.set_distance_threshold() rejects only negatives.

The failure mode is a false cache hit so the caller gets a cached response to a prompt they never asked, believing fuzzy matching was off.

Fix

Fall back to the configured default only when the override is None. Applied in SemanticCache.check(), SemanticCache.acheck(), and SemanticMessageHistory.get_relevant().

Tests

Added test_check_with_zero_distance_threshold and test_acheck_with_zero_distance_threshold to tests/integration/test_llmcache.py. Each stores a prompt, asserts a near-paraphrase does not hit at distance_threshold=0, and asserts it does hit at the cache default (so the test fails for the right reason rather than by accident).

Verified against the real suite (Python 3.12, Redis 8.4 via the repo's compose fixture):

  • before the fix: both new tests fail with AssertionError: assert [{'entry_id': ...}] == []
  • after the fix: both pass
  • full tests/integration/test_llmcache.py: 62 passed

Scope note

I left the ttl = ttl or self._ttl occurrences alone. A TTL of 0 isn't meaningful in Redis and the suite already rejects it (test_bad_ttl), so the falsy-fallback is harmless there. Happy to widen the scope if maintainers prefer consistency.


Note

Low Risk
Small, targeted behavior fix in lookup paths with clear regression tests; no API or schema changes beyond honoring an already-valid threshold value.

Overview
Fixes a bug where per-call distance_threshold overrides used distance_threshold or self._distance_threshold, so 0 (exact-match-only in Redis COSINE distance) was treated as unset and replaced by the instance default—causing false cache hits and fuzzy context when callers intended strict matching.

SemanticCache.check(), SemanticCache.acheck(), and SemanticMessageHistory.get_relevant() now fall back to the configured default only when the override is None.

Integration tests cover sync/async cache checks and semantic history retrieval: near-paraphrases must not match at distance_threshold=0, while the same queries still match at the default threshold.

Reviewed by Cursor Bugbot for commit 5620780. Bugbot is set up for automated code reviews on this repo. Configure here.

A per-call distance_threshold of 0 was resolved with a truthiness check
(`distance_threshold or self._distance_threshold`) and therefore silently
replaced by the instance default. Callers requesting exact-match-only lookups
received fuzzy cache hits instead.

0 is a valid threshold: set_threshold() accepts 0 <= x <= 2 and
VectorRangeQuery.set_distance_threshold() rejects only negatives. Only None
should fall back to the configured default.

Affects SemanticCache.check(), SemanticCache.acheck(), and
SemanticMessageHistory.get_relevant(). Adds regression tests covering the
sync and async cache paths.
Copilot AI review requested due to automatic review settings July 14, 2026 03:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an API correctness bug where passing distance_threshold=0 (intended to mean exact-match-only) was silently ignored due to a truthiness fallback, causing unexpected fuzzy cache hits. The change aligns per-call overrides with the existing contract that 0 is a valid distance threshold.

Changes:

  • Update SemanticCache.check() and SemanticCache.acheck() to fall back to the instance default only when distance_threshold is None (so 0 is honored).
  • Update SemanticMessageHistory.get_relevant() to use the same None-only fallback semantics for distance_threshold.
  • Add integration regression tests covering the sync/async semantic cache distance_threshold=0 behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
redisvl/extensions/cache/llm/semantic.py Fix per-call threshold override logic in check() / acheck() to honor distance_threshold=0.
redisvl/extensions/message_history/semantic_history.py Fix per-call threshold override logic in get_relevant() to honor distance_threshold=0.
tests/integration/test_llmcache.py Add integration regression tests for check() / acheck() when distance_threshold=0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread redisvl/extensions/message_history/semantic_history.py
Addresses review feedback: the falsy-override fix in semantic_history.py had no
regression test on that code path.
Copilot AI review requested due to automatic review settings July 14, 2026 04:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@nickleodoen

nickleodoen commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Fixed this up in 5620780.

Added test_semantic_get_relevant_with_zero_distance_threshold to tests/integration/test_message_history.py, alongside the existing get_relevant(..., distance_threshold=0.5) coverage as suggested. It stores a message, sets the instance threshold to 0.5, then asserts a near-paraphrase does not match at distance_threshold=0 and does match at the configured default - so the test fails for the right reason rather than by accident.

Verified against the pre-fix source: with distance_threshold or self._distance_threshold restored in semantic_history.py, the new test fails with AssertionError; with the fix in place it passes. Full test_message_history.py suite: 29 passed! We are chilling for sure.

@vishal-bala vishal-bala self-assigned this Jul 14, 2026
@vishal-bala vishal-bala added the auto:patch Increment the patch version when merged label Jul 14, 2026

@vishal-bala vishal-bala left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks for contributing 🙌

@vishal-bala
vishal-bala merged commit 074e8bf into redis:main Jul 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto:patch Increment the patch version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SemanticCache.check(distance_threshold=0) silently ignores the override

3 participants