Claude Media
Claude CodeでMeilisearchの日本語検索を設定ファイルで管理する

Claude CodeでMeilisearchの日本語検索を設定ファイルで管理する

Meilisearchの日本語向け設定(localizedAttributes・dictionary・typoTolerance・rankingRules)を1つのJSONに集め、Claude Codeに編集させ、反映は人が行う手順です。

Meilisearchの日本語検索は、インデックスの設定で大きく変わります。日本語の分割、タイポ許容、ランキングルールはどれも設定項目で、API経由で1つずつ変えていくと、何をいつ変えたかが残りません。この記事では、設定を1つのJSONファイルに集めてgit管理し、Claude Codeには編集と検証を任せ、反映は人が決めた1コマンドで行う構成を説明します。

日本語検索で効く設定は4つだけ

Meilisearchのトークナイザー(文を語に分ける処理)はCharabiaというライブラリで、日本語にはlindera IPA辞書による分割が当たります。言語ごとの専用処理があるので、スペースのない日本語でも最初から検索はできます。ただし調整できるのは設定側です。

日本語向けに触る設定は次の4つです。

設定キー何を決めるか日本語での使いどころ
localizedAttributes何を決めるか属性ごとの言語日本語での使いどころ日本語の属性にロケール jpn を固定する
dictionary何を決めるか分割したくない語日本語での使いどころ製品名・社内用語を1語として扱う
typoTolerance何を決めるか誤字をどこまで許すか日本語での使いどころ数字や短い語での誤ヒットを抑える
rankingRules何を決めるか並び順の優先度日本語での使いどころ新しい記事を同点内で上に出す

この4つを meili/settings.json にまとめます。Claude Codeに「日本語検索を良くして」と頼むと、API呼び出しが散らばりがちです。ファイルを正にしておけば、変更はファイルの差分として見えます。

設定ファイルを1つにまとめる

前提として、インデックスの各ドキュメントに title_ja と body_ja という日本語の属性があるとします。Meilisearch側の推奨は、言語ごとにインデックスを分ける構成です。1つのインデックスで複数言語を持つときは、属性名に言語の接尾辞を付け、localizedAttributes で言語を宣言します。中国語と日本語を同じフィールドに混ぜるのは避けるよう、ドキュメントに書かれています。

{
  "searchableAttributes": ["title_ja", "body_ja"],
  "localizedAttributes": [
    { "attributePatterns": ["*_ja"], "locales": ["jpn"] }
  ],
  "dictionary": ["サブエージェント", "コンテキストウィンドウ"],
  "typoTolerance": {
    "enabled": true,
    "minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 },
    "disableOnNumbers": true
  },
  "rankingRules": [
    "words", "typo", "proximity", "attributeRank",
    "sort", "wordPosition", "exactness",
    "published_at:desc"
  ]
}

各項目の意味を順に見ます。

searchableAttributes は並び順が重みになります。先に書いた属性ほど関連度への影響が大きいので、タイトルを本文より前に置いています。

dictionary に入れた語は、デフォルトのトークナイザーを上書きして、分割されない1つの語として扱われます。公式は、日本語のようにスペースで区切らない言語で、ドメイン固有の語を入れる用途を挙げています。例の2語はあくまで例です。自分のデータで、分割されて困った語だけを足します。

typoTolerance の oneTypo: 5 と twoTypos: 9 は、デフォルト値をそのまま明示したものです。5文字以上で1つ、9文字以上で2つまでの誤字を許します。上限は1語あたり2つで、設定では増やせません。disableOnNumbers を true にすると、2024 が 2025 や 2004 にヒットする誤爆を防げます。

rankingRules は、組み込みの7ルールの後ろに自作ルールを足した形です。published_at:desc は降順の自作ルールで、先頭ではなく組み込みルールの後ろに置くのが公式の勧めです。先頭に置くと関連度ではなく値の順に並んでしまいます。対象の属性は、インデックス内の全ドキュメントに存在する必要があります。

日本語のタイポ許容は実測で決める

タイポ許容の規則は、語の文字数で決まります。1〜4文字は誤字を許さず、5〜8文字で1つ、9文字以上で2つです。英単語ならこの区分が直感に合います。

タイポ許容のページには、この文字数を日本語でどう数えるかの説明がありません。日本語の語がこの区分のどこに当たるかは、手元のデータで確かめる箇所です。

確かめ方は、showRankingScore を付けた検索で、誤字を含むクエリを数件投げることです。

curl -s "$MEILI_URL/indexes/docs_ja/search" \
  -H "Authorization: Bearer $MEILI_SEARCH_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary '{"q":"コンテキストウィンドー","locales":["jpn"],
                  "showRankingScore":true,"limit":3}'

検索時の locales は、localizedAttributes と食い違ったときにクエリ側が優先されます。ユーザーが言語を選ぶUIでは便利ですが、指定を間違えると調整した設定を打ち消します。日本語専用のインデックスなら jpn を固定で渡すのが安全です。

反映は人が決めた1コマンドで行う

設定の更新は非同期で、タスクとして積まれます。dictionary を更新すると、インデックス内の全ドキュメントが再インデックスされるとドキュメントにあります。大きなインデックスでは時間がかかるので、反映のタイミングは人が決めたいところです。

PATCH /indexes/{index}/settings は、送ったフィールドだけを更新する全設定の更新エンドポイントです。これを包むスクリプトを用意します。

#!/usr/bin/env bash
# scripts/apply-meili-settings.sh
set -euo pipefail
jq empty meili/settings.json            # JSONとして壊れていないか
curl -s -X PATCH "$MEILI_URL/indexes/docs_ja/settings" \
  -H "Authorization: Bearer $MEILI_ADMIN_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @meili/settings.json    # taskUid が返る

応答のタスクIDは、GET /tasks/{taskUid} で状態を見ます。タスクが完了するまでは、検索結果に変更が出ません。

Claude Codeに任せる範囲を設定で決める

ここからがClaude Code側の設定です。役割分担は次のとおりです。

  • Claude Code: meili/settings.json の編集、検証用クエリの実行、結果の読み取り
  • 人: 管理用APIキーの管理と、apply-meili-settings.sh の実行

管理用キーを環境変数 MEILI_ADMIN_KEY に入れる前提で、.env を読ませない設定にします。.claude/settings.json に次を書きます。

{
  "permissions": {
    "deny": ["Read(./.env)", "Bash(*apply-meili-settings*)"]
  }
}

Read(./.env) は、現在のディレクトリの .env を読む操作に一致するルールです。評価の順序はdeny、ask、allowで、最初に一致したものが結果を決めます。denyはどの設定スコープから来てもallowより先に効きます。

Bashのルールは引数まで縛る書き方が壊れやすいとpermissionsのページに注意があります。上の Bash(*apply-meili-settings*) は、スクリプト名を含む呼び出しを止める意図の書き方ですが、sh -c やラッパー経由の変形すべてを塞げるとは限りません。本当に止めたいなら、管理用キーそのものをClaude Codeの環境に渡さないのが確実です。検索用の読み取りキーだけを渡し、管理用キーは人のシェルに置きます。キーの置き場所を分ける考え方は、kintoneプラグインの署名鍵を読ませない設定でも同じです。

次に、編集のたびにJSONの構文を確かめるフックを足します。PostToolUse イベントに Edit|Write のマッチャーを付け、編集されたファイルのパスを jq で取り出す形は、公式のフックガイドにある自動フォーマットの例と同じ型です。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "f=$(jq -r '.tool_input.file_path'); case \"$f\" in */meili/settings.json) jq empty \"$f\" || { echo 'meili/settings.json is invalid JSON' >&2; exit 2; };; esac"
          }
        ]
      }
    ]
  }
}

meili/settings.json 以外のファイルでは何もしません。壊れたJSONを書いたときだけ、終了コード2でstderrを返します。フックを使った自動実行の別の例は、LWCのJestテストをhooksで自動実行する設定にあります。

最後に、CLAUDE.md に作業のルールを書きます。

## Meilisearch の設定
- 検索設定は `meili/settings.json` だけを編集する。API を直接叩いて設定を変えない
- `rankingRules` の自作ルールは組み込み7ルールの後ろに置く
- 変更したら、誤字・表記ゆれ・製品名の3種類のクエリを `showRankingScore` 付きで検証する
- 反映(`scripts/apply-meili-settings.sh`)は人が実行する

検証クエリを先に決めて反復させる

設定を変えた後に確かめる作業は、Claude Codeが得意な部分です。検証クエリを meili/queries.txt に1行1クエリで置き、Claudeに実行させて上位3件を読ませます。

例えば次のような分類で並べます。

  • 誤字: コンテキストウィンドー
  • 表記ゆれ: サブエージェント と サブ エージェント
  • 製品名: Claude Code
  • 数字: 2024

結果を見て、dictionary に足すか、minWordSizeForTypos を変えるかを提案させます。提案は meili/settings.json への差分として受け取り、人が内容を見てから反映します。

反映前の確認には、Claudeに差分を要約させる使い方もあります。ただし dictionary の更新は全ドキュメントの再インデックスを伴うので、変更が語の追加だけなのか、localizedAttributes や rankingRules まで動くのかを、差分で先に確認します。

検索エンジンを替える場合との違い

同じ「Claude Codeと検索エンジン」でも、扱うものが違います。Typesenseのマネージド版にMCPで接続して、クラスタの操作を会話で行う例はTypesense Cloud MCPの記事にあります。この記事は、MCPを使わず、設定ファイルとスクリプトでセルフホストのMeilisearchを管理する構成です。会話から操作するより、変更がファイルの差分に残るぶん、レビューしやすくなります。

分割と並び順で起きやすいつまずき

設定ファイルに落とすと、次のような落とし穴も差分として見えるようになります。ドキュメントの記述から拾えるものを挙げます。

  • 中国語と日本語を同じ属性に混ぜない。自動判別が難しいため、言語ごとのインデックスか、title_zh と title_ja のように属性を分ける構成を勧めています
  • 自作の rankingRules を先頭付近に置かない。関連度ではなく値の辞書順で並んでしまいます
  • 自作ルールの属性が一部のドキュメントに無いと、並びが未定義になる。インデックス前に属性の有無を確かめます
  • 組み込みの attribute ルールは attributeRank や wordPosition と併用できない。併記するとエラーになります
  • locales を検索時に渡すと、索引側の localizedAttributes より優先される

区切り文字を足したいとき

デフォルトでは、空白や句読点が語の境界になります。区切りを足すのが separatorTokens で、逆に記号を語の一部として残すのが nonSeparatorTokens です。

ドキュメントの例では、プログラミング用語の C++ や C# を検索できるよう + と # を非区切りにしています。日本語の記事でも、C# のような語を検索したいなら同じ考え方が使えます。この2つも meili/settings.json に足せば、dictionary と同じく差分で管理できます。ただし、どちらの設定も更新はタスクとして非同期に処理されるため、反映後に検証クエリを回し直します。

言語ごとにインデックスを分けるときの構成

ドキュメント数が増えたり、日本語以外の言語を足したりする場合は、言語ごとに別インデックスを作る構成が推奨されています。ストップワードや区切り文字を言語ごとに持てて、日本語や中国語に専用のトークナイザーを当てやすいためです。単一インデックスは、PoCや100万件未満のデータなら許容される、という位置づけです。

分けた場合も、設定ファイルはインデックスごとに置くと管理しやすくなります。例えば meili/docs_ja.json と meili/docs_en.json です。複数言語を横断して検索したいときは、マルチ検索かフェデレーテッド検索で、複数のインデックスをまとめて問い合わせます。Claude Codeへの指示も、「どのファイルを編集するか」を明示できるので、取り違えが起きにくくなります。

キーワード検索の分割がうまく合わない場合は、ハイブリッド検索も選択肢です。埋め込みモデルは言語ごとのトークナイザーとは別に動くので、多言語モデルを使えば、キーワード側の分割に依存しない検索結果を足せます。

自分で動かす手元の手順

Meilisearchを手元で試すには、公式のDockerイメージを使います。マスターキーを付けて起動するときは、環境変数 MEILI_MASTER_KEY を渡します。

docker run -it --rm \
  -p 7700:7700 \
  -e MEILI_MASTER_KEY='MASTER_KEY' \
  -v $(pwd)/meili_data:/meili_data \
  getmeili/meilisearch:latest

マスターキーは最初の管理用キーとして使い、運用では検索専用のAPIキーを別に発行します。Claude Codeには検索専用キーだけを渡す、という分け方になります。

まとめ

日本語検索の調整は、設定の数が少ないぶん、変更履歴が残るかどうかで運用のしやすさが変わります。設定をJSONに集め、編集と検証はClaude Code、反映は人、というふうに作業を分けると、Claudeに任せる範囲を権限とフックで機械的に決められます。タイポ許容の効き方は日本語での説明が公式にないので、最初の一周は検証クエリの結果を見ながら決めてください。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. meilisearch.com/docs/learn/resources/language
  2. meilisearch.com/docs/capabilities/indexing/how_to/handle_multilingual_data…
  3. meilisearch.com/docs/capabilities/full_text_search/relevancy/typo_toleranc…
  4. meilisearch.com/docs/capabilities/full_text_search/relevancy/ranking_rules…
  5. meilisearch.com/docs/capabilities/full_text_search/how_to/configure_dictio…
  6. code.claude.com/docs/en/permissions
  7. code.claude.com/docs/en/hooks-guide
この記事を共有:XはてブLinkedIn