Skip to content

fix(valkey): post-filter metadata_filter in vector search (issue #5795)#5796

Open
devin-ai-integration[bot] wants to merge 1 commit into
devin/valkey-5703-basefrom
devin/1778693042-fix-issue-5795
Open

fix(valkey): post-filter metadata_filter in vector search (issue #5795)#5796
devin-ai-integration[bot] wants to merge 1 commit into
devin/valkey-5703-basefrom
devin/1778693042-fix-issue-5795

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

Summary

Fixes #5795. In ValkeyStorage._vector_search (introduced by PR #5703), metadata_filter was being translated into @{key}:{value} clauses of the FT.SEARCH query. But the memory_index FT schema only defines fields for embedding, scope, categories, created_at, and importance — so any metadata key (e.g. agent_id) refers to a non-existent index field. On a real Valkey Search backend this either errors out or silently returns wrong results. The existing test suite only passes because ft.search is mocked.

This PR implements Option B from the issue (post-filter outside FT.SEARCH):

  1. Remove the buggy clauses. _vector_search no longer emits @{key}:{value} query parts for metadata_filter — only the indexed fields scope and categories remain in the FT query.
  2. Post-filter on the deserialized records. A new helper _metadata_matches is applied in Python after FT.SEARCH returns, mirroring the pattern used by LanceDBStorage. Each filter value is compared first natively and then as a string, so callers can pass either {"page": 5} or {"page": "5"}.
  3. Oversample the KNN limit. When a metadata_filter is supplied, _vector_search requests limit * 3 candidates from FT.SEARCH (and uses the same value for FtSearchLimit) so post-filtering doesn't under-return. Results are trimmed back to the original limit after filtering.

Why post-filter (Option B) instead of materialising fields (Option A)?

Option A would require either declaring a fixed set of metadata keys upfront (incompatible with arbitrary user metadata) or dynamically mutating the FT schema, which Valkey Search does not really support and would complicate index creation/upgrade. Post-filtering matches the existing approach already used by LanceDBStorage for the same combination of filters.

Tests

  • Updated test_search_with_metadata_filter_only, test_search_with_combined_filters, test_search_with_numeric_metadata_values, and test_search_with_multiple_metadata_filters_uses_and_logic — they previously asserted the buggy server-side query (@agent_id:{...} etc). They now assert that no @<metadata_key> clauses leak into the FT query and that the KNN limit is oversampled when needed.
  • Added a new TestValkeyStorageMetadataFilterPostFilter class covering:
    • FT.SEARCH query never references unindexed metadata fields, even with multiple keys.
    • Records that don't satisfy metadata_filter (wrong value or missing key entirely) are excluded.
    • Numeric metadata values match whether the caller passes the native value or its string form.
    • KNN limit is oversampled to limit * 3 only when metadata_filter is provided.
    • Results are trimmed to the originally requested limit after post-filtering.
    • The _metadata_matches helper handles AND logic, type coercion, missing keys, and empty filters.

Lint (ruff), type check (mypy), and all 192 tests in lib/crewai/tests/memory/storage/ pass locally.

Review & Testing Checklist for Human

This is a yellow-risk change — it changes the semantics of metadata_filter from "broken on real Valkey Search" to "applied as a Python post-filter".

  • Base branch. This PR targets devin/valkey-5703-base (a vendored copy of PR Feat/valkey 4 storage #5703's head branch pushed to the upstream repo so the diff is reviewable as a delta). Once PR Feat/valkey 4 storage #5703 merges, this PR's base should be retargeted to main (or the fix can be cherry-picked into PR Feat/valkey 4 storage #5703 directly). The fix branch contains only the issue feat(valkey): metadata_filter fields not indexed in memory_index FT schema #5795 commit on top of PR Feat/valkey 4 storage #5703.
  • End-to-end check against a real Valkey Search instance (the existing tests all mock ft.search). Spin up valkey/valkey-bundle:latest, save a few records with distinct metadata, and call ValkeyStorage.search(...) with both matching and non-matching metadata_filter values to confirm post-filtering returns the expected records.
  • Oversampling tradeoff. limit * 3 is the same factor used by LanceDBStorage. If your workloads expect very selective metadata filters over large indexes, you may want a larger oversample factor or to combine post-filtering with _find_records_by_metadata for a pre-filter intersection.
  • Confirm the docstrings for asearch/search still accurately describe metadata_filter semantics in light of the post-filter implementation.

Notes

  • The buggy code lives only on PR Feat/valkey 4 storage #5703's branch — there is no equivalent issue on main because valkey_storage.py doesn't exist there yet.
  • The fix preserves the public signatures of search, asearch, and _vector_search.
  • I introduced one new @staticmethod (_metadata_matches) so the matching logic is testable on its own and documented in one place.

Link to Devin session: https://app.devin.ai/sessions/068aff28e79a49b297b5d70a9bdd3b9b

…T clauses

The memory_index FT schema only defines fields for embedding, scope,
categories, created_at and importance. Building @{key}:{value} clauses
from metadata_filter referenced fields that are not indexed, causing
FT.SEARCH to error or silently return wrong results.

Apply metadata_filter as a Python post-filter on the deserialized
records (matching the pattern used by LanceDBStorage) and oversample
the KNN limit (limit * 3) when a metadata_filter is supplied so we
don't under-return after filtering. Updated tests that previously
asserted the buggy server-side query and added regression tests for
the post-filter behaviour.

Closes #5795

Co-Authored-By: João <joao@crewai.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

Prompt hidden (unlisted session)

@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e5fc8706-b8b6-42d3-a660-3cd611bf3cbe

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1778693042-fix-issue-5795

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants