Skip to content

fix: use tiktoken for accurate CJK token counting in document chunking - #1357

Open
Sanjay-Kirti wants to merge 1 commit into
khoj-ai:masterfrom
Sanjay-Kirti:fix/cjk-tokenizer-1354
Open

fix: use tiktoken for accurate CJK token counting in document chunking#1357
Sanjay-Kirti wants to merge 1 commit into
khoj-ai:masterfrom
Sanjay-Kirti:fix/cjk-tokenizer-1354

Conversation

@Sanjay-Kirti

Copy link
Copy Markdown

Summary

Fixes #1354

Replace text.split() tokenizer in TextToEntries with tiktoken cl100k_base encoding to fix markdown/org chunking for CJK (Chinese, Japanese, Korean) languages.

Problem

TextToEntries.tokenizer() uses text.split() which splits on whitespace. CJK languages don't use spaces between words, so a 744-token Chinese document was counted as ~152 tokens. This caused MarkdownToEntries (and OrgToEntries) to save entire CJK documents as single entries instead of chunking by headings severely degrading search quality for CJK users.

Changes

  • src/khoj/processor/content/text_to_entries.py: Replace text.split() with tiktoken.get_encoding("cl100k_base").encode() in TextToEntries.tokenizer(). Encoder is lazy initialized and cached as a class attribute.
  • tests/test_markdown_to_entries.py: Add 3 CJK specific test cases:
    • Chinese markdown is chunked by headings (core bug fix)
    • Small CJK content stays as single entry (regression test)
    • Mixed CJK/English markdown is properly chunked

Token Count Improvements

Concrete examples of how tiktoken correctly counts tokens compared to the old whitespace-split method:

Text text.split() (old) tiktoken (new) Result
Chinese sentence (65 chars) 1 token ❌ 69 tokens ✅ Accurate counting
Full CJK markdown example 11 tokens ❌ 212 tokens ✅ Properly chunks now
Japanese sentence 1 token ❌ 55 tokens ✅ Accurate counting
English sentence (18 words) 18 tokens ✅ 18 tokens ✅ Identical behavior

Why this is safe

All 3 call sites only use len(TextToEntries.tokenizer(...)) they never inspect individual tokens. The return type change from List[str]List[int] has no impact. tiktoken is already a project dependency (used in helpers.py for chat token counting).

Test Results

Ran the full test suite locally with Postgres/pgvector enabled:

  • All 12/12 tests in test_markdown_to_entries.py pass (including the 3 new CJK ones)
  • No regressions for English markdown processing

@Sanjay-Kirti
Sanjay-Kirti force-pushed the fix/cjk-tokenizer-1354 branch from 98ae397 to 0709fb4 Compare June 30, 2026 08:11

@debanjum debanjum left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for submitting a PR to fix this! Left some comments, can merge the changes once they're resolved and CI passes

Comment thread tests/test_markdown_to_entries.py Outdated
Comment thread tests/test_markdown_to_entries.py Outdated
Comment thread src/khoj/processor/content/text_to_entries.py Outdated
Replace text.split() tokenizer in TextToEntries with tiktoken cl100k_base
encoding. text.split() splits on whitespace, which severely undercounts
tokens for CJK languages (Chinese, Japanese, Korean) that don't use
spaces between words. This caused CJK markdown files to be saved as
single entries instead of being chunked by headings, degrading search
quality.

The fix uses tiktoken (already a project dependency) with lazy-initialized
encoder. All call sites only use len() on the result, so the return type
change from List[str] to List[int] is safe.

Also adds CJK-specific test cases and adjusts existing test thresholds
to account for tiktoken's more accurate token counting.

Fixes khoj-ai#1354
@Sanjay-Kirti
Sanjay-Kirti force-pushed the fix/cjk-tokenizer-1354 branch from 0709fb4 to c5f4660 Compare August 2, 2026 08:16
@Sanjay-Kirti

Sanjay-Kirti commented Aug 2, 2026

Copy link
Copy Markdown
Author

@debanjum Thanks for the review, I've addressed all the feedback and just pushed the updates:

  1. Switched from cl100k_base to the more modern o200k_base in ext_to_entries.py for better Indic language support.
  2. changed the CJK tests in test_markdown_to_entries.py per your suggestion:
  • Removed the unnecessary small content test.
  • Merged the mixed CJK/English test into test_cjk_markdown_is_chunked_by_headings. The single test now validates chunking with 2 Chinese notes followed by 1 English note.
    All tests are passing locally. Let me know if anything else is needed or if you want me to work on another issue.
    Would love to contribute.

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.

Markdown chunking fails for CJK languages due to text.split() tokenizer undercounting tokens

2 participants