韓国語政策データのハイブリッド検索 — pgvectorと全文検索を組み合わせる
MyHyetaekで意味検索とキーワード検索を組み合わせる理由を説明します。
韓国語政策データのハイブリッド検索 — pgvectorと全文検索を組み合わせる
「なぜ純粋なベクトル検索だけでは、ユーザーの意図を正確に捉えられないのでしょうか?」GRAXELの主力サービスの一つである「MyHyetaek」の開発中、私はこの壁に激しくぶつかりました。11,600件にも及ぶ複雑な韓国語の政府支援策データを、AIを用いていかに正確に検索可能にするか。PostgreSQLのpgvectorと、伝統的な全文検索(FTS)を融合させた「ハイブリッド検索」の実装プロセスと、そこで得た知見を共有します。
ベクトル検索の限界とRRFの導入
当初、私は768次元のエンベディングモデルを用いて、単純なコサイン類似度のみで検索を実装していました。しかしテスト運用を開始すると、「青年飛躍口座」のような特定の固有名詞や、「城南市」のような厳密な地域名を含むクエリに対して、ベクトル検索は意味が似ているだけの全く無関係な政策(例: 別の地域の青年向け積立制度)を上位に返してしまうという致命的な弱点を露呈しました。
これを解決するために導入したのが、ハイブリッド検索とRRF(Reciprocal Rank Fusion)アルゴリズムです。pgvectorを用いたベクトル類似度スコアと、PostgreSQLのtsvectorを用いた全文検索のBM25スコア。次元の異なるこの2つのスコアを、順位の逆数(1 / (k + rank))という形で数学的に統合しました。この実装にはSupabaseのAIガイドの構造を大いに参考にし、k=60というパラメータ設定で運用しています。
テキストチャンキングで犯した最大の過ち
この検索システムを構築する中で、私はインデックス作成において非常に愚かな失敗を犯しました。初期のデータ投入時、処理を簡略化するために、すべての政策文書を「機械的に500文字ずつ」で分割(チャンキング)してエンベディングを生成したのです。
その結果何が起きたか。政府の文書特有の「■ 支援対象:」「■ 申請方法:」といった重要な文脈の境界線が真っ二つに分断され、検索結果が完全に崩壊しました。AIは「支援対象」の条件だけを読み取り、それが何の政策に関するものなのかを紐付けできなくなってしまったのです。この失敗を受けて、私はデータベース内の11,600件のベクトルデータを全て破棄しました。そして、HTMLの見出しタグ(H2, H3)や特定の記号を基準にして、意味的なまとまり(セマンティック・チャンク)単位で分割し直すという、泥臭い再構築作業を丸3日かけて行いました。
ミリ秒の戦い:インデックスのチューニング
ハイブリッド検索は計算コストが高くなります。少しでも応答速度を上げるため、ベクトルインデックスにはHNSW(Hierarchical Navigable Small World)を採用しました。インデックス構築時にef_constructionを増やし、検索時にはef_searchを100〜150の範囲で動的に調整することで、再現率(Recall)と速度のバランスを取っています。現在、Oracle ARMサーバー上での検索クエリの実行時間は、複雑な結合処理を含めても平均180〜260msで安定しています。
AIを活用した検索技術は、単に最新のモデルを使うだけではなく、データ構造への深い理解が必要です。私たちの技術的な取り組みに興味がある方は、ぜひ開発ブログの他の記事や、具体的な機能についてはお問い合わせを通じてご質問ください。泥臭いチューニングの裏話はまだまだ尽きません。
共有
関連記事
同じテーマやタグに基づき、GRAXELの運用文脈を続けて確認できます。
MyHyetaek RAG運用記 — 11,600件の政府支援制度を検索可能にするまで
GRAXELが政府支援制度データをどのように整理し、検索とAI回答につなげているかを紹介します。
Next.js 15とnext-intlで韓国語・英語・日本語ページを運用する
GRAXELポータルの多言語ルーティング、メタデータ、検索エンジン向け整備についてまとめます。
OllamaでローカルLLMを運用に組み込む — 使いどころと限界
GRAXELがローカルLLMをどのようにコスト削減と自動化に使うかを整理します。