:從配置到端到端搜索實(shí)踐)
Milvus 文本分析器 Pinyin Filter拼音過濾器從配置到端到端搜索實(shí)踐【免費(fèi)下載鏈接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mi/milvus本文對應(yīng)倉庫設(shè)計(jì)文檔docs/design-docs/design_docs/20260209-pinyin_filter.md即 Milvus MEPMilvus Enhancement Proposal中關(guān)于“Pinyin Filter for Text Analyzer”的實(shí)現(xiàn)提案。導(dǎo)讀本文深入介紹 Milvus 全文檢索文本分析器中的內(nèi)置Pinyin Filter拼音過濾器它能把中文分詞后的漢字 token 自動轉(zhuǎn)寫成拼音拉丁字母讓用戶直接用拼音輸入即可命中中文內(nèi)容支撐人名/地名檢索、輸入法拼音聯(lián)想search-as-you-type、以及無中文輸入法環(huán)境下的跨輸入法搜索等場景。讀完本文你將掌握該過濾器在 Milvus 配置 JSON 中的全部參數(shù)與默認(rèn)值、它在 tantivy 分詞管線底層的 token 展開實(shí)現(xiàn)原理并能夠基于官方測試用例在 Go SDK 中端到端地創(chuàng)建啟用了拼音過濾的集合并用text_match完成中/拼音混合搜索。1. 背景與動機(jī)為什么需要在全文檢索管線里做拼音轉(zhuǎn)換Milvus 對中文全文檢索的支持此前依賴于 Jieba 等分詞器完成“詞切分”但分詞產(chǎn)物始終是漢字本身沒有任何內(nèi)置手段用拼音輸入去命中中文內(nèi)容。這對大量中文場景是硬需求姓名/地名檢索用戶習(xí)慣敲拼音例如輸入zhangsan期望命中“張三”輸入beijing期望命中“北京”自動補(bǔ)全與邊打邊搜絕大多數(shù)設(shè)備的輸入法是把拼音按鍵流轉(zhuǎn)成漢字若索引與查詢兩側(cè)都能按拼音匹配搜索體驗(yàn)會更快更自然跨輸入法檢索部分用戶環(huán)境沒有中文輸入法只能使用拉丁字符檢索中文數(shù)據(jù)。沒有拼音過濾器時(shí)用戶只能自維護(hù)一個(gè)拼音映射字段或在應(yīng)用層做轉(zhuǎn)換既增加寫入側(cè)復(fù)雜度與存儲開銷又難以保證兩端轉(zhuǎn)換規(guī)則一致。將其實(shí)現(xiàn)為“分詞管線內(nèi)的一個(gè) filter”則可以在索引構(gòu)建寫入與查詢改寫兩側(cè)天然復(fù)用同一套邏輯屬于更優(yōu)雅的方案。這也在該 MEP 的“Rejected Alternatives被否決的備選方案”中得到了印證應(yīng)用層維護(hù)拼音字段復(fù)雜且有存儲開銷而獨(dú)立拼音分詞器不如 filter 可組合——filter 可以疊加在 Jieba、standard 等任意分詞器之后再與停用詞、小寫化等其它 filter 串聯(lián)。2. 公共接口在 Analyzer 配置里啟用pinyinfilter該過濾器以新的 filter 類型pinyin暴露在 analyzer 配置 JSON 中可掛載到任意 analyzer 的 filter 管線。下面配置即官方 MEP 文檔與 Rust 單測pinyin_filter.rs使用的形態(tài){ tokenizer: jieba, filter: [ { type: pinyin, keep_original: true, keep_full_pinyin: true, keep_joined_full_pinyin: false, keep_separate_first_letter: false } ] }2.1 四個(gè)布爾參數(shù)的含義與默認(rèn)值參數(shù)類型默認(rèn)值說明keep_originalbooltrue輸出中保留原始中文 tokenkeep_full_pinyinbooltrue把每個(gè)漢字單獨(dú)輸出為對應(yīng)拼音 token例中文 →zhong、wenkeep_joined_full_pinyinboolfalse把整詞所有漢字的拼音拼成一個(gè)連續(xù) token例中文 →zhongwenkeep_separate_first_letterboolfalse把整詞每個(gè)字拼音首字母拼成一個(gè) token例中文 →zw2.2 字符串簡寫形式當(dāng)不需要任何定制時(shí)可以直接把pinyin作為字符串寫進(jìn) filter 數(shù)組此時(shí)使用全部默認(rèn)選項(xiàng)即keep_originaltrue、keep_full_pinyintrue、其余為false。MEP 文檔明確說明“When used with no parameters (i.e.,pinyinas a plain string filter), the default options apply.”從源碼看這一簡寫確實(shí)落到了SystemFilter的Fromstr分支pinyin Self::Pinyin(PinyinFilter::default()),見 filter.rs而 JSON 對象形式則由create_filter中pinyin PinyinFilter::from_json(params)分支負(fù)責(zé)見 filter.rs。使用提示前提與限制以上配置適用于啟用全文檢索能力VARCHAR 字段開啟 analyzer match的集合字段不同語言 SDK 的“啟用 analyzer”開關(guān)名稱略有差異Go 側(cè)為WithEnableAnalyzer(true).WithEnableMatch(true)具體見下文第 6 節(jié)。若 analyzer JSON 中 filter 元素既非字符串也非含type的 JSON 對象或type不是字符串、不屬于已注冊類型構(gòu)建 analyzer 都會失敗并返回明確錯(cuò)誤如unsupport filter type: xxx、no type field in filter params這部分校驗(yàn)邏輯同樣位于 filter.rs。3. 實(shí)現(xiàn)位置與整體架構(gòu)拼音過濾器的實(shí)現(xiàn)位于 Milvus 為全文檢索準(zhǔn)備的 tantivy-binding Rust crate 內(nèi)與 RegexFilter、SynonymFilter 等既有過濾器處于同一目錄、同一種插件模式之下pinyin_filter.rs —— 核心過濾器實(shí)現(xiàn)filter.rs —— 在系統(tǒng)過濾器分發(fā)系統(tǒng)中完成注冊mod.rs —— 模塊聲明與導(dǎo)出Cargo.toml —— 引入第三方依賴pinyin 0.10中文轉(zhuǎn)拼音庫。3.1 三個(gè)組成類型的職責(zé)MEP 文檔把實(shí)現(xiàn)拆成三層源碼中一一對應(yīng)PinyinFilter—— 實(shí)現(xiàn)tantivy::tokenizer::TokenFiltertrait內(nèi)部只保存一份PinyinOptions配置其transform()負(fù)責(zé)把上游 tokenizer 包裝成新的 tokenizer。PinyinFilterWrapperT—— 泛型包裝器Tokenizer實(shí)現(xiàn)里創(chuàng)建出實(shí)際的 token 流對象并持有一份克隆的PinyinOptions見 pinyin_filter.rs。PinyinFilterStreamT—— 真正執(zhí)行轉(zhuǎn)換的 token 流通過緩存隊(duì)列 游標(biāo)方式把上游進(jìn)來的 1 個(gè) token 展開成多個(gè)輸出 tokencache: VecTokenindex: usize見 pinyin_filter.rs。配置解析入口PinyinFilter::from_json會逐項(xiàng)讀取四個(gè) key任何一項(xiàng)若傳了非布爾值都會直接報(bào)錯(cuò)例如keep_original must be a boolean value未出現(xiàn)的 key 保持默認(rèn)值見 pinyin_filter.rs。3.2 在全文檢索整體鏈路中的位置從調(diào)用關(guān)系看該 crate 的create_analyzer/create_analyzer_by_jsonanalyzer.rs負(fù)責(zé)把 analyzer 配置 JSON 解析成 tantivy 的TextAnalyzer其中 filter 數(shù)組逐項(xiàng)生效字符串元素走SystemFilter::from對象元素走create_filter最后統(tǒng)一transform(builder)追加到管線見 analyzer.rs。這套 analyzer 被 Milvus 的 DataNode/QueryNode該 MEP 標(biāo)記的 Component在寫入側(cè)建索引與查詢側(cè)解析用戶文本時(shí)共用因此拼音過濾天然同時(shí)作用于“索引端分詞”與“查詢端分詞”。4. 底層原理token 展開邏輯與細(xì)節(jié)校對4.1 處理流程PinyinFilterStream::advance()對上游如 Jieba傳入的每個(gè) token 執(zhí)行如下處理見 pinyin_filter.rs若keep_originaltrue先把原始 token 原樣壓入緩存隊(duì)列遍歷 token 文本的每一個(gè)字符通過pinyincrate 的ToPinyintrait 轉(zhuǎn)換to_pinyin().flatten()——注意flatten()意味著無法轉(zhuǎn)寫的字符會被靜默跳過天然實(shí)現(xiàn)“只處理漢字、忽略非中文字符”依配置產(chǎn)出派生 tokenkeep_full_pinyintrue每字拼音作為獨(dú)立 tokenchar.plain()keep_joined_full_pinyintrue逐字拼接進(jìn)join_pinyin非空才整體壓入一個(gè) tokenkeep_separate_first_lettertrue逐字取char.first_letter()拼進(jìn)first_letter非空才壓入空轉(zhuǎn)寫結(jié)果純 ASCII/數(shù)字等 token不會生成任何空 token。4.2 一個(gè)值得注意的源碼級細(xì)節(jié)offset 與 position 的精確語義MEP 文檔概述稱“All generated tokens share the sameoffset_from,offset_to, andpositionas the original token”。對照真實(shí)源碼需要做一處更精確的說明offset 確實(shí)完全繼承原 token但position 并非一律相同——逐字全拼 tokenkeep_full_pinyin的 position 會按字序號遞增start_position token.position index僅當(dāng)index position_length并且position_length強(qiáng)制設(shè)為1其意圖是讓逐字拼音可作為相互獨(dú)立的詞位參與短語/臨近匹配而整詞拼接 tokenkeep_joined_full_pinyin、keep_separate_first_letter則原樣沿用原 token 的position與position_length。if self.options.keep_full_pinyin { let mut start_position self.tail.token().position; if index self.tail.token().position_length { start_position start_position index; } self.cache.push(Token { text: char.plain().to_string(), offset_from: self.tail.token().offset_from, offset_to: self.tail.token().offset_to, position: start_position, position_length: 1, }) }見 pinyin_filter.rs。4.3 依賴選型轉(zhuǎn)換依賴 pinyin Rust crate版本 0.10它提供不帶聲調(diào)的純拼音plain()與首字母first_letter()兩類輸出正好覆蓋本文檔需要的全部三種拼音形態(tài)。其取舍無音調(diào)、按字轉(zhuǎn)換也決定了該過濾器的定位是“輔助召回/聯(lián)想”而非“語義理解”。5. 分詞輸出示例速查沿用 MEP 文檔示例輸入文本“中文測試”由 Jieba 分詞為“中文”與“測試”兩個(gè) token 后不同配置組合的最終 token 輸出如下配置輸出 tokenskeep_originaltrue, keep_full_pinyintrue中文、zhong、wen、測試、ce、shikeep_originaltrue, keep_joined_full_pinyintrue中文、zhongwen、測試、ceshikeep_originaltrue, keep_separate_first_lettertrue中文、zw、測試、cs全部選項(xiàng)開啟中文、zhong、wen、zhongwen、zw、測試、ce、shi、ceshi、cs可以把上表理解為“索引側(cè)倒排里每種形態(tài)各占一個(gè)詞項(xiàng)”查詢文本在查詢側(cè)也會走同樣的展開邏輯——這正是查詢“中文”“zhongwen”“zw”都能命中同一批文檔的根因。6. 端到端落地Go SDK 中的建集合、驗(yàn)詞、檢索配套倉庫在 tests/go_client/testcases/pinyin_filter_test.go 提供了完整的 L0 級可合并進(jìn) CI 的輕量場景Go SDK 端到端用例可以直接當(dāng)作使用范本。6.1 定義 analyzer 與集合用 Go 的字段屬性開關(guān) analyzer JSON 創(chuàng)建一個(gè) VARCHAR 字段參與全文檢索pinyin_filter_test.gofunc pinyinAnalyzerParams(keepOriginal bool) map[string]any { return map[string]any{ tokenizer: jieba, filter: []any{ map[string]any{ type: pinyin, keep_original: keepOriginal, keep_full_pinyin: false, keep_joined_full_pinyin: true, keep_separate_first_letter: false, }, }, } } // 建集合VARCHAR 字段開啟 analyzer match并掛上含 pinyin filter 的 analyzer 參數(shù) schema : entity.NewSchema().WithName(collectionName). WithField(entity.NewField().WithName(id).WithDataType(entity.FieldTypeInt64).WithIsPrimaryKey(true)). WithField(entity.NewField().WithName(text).WithDataType(entity.FieldTypeVarChar).WithMaxLength(1024). WithEnableAnalyzer(true).WithEnableMatch(true).WithAnalyzerParams(analyzerParams)). WithField(entity.NewField().WithName(vector).WithDataType(entity.FieldTypeFloatVector).WithDim(2))提示該用例中拼音開關(guān)組合是keep_full_pinyinfalsekeep_joined_full_pinyintrue即只為每詞保留一個(gè)整詞拼音如zhongwen刻意不產(chǎn)生逐字拼音與首字母形式——這正好用來驗(yàn)證“沒開的形態(tài)不會被命中”。6.2 用 RunAnalyzer 直接觀察分詞結(jié)果免建索引排障寫入前就能用RunAnalyzer把 analyzer 實(shí)際跑一遍、核對展開后的 tokenpinyin_filter_test.goresults, err : mc.RunAnalyzer(ctx, client.NewRunAnalyzerOption(中文測試). WithField(collectionName, text)) require.NoError(t, err) tokens : make([]string, len(results[0].Tokens)) for i, token : range results[0].Tokens { tokens[i] token.Text }用例斷言keep_originaltrue時(shí)“中文測試”應(yīng)輸出[中文, zhongwen, 測試, ceshi]單獨(dú)一個(gè)“中文”輸出[中文, zhongwen]而當(dāng)keep_originalfalse時(shí)輸出只剩[zhongwen, ceshi]見 pinyin_filter_test.go。這直觀印證了第 5 節(jié)的表格也是日常排查“為什么某拼音查不到”的首選工具。6.3 寫入、建索引后按拼音檢索測試覆蓋了 sealed已封口/已索引段、unsealed未索引封口段與 growing增長段三類數(shù)據(jù)路徑先寫入 3000 行、flush 成已索引 sealed 段再寫 500 行封口成未建索引 sealed 段加載集合后再寫入 500 行增長段。檢索統(tǒng)一用全文匹配函數(shù)text_match作為 Search 的過濾條件pinyin_filter_test.gofilter : fmt.Sprintf(text_match(text, %q), queryText) // 也可指定 minimum_should_match // text_match(text, 中文, minimum_should_match2) result, err : mc.Search(ctx, client.NewSearchOption(collectionName, limit, []entity.Vector{entity.FloatVector{0, 0}}). WithANNSField(vector). WithFilter(filter). WithOutputFields(id, text))關(guān)鍵斷言矩陣pinyin_filter_test.go查詢文本minimum_should_match期望結(jié)果語義驗(yàn)證zhongwen—命中 3 個(gè)目標(biāo)行整詞拼音可檢索核心場景中文2命中 3 個(gè)目標(biāo)行原文檢索不受影響zhong—空結(jié)果未開啟keep_full_pinyin逐字拼音被正確禁用zw—空結(jié)果未開啟keep_separate_first_letter首字母被正確禁用可見拼音開關(guān)具備精確的啟停語義開哪個(gè)開關(guān)、就只會命中哪種拼音形態(tài)不存在“漏禁”情況minimum_should_match參數(shù)則保證“中文”這類跨兩個(gè)分詞詞項(xiàng)的查詢能要求全部詞項(xiàng)命中避免誤召回。6.4 單元測試側(cè)的三場景驗(yàn)證Rust 側(cè)的單元測試與實(shí)現(xiàn)同文件的 pinyin_filter.rsmod tests同樣覆蓋三個(gè)場景且全部以 Jieba 為上游分詞器、用is_subset子集匹配做斷言整詞全拼keep_joined_full_pinyintrue→ 期望包含zhongwen、ceshi逐字全拼keep_full_pinyintrue→ 期望包含zhong、wen、ce、shi首字母keep_separate_first_lettertrue→ 期望包含zw、cs。單測與上述 E2E 用例在輸入“中文測試”上完全一致形成了“Rust 過濾邏輯 ? SDK 端到端行為”的雙層證據(jù)閉環(huán)。7. 兼容性、遷移與選型建議完全向后兼容、純增量特性不修改任何既有 analyzer 語義MEP 聲明對現(xiàn)有配置無影響已有集合無需遷移。用戶只需在 analyzer 配置里主動添加pinyinfilter 即可“opt-in”啟用。二進(jìn)制體積影響可控新增依賴僅pinyin 0.10一個(gè) crateMEP 評估為“slightly increases compiled binary size”對部署影響很小。推薦組合供選型參考若目標(biāo)是中文姓名/地名拼音檢索通常建議keep_joined_full_pinyintrue支持整詞拼音如zhangsan、beijing并搭配keep_originaltrue保住原文匹配若還需要“首字母縮寫”檢索類似輸入法聲母聯(lián)想zs再加keep_separate_first_lettertrue若需要容納“拼音逐字匹配長詞中某個(gè)字”則開keep_full_pinyintrue。三個(gè)開關(guān)也可全開代價(jià)只是倒排詞項(xiàng)數(shù)變多。注意事項(xiàng)拼音轉(zhuǎn)換只作用于漢字字符數(shù)字、拉丁字符等無法轉(zhuǎn)寫的部分會被flatten()跳過但其原文仍會因keep_original或分詞器自身行為保留不會被誤刪。另外過濾器產(chǎn)出的全是無音調(diào)純拼音音調(diào)無關(guān)的模糊拼音本身就是其設(shè)計(jì)目標(biāo)若需要拼音與漢字的語義消歧同音字仍需配合其它字段/模型手段。8. 延伸閱讀本提案原始文檔docs/design-docs/design_docs/20260209-pinyin_filter.md核心實(shí)現(xiàn)internal/core/thirdparty/tantivy/tantivy-binding/src/analyzer/filter/pinyin_filter.rs過濾器注冊與分發(fā)internal/core/thirdparty/tantivy/tantivy-binding/src/analyzer/filter/filter.rsanalyzer 解析入口internal/core/thirdparty/tantivy/tantivy-binding/src/analyzer/analyzer.rs依賴聲明pinyin 0.10internal/core/thirdparty/tantivy/tantivy-binding/Cargo.tomlGo SDK 端到端用例建集合/驗(yàn)詞/檢索全覆蓋tests/go_client/testcases/pinyin_filter_test.go【免費(fèi)下載鏈接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mi/milvus創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考