Skip to content

fix: coerce None filter_rate in Label/Int Filter charts - #851

Open
daviddallakyan2005 wants to merge 2 commits into
zilliztech:mainfrom
daviddallakyan2005:filter-rate-none-crash
Open

fix: coerce None filter_rate in Label/Int Filter charts#851
daviddallakyan2005 wants to merge 2 commits into
zilliztech:mainfrom
daviddallakyan2005:filter-rate-none-crash

Conversation

@daviddallakyan2005

@daviddallakyan2005 daviddallakyan2005 commented Aug 15, 2026

Copy link
Copy Markdown

Summary

Label Filter and Int Filter Streamlit charts crash with TypeError when a result row has filter_rate=None. dict.get(metric, 0) still returns None when the key is present, so min/sort compare None with a float.

PerformanceCustomDataset never passed filter_rate into the case (unlike LabelFilterPerformanceCase). Custom-dataset filter runs therefore land in result files with filter_rate=None. The same gap existed on CloudPayloadSearchCase, CloudColdLatencyCase, and CloudMultiTenantSearchCase when label_percentage is set.

This PR coerces missing or None metric values to 0 in getRange and the x-axis sort so leftover result files still render. It sets filter_rate from LabelFilter on PerformanceCustomDataset and those three Cloud cases.

How to test

pip install -e '.[test]'
PYTHONPATH=. python3 -m pytest tests/test_filter_charts.py tests/test_custom_dataset_filter.py tests/test_cloud_payload_case.py tests/test_cloud_cold_latency_case.py tests/test_cloud_payload_search.py tests/test_multitenant_case.py -q -k 'not turbopuffer'
make lint

Expected: 50 passed, 5 deselected (optional turbopuffer extra). black --check and ruff check vectordb_bench clean.

To reproduce the crash without the chart-side coerce: open Label Filter (or Int Filter) on a custom-dataset filter result whose case.filter_rate is None.

PerformanceCustomDataset never set filter_rate, so Streamlit getRange/sort
TypeError'd when the key was present with a None value. Mirror
LabelFilterPerformanceCase and treat None as 0 on the chart axis.
@sre-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: daviddallakyan2005
To complete the pull request process, please assign xuanyang-cn after the PR has been reviewed.
You can assign the PR to them by writing /assign @xuanyang-cn in a comment when ready.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@jamesgao-jpg jamesgao-jpg 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.

First-round independent review of the effective diff at head 660d8f6. The core fix is correct: the getRange/sort coercion stops the TypeError, and PerformanceCustomDataset now propagates filter_rate = 1.0 - label_percentage matching LabelFilter. The 8 new tests pass on this head. Comments below: one P2 (the same derivation is missing for the three Cloud label-filter producers), one P3 (formula-drift nit), and one note on the chart coercion. Review comments only — no approval state implied.

Comment thread vectordb_bench/backend/cases.py Outdated
gt_neighbors_field=dataset_config.gt_col_name,
scalar_labels_file=f"{dataset_config.scalar_labels_name}.parquet",
)
filter_rate = (1.0 - label_percentage) if (use_filter and label_percentage is not None) else None

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.

The same derivation is missing for the three Cloud label-filter producers. CloudPayloadSearchCase, CloudColdLatencyCase, and CloudMultiTenantSearchCase (filters properties at cases.py:689-691, 759-761, 879-881) all return LabelFilter when label_percentage is set, but their filter_rate stays None. Verified on this head:

CloudPayloadSearchCase(dataset_with_size_type='Medium Cohere (768dim, 1M)', label_percentage=0.2)
  -> case.filter_rate = None | filters.type = Label | filters.filter_rate = 0.8

These cases are reachable through the CLI (--case-type CloudPayloadSearchCase ... --cloud-label-percentage 0.2), and their results land on the Label Filter page (filters.type == StrEqual, and the dataset equals the sidebar DatasetWithSizeType managers). Pre-PR they crashed the page like custom-dataset runs; with this PR's chart coercion they no longer crash, but case.filter_rate stays None in the results and the point is dropped from the chart (plotly turns None x into NaN), so the run is invisible at its true filter rate — the exact contract this line establishes for PerformanceCustomDataset is left broken for three sibling producers. Suggest deriving filter_rate in those three __init__s the same way (or centrally from case.filters.filter_rate in frontend/components/check_results/data.py), plus a focused test for at least one Cloud case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

CloudPayloadSearchCase, CloudColdLatencyCase, and CloudMultiTenantSearchCase now set filter_rate from LabelFilter when label_percentage is set. I added a test for CloudPayloadSearchCase with Medium Cohere at 0.2, plus the same assertion on the cold-latency and multi-tenant label-filter paths.

Comment thread vectordb_bench/backend/cases.py Outdated
gt_neighbors_field=dataset_config.gt_col_name,
scalar_labels_file=f"{dataset_config.scalar_labels_name}.parquet",
)
filter_rate = (1.0 - label_percentage) if (use_filter and label_percentage is not None) else None

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.

Minor (P3): prefer deriving this from the filter object to avoid formula drift. LabelFilterPerformanceCase.__init__ computes the same value as filters = LabelFilter(label_percentage=...); filter_rate = filters.filter_rate, so 1.0 - label_percentage is now duplicated in two places and will silently diverge if LabelFilter semantics ever change. filter_rate = LabelFilter(label_percentage=label_percentage).filter_rate if (use_filter and label_percentage is not None) else None keeps a single source of truth.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

PerformanceCustomDataset now takes filter_rate from LabelFilter(label_percentage=...).filter_rate, matching LabelFilterPerformanceCase.

def getRange(metric, data, padding_multipliers):
minV = min([d.get(metric, 0) for d in data])
maxV = max([d.get(metric, 0) for d in data])
values = [d.get(metric) or 0 for d in data]

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.

Two small notes on this coercion (identical in int_filter/charts.py):

  1. A one-line rationale comment would help — d.get(metric) or 0 is easy to "simplify" back to d.get(metric, 0), which reintroduces the crash for present-but-None values.

  2. For the legacy filter_rate=None rows this is meant to keep rendering: plotly converts None x to NaN, so the point is dropped from the line (not drawn at 0), while the axis still extends to 0. The run is invisible rather than misplaced. Once the producers are fixed (see comment on cases.py:442) this path is mostly unreachable; consider dropping unknown rows before the axis computation instead of padding the range with 0.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I added a comment that dict.get(metric, 0) still returns None when the key is present. I left the coerce in place so leftover result files with filter_rate=None still do not TypeError on min or sort. Dropping those rows would change the axis for mixed leftover files, which I left out of this follow-up.

Derive it from LabelFilter so Cloud payload, cold-latency, and
multi-tenant runs land on the Label Filter chart at the true rate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants