👋

ローカルLLMナレッジボットを改良して再ベンチした

に公開

前回作った みなと中央銀行ナレッジボット の続編です。
社内エンジニア向けの発表でいただいた質問がありました。

「これ、ナレッジ量が増えていったらどうなるんですか? 速度遅くなる? 最終的に全く回答が返ってこなくなる?」

…確かに、ベンチで見せたのはたかが35ファイル / 16万字。中規模銀行のリアルなナレッジ群なら何千〜何万ファイルあってもおかしくない。

ということで今回は、質的改良 + 量的検証 の二本立てで挑みました。

  • 質的改良: 前回ベンチで見えた弱点を直す(プロンプト改修 / チャンキング再設計 / HyDE 導入 / ナレッジ表現強化)
  • 量的検証: ナレッジを 30倍(28支店 × 35ファイル = 980ファイル / 約747万字)に水増しして、検索品質と速度の劣化を実測
  • ついでに 2026年5月時点でホットな Qwen3:8B / Gemma3:12B を投入して世代交代の効果も測定

結論から言うと:

  • 出典精度は 0.963 → 1.000(満点) に到達。検索層の改良が綺麗に効いた。
  • キーワードスコアは小幅向上(qwen2.5:7b で +2.7pt、3b で +5pt)。
  • Gemma3:12B が新王座 に。0.856 で Qwen2.5-7B を上回る。
  • Qwen3:8B は「思考モード」の罠 にハマり、半数の質問で 180 秒タイムアウト。設定次第ではあるが、ベンチ用途では一筋縄ではいかない。
  • HyDE 導入で全体的に応答時間が +5〜10秒 程度伸びた。精度↔速度のトレードオフ案件。

スケール検証編は別記事に分割します(本記事末尾で方法論だけ予告)。まずは質的改良 + 4モデルベンチの結果をどうぞ。


1. 前回のおさらいと、出てきた課題

前回作ったのはみなと中央銀行(架空)の社内ナレッジボットでした。Mac メニューバー常駐の Tauri 2 アプリで、Cmd+Shift+B で呼び出すと数秒で社内規程を引いて答える、というやつです。

完成形のシステムスタック:

レイヤ 採用
LLM Qwen2.5-7B-Instruct (Q4_K_M) on Ollama
Embedding multilingual-e5-base
Vector DB ChromaDB(cosine + MMR)
API FastAPI :7321
UI Tauri 2 メニューバー常駐
ナレッジ Markdown 35ファイル、約16万字

前回のベンチ(30問・Qwen2.5-3B vs 7B)で次の弱点が見えていました。

# 課題
A 7Bの過剰謙遜癖 「確認できません」と言ってから正解を続けて書く
B 3Bのハルシネーション Q08で「年収650万×12ヶ月=7800万円」と暴走計算
C 検索の語彙ミスマッチ Q21「30日以内×1回」と文書側「2回まで救済」が埋め込み近接せず取りこぼし
D ナレッジ表現の弱さ Q01「PScore満点何点?」が「0〜1000点」とだけ書かれていてヒットせず
E 採点方式の粗さ 部分一致のみで意味的同義を拾えない

A〜D に着手し、E は次回の宿題として残しました。


1.5. 改良の優先順位はどう決めたか

ベンチ結果を眺めて「直したい場所」をリストアップしたら12項目くらい出てきました。全部やると工数も時間もかかるので、「インパクト × 軽さ」で優先順位を切ることに。

優先 改良項目 インパクト 軽さ
🔥 即やる プロンプト改修(過剰謙遜・計算手順)
🔥 即やる MMR / Top-K チューニング
🔥 即やる ナレッジ表現強化(同義語併記) 高(該当問題に直撃)
🔥 即やる チャンキング再設計(H3 / 小さく / 重複多く)
⚡ 次やる HyDE(クエリ拡張) 高(語彙ミスマッチ根治)
🔬 次やる Reranker 追加 高(スケール時)
🧪 後で Self-RAG / 自己検証ループ
📊 後で LLM-as-a-Judge による採点刷新 中(評価信頼性)
🌐 後で 別Embeddingモデル比較(bge-m3など) 不明
📦 後で PyInstaller でAPI同梱 .app化 配布性

「即やる」グループだけで今回の改良パッケージは完成。


2. 今回の改良点ぜんぶ盛り

改良1: システムプロンプトの全面書き換え(7条構成)

過剰謙遜を矛盾出力として禁止、数値計算は式抜出→代入→単位確認→照合の4段強制にしました。

3. 【回答前の自己確認】質問に対する答えが参考情報の中に明確に含まれているかを最初に判定する。
   - 含まれている場合: 前置きは一切付けず、即座に答えを書く。
   - 含まれていない場合のみ: 「提供された情報からは確認できません」と明記し...
   - 絶対禁止: 「確認できません」と書いたあとに正解を続けて書くこと。
              これは矛盾した回答であり、ユーザーを混乱させる。

5. 【数値計算の手順】
   (a) 参考情報から計算式を抜き出す
   (b) 質問の数値を式に代入する
   (c) 単位を必ず確認する(年収は年額、月返済は月額)
   (d) 計算結果を提示し、合格基準と照合する
   絶対に「年収 × 12」のような誤った換算をしない。

改良2: チャンキング戦略の再設計

観点 Before After
分割粒度 H2 単位のみ H2 + H3 両対応
チャンクサイズ 512文字 384文字
オーバーラップ 64文字 96文字
コンテキスト前置 なし 「【文書タイトル|セクション】」を各チャンク先頭に挿入

チャンク数は 310 → 618 にほぼ倍増。検索粒度が細かくなり、節境界をまたぐ情報の取りこぼしも減った。

改良3: HyDE(Hypothetical Document Embeddings)導入

これが今回の目玉。「質問文の埋め込みベクトル」と「文書の埋め込みベクトル」が離れてしまう問題(語彙ミスマッチ)に対し、LLM に仮想文書を書かせてからクエリに連結する手法です。

# rag/hyde.py の中身(要約)
HYDE_PROMPT = """
あなたは銀行の社内ナレッジ検索アシスタントです。
以下の質問に対して、銀行の社内文書(就業規則、ローン審査基準、業務マニュアル等)の
関連箇所が**どのように書かれているか**を、簡潔な日本語の擬似文書(200字程度)として書いてください。

ルール:
- 実在の数値や条件は推測しない
- 銀行ドキュメント風の硬めの文体で書く
- 質問への回答ではなく、文書側の表現として書く

質問: {query}

擬似文書:
"""

def expand_query_with_hyde(query):
    # 軽量モデル (3B) で仮想文書を生成
    hypothetical = ollama.chat(model="qwen2.5:3b-instruct", ...)
    return f"{query}\n\n{hypothetical}"

例えば「PScore の満点は?」という質問に対し、3B モデルが「PScore は個人ローン審査用のスコアリングモデルで、満点は◯◯点である」みたいな擬似文書を書く。これを元クエリに連結して埋め込み計算する。

狙い: 短いクエリだけだとベクトル空間で文書から離れてしまうが、擬似文書を連結することで「文書側の語彙空間」に近づける。

コスト: 3B モデルでの推論が1回挟まるので、検索が1問あたり +3〜5秒 遅くなる。

改良4: ナレッジ表現の補強

検索でハマった2件をピンポイントで救済しました。

- PScore は個人ローン審査用の自社開発スコアリングモデル。0〜1000点で算出し...
+ PScore は個人ローン審査用の自社開発スコアリングモデル。**満点は1000点**
+ (0〜1000点のスケール)で算出し...スコア上限・最高点は **1000点**。

- 過去2年間に30日以内の遅延が2回以下: 要稟議だが救済可能
+ **過去2年間に30日以内の遅延が2回以下**(つまり遅延が **0回・1回・2回** の場合):
+ 要稟議だが救済可能。1回のみの場合も同様に救済対象とする。

満点」「1回も含む」のような質問者が使いそうな語を文書側に追加。これだけで該当問題の検索ヒット率が変わるはず。

🔍 コラム: 「有給制度について」と聞いた時、なぜ HR の文書が選ばれるのか?

エンジニアからもう一つ質問が出ました。

「『有給制度について教えて』って聞いたら HR/01-work-regulations.md が返ってくる。これって裏では「これは人事の質問だから HR を引け」っていう分類器ロジックが動いてるんですか?

答え: 分類器は存在しません

このシステムには「人事系→HR」「ローン系→RTL」のような明示的な振り分けロジックは1行も書いていない。代わりに動いているのはベクトル類似度による暗黙の選択だけです。

流れ

  1. 「有給制度について教えて」を multilingual-e5-base に通す → 768個の数字(ベクトル) に変換
  2. 事前に同じ方式でベクトル化済みの 618 個のチャンクすべてと cosine類似度 を計算
  3. 類似度が高い順に上位30件取り出し、MMR で多様性確保しつつ上位8件選ぶ
  4. その8件を LLM のプロンプトに埋め込んで回答生成

「人事の質問」と判定しているのではなく、ベクトル空間で『有給制度について教えて』の近傍にいるチャンクが、たまたま HR/01 の節だっただけ

数値ベクトル化のイメージ

e5 モデルは事前学習で「似た意味のものは近い座標、違う意味のものは遠い座標に置く」よう仕込まれているので、結果としてこういう空間ができている:

         休暇クラスター                     ローンクラスター
            ★ 年休                          ★ 個人ローン
       ★ 年次有給休暇                   ★ 住宅ローン審査
            ★ 有給                          ★ DTI
         ★ 産休制度

「有給制度について」のベクトルは左上の休暇クラスターに着地するので、近傍の HR チャンクが上位に来る。判定ロジックを書かなくていい、というのが RAG の素晴らしいところ

メリットと注意点

メリット 注意点
分類ルール不要、運用が楽 なぜ選ばれたか説明が難しい
同義語に強い(年休/有給/有休すべて拾う) 語彙の遠いケースで取りこぼす(→今回 HyDE で補強)
新カテゴリ追加に再学習不要 クエリが短いと埋め込みがブレやすい
多言語対応も自動(e5 は多言語事前学習済み) 部署横断の質問は混在しがち

このシステムが「意味で引く検索」と呼ばれる所以がここにあります。Google 検索が「年休」と入れて「有給」のページもヒットさせるのと同じ仕組み。


改良5: API/UI の機能追加

ベンチが回せるよう、/chatmodeluse_hyde フラグを追加:

class ChatRequest(BaseModel):
    query: str
    history: List[ChatMessage] = []
    use_hyde: bool = True
    model: str | None = None  # ベンチ用にモデル切替可能

3. 2026年5月時点の主要ローカルLLM

ベンチに投入する候補モデルを最新情報で再確認しました。M5 MacBook Air 24GB で動かせる現実的なサイズ帯(Q4_K_M量子化)で比較すると:

モデル サイズ 公開元 特徴
Qwen2.5-7B-Instruct 5GB Alibaba 既存ベースライン
Qwen3-8B 5.5GB Alibaba 同クラス日本語トップとの評判(Apr 2025リリース)
Gemma 3 12B 8GB Google 128K context、140言語
Gemma 3 27B 17GB Google M5 24GBにはやや重い
Qwen3-14B 9GB Alibaba Qwen2.5-32B相当の精度を半分のVRAMで
Llama-3.1-Swallow-8B v0.2 5.5GB 東工大/AIST 日本語追加学習版

今回は Qwen3-8BGemma 3 12B を投入することにしました。

なぜこの2つか

選定の判断軸:

  1. 同クラスでサイズ感が違う: 8B と 12B でクラス内の最強候補をそれぞれ拾う
  2. 公開元が違う: Alibaba と Google で開発系統が異なる → 失敗モードが偏らない
  3. 特性が違う: Qwen3 は推論強化系(Thinking モード)、Gemma3 は長コンテキスト系(128K)
  4. M5 24GB で現実的に動く: 14B / 27B は試したかったが Metal Shared Memory の余裕的に断念
  5. Swallow 系(Llama 日本語追加学習版)も候補だったが、Ollama ライブラリでの取り回しが煩雑だったため今回は見送り(次回再評価予定)

新モデル投入の効果を見たいなら、開発系統が違うものを並べた方が知見が広い」というのが定石。Qwen3 と Qwen2.5 は系統が同じなので Qwen2.5 → Qwen3 の差は「世代差」、Qwen2.5 → Gemma3 の差は「会社差・系統差」として読める。


4. 改良後の30問ベンチマーク結果

4モデル × 30問(HyDE 有効)で実施。前回(2026-04-28)と同じ問題セットなので Before/After 比較ができます。

総合スコア

モデル キーワード 出典精度 平均応答秒
qwen2.5:3b-instruct 0.675(前回 0.642) 1.000(前回 0.963) 14.5s(前回 8.8s)
qwen2.5:7b-instruct 0.844(前回 0.822) 1.000 27.1s(前回 21.3s)
gemma3:12b(新) 0.856 ★トップ 1.000 68.5s
qwen3:8b(新) 0.250 ※ 0.296 ※ 147.6s ※

※ qwen3:8b は途中から 180秒タイムアウトを連発したため、数値は単純比較不可。詳細後述。

主な観察

🎯 出典精度が全モデル 1.000 達成

前回 0.963 → 今回 1.000(パーフェクト)。チャンキング再設計とHyDE による検索層の改良が綺麗に効いたことが分かります。「正しい文書は引けている、あとは LLM が正しく抽出するかどうかだけの勝負」という、RAG として理想的な状態に到達しました。

📈 キーワードスコアの伸び

モデル 前回 今回 差分
qwen2.5:3b 0.642 0.675 +0.033
qwen2.5:7b 0.822 0.844 +0.022

地味に見えるが、伸びはピンポイント救済(Q01, Q21)と新規問題への適応の合算。ナレッジ追記とプロンプト改修が想定通り効いている。

⏱ 応答時間の悪化(HyDEの代償)

モデル 前回 今回 増分
qwen2.5:3b 8.8s 14.5s +5.7s
qwen2.5:7b 21.3s 27.1s +5.8s

HyDE で 3B モデルによる仮想文書生成が挟まるため、ほぼ同等の +5〜6秒。これは想定通りで、精度↔速度のトレードオフ。仮想文書を作らずに HyDE をオフにすれば前回と同じ速度に戻せる。


5. ピンポイント救済の効果

改良の効果が一番分かるのが、前回失敗していた問題が今回どうなったかです。

Q01「PScoreの満点は何点ですか?」

期待キーワード: 1000

モデル 前回 今回
qwen2.5:3b 0.0 1.0
qwen2.5:7b 0.0 1.0
gemma3:12b - 1.0 ✓

前回両モデルが「確認できません」を返してきたのが、ナレッジに**「満点は1000点」と明記**したことで一気に救済。

qwen2.5:7b の回答(今回):
個人ローン審査用のPScoreは満点1000点です。[出典: RTL/05-loan-screening-detail.md]

Q05「法人融資のCScore合格ライン、優良先は何点以上?」 ※残念な後退

期待キーワード: 120

モデル 前回 今回
qwen2.5:7b 1.0(ただし「確認できません」→「120点以上」の矛盾出力) 0.0

これはプロンプト改修の副作用。前回は「確認できません + 120点以上」と矛盾出力していたものを、今回プロンプトで「書いたあとに否定するな」と明示的に禁じたため、7B は安全側に倒して「確認できません」だけで止めてしまった。

提供された情報からは確認できないため、直属の上司または担当部署にご相談ください。
関連部署は法人営業部 業種別チームです(内線3201〜3208)。

学び: プロンプトで「矛盾出力禁止」を強くすると、自信のない正答すら出さなくなる。実は前回の「矛盾出力」のほうが、ユーザー体験的にはマシだった可能性すらある。LLMの自信度の校正は深い問題で、シンプルなプロンプトでは越えられない壁を感じた。

3B/qwen3:8b/gemma3:12b は同じ問題に 1.0 で正答。7B だけがプロンプトに過剰反応するのは興味深い現象。

Q21「過去2年で30日以内の遅延が1回あったお客様への対応は?」

期待キーワード: 要稟議, 救済, 30日以内

モデル 前回 今回
qwen2.5:3b 0.33 0.67
qwen2.5:7b 0.0 1.0
gemma3:12b - 1.0 ✓

ナレッジに「0回・1回・2回 いずれも救済」と書き換えたのが効いた典型例。


6. Gemma3:12B が王座を取った

総合スコア 0.856 で新王座。Qwen2.5-7B を超えた。

強み

  • lookup, conditional, exception, format, industry, process, selfcheck で 1.0 多数。とにかく「ナレッジに書いてあることを丁寧に拾う」のが上手。
  • 構造化された回答が綺麗: 箇条書きや見出しを自然に使い、見やすい。

例(Q11 産休中の住宅ローン申込):

産休中のお客様が住宅ローンを申込まれた場合の必要書類と判定方法について、以下の通りです。

必要書類:

  • 直近1年分の源泉徴収票(産休前のフル稼働年)
  • 復職証明書(勤務先発行、復職時期と職位明記)
  • 夫婦合算所得申告書

判定方法:
夫婦合算所得を「世帯収入」とみなし、復職後の単独所得でDTIを再計算...

UI でそのまま表示して読みやすい体裁。

弱み

  • 計算問題でややランダム: Q08(DTI と PScore の複合計算)で年間返済額を 12.7 × 12 = 152.4万円 ではなく (12.7+2)×12 = 172.4万円 と書いて DTI 26.5% を出してしまった(正解は 27.1%)。式の組み立てはできるが数値間違いをすることがある
  • 応答時間が長い: 平均 68.5秒。Qwen2.5-7B の 2.5倍。M5 24GB で 12B は Metal シェアード メモリを食うため、推論速度がやや遅くなる。
  • ICRをLCRと混同: Q23 で「ICRとは流動性カバレッジ比率の略称」と誤定義(正解はインタレスト・カバレッジ・レシオ)。Gemma の学習データに金融固有用語の偏りがあるかも。

総合所感

精度では Qwen2.5-7B を上回るが、速度で劣る。実運用なら「通常はQwen2.5-7Bでサクサク、複雑な質問だけ Gemma3:12B で精緻に」のような切替アーキも考えられる。


7. Qwen3:8B の「思考モード」沼

Qwen3 系は デフォルトで「Thinking」モードが有効 で、回答前に <think>...</think> ブロックで大量の推論を吐き出します。これがベンチでハマった原因。

何が起きたか

  • Q01〜Q08 までは正常応答(しかも正答率100%、43〜74秒)
    • PScore 満点 → 1000点 ✓
    • DTI 計算 → 26.4% ✓
    • 複合計算 Q08 → 27.1% / 930点 / 承認 すべて正答 ✓
  • Q09 以降、180秒のタイムアウトを 22問連続で発生
  • 結果としてキーワードスコア 0.25、出典精度 0.296 という見かけ上「壊滅」スコアに

これはモデルが暴走したわけではなく、Thinking モードで推論ステップを長く取りすぎてベンチクライアントのタイムアウト(180秒)に間に合わなかったというのが真相と思われる。Ollama 経由の Qwen3 では、/no_think 指示でThinkingモードを切るか、num_predict を絞るなどの設定が必要。

答えられた問題では強い

Q08 の複合計算で:

DTIは27.1%、PScoreは930点と評価されます。
DTI計算: (12.7万 + 2万)×12 ÷ 650万 = 27.1%(上限35%内)
PScore計算: 年収170 + 勤続130 + 雇用100 + 既存債務230 + 信用300 = 930点

…と、Qwen2.5-7B と同水準の精密回答 を返してきた。能力は十分にあるが、インフラ側の設定で殺している状況。

教訓

新モデルを本番投入する前に、Thinking モードのような「デフォルト挙動」を必ず確認すること。Qwen3 / DeepSeek-R1 / OpenAI o1 系は推論を長く取るのがデフォルト。RAG のように「短い質問に1〜数秒で答える」用途では、Thinking を切ったほうが体感が良くなる場合が多い。

次回 Qwen3 を改めて評価する際は、API 側で model_params を渡せるようにして /no_think フラグや enable_thinking=false を明示する必要がある。


8. カテゴリ別の比較

カテゴリ qwen2.5:3b qwen2.5:7b gemma3:12b 備考
単純参照 (lookup) 1.000 0.800 1.000 7Bが過剰謙遜で1問落とした
計算 (calculation) 0.444 0.667 0.611 DTIなど数値演算は7Bが堅実
条件分岐 (conditional) 0.584 0.917 0.833 7Bが最強
例外処理 (exception) 0.334 1.000 1.000 ナレッジ追記で同点に
用語説明 (explanation) 1.000 1.000 0.834 gemmaがICR誤定義
形式整理 (format) 0.000 1.000 1.000 3Bは構造化問題に弱い
業種別 (industry) 0.667 1.000 1.000
複合推論 (multi_hop) 0.834 0.541 0.500 3Bが意外な大健闘
範囲外 (out_of_scope) 0.611 0.611 0.611 全モデル同水準で適切に「分かりません」
業務フロー (process) 0.472 0.917 1.000 gemmaが構造化で勝った
自己査定 (selfcheck) 1.000 1.000 1.000 全員パーフェクト

注目ポイント

  • 計算系は7Bが堅実: プロンプトの計算手順強制が効いている。
  • 例外処理が大幅改善: 0.167(前回7B)→ 1.000(今回7B / gemma3)。ナレッジ表現の改善 + HyDE の合わせ技。
  • 複合推論で3Bが7Bを上回る逆転現象: Q28 派遣社員問題で3Bが「派遣加点70点、必要年収859万」と具体的に答え 0.67、対して7Bは0.33。3Bは時に大胆な推測を当てる
  • out_of_scope は全モデル横並び: 「分からない時は分かりません + 上司相談」のシステムプロンプトはモデル規模に関わらず守られる。

9. 結論と次のステップ

改良のスコア

項目 結果
検索層の改良(HyDE + チャンキング + ナレッジ追記) 出典精度 100% 達成
プロンプト改修(過剰謙遜抑止 + 計算手順) ◯ 効いたが副作用あり(Q05後退)
新モデル投入(Gemma3:12B) 新王座、+1.4pt 改善
新モデル投入(Qwen3:8B) ❌ Thinking モード対策必要
応答時間 🔻 HyDEで +5〜6秒

次にやること

  1. Qwen3:8B の Thinking モード対策: /no_think または API 側パラメータでオフにして再ベンチ
  2. HyDE の ON/OFF 比較ベンチ: 改良効果のうち HyDE 単独の貢献度を切り分け
  3. 採点方式の刷新: 部分一致 → LLM-as-a-Judge で意味的同義も拾う
  4. スケールテスト本番: 後述する 980ファイル / 747万字データセットで実測
  5. アンサンブル戦略: 「通常は Qwen2.5-7B、複雑な質問だけ Gemma3:12B」のような切替アーキ

10. エンジニアからの宿題: スケールしたら壊れるのでは?(次回予告)

冒頭で触れたエンジニアからの問い「ナレッジが増えたらどうなる?」への正面回答編です。

理論的な見立て

ナレッジ量を N倍 に増やした時、各レイヤがどう振る舞うか:

レイヤ スケーリング 100倍時の見込み
ingest(初回取込) O(N) 線形 100倍時間(1回きりなので許容)
ベクトルDBサイズ O(N) 線形 31000チャンクで約95MB(問題なし)
ベクトル検索 O(log N) 対数(HNSW) 100倍でも検索は2倍程度しか遅くならない
LLM コンテキスト O(1) 一定 top_k=8 で固定、ナレッジ量と無関係
LLM 生成時間 O(1) 一定 知識量と無関係に同じ秒数

RAG の美しいところは、LLM に渡す情報量が常に一定なこと。つまり「ナレッジが増えてLLMが詰まる」状況は構造的に起きない。応答時間のボトルネックは常に LLM 生成(10〜60秒)で、検索の数十ミリ秒は無視できる規模。

全く回答が返ってこない」も基本的に起きません。最悪「確認できません、上司にご相談ください」が返るだけ。むしろ警戒すべきは「間違った答えを自信を持って返すハルシネーション」のほうで、こちらは出典提示で抑止しています。

検索精度は劣化する

ただし、速度は耐えても精度は劣化します。ナレッジが増えると:

  • 似た内容のチャンクが100件出てくる
  • 部署違いの似た文書が混在する
  • LLM の参照集中で関係ある1件が埋もれる

対策の定石は Reranker(再ランキングモデル)の後段追加。Top-30 を取得 → Reranker で真の Top-5 に絞る、というアーキ変更が 1万ファイル超で必要になると言われています。

実証データセットを用意した

ということで「理屈はそうだが本当か」を実測するため、現状35ファイルのナレッジを30倍に水増しした合成データセットを用意しました。

設計: 28支店 × 35基底ファイル = 980 ファイル

  • 支店構成: 東京10 / 神奈川6 / 千葉5 / 埼玉7(関東地盤らしい配分)
  • 各支店ごとに LLM で生成したローカルプロファイル: 支店長名(架空)、エリア特性、内線番号体系、3〜5個のローカルルール
  • 基底ファイルからの差異化要素:
    • タイトルに支店名を付与(「みなと中央銀行 渋谷支店 預金商品ガイド」)
    • 一部の固有名詞・内線番号を支店別に置換
    • 末尾に「支店ローカル運用」セクションを挿入
    • 「よくある質問」に支店固有Q&Aを1〜2問追加

サンプル(渋谷支店):

## 渋谷支店のローカル運用

本セクションは渋谷支店(支店長:藤村 直人)固有の運用ルールをまとめたものです。
エリア特性:若年層顧客が多く、SNS関連のトラブル・フィッシング被害相談が突出して多い。

### 主なローカルルール
1. 若年層顧客の新規口座開設時は、SNS経由の特殊詐欺・パパ活送金トラブル防止のため、
   初回入金前に「資金移動目的確認シート(渋谷様式SBY-01)」を必ず取得する。
2. 20代単身者の定期預金100万円超は、ヒアリングに加え役席承認を要する。
...

この設計の狙い

検索の困難さは「文書数」より「似ているがちょっと違う文書の密度」に依存します。
ランダムに別ジャンルの文書を水増ししても本番環境を再現できない。
だから本番に近い「支店別運用差」という分かりやすい同質性のデータで水増しすることで、「鈴木店長は誰?」「渋谷支店のローカルルールは?」のような新質問の正解性と、「年休の申請方法は?」のような既存質問が30倍ノイズの中で正解を引けるかの両方が測れます。

実測結果は次回記事に詳述します。理論通り検索は1秒未満で返るのか、精度はどれくらい劣化するのか、Reranker は本当に必要になるのか、本気で測る予定。

実用的な推奨スタック

2026年5月時点で「銀行ナレッジボット類型」を本気で組むなら:

  • 検索層: multilingual-e5-base + ChromaDB + MMR + HyDE 必須
  • 生成層:
    • 速度優先 → Qwen2.5-7B(速くて十分賢い)
    • 精度優先 → Gemma3:12B(綺麗だが遅い)
    • ストレッチ → Qwen3-14B(未試)
  • ナレッジ表現: 質問者が使いそうな表現をナレッジ側に同義語として併記
  • プロンプト: 過剰謙遜対策は入れるが、安全側に倒しすぎない加減が要

用語ミニ集

  • HyDE (Hypothetical Document Embeddings): クエリを LLM に渡し「もし答えがあるならどう書かれているか」の仮想文書を生成、それを検索クエリに連結する手法。語彙ミスマッチに強い。Gao et al. 2022。
  • Thinking モード: Qwen3 / DeepSeek-R1 系で導入された、回答前に推論ステップを長く取る挙動。<think>...</think> ブロックを生成。RAG のような短答用途では切る方が体感良いことが多い。
  • チャンキング戦略: 文書をどの粒度でベクトル化単位に分割するか。今回は H2 → H2+H3、512文字 → 384文字、overlap 64→96 に変更。

ここまでの改良で、RAG として「正しい文書を引く」部分はほぼ完成形になりました。次のフロンティアは「LLM 自身が出典を正しく扱う能力」と「スケールしたときの劣化耐性」。続編に乞うご期待。

Discussion