diff --git a/src/components/Collections/SearchQuality/SearchQualityPanel.jsx b/src/components/Collections/SearchQuality/SearchQualityPanel.jsx
index 9c2e56829..13376a316 100644
--- a/src/components/Collections/SearchQuality/SearchQualityPanel.jsx
+++ b/src/components/Collections/SearchQuality/SearchQualityPanel.jsx
@@ -76,6 +76,11 @@ VectorTableRow.propTypes = {
};
const SAMPLE_SIZE = 100;
+// Over-fetch so all-zero-vector points (e.g. bookkeeping markers) can be dropped
+// and still leave SAMPLE_SIZE usable points. All-zero vectors score 0.0 against
+// everything, corrupting the recall average.
+const OVERFETCH_FACTOR = 2;
+const isZeroVector = (vec) => Array.isArray(vec) && vec.length > 0 && vec.every((v) => v === 0);
const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo, ...other }) => {
const { client } = useClient();
@@ -180,11 +185,12 @@ const SearchQualityPanel = ({ collectionName, vectors, loggingFoo, clearLogsFoo,
try {
const sampleResult = await client.scroll(collectionName, {
with_payload: false,
- with_vector: false,
- limit: SAMPLE_SIZE,
+ with_vector: true,
+ limit: SAMPLE_SIZE * OVERFETCH_FACTOR,
});
- const pointIds = sampleResult.points.map((point) => point.id);
+ const usablePoints = sampleResult.points.filter((p) => !isZeroVector(p.vector));
+ const pointIds = usablePoints.slice(0, SAMPLE_SIZE).map((point) => point.id);
const total = pointIds.length;
loggingFoo &&
diff --git a/src/components/Collections/SearchQuality/searchQualityPannel.test.jsx b/src/components/Collections/SearchQuality/searchQualityPannel.test.jsx
index c90444e12..55c7617eb 100644
--- a/src/components/Collections/SearchQuality/searchQualityPannel.test.jsx
+++ b/src/components/Collections/SearchQuality/searchQualityPannel.test.jsx
@@ -84,6 +84,33 @@ describe('SearchQualityPannel', () => {
});
});
+ it('should exclude all-zero-vector points from the sample', async () => {
+ const scrollMock = vi.fn().mockResolvedValue({
+ points: [
+ { id: 'zero-marker', vector: [0, 0, 0] },
+ { id: 'normal-1', vector: [0.1, 0.2, 0.3] },
+ ],
+ });
+ const queryPointsMock = vi.fn().mockResolvedValue({
+ data: { result: { points: [{ id: 1 }, { id: 2 }] }, status: 'ok', time: 0.005 },
+ });
+ useClient.mockReturnValue({
+ client: { scroll: scrollMock, api: vi.fn().mockReturnValue({ queryPoints: queryPointsMock }) },
+ });
+
+ render(
+
+
+
+ );
+ fireEvent.click(screen.getAllByTestId('index-quality-check-button')[0]);
+
+ await waitFor(() => expect(queryPointsMock).toHaveBeenCalled());
+ const queriedIds = queryPointsMock.mock.calls.map(([args]) => args.query);
+ expect(queriedIds).not.toContain('zero-marker');
+ expect(queriedIds).toContain('normal-1');
+ });
+
it('should toggle advanced mode', () => {
render(