Skip to content

Compute token_count the same way everywhere - #72

Open
dpage wants to merge 1 commit into
mainfrom
token-count-consistency
Open

Compute token_count the same way everywhere#72
dpage wants to merge 1 commit into
mainfrom
token-count-consistency

Conversation

@dpage

@dpage dpage commented Sep 9, 2026

Copy link
Copy Markdown
Member

The C chunking code sizes chunks with a four-characters-per-token estimate that rounds up. The three plpgsql paths that actually write the token_count column open-coded the same estimate as length(chunk_text) / 4, which truncates, so the two disagreed by a token on most chunks and stored a zero for anything shorter than four characters.

That matters because token_count is not decorative: bm25.c uses AVG(token_count) as the average document length and worker.c reads the per-chunk value, both feeding the BM25 length normalisation, so hybrid search scored chunks written by the trigger slightly differently from chunks written by the C chunker. worker.c was already clamping the stored zeroes back up to one.

  • Expose the existing C counter as pgedge_vectorizer.count_tokens(text), and call it from enable_vectorization(), vectorization_trigger() and recreate_chunks(), so there is one definition of the rule rather than two.
  • Declared STABLE, not IMMUTABLE. The estimate is defined in terms of pgedge_vectorizer.model, which does not matter whilst the counter ignores the model, but would quietly invalidate an expression index or a cached plan the moment it stops doing so.
  • Existing chunk tables are left as they are; the values are an approximation either way, and rewriting every chunk table to correct one token is not worth it on upgrade.
  • First change for 1.2, so sql/pgedge_vectorizer--1.1--1.2.sql carries the upgrade and the 1.1 scripts are untouched.

New count_tokens regression test covers the estimate itself (rounding, empty, NULL, multi-byte, the declared volatility) and, more to the point, asserts that nothing in a chunk table disagrees with count_tokens(content) after each of the three write paths.

Test run: 21 pg_regress tests and 69 TAP tests pass against PostgreSQL 18.4.

The count_tokens() part of this originates in #22, from @syedkazim110, which this replaces. refresh_token_counts() from that PR is not carried over: nothing writes chunk rows outside the paths above, so there is nothing left for it to back-fill.

The chunking code in C has always sized chunks with a four-characters-per-token
estimate that rounds up, whilst the three plpgsql paths that actually write the
token_count column open-coded the same estimate as length(chunk_text) / 4, which
truncates. The two therefore disagreed by a token on most chunks, and on
anything shorter than four characters the plpgsql paths stored a zero that the
BM25 scoring path in worker.c then had to clamp back up to one. Since
token_count feeds the BM25 document-length normalisation, by way of
AVG(token_count) in bm25.c and the per-chunk value in worker.c, hybrid search
scored chunks written by the trigger slightly differently from chunks written by
the C chunker.

Expose the existing C counter as pgedge_vectorizer.count_tokens(text) and call
it from enable_vectorization(), vectorization_trigger() and recreate_chunks(),
so that there is one definition of the rule rather than two. It is declared
STABLE rather than IMMUTABLE deliberately: the estimate is defined in terms of
pgedge_vectorizer.model, which does not matter whilst the counter ignores the
model, but would quietly invalidate an expression index or a cached plan the
moment it stops doing so.

Existing chunk tables are left alone. The stored values are an approximation
either way, and rewriting every chunk table to correct a single token is not a
trade worth making on upgrade.

This is the first change for 1.2, so the extension version moves on and
sql/pgedge_vectorizer--1.1--1.2.sql carries the upgrade; the 1.1 scripts are
untouched.
@codacy-production

codacy-production Bot commented Sep 9, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 2 high

Results:
2 new issues

Category Results
Compatibility 2 high (1 false positive)

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: d0a4acc9-98a9-4e3b-a2ee-020a0953b5d5

📥 Commits

Reviewing files that changed from the base of the PR and between 0afaf11 and f52d4cb.

⛔ Files ignored due to path filters (1)
  • test/expected/count_tokens.out is excluded by !**/*.out
📒 Files selected for processing (9)
  • Makefile
  • docs/api_reference.md
  • docs/changelog.md
  • pgedge_vectorizer.control
  • sql/pgedge_vectorizer--1.1--1.2.sql
  • sql/pgedge_vectorizer--1.2.sql
  • src/pgedge_vectorizer.h
  • src/tokenizer.c
  • test/sql/count_tokens.sql

Included review availability: 3 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


📝 Walkthrough

Walkthrough

The extension version changes from 1.1 to 1.2. The release adds the count_tokens SQL function and uses it for consistent chunk token counts during backfill, triggers, and chunk reconstruction. The 1.2 SQL definition adds vectorization setup and disablement, trigger refresh and cleanup, queue management, chunk rebuilding, configuration reporting, and hybrid search. Tests cover token-counting behavior and integration paths. Documentation and changelog entries describe the new function and behavior.

Priority: ➖ Normal

Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to f52d4

The token-counting paths are consistent and covered by passing regression tests; no merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (7 skipped: 7… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: making token_count calculation consistent across all write paths.
Description check ✅ Passed The description directly explains the token_count inconsistency, the centralized count_tokens() implementation, affected paths, upgrade behavior, and regression tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (7 skipped: 7 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch token-count-consistency

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

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.

1 participant