ガイド · 1 分で読めます

AgentKits Memoryのハイブリッド検索は「日本語」を二度解決しなければならない

BM25は意味を取りこぼし、ベクトル検索は正確なエラー文字列を取りこぼす。AgentKits Memoryは両方を融合させているが、日本語・中国語・韓国語のクエリではキーワード検索側にもう一段の判断が必要になる。

AgentKits Memoryのハイブリッド検索は「日本語」を二度解決しなければならない

2つの壊れ方をする2種類のクエリ

AIコーディングアシスタントのメモリに「認証パターン」と尋ねたとき、期待するのは「JWTとリフレッシュトークンを使う」というエントリがヒットすることだ——文字としては一致する単語が一つもなくても。これは「意味」の問題であり、キーワード検索はここが苦手だ。逆に、同じメモリに ModuleNotFoundError: No module named 'networkx' という正確な文字列を尋ねたとき、欲しいのはその厳密なエラーを含む1件のエントリであって、「なんとなくPythonに関係のありそうな」5件ではない。これは「同一性」の問題であり、埋め込みによる類似度は正確な文字列を「似たような」テキストの近傍にぼかしてしまうため、ベクトル検索はここが苦手だ。

以前記事にした、AIコーディングアシスタント向けのローカルファースト永続メモリ層であるAgentKits Memoryは、どちらか一方を選ばない。その HybridSearchEngine は、すべてのクエリに対してSQLite FTS5によるキーワード検索とローカル埋め込みによるベクトル検索の両方を実行し、2つのスコアを融合する。これは勘に基づく決定ではない——検索研究が2つのアプローチを別々に測定するたびに繰り返し見つけている結果と一致している。ECサイト向けの検索ベンチマークWANDSでは、チューニングされたハイブリッド構成がNDCG 0.7497に達し、BM25単独の0.6983、ベクトル検索単独の0.6953を上回った。どちらの手法も得意なクエリと苦手なクエリがあり、片方だけを選ぶよりも両者を融合させたほうが優れているという結果だ。(Denser AI)

融合の実際の仕組み

実装はブラックボックスではなく、素直な重み付き合成になっている。デフォルトでは、キーワードスコアに0.3、意味的スコアに0.7を掛けて足し合わせる——両者とも合成前に0〜1の範囲に正規化される。BM25によるランキングはFTS5自体の bm25() 関数から得られ、意味的類似度は、ローカルで生成したクエリの埋め込みと保存済みエントリの埋め込みとのコサイン距離から計算される。クラウドの埋め込みAPIは使わず、READMEに明記されている多言語対応モデルmultilingual-e5-smallのONNX実装を使う。どちらか一方でしかヒットしなかった場合、そのエントリは単独のスコアをそのまま保持し、もう片方が沈黙したことでペナルティを受けることはない。

デフォルトで意味的スコア側に70%の重みを置いているのは、明確な賭けだ。このメモリシステムのエントリの多くは、商品コードのようなものではなく、意思決定や要約といった散文であるため、多くの場合は正確な言い回しより意味のほうが重要になる——しかし残り30%のキーワード側の下支えがあるからこそ、正確なエラー文字列や関数名が埋め込み空間の中で埋もれてしまうことを防いでいる。これはまさに、ベクトル単独の検索が抱えることが知られている失敗パターンそのものだ。

「キーワード」の内側に隠れているCJK問題

ここからが、一般的なRAGの話には出てこない部分だ。「キーワード検索」は、ある単語がどこで終わり次の単語がどこから始まるかを判定できることを前提にしている。しかし日本語・中国語・韓国語のテキストには、単語の間にスペースがなく、その前提がそもそも成り立たない。FTS5の標準トークナイザーである unicode61 は単語境界を前提に設計されており、CJKテキストを検索可能な単位に分割することができない。AgentKits Memoryはこれに対し、FTS5の trigram トークナイザーをデフォルトとすることで応えている。これは単語単位ではなく重複する3文字の連続を索引化する手法であり、そもそも単語境界を確実に見つけられない状況を前提に設計された戦略だ。

このデフォルトはトレードオフなしでは成立せず、コード自体もその点を隠していない。日本語向けの文字n-gram索引は、情報検索の文献において「スプリアスな一致」——たまたま同じn-gramを共有しているだけの無関係な単語同士がヒットしてしまう問題——を起こしやすいことが知られている。一方、その代替である適切な形態素解析による単語分割は、逆の問題を抱える。解析器の辞書は常に不完全であり、学習していない語を静かに取りこぼしてしまう。(Whoosh公式ドキュメントのn-gram索引の解説。日本語トークナイズに関する一般的な調査でも、両方向のこの種のトレードオフが同様に指摘されている。)AgentKits Memoryの HybridSearchEngine は、どちらか一方を盲目的に選ぶのではなく、この問題を実装レベルで回避しようとしている。初期化時に、ローカルのSQLiteビルドが実際にサポートしている最良のトークナイザー(trigram、次いでporter、最後にunicode61の順)をその場で確認し、trigramを形成するには短すぎる3文字未満のCJKクエリに対しては、何も返さない代わりに単純な LIKE 検索にフォールバックする。n-gramによる妥協ではなく、本物の単語単位の日本語分割を求めるチームのためには、lindera-sqlite バックエンドをオプションのアップグレード経路として用意しており、この判断をすべてのユーザーにデフォルトで強制するのではなく、選べる形にしている。

節約分は「勝ち取って終わり」ではなく「守り続ける」必要がある

2つの検索エンジンを走らせ、2つのトークナイザー戦略を調停するというエンジニアリングは、その結果を全文のまま丸ごとモデルのコンテキストウィンドウに吐き出してしまえば無意味になる。付随する TokenEconomicsTracker と、検索エンジンの3層構成(compact → timeline → full)が実際に果たしている役割はそこにある。第1層はヒットごとにおよそ50トークン——ID、スコアの内訳、100文字のスニペット、推定トークン数——しか返さない。これにより、アシスタントは1件分の全文取得のコストで10件の候補に目を通し、そのうえでどれを本当に全文取得する価値があるか判断できる。READMEはこのプログレッシブ・ディスクロージャー(段階的開示)のパターンが、最初からすべてを取得する場合と比べておよそ70%のトークンを節約すると説明しており、構成によっては最大87%になるとも記載している。いずれにせよ要点は同じで、2つの検索シグナルを融合させる工夫は、その上に載る層が節約分をすぐに使い切ってしまわない場合にのみ意味を持つということだ。

誠実に言えば

これによってAgentKits Memoryの検索が何らかの最終的な意味で「解決済み」になったわけではない。トライグラム・トークナイズは、選び取られ、かつそう明記されている妥協であって、適切な形態素解析エンジンに匹敵するという主張ではない。ここで示されているのは、ある種の設計上の誠実さだ。キーワードスコアと意味的スコアを融合するハイブリッド検索エンジンは、2026年時点のRAGアーキテクチャとしてはもはや当たり前の要件になっている。しかし「キーワード」という操作が英語とCJKテキストとで同じように定義できるわけではないと認め、unicode61 が誰にでも通用すると決めつけるのではなく、明示的なフォールバックの段階を組み込むこと——これは、実際に日本語で書かれたクエリに対して検索を機能させようとして初めて見えてくる部分だ。


AgentKits MemoryはGitHubで公開されているほか、npmパッケージ @aitytech/agentkits-memory としても利用できる。導入について質問があれば、[email protected] までお問い合わせを。

オープンソースを探索

開発者向けのオープンソースツールを構築・メンテナンスしています。GitHubでリポジトリをご覧ください。

GitHubで見る

関連記事

ガイド

COBOLモダナイゼーションツールの多くは「プログラム」で止まっている。実際に移行が壊れるのは、それを呼び出す「バッチジョブ」の側だ。

COBOL中心の依存関係マッピングツールは、JCLを「ジョブ名といくつかのDD文」程度の薄いラッパーとして扱いがちだ。しかし条件コード分岐、PROCのネスト、GDGの世代管理は、それ自体が独立した制御フローを持っており、依存関係グラフから日常的に抜け落ちている。Legacy Dragonが、後付けのメタデータとしてではなく、JCLを同じASTの中の第一級言語としてパースしている理由。

ガイド

322種の声、142言語、サーバーなし — ブラウザタブにそれだけの音声を収める方法

PrivateAIの音声合成ツールは322以上の声、142言語をすべてオンデバイスでカバーする。速度ではなく「声と言語のカバー範囲」こそがブラウザ型TTSの本当の難所である理由と、オープンソース勢・有料クラウドAPIとの比較。

ガイド

AgentKits Marketingには28個のスキルがある。だが一度に読み込むのは常に5個まで

Marketing Kitの`skills-registry.json`は28個のスキルを5カテゴリーに分類し、完全な依存関係グラフを持つ。社内の調査文書は、一度に読み込む量を制限する理由をはっきり名指ししている——「コンテキスト・ロット(context rot)」だ。実際に実装されたセレクターがどう動くのか、そしてなぜそれがきっかけとなった調査提案よりシンプルなのかを見る。