feat: normalize Traditional Chinese to Simplified for BM25 cross-match - #234
Open
gdccyuen wants to merge 1 commit into
Open
feat: normalize Traditional Chinese to Simplified for BM25 cross-match#234gdccyuen wants to merge 1 commit into
gdccyuen wants to merge 1 commit into
Conversation
Add OpenCC t2s (Traditional→Simplified) normalization before jieba tokenization in both _tokenize_cjk_segment and tokenize2stw_remove. This is the single chokepoint used by both ingest (build_content_search_text, build_path_search_text via tokenize_contents_for_retrieval) and query (lexical_ranker via tokenize_for_retrieval), so symmetry is automatic. Without this, '營收' (Traditional) and '营收' (Simplified) produce different BM25 tokens and never cross-match. With OpenCC, both normalize to '营收' before jieba cuts, so a 繁体 document is findable by a 简体 query and vice versa. OpenCC only converts CJK characters — English, numbers, and punctuation are untouched. PostgreSQL 'simple' tsvector config is unchanged; the normalization happens entirely in the Python application layer before text reaches the database. Existing content_search_tsv rows are pre-normalization and require re-ingest to benefit fully. No migration needed — new ingestions produce normalized tokens going forward.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Normalizes Traditional Chinese (繁體) to Simplified (简体) before jieba tokenization, so BM25 cross-matches 繁/简 variants. A document ingested with "營收" becomes findable by a query for "营收", and vice versa.
Why
Knowhere's BM25 search uses jieba (a Simplified Chinese tokenizer) + PostgreSQL
'simple'tsvector. Without normalization, 繁/简 variants produce different tokens:營收→ jieba cuts["營收"]→ stored as營收incontent_search_text营收→ jieba cuts["营收"]→ query tsquery is营收營收 @@ 营收→ no matchWith OpenCC t2s applied before jieba:
營收and营收→ OpenCC →营收→ jieba →["营收"]→ matchChanges
Dependency (
packages/shared-python/pyproject.toml)Added
opencc-python-reimplemented>=0.1.7— pure Python, no native deps. Only converts CJK characters; English, numbers, and punctuation are untouched.Normalization (
packages/shared-python/shared/utils/text_utils.py)New
_normalize_cjk_to_simplified(text)helper with a module-level lazyOpenCC('t2s')instance. Applied at two chokepoints:_tokenize_cjk_segment— used bytokenize_for_retrieval→tokenize_contents_for_retrieval, which powers:build_content_search_text,build_path_search_text,build_lexical_text(inlexical_text.py)lexical_ranker.pytokenize2stw_remove— used by docx, atlas, excel, markdown parsers for chunktokensmetadataBoth ingest and query go through the same normalization, so symmetry is automatic — no need to change PostgreSQL config or the
'simple'tsvector.Tests (
packages/shared-python/shared/tests/test_text_utils_cjk.py)11 unit tests:
_normalize_cjk_to_simplified: 繁→简 conversion, English unchanged, punctuation unchanged, empty string, mixed 繁/简tokenize_for_retrieval: 繁 and 简 produce identical tokens, mixed 繁/简+English produces identical tokens, English still tokenized correctlytokenize_contents_for_retrieval: 繁 and 简 produce identical token stringstokenize2stw_remove: 繁 and 简 produce identical token stringsBackward compatibility
content_search_tsvrows are pre-normalization. They continue to work for matching queries (same pre-normalization tokens on both sides). Full 繁↔简 cross-match requires re-ingest. No migration needed — new ingestions produce normalized tokens going forward.'simple'PostgreSQL tsvector config is unchanged. All normalization happens in the Python application layer before text reaches the database.Verification
3 pre-existing failures in
test_page_memory_vlm_limiter.pyare unrelated (VLM AI serviceTypeError— confirmed present onmainwithout this change).Design decision: OpenCC vs zhparser
This implements the ADR-recommended "OpenCC + jieba + simple tsvector" stack rather than PostgreSQL
zhparser:繁↔简 is the higher-priority gap; synonyms can be addressed separately if needed.