ToC
作業手順書の検索精度を上げたい
Hugoで管理している作業手順書を、AIエージェントから検索できるようにしたいと考えました。
MCP(Model Context Protocol)経由でリモートの検索サーバーに問い合わせ、関連する手順書を返す構成です。
ただし、検索精度が悪いとエージェントが誤った手順を参照してしまいます。
とくに日本語の文書は英語のように単語がスペース区切りになっていないため、検索方式を安易に選ぶと期待した文書にたどり着けません。
今回は、作業手順書の全文検索で日本語をどう扱うかを整理し、最終的にSQLite + FTS5 + Janomeの構成を採用した理由をまとめます。
日本語全文検索が難しい理由
英語の文章であれば、空白を区切りとして単語を比較できます。 一方で、日本語には単語境界が明示されません。
例えば、アカウント削除手順という文字列には、次のような単語が含まれています。
- アカウント
- 削除
- 手順
しかし、文字列としては連続しているだけなので、検索エンジン側で適切に分割しない限り、削除や手順での検索精度が安定しません。
作業手順書では、障害対応、アカウント削除、ロールバックのような専門用語を正確に見つけたい場面が多く、ここが重要なポイントになります。
検索方式を比較してみる
障害対応手順という文書を検索対象にすると、候補になりやすい方式は大きく3つあります。
| 方式 | 取り扱い | 特徴 |
|---|---|---|
| LIKE | 文字列をそのまま比較 | 実装は簡単だが、部分一致のノイズが多い |
| trigram | 3文字単位に分割 | 曖昧検索には強いが、2文字以下の語が苦手 |
| 形態素解析 | 意味のある単語に分割 | 日本語の用語検索と相性が良い |
LIKE検索
最も単純なのは、SQLのLIKEで部分一致検索する方法です。
SELECT *
FROM documents
WHERE content LIKE '%障害%';
すぐ試せるのは利点ですが、検索は文字列の部分一致です。
そのため、意図しない箇所に含まれる文字列まで拾いやすく、文書数が増えるほどノイズが気になってきます。
trigram検索
trigramは文字列を3文字単位に分割してインデックス化する方式です。
たとえば、障害対応手順であれば、障害対、害対応、対応手のような単位で分割されます。
部分一致検索よりは検索性能を上げやすいのですが、日本語の作業用語では障害や運用のような2文字語が非常に多く、この方式だとヒットしないケースが出ます。
形態素解析
形態素解析では、文章を意味のある単語に分割します。
入力: アカウント削除手順を実施する
分割結果:
- アカウント
- 削除
- 手順
- 実施
- する
助詞や記号を除外し、名詞や動詞などをインデックス対象にすれば、実際に検索したい語をそのまま扱えます。
作業手順書のように専門用語中心の文章では、この方式が最も自然でした。
実際のクエリで比べると
具体的な検索語で比較すると、方式ごとの違いが見えやすくなります。
| クエリ | trigram | LIKE | 形態素解析 |
|---|---|---|---|
運用 | 2文字なので弱い | ヒットする | 正確にヒットする |
障害対応 | 条件次第 | ヒットする | 障害と対応で扱える |
害対 | ヒットする可能性がある | ヒットする可能性がある | ヒットしない |
害対のような意味を持たない文字断片でヒットしにくいのは形態素解析の利点です。
辞書に存在する語をベースに検索するため、ノイズを抑えやすくなります。
辞書にない固有名詞や製品名の扱いには工夫が必要ですが、今回の対象は社内の作業手順書で文書量もそれほど大きくないため、まずは標準辞書で十分と判断しました。
SQLite + FTS5 を選んだ理由
検索基盤としては、SQLiteのFTS5を利用しています。
SQLiteはファイルベースで扱えるため、検索用DBを1ファイルで管理できます。
作業手順書の検索は読み取り中心で、更新頻度もビルド時に限定されます。
そのため、常時起動する検索サーバーや外部の検索エンジンを別途運用するよりも、かなりシンプルです。
FTS5によるインデックス検索というのがもう一つの選定理由です。
LIKEによる全件走査と違い、転置インデックスを使った検索になるため、文書数が増えても検索速度が大きく落ちにくくなります。
SELECT
d.title,
d.url,
d.content,
d.tags,
d.section
FROM documents_fts f
JOIN documents d ON d.id = f.rowid
WHERE documents_fts MATCH '障害 OR 対応'
ORDER BY rank
LIMIT 10;
rankで関連度順に並べられるのもFTS5の重要な特徴です。障害対応というクエリをJanomeで障害と対応に分解してFTS5に渡せば、その語により強く一致する手順書が先頭に並びます。
単にヒットした文書一覧を返すだけでなく、上位数件だけをAIエージェントに渡すといった簡易な絞り込みがしやすくなります。
大がかりな再ランキング基盤を用意しなくても、関連度順に近い手順書を抽出できるのが実務上かなり便利でした。
ここでいう関連度は、埋め込みベクトルの意味的な近さではなく、検索語との一致度に基づくスコアです。
それでも、作業手順書の検索では「同じ用語がどれだけ含まれているか」が重要なケースが多く、最初の絞り込みとして十分機能します。
なお、SQLiteは拡張機能を組み合わせることでベクトル検索も同じ基盤に載せやすく、将来的に意味的な類似度検索を追加したくなっても検索基盤を大きく変えずに発展させやすい点もあります。
形態素解析ライブラリにJanomeを使う
日本語の形態素解析ライブラリとしては、MeCabやSudachiPyも候補になります。
今回は、Pythonだけで完結しやすいJanomeを選びました。
理由はシンプルで、導入コストが低かったためです。
pip install janomeだけで利用できる- 追加のシステムパッケージを極力減らせる
- 小規模な文書集合に対しては十分な精度がある
ビルド時: トークンのインデックス登録
各文書を形態素解析して名詞・動詞・形容詞などを抽出し、FTS5用のトークン列としてDBに登録しておきます。
ビルド時に一度だけ処理するため、検索時にあらためて解析する必要がなく、レスポンスを軽くできます。
from janome.tokenizer import Tokenizer
tokenizer = Tokenizer()
def extract_terms(text: str) -> list[str]:
terms = []
for token in tokenizer.tokenize(text):
part_of_speech = token.part_of_speech.split(',')[0]
base_form = token.base_form
surface = token.surface
if part_of_speech in {"名詞", "動詞", "形容詞"}:
term = base_form if base_form != "*" else surface
terms.append(term)
return terms
検索時: クエリの形態素解析と照合
検索クエリもJanomeで分解し、得られたトークンをORで結合してFTS5に渡します。ORDER BY rankを付けることで、関連度順に結果を受け取れます。
tokens = tokenize_query(query)
fts_query = " OR ".join(tokens)
sql = """
SELECT
d.title,
d.url,
d.content,
d.tags,
d.section
FROM documents_fts f
JOIN documents d ON d.id = f.rowid
WHERE documents_fts MATCH ?
ORDER BY rank
LIMIT ?
"""
形態素解析で有効なトークンが得られない場合は、LIKE検索にフォールバックするようにしておくと、記号混じりの検索語や辞書外の語に対しても最低限の検索性を残せます。
今回の構成
全体の流れは次のようになります。
- Hugoで作業手順書をビルドする
- 生成済みコンテンツをJanomeで形態素解析する
- 抽出したトークンをSQLite FTS5に登録する
- 検索用DBを配置する
- AIエージェントがMCP経由で検索クエリを送る
- クエリも形態素解析してFTS5で照合する
rank順の上位結果を返し、近い手順書を優先して参照する
この構成であれば、文書作成はこれまで通りHugoで管理しつつ、検索時だけ日本語向けの前処理を追加できます。
全文検索のために大きな別基盤を持たなくてよいのも実運用では助かる点でした。
作業手順書は追加・修正の頻度が高く、検索データを常に最新に保つ必要があります。
SQLiteはDBがファイル1つで完結するため、Hugoのビルドに合わせて毎回まるごと再生成するだけで済みます。
「記事を書く → ビルド → 検索DBも更新される」というサイクルをシンプルに回せるのが、この構成の実用上の利点の一つです。
まとめ
日本語の作業手順書を検索対象にする場合、単純な部分一致だけでは精度が足りず、trigramだけでは2文字語の扱いが厳しいことがあります。
そのため、運用や障害のような短い重要語を正しく拾いたいなら、形態素解析ベースの検索が扱いやすいと感じました。
今回の要件では、SQLite + FTS5 + Janomeの組み合わせが、次の点でちょうどよい落としどころでした。
- 構成が軽い
- 日本語の単語単位で検索できる
rankで関連度順に並べられるため、近い手順書を簡易に抽出できる- ビルド時に前処理を寄せられる
- 将来的に類似度検索を同系統の構成へ拡張しやすい
- AIエージェントから利用しやすい
今後は辞書にない専門用語や略語をどう補強するか、検索結果のランキングをどう改善するかが次の検討ポイントになりそうです。