Skip to content

Score term queries with term frequencies - #12

Merged
iamcarbon merged 1 commit into
mainfrom
claude/tantivy-term-frequency-bug-12b143
Sep 15, 2026
Merged

iamcarbon merged 1 commit into
mainfrom
claude/tantivy-term-frequency-bug-12b143

Conversation

@myluma

@myluma myluma commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

What

.term built its tantivy TermQuery with IndexRecordOption::Basic. That option skips term frequencies, so BM25 counted every match once. .term now uses WithFreqs, the same option tantivy's QueryParser uses for its term leaves (rust/src/lib.rs).

Why

On a text field, a document containing the token three times ranked the same as one containing it once. .term also scored differently from .parsed and from a one-token .phrase for the same token. That mismatch forced an app-side workaround of building a one-token phrase instead of a term.

Checked against tantivy 0.26.1 with two equal-length docs ("apple" ×3 vs ×1):

Query ×3 doc ×1 doc
TermQuery + Basic (old .term) 0.182322 0.182322
TermQuery + WithFreqs (new .term) 0.286505 0.182322
QueryParser "apple" 0.286505 0.182322

Reviewer notes

  • Behavior change: .term scores on text fields change, so relevance ordering can shift. Worth a line in the release notes.
  • No change for fields indexed without frequencies, such as raw strings and numeric fields. Tantivy lowers the requested option to what the field stores, so they keep scoring as Basic.
  • Test: termScoresUseTermFrequency in StructuredQueryTests checks that the ×3 doc outscores the ×1 doc. It also checks that .term scores match .parsed and a one-token .phrase.
  • Test results: swift test passed (393 tests) against a locally built libtantivy_ffi.a, and cargo test passed (15 tests). I didn't rebuild the old code to watch the new test fail; the table above shows old .term scores both docs the same, which would fail its ×3 > ×1 check.

🤖 Generated with Claude Code

The structured `term` node built its TermQuery with IndexRecordOption::Basic,
which skips term frequencies: BM25 counted every match once, so `.term`
ranked a document holding the token three times level with one holding it
once, and scored unlike `.parsed` for the same token. Build it WithFreqs, as
tantivy's QueryParser does. Fields indexed without freqs (raw strings,
numerics) downgrade to Basic, so their scores are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@iamcarbon
iamcarbon merged commit b87e7b7 into main Sep 15, 2026
1 check passed
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