繁體中文全文搜尋引擎實戰筆記(一):選型與架構設計

閱讀偏好
繁體中文全文搜尋引擎實戰筆記(一):選型與架構設計

繁體中文全文搜尋引擎實戰筆記(一):選型與架構設計

這是三篇系列文章的第一篇。我用 Meilisearch + BGE-M3 在 Oracle Cloud 免費方案上,為一個 1800+ 篇繁體中文文章的知識庫建了一套搜尋系統。過程中踩了一輪中文分詞的坑,研究了市面上幾乎所有選項,最後疊加了四種互補手段才解決問題。這篇記錄選型過程和架構設計的決策脈絡。


起點:為什麼要自己建搜尋

我手上有一批教會歷年累積的文章——講道記錄、事工報導、人物見證,總共 1800 多篇。需要一套能讓同工透過網路搜尋、收藏、下載文章的方案。

一開始最直覺的做法是用 Google NotebookLM,但 1800 篇的量遠超過它的上傳限制,分資料夾或合併檔案雖然可用,但有其缺點,會失去全局搜尋能力,所以想了一輪後決定自建 XD

部署環境秉持站在巨人肩膀上的精神 XD ,完全使用 Oracle Cloud Free Tier(ARM64, 4 OCPU, 24GB RAM) 的方案。

繁體中文搜尋預處理工具-trad-zh-search-開源

假如有人想試試我的方案,我已經將它打包可以單獨搭配的套件,有興趣的人可以試試:

參考文章: 繁體中文搜尋預處理工具-trad-zh-search-開源

GitHub: notoriouslab/trad-zh-search

從 RAG 對話退到純搜尋

最早想做完整的 RAG 對話系統,選了 Open WebUI + Ollama(BGE-M3 embedding + Qwen3 8B 生成)。但很快撞牆:

  • Oracle CPU-only 跑不動 LLM — Qwen3 8B 只有 3-8 tokens/sec,換過好幾個模型都太慢
  • 使用量不確定 — 可以接 API 做生成,但不想一開始就綁付費方案
  • 需求多元 — 宗教類的文章使用方式很多元,可能從查詢歷史文獻到特定人名做了什麼事,或是神學探討都已可能,與其需求複雜化,不如先從最基本的可以找到文章開始。

所以退一步:先做好「搜尋」這件事。 最後選了 Meilisearch 搭配 BGE-M3 做 hybrid search。Embedding 不需要 LLM 那麼多資源,CPU 跑得動。

結果人算不如天算 —— 就在繁體中文搜尋上踩了一大輪坑 XD


CJK 分詞:所有中文搜尋的痛

症狀

搜尋「生命河基金會」,預期回傳 1 篇精確匹配,實際回傳 175 篇

原因:Meilisearch 內建的中文分詞引擎是 Rust 版 jieba(charabia),用簡體中文語料訓練,它把「生命河基金會」拆成了:

生命 / 河 / 基金 / 會

於是所有包含「生命」或「會」的文章都被撈出來。

Meilisearch 本身的語法也有限制

語法支援
"精確詞組" 雙引號
word1 word2 多詞(預設 OR)
-排除詞
AND / OR 布林運算
* 萬用符號
正則表達式

Meilisearch 的定位是「打字即搜」的前端體驗(類似 Algolia),並不是全功能搜尋引擎。

Dictionary API——止痛藥

Meilisearch 提供 settings/dictionary API 可以加入自訂詞條。但實測發現只適合補幾百個專有名詞,大量灌入不切實際,專有名詞(人名、機構名)根本窮舉不完。

結論:Dictionary API 是止痛藥,不是治本方案。

Typesense:比 Meilisearch 更差(繁體中文環境)

網路上另外推薦的是 Typesense,但看了 Github 上的 Issue 後發現,它的中文支援能力可能比 Meilisearch 更差(對了,真的不要太相信 AI 給的推薦,一定要自己上 Github or Reddit 上看使用者的經驗或 Issue,會少走錯路,我一開始就是太相信 AI …)

項目TypesenseMeilisearch
分詞引擎ICU 字典(基本逐字切)Rust jieba(至少懂詞組)
自訂字典不支援(要重編譯 ICU)有 API
CJK Issue#52,2019 年開到現在未解

Typesense創辦人坦承:「Since I don’t know Chinese, I don’t have much insights.」

**Reddit/GitHub 共識:Typesense 和 Meilisearch 的 CJK 支援都是「meh」XD **

搜尋引擎全景比較

工具繁中分詞自訂字典布林語法向量搜尋部署難度RAM
Meilisearchjieba(中等)有 API有(hybrid)
TypesenseICU(差)不支援有(hybrid)
Elasticsearch + IKIK Analyzer(好)完整完整需 plugin高(8GB+)
OpenSearch同 ES 生態同上完整有 neural search
Manticore SearchCJK ngram
SQLite FTS5 + jieba可自訂 tokenizer完整AND/OR/NOT/NEAR極低
Tantivy(Rust)完全可自訂完整完整需自建

選型小結:

  • 要最好的繁中 keyword search → Elasticsearch + IK Analyzer(但很重)
  • 要輕量 + 語意搜尋 → Meilisearch + embedding model
  • 要零部署 → SQLite FTS5
  • 要完全自訂 → Tantivy(需要 Rust 經驗)

所以我選了 Meilisearch,因為它輕量、有 hybrid search,而且 CPU 友善,分詞問題用其他方式繞過。


中文分詞工具深度比較

jieba 家族

jieba(原版)jieba-tw(APCLab)jieba-php(fukuball)
技術原理DAG + HMM 統計模型同 jieba,替換繁體詞庫Python 版移植
詞庫大小~57 萬(簡體為主)~30 萬(繁體)含繁體字典
繁中支援好(big 模式)

jieba 有三種模式:精確模式(最準)、全模式(列出所有可能組合)、搜尋引擎模式(在精確模式基礎上再拆分長詞,提高召回率)。

jieba-php 作者自己在 README 寫:「中文斷詞目前使用 LLM 大語言模型會得到更好的斷詞結果。」

jieba vs CKIP 的本質差異

jieba 家族CKIP Transformers(中研院)
技術DAG + HMM 統計模型Transformer(BERT)
繁中 F185-90%(推估)97.6%(官方數據,特定 benchmark)
NER(命名實體辨識)有(人名/地名/組織)
遇到新詞需手動加字典模型自己判斷
速度極快(毫秒級)較慢(CPU 數百毫秒/句)

結論:jieba 對繁中的支援在 70-85 分的天花板,要突破就得試試 Transformer(CKIP)。

CKIP 家族全貌

CKIP 是中研院開發的繁體中文 NLP 工具系列:

ckipnlp                ← 最上層:統一介面 pipeline
  ├── ckip-transformers  ← 後端 A:Transformer 模型(BERT/ALBERT)
  ├── CkipTagger         ← 後端 B:TensorFlow 神經網路
  └── CkipClassic        ← 後端 C:傳統統計方法

搜尋引擎場景直接用 ckip-transformers 就夠了,模型有三種大小:ALBERT Tiny(4M)、BERT Tiny(12M)、BERT Base(102M)。我本來選 BERT Base,也跑了一堆文章,但後來覺得他速度實在有點慢,所以又冇起來測試了一輪,結果發現 …

CKIP 模型分詞比較報告

句子albert-tinybert-tinybert-base誰最好
改革宗教會改革 | 宗教會改革 | 宗教 | 會改革 | 宗教 | 會都不好,需詞典
護教學護 | 教學護 | 教學護教學 ✅bert-base
因信稱義因 | 信 | 稱義因 | 信 | 稱義因 | 信 | 稱 | 義albert/bert-tiny
神不理我神 | 不 | 理 | 我 ✅神不理 | 我 ❌神 | 不 | 理 | 我 ✅albert/bert-base
睡著睡 | 著睡著 ✅睡著 ✅bert-tiny/base
神愛我神愛 | 我 ❌神 | 愛 | 我 ✅神 | 愛 | 我 ✅bert-tiny/base
信主了信主 | 了 ✅信主 | 了 ✅信 | 主 | 了 ❌albert/bert-tiny
佈道大會佈道大會佈道 | 大會佈道 | 大會看語境都行
YouTubeYou | Tube ❌You | Tube ❌YouTube ✅bert-base

CKIP 模型分詞速度比較

模型23 句耗時相對速度
albert-tiny0.26s1x(最快)
bert-tiny0.50s1.9x
bert-base3.46s13.3x(最慢)

沒有哪個模型穩定最好。 三者各有勝負:

  • bert-base:贏在「護教學」「YouTube」,但輸在「因信稱義」「信主」
  • albert-tiny:贏在「因信稱義」「信主」,但輸在「神愛」黏一起、YouTube 切碎
  • bert-tiny:最均衡,但也有「神不理」黏一起的問題

15/23 句三者結果完全一樣——生活化口語切得都很好(小組、聚會、奉獻、拜拜…)。

差異集中在神學專有名詞邊界模糊的詞,而這些正好是自訂詞典能修的,跑完測試後我又改用了 albert-tiny :P

關鍵心得:NLP 最佳分詞 ≠ 搜尋最佳分詞

這是整個研究中最重要的一個發現。

CKIP 的 97.6% F1 是 NLP 語法正確度的指標,但搜尋引擎更重視的是 recall(不漏結果),而非語法精準。

舉個例子:CKIP 把「生命河基金會」切成一個 token,語法上完美。但使用者搜「生命河」時,因為 index 裡只有完整的「生命河基金會」token,反而匹配不到

jieba 的搜尋引擎模式會切成「生命河 / 基金會 / 生命 / 基金」,語法不精確,但召回率更高。

所以 CKIP 的最大價值在 NER(辨識人名、組織名),不在搜尋分詞本身。


Embedding 模型選型

BGE-M3——最終選擇

BGE-M3 專為多語言設計,繁體中文原生支援:568M 參數,1024 維向量,支援 dense + sparse + ColBERT 三模式檢索,max tokens 8192。

繁中 Embedding 實測排名

台灣開發者 ihower 用 DRCD(台灣閱讀理解語料庫,1,000 段落 + 3,493 題)做的評測,是目前最有參考價值的繁中實測數據:

模型類型Hit Rate (top-5)備註
Voyage Multilingual-2付費 API97%(第一)最貴
multilingual-e5-large開源95%512 token 限制
BGE-M3開源前段班我在用的
OpenAI text-embedding-3-small付費 API競爭力強性價比高
Google text-embedding-004付費 API極差繁中支援炸裂

ihower 的關鍵結論:「有沒有支援中文差很多」——沒有中文訓練資料的模型直接爆炸。

為什麼不用 Jina v3

BGE-M3Jina v3
參數量568M570M
推理延遲29ms85ms(慢 3 倍)
授權MITCC BY-NC 4.0

Jina v3 有獨特的 Task LoRA 適配器,但繁中缺乏專項評測、延遲多 3 倍、CC BY-NC 授權有商業限制,純搜尋場景沒有明顯優勢,所以沒有選它。

小模型可以省資源嗎?

multilingual-e5-small(118M 參數)看起來很誘人,但 MIRACL 中文 benchmark 的 nDCG@10 只有 45.9,BGE-M3 是 63.9——差 18 分,而且 mE5 全系列 max tokens 只有 512,長文章後半段直接被截斷。

BGE-M3 仍然是目前開源繁中檢索的最佳選擇。 

另外值得關注 Qwen3-Embedding-0.6B,在 MTEB/CMTEB 全面超越 BGE-M3,但尚未有繁中專項評測,而我已經開用了BGE-M3 匯入一堆文章,也不想再換核心重跑了。

2026-03-28 更新:Qwen3-Embedding-0.6B vs BGE-M3 繁中實測

終於實測了。結論:BGE-M3 在繁中檢索場景依然完勝 Qwen3-Embedding-0.6B。

測試環境:Oracle ARM64 Ampere A1(4 core, 24GB RAM),ONNX Runtime CPU 推理。

速度比較

指標BGE-M3 ONNX INT8Qwen3-Embedding-0.6B ONNX uint8Qwen3 ONNX FP32
短 query(搜尋)~30ms~140ms~350-430ms
長文本(1000 字)~數秒~3.6s~3.6s
架構Encoder(雙向注意力)Decoder(因果注意力)Decoder

Qwen3 的 decoder 架構先天比 encoder 慢——同樣 0.6B 參數,推理速度差 5-15 倍。

品質比較(致命差距在區分度)

Query: 「產假」BGE-M3Qwen3 uint8Qwen3 FP32
勞基法條文:產假八星期(相關)0.6090.9950.463
內部規章:產假規定(相關)0.6010.9940.436
資產管理辦法(不相關)0.3710.9920.382
相關 vs 不相關 gap~0.2~0.002~0.06
  • Qwen3 uint8:所有文件 cosine similarity 都在 0.991-0.995,相關和不相關幾乎分不開。uint8 量化把 embedding 空間壓扁了。
  • Qwen3 FP32:區分度只有 0.06(相關 0.44 vs 不相關 0.38),BGE-M3 是 0.2——差 3 倍以上。

可能原因:Qwen3-Embedding 使用 decoder 架構,mean pooling 可能不是最佳 pooling 策略(decoder 通常用 last token pooling)。但 ONNX 導出版使用 mean pooling,且 uint8 版的 sentence_embedding_quantized 直接輸出也有同樣問題。

結論:BGE-M3 在 ARM64 CPU 上是繁中 embedding 的最佳選擇。 速度快、區分度好、ONNX INT8 量化後品質保持完好。Qwen3-Embedding 可能在 GPU 推理 + FP16/BF16 下表現不同,但在 CPU + 量化場景完全不行。


決策框架與最終架構

場景選型指南

  • 幾百到幾千篇繁中文章 + 輕量部署 → Meilisearch hybrid search + bigram 雙欄位
  • 需要精確 keyword search + 布林語法 → Elasticsearch + IK Analyzer(最成熟但最重)
  • 想要最佳分詞品質且不怕工程量 → CKIP Transformers 前置分詞 + Meilisearch
  • 預算充足 → Algolia / Azure Cognitive Search

架構哲學

這次的經驗讓我學到一個原則:先解決「找到」,再解決「理解」。

搜尋和 AI 對話是兩個獨立的需求,搜尋是基礎設施,做好了誰都能用;AI 對話是上層應用,每個人需求不同,Open WebUI 不是不好,但它把搜尋和 AI 綁在一起 —— 當 LLM 跑不動時,搜尋也跟著廢了。

最終架構

前端(靜態 HTML)
  ↓ /multi-search API
Meilisearch
  ├── keyword search(jieba 原文 + bigram 欄位 + CKIP 欄位)
  ├── vector search(BGE-M3 ONNX INT8 embedding)
  └── synonyms(專業術語同義詞擴展)

三路合併,最高分排序

Cross-encoder Reranking(BGE-reranker-v2-m3 ONNX INT8)

Entity-Aware Scoring(主詞硬篩 + coverage + 別名展開)

部署在 Oracle Cloud Free Tier 上,零成本,但花了很多時間測試。

優化路線圖

優先順序項目觸發條件
1Bigram + CKIP 雙欄位基礎必做
2Cross-encoder Reranking文章量 2000+,前幾名不準
3Entity-Aware Scoring人名查詢結果混雜
4Paragraph Chunking + RRF需要 RAG 問答或細節搜尋

原則:先用 80 分方案上線,有明確痛點再追 90 分。


參考資源

下一篇:(二)建置實戰與測試——四種解法的實作細節、ONNX Runtime 42 倍加速、以及從 516 篇到 7600+ 篇的四輪品質演進數據。

作者 Jacobmei:帶領街口支付對接國際巨頭 Apple,推動台灣金融科技國際化實踐。

← 回文章列表