Skip to content

fix(llm): don't panic in rerank_topk_filter on an empty document list - #275

Open
linhongyu510 wants to merge 1 commit into
pathwaycom:mainfrom
linhongyu510:fix/rerank-topk-filter-empty-docs
Open

linhongyu510 wants to merge 1 commit into
pathwaycom:mainfrom
linhongyu510:fix/rerank-topk-filter-empty-docs

Conversation

@linhongyu510

Copy link
Copy Markdown

What's wrong

rerank_topk_filter (python/pathway/xpacks/llm/rerankers.py) opens with:

docs, scores = zip(*sorted(zip(docs, scores), key=lambda tup: tup[1], reverse=True))

zip(*[]) yields nothing, so unpacking into two names raises ValueError when a
row's document list is empty. Because this runs inside a UDF in the engine, the
failure is not a recoverable error — it takes down the worker:

thread 'pathway:work-0' panicked at src/engine/report_error.rs:152:14:
ValueError: not enough values to unpack (expected 2, got 0)

Reproduced against the real function through pw.debug:

df = pd.DataFrame({"q": ["no matches"], "docs": [()], "scores": [()]})
t = pw.debug.table_from_pandas(df)
out = t.select(r=rerank_topk_filter(pw.this.docs, pw.this.scores, 3))
pw.debug.compute_and_print(out)     # -> panic above

Why it matters

A query that retrieved nothing is a normal outcome, not an error — a metadata
filter that excludes everything, a filepath_globpattern matching no file, a
freshly started pipeline whose index is still empty. The row still exists; only
its document list is empty.

rerank_topk_filter is a documented public helper: the LLM xpack overview points
users at it directly ("once you rank the documents, you can use
rerank_topk_filter to choose k best documents",
docs/2.developers/4.user-guide/50.llm-xpack/10.overview.md:104). In a
long-running streaming deployment, one such row ends the whole pipeline.

Note this does not affect BaseRAGQuestionAnswerer, which truncates via
_limit_documents (a plain slice, safe on empty input). It affects pipelines
built with the public helper as documented.

The fix

Return empty lists for that row and leave every other row alone:

if not docs:
    logging.info("Number of docs after rerank: 0")
    return ((), ())

Rows that do have documents still sort by score descending and truncate to k
verified in the same run that exercises the empty row:

q           | r
has matches | ((pw.Json({'text': 'b'}),), (3.0,))     # k=1 picked score 3.0
no matches  | ((), ())

Tests

Two cases added to python/pathway/xpacks/llm/tests/test_rerankers.py, following
the existing test_rerank_topk_filter convention (pw.schema_from_types,
table_from_rows, assert_table_equality):

  • test_rerank_topk_filter_empty_docs — an empty row yields empty lists rather
    than panicking.
  • test_rerank_topk_filter_keeps_filtering_with_mixed_rows — an empty row
    alongside a populated one, so the fix cannot pass by short-circuiting the
    ranking for everybody.

CHANGELOG

Added an entry under Unreleased / Fixed. The surrounding entries there describe
exactly this class of change (removed worker panics), so it seemed in scope
rather than noise — happy to drop it if you'd rather keep xpack changes out of
the changelog.

Verification

test_rerankers.py -k topk                          3 passed
black 24.10.0 --check (CI pins black>=24,<25)      2 files unchanged
flake8 7.x (CI pins flake8>=7,<8, repo setup.cfg)  0 findings

Whole LLM xpack test directory, excluding test_parsers.py which fails to
collect locally for a missing optional dependency — identical failure count
before and after, with exactly my two new tests added to the passing side:

baseline:   101 failed, 150 passed, 3 skipped, 16 xfailed
with fix:   101 failed, 152 passed, 3 skipped, 16 xfailed

Those 101 are missing optional LLM dependencies in my local environment (the
same set fails on an unmodified checkout), not regressions.

Reverse-verified: with the source change reverted and both tests kept, the two
new tests fail and the existing test_rerank_topk_filter still passes — so they
pin this behaviour specifically, and the fix does not alter the ranking path.

AI disclosure

This change was prepared with AI assistance. The defect was found by auditing the
xpack's filtering and ranking helpers for empty-input boundaries, then confirmed
by running the real function through the engine and observing the panic; the
quoted panic text, doc reference, test counts and baseline comparison were all
produced by running the code.

@CLAassistant

CLAassistant commented Sep 16, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@linhongyu510
linhongyu510 force-pushed the fix/rerank-topk-filter-empty-docs branch from 66d86e5 to 835987d Compare September 18, 2026 18:18
@linhongyu510

Copy link
Copy Markdown
Author

Rebased onto current main (4e0067a) — the PR was showing conflicts because 0.33.0 was released in the meantime. 0 commits behind now, and mergeable is back to true.

The conflict was in CHANGELOG.md and needed a real decision rather than picking a side. The release froze the ### Fixed list my entry was sitting in into the ## [0.33.0] section, and git's auto-merge wanted to keep my line there — which would have filed this fix under a version that does not contain it, while dropping three of your 0.33.0 ### Changed lines (the deltalake/litellm dependency refresh, the boto3 removal, and the faster graph construction).

So I kept your 0.33.0 section byte-for-byte and moved my entry to a new ### Fixed under [Unreleased]. Verified by diffing against upstream rather than trusting the merge:

$ diff <(git show origin/main:CHANGELOG.md) CHANGELOG.md
7a8,10
> ### Fixed
> - `pathway.xpacks.llm.rerankers.rerank_topk_filter` no longer trips a bare worker panic ...
>

Three added lines, zero deletions, zero modifications.

I also re-verified the defect is still live in the released 0.33.0 rather than assuming my earlier run still applied — installed pathway==0.33.0 from PyPI and ran the two new tests against the shipped rerankers.py:

with the fix     2 passed
stock 0.33.0     2 failed
                 ValueError: not enough values to unpack (expected 2, got 0)
                 pathway/xpacks/llm/rerankers.py:55

That is the panic path this PR closes, reproduced on the published wheel.

One blocker left that I cannot clear myself: CLA assistant still reports not_signed on this PR. I will get that signed.

rerank_topk_filter started with

    docs, scores = zip(*sorted(zip(docs, scores), ...))

zip(*[]) produces nothing to unpack, so a row whose document list is empty
raised ValueError inside the UDF. Because this runs in the engine, the failure
surfaces as a worker panic rather than a recoverable error:

    thread 'pathway:work-0' panicked at src/engine/report_error.rs:152:14:
    ValueError: not enough values to unpack (expected 2, got 0)

A query that matched no documents is a normal outcome, not an error, and this is
a documented public helper (llm-xpack overview points users at it for choosing
the k best documents after ranking). In a long-running streaming pipeline one
such row takes the whole pipeline down.

Return empty document and score lists for that row instead. Rows with documents
are unaffected: they still sort by score descending and truncate to k.

Also added a CHANGELOG entry under Unreleased / Fixed, matching the existing
entries that describe removed worker panics.
@linhongyu510
linhongyu510 force-pushed the fix/rerank-topk-filter-empty-docs branch from 835987d to 1a1b1a4 Compare September 20, 2026 17:30
@linhongyu510

Copy link
Copy Markdown
Author

CLA is signed now — license/cla reports success on this PR.

I also rebased onto current main (b614527), since two Daily Pathway examples refresh commits had landed since the last push. They only touch notebooks under examples/, so the rebase was clean: 0 commits behind, diff unchanged at +61/-0 across the same three files, and the CHANGELOG entry is still under [Unreleased] (verified by diffing against origin/main — three added lines, no deletions).

Re-ran the two regression tests after the rebase rather than assuming the earlier run still applied: both pass against the published pathway==0.33.0 with this fix applied, and both fail without it (ValueError: not enough values to unpack (expected 2, got 0) at rerankers.py:55).

The CI workflow is waiting on maintainer approval whenever you have a moment.

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.

2 participants