Metadata-Version: 2.4
Name: trad-zh-search
Version: 0.1.1
Summary: Traditional Chinese text preprocessing for search engines — CKIP segmentation + bigram indexing with pluggable domain dictionaries
Project-URL: Homepage, https://github.com/notoriouslab/trad-zh-search
Project-URL: Repository, https://github.com/notoriouslab/trad-zh-search
Author: JacobMei
License-Expression: MIT
License-File: LICENSE
Keywords: bigram,ckip,meilisearch,nlp,search,tokenizer,traditional-chinese
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.9
Requires-Dist: pyyaml>=6.0
Provides-Extra: ckip
Requires-Dist: ckip-transformers>=0.3.2; extra == 'ckip'
Provides-Extra: dev
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# trad-zh-search

可單獨搭配主流搜尋引擎，專門給繁體中文使用的繁體中文文本預處理工具 —— CKIP 分詞 + bigram 索引生成，附可選擇的領域字典系統。

**需要 Python 3.9+** · 屬於 [notoriouslab](https://github.com/notoriouslab) 開源工具組的一員。

> [English README](README.en.md)

---

## 這個工具用了什麼

```
你的文本 → trad-zh-search → 搜尋引擎（Meilisearch / Elasticsearch / ...）
```

| 元件 | 是什麼 | 需要裝嗎 |
|------|--------|---------|
| **CKIP** ([ckip-transformers](https://github.com/ckiplab/ckip-transformers)) | 中研院開發的繁體中文 NLP 工具，用 Transformer 模型做分詞 | 選裝（`pip install trad-zh-search[ckip]`） |
| **albert-tiny** | CKIP 用的分詞模型，只有 ~50MB，CPU 就能跑 | 隨 CKIP 自動下載 |
| **Bigram** | 把中文字兩兩組合（「聖靈」→「聖靈」「靈充」「充滿」），確保搜尋不漏 | 內建，不需要額外安裝 |
| **領域字典** | YAML 格式的專有名詞表，告訴分詞器哪些詞不要拆開 | 內建基督教繁中字典，也可自己建 |
| **PyYAML** | 讀寫 YAML | 自動安裝 |

**最小安裝**只需要 PyYAML（幾 KB），就能用 bigram 模式。加裝 CKIP 會多 PyTorch（~700MB）+ 模型（~50MB），但分詞品質大幅提升。

---

## 為什麼需要這個工具

主流搜尋引擎（Meilisearch、Elasticsearch、SQLite FTS5）的中文支援大多依賴 jieba（簡體中文訓練），對繁體中文分詞品質差，這問題已經很多年了，但沒有一個很好的解決方案，我最近整理了一些文章，在過程中陸續找出了適合自己的解法，提供出來給大家參考使用。

先看看實際差異：

| 原文         | jieba 分詞               | CKIP + 自訂字典           |
| ---------- | ---------------------- | --------------------- |
| 聖靈充滿的經歷    | 聖靈 / 充滿 / 的 / 經歷       | **聖靈充滿** / 的 / 經歷     |
| 因信稱義的教義    | 因信 / **稱義的** / 教義      | **因信稱義** / 的 / **教義** |
| 台北靈糧堂的主日崇拜 | 台北 / 靈糧堂 / 的 / 主日 / 崇拜 | **台北靈糧堂** / 的 / 主日崇拜  |
| 靈糧教牧宣教神學院  | 靈糧 / 教牧 / 宣教 / 神學院     | **靈糧教牧宣教神學院**         |

jieba 把「稱義的」黏在一起、「聖靈充滿」拆成兩個詞、機構名全部切碎，CKIP + 自訂字典都能正確處理。

### 實戰數據

在一個 8,000+ 篇繁體中文文章的搜尋系統上實測（80 組 benchmark query）：

- **CKIP 分詞 + bigram**：Top-1 搜尋結果有 **21%** 的查詢獲得改善（17/80 好轉，3/80 變差）
- **albert-tiny 模型** vs bert-base：分詞結果完全一致，但速度快 4 倍、模型小 10 倍
- **自訂字典**（915 詞）：機構名、人名不再被拆開，搜「靈糧教牧宣教神學院」直接命中

### 功能特色

| 特色 | 說明 |
|------|------|
| CKIP 分詞 | ckip-transformers（albert-tiny），繁體中文專用 |
| Bigram 索引 | CJK 字符滑動窗口 bigram，確保子字串都能搜到 |
| 可選 CKIP | 沒裝 CKIP 時自動退回 bigram-only——`tokens` 回空 list、`used_ckip` 為 False，bigram 照常產生 |
| 領域字典 | YAML 格式，可插拔。首發基督教繁中字典（915 自訂詞） |
| 自動建字典 | 丟一批文件進去，CKIP NER 自動提取專有名詞 |
| Meilisearch Adapter | TokenResult → 多欄位文件格式，一行搞定 |

---

## 快速開始

```bash
pip install trad-zh-search

# （選裝）CKIP 分詞支援
pip install trad-zh-search[ckip]
```

> **CKIP 安裝須知**
>
> `trad-zh-search[ckip]` 會一併安裝 PyTorch（約 700MB+），這是 ckip-transformers 的底層依賴。
> 首次呼叫 `tokenize()` 時會自動從 HuggingFace 下載 albert-tiny 模型（約 50MB），需要對外網路。
> 離線環境請參考 [HuggingFace offline mode](https://huggingface.co/docs/transformers/installation#offline-mode)。
>
> 不裝 CKIP 也能用——自動退回 bigram-only 模式，仍比 jieba 更適合繁體中文。

### 三行程式碼

```python
from trad_zh_search import tokenize

result = tokenize("轉型正義委員會的調查報告")
print(result.bigrams)   # ['轉型', '型正', '正義', '義委', '委員', '員會', '會的', '的調', '調查', '查報', '報告']
print(result.tokens)    # CKIP 分詞結果（有裝 CKIP 時）
print(result.used_ckip) # True / False
```

### 搭配領域字典

```python
from trad_zh_search import tokenize, load_dictionary

# 載入內建基督教繁中字典
ck_dict = load_dictionary("christian-zh-hant")
result = tokenize("台北靈糧堂的主日崇拜", dictionary=ck_dict)
# CKIP + 自訂詞合併：「台北靈糧堂」不會被切成「台北/靈糧/堂」
```

### 從文件自動建字典

```python
from trad_zh_search import build_dictionary, save_dictionary

# 丟一批文本 → CKIP NER 提取專有名詞 → 產生字典
texts = [open(f).read() for f in my_articles]
my_dict = build_dictionary(texts, min_freq=2)
save_dictionary(my_dict, "my_domain.yaml")  # 存檔可人工微調
```

### Meilisearch 整合

```python
from trad_zh_search import tokenize, load_dictionary
from trad_zh_search.adapters.meilisearch import to_meilisearch, to_meilisearch_synonyms

ck_dict = load_dictionary("christian-zh-hant")

# 文件預處理
doc = to_meilisearch(
    fields={
        "title": tokenize(title, dictionary=ck_dict),
        "content": tokenize(content, dictionary=ck_dict),
    },
    original={"id": doc_id, "title": title, "content": content},
)
# → {"id": ..., "title": ..., "title_ckip": "...", "title_bigram": "...",
#    "content": ..., "content_ckip": "...", "content_bigram": "...", ...}

# 同義詞設定
synonyms = to_meilisearch_synonyms(ck_dict)
# → {"敬拜": ["崇拜", "主日"], "崇拜": ["敬拜", "主日"], ...}
```

**搜尋建議**：將以下欄位依序加入 `searchableAttributes`（順序即權重，越前面越重要）：

```
title_ckip → title_bigram → content_ckip → content_bigram
```

實戰 benchmark 顯示 CKIP 和 bigram 互補——CKIP 提供精確的詞邊界匹配，bigram 確保不漏掉子字串。

---

## TokenResult

```python
@dataclass
class TokenResult:
    original: str        # 輸入原文（截斷後）
    tokens: list[str]    # CKIP 分詞結果（無 CKIP 時為空 list）
    bigrams: list[str]   # CJK bigrams（永遠產生）
    used_ckip: bool      # 是否使用了 CKIP
```

---

## 字典格式

YAML 格式，三個可選區塊：

```yaml
# 自訂分詞詞庫（Phase 1 核心）
ckip_custom_words:
  - 轉型正義
  - 國家人權委員會

# 別名映射（目前供參考，未來 entity-resolver 會使用）
aliases:
  人權會: 國家人權委員會

# 同義詞組（adapter 可直接輸出為搜尋引擎 synonyms）
synonyms:
  判決: [裁定, 裁判]
```

---

## 字典人名聲明

內建的 `christian-zh-hant` 字典包含部分知名牧師、神學家、聖經人物及歷史人物的姓名，均來自各教會公開網站及公開出版物。若當事人希望移除，請[開 issue](https://github.com/notoriouslab/trad-zh-search/issues) 告知，我們會盡速處理。

---

## API 參考

| 函式 | 說明 |
|------|------|
| `tokenize(text, dictionary?, max_chars?)` | 分詞 + bigram，回傳 TokenResult |
| `tokenize_batch(texts, dictionary?, batch_size?)` | 批次版本 |
| `load_dictionary(name)` | 載入內建字典 |
| `load_dictionary_file(path)` | 載入 YAML 字典檔 |
| `merge_dictionaries(*dicts)` | 合併多個字典 |
| `save_dictionary(dictionary, path)` | 儲存字典為 YAML（原子寫入） |
| `build_dictionary(texts, min_freq?)` | NER 自動提取字典（需要 CKIP） |
| `to_meilisearch(fields, original?)` | TokenResult → Meilisearch 文件格式 |
| `to_meilisearch_batch(fields_list, originals?)` | 批次版本 |
| `to_meilisearch_synonyms(dictionary)` | 同義詞 → Meilisearch 雙向格式 |

詳細參數說明請參考各函式的 docstring。

---

## Roadmap

目前 v0.1 專注在核心分詞 + bigram + 字典系統。以下是規劃中但尚未實作的功能：

### 更多搜尋引擎 Adapter

| Adapter | 狀態 | 說明 |
|---------|------|------|
| Meilisearch | v0.1 已完成 | 多欄位格式 + 同義詞轉換 |
| Elasticsearch / OpenSearch | 規劃中 | `_analyze` 自訂分析器 + mapping 生成 |
| SQLite FTS5 | 規劃中 | trigram tokenizer + 自訂 tokenize 函式 |
| Orama | 規劃中 | 瀏覽器端全文搜尋，適合 PWA / 離線場景 |
| Typesense | 考慮中 | 另一個熱門的開源搜尋引擎 |

### 進階文本處理

| 功能 | 狀態 | 說明 |
|------|------|------|
| Entity Resolver（別名展開） | 規劃中 | 搜「周牧師」自動展開為「周神助 OR 周巽正」 |
| Synonym Expander | 規劃中 | 搜「宣教」自動展開為「宣教 OR 差傳 OR 宣植」 |
| 簡繁正規化 | 考慮中 | 輸入簡體自動轉繁體再分詞（opencc s2twp） |
| 停用詞過濾 | 考慮中 | 可插拔的停用詞表 |

### 工具 & 整合

| 功能 | 狀態 | 說明 |
|------|------|------|
| CLI 工具 | 規劃中 | `trad-zh-search tokenize "你的文本"` 命令列直接用 |
| 更多領域字典 | 歡迎貢獻 | 法律、醫療、教育... |
| 字典品質報告 | 考慮中 | 分析字典覆蓋率、衝突、冗餘 |

有想法或需求？歡迎[開 issue](https://github.com/notoriouslab/trad-zh-search/issues) 討論。

---

## 貢獻

歡迎貢獻領域字典、回報 bug、或提交 PR。詳見 [CONTRIBUTING.md](CONTRIBUTING.md)。

---

## 授權

MIT
