エージェントRAGをローカルモデルだけで回してみた
1. きっかけ
前回、
のような記事を書きました。
埋め込みとLLMをローカルモデルにして、下記の事項がどうなるかを確認してみたいと考えました。
-
ローカルLLMでツール呼び出しはできるのか — エージェント的RAGは
search/fetchを呼んで、scopeを選んで、4箇所でスキーマ通りのJSONを返す必要がある。27Bの量子化モデルで成立するのか - 768次元のローカル埋め込みで検索は落ちるのか — 3072次元のクラウドAPIを337MBのモデルで置き換えられるのか
実際にやってみた結果が下記です。
| 確認内容 | 結果 |
|---|---|
| ツール呼び出し | 普通にできた。 構造化出力の失敗0件、スキル選択100%、指示追従性はむしろクラウドより良い |
| ただし使い方は変わる | fetch が 8.58 → 2.23回/問 と激減。なのに精度は同じ(共通24問で 15/24 の同点) |
| 埋め込み | ハイブリッド検索では完全に同値(65% / 83%)。純ベクトル検索だけ −8pt |
| おまけ | API課金がゼロになる代わりに、レイテンシは3〜4倍 |
余談:コードは、下記のリポジトリにあります。
コーパス・チャンク分割・golden set(46問)・プロンプト・k=5 は前回から据え置きで、差し替えたのはモデルだけです。
2. 使ったモデルとホスティング
| 役割 | モデル | サイズ / 次元 | 実行環境 |
|---|---|---|---|
| 生成(クラウド・前回のベースライン) | gpt-5.6-luna |
— | OpenAI API |
| 生成(ローカル) |
Qwen3.8-27B(unsloth GGUF UD-Q4_K_XL) |
約18GB | RTX 4090 / llama.cpp |
| 埋め込み(クラウド) | text-embedding-3-large |
3072次元 | OpenAI API |
| 埋め込み(ローカル) |
ruri-v3-310m(Targoyle GGUF q8_0) |
337MB / 768次元 | CPU / llama.cpp |
それぞれのモデルは下記のような理由で選択しました。
-
Qwen3.8-27B:2026年9月時点でRTX 4090にて動作するモデルのなかでは最も性能が高いモデルの1つであるため。また、推論時間がやたらに長くて使いにくいという噂があったのでeffortをlowで適応すると使える範囲なのかを確かめたかったため
-
ruri-v3-310m:Qwen3.8-27B にGPUメモリを優先したかったので。 日本語 retrieval のベンチが
text-embedding-3-largeとほぼ同じ(P@1 0.555 vs 0.554)なのに337MBという省メモリに期待して採用
下記は、ホスティングの概要図です。
下記のようなDocker ComposeファイルでQwen3.8-27B, ruri-v3-310mを別々のllama-serverでホストしています。
services:
llama-server:
image: ghcr.io/ggml-org/llama.cpp:server-cuda-b10450
container_name: agentic-rag-llama
ports:
- "127.0.0.1:8080:8080"
volumes:
- ../data/models:/models
environment:
LLAMA_CACHE: /models
HF_HOME: /models
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
command:
- "-hf"
- "unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_XL"
- "--jinja"
- "--chat-template-kwargs"
- '{"reasoning_effort":"low"}'
- "-ngl"
- "99"
- "-c"
- "24576"
- "--cache-type-k"
- "q8_0"
- "--cache-type-v"
- "q8_0"
- "--temp"
- "0.7"
- "--top-p"
- "0.80"
- "--top-k"
- "20"
- "--presence-penalty"
- "1.5"
- "--parallel"
- "1"
- "--host"
- "0.0.0.0"
- "--port"
- "8080"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 60m
restart: "no"
llama-embed-ruri:
profiles: ["ruri"]
image: ghcr.io/ggml-org/llama.cpp:server-b10450
container_name: agentic-rag-embed
ports:
- "127.0.0.1:8081:8081"
volumes:
- ../data/models:/models
environment:
LLAMA_CACHE: /models
HF_HOME: /models
command:
- "-hf"
- "Targoyle/ruri-v3-310m-GGUF:q8_0"
- "--embeddings"
- "--pooling"
- "mean"
- "-ngl"
- "0"
- "-c"
- "8192"
- "-b"
- "8192"
- "-ub"
- "8192"
- "--parallel"
- "1"
- "--host"
- "0.0.0.0"
- "--port"
- "8081"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8081/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
# 生成モデル(18GB)と違い 337MB なので短くてよい
start_period: 15m
restart: "no"
3. ローカルLLMでツール呼び出しはできるのか
3.1 結論: できた
A: gpt-5.6-luna
|
B: Qwen3.8-27B | |
|---|---|---|
| 構造化出力の失敗 | 0件 | 0件 |
| スキル選択 正解率(境界20件) | 90% | 100% |
| search 上限超過(=指示に従えていない回数) | 15回 | 3回 |
実施する前は「ローカルLLMで性能がかなり高いとはいえgpt-5.6-lunaには惨敗するんだろうな」と考えていました。結果としては、「スキル選択の成功率」や「search 上限超過回数」などからクラウドモデルとあまり遜色のないということがわかり、少しおどろきました
3.2 検索性能面
エージェント検索によって正しいページを取得できたかは下記のようになります。
| 件数 | |
|---|---|
| 両方正解 | 11 |
| クラウドのみ正解 | 4 |
| ローカルのみ正解 | 4 |
| 両方不正解 | 5 |
一部、正解しているものが異なりますが、概ね同程度の結果になるようです。
API側では、一部レート制限があったので、制限が出ていないものを比較しています。
4. 768次元のローカル埋め込みで検索は落ちるのか
エージェントを使用しない、ベクトル検索の結果を以下に示します。
text-embedding-3-large(3072d / API) |
ruri-v3-310m(768d / CPU) |
差 | |
|---|---|---|---|
| easy ベクトル検索 | 19/20 (95%) | 18/20 (90%) | −1問 |
| easy ハイブリッド | 20/20 (100%) | 20/20 (100%) | ±0 |
| multihop ベクトル検索 | 13/26 (50%) | 11/26 (42%) | −2問 |
| multihop ハイブリッド | 16/26 (62%) | 17/26 (65%) | +1問 |
| 埋め込みスループット | 56.7 件/s | 5.1 件/s(455チャンクで約1.5分) | 11倍遅い |
- ベクトル検索のみではクラウドモデルの性能が高いです
- 一方で、ハイブリッド検索ではローカルモデルの性能が高いです
ruri は BM25と性質が異なるので組み合わせるうまみが大きいなどがあるのだと思います。
5. レイテンシとコスト
| multihop 26問 | 全クラウド | 全local |
|---|---|---|
| naive | 3.6秒 | 14.4秒 |
| hybrid | 4.0秒 | 13.5秒 |
| agentic | 36.7秒 | 108.7秒 |
| agentic トークン in / out | 64,019 / 2,395 | 30,636 / 4,366 |
| 総実行時間 | 18.9分(並列2) | 79.2分(46問・直列) |
| API課金 | あり | なし |
レイテンシは3〜4倍になる。Qwen 3.8-27B は遅いという噂でしたが、effortを下げると使えなくはなさそうだと思いました。
6. まとめと、正直なところ
6.1 まとめ
- ローカル27Bでツール呼び出しは普通にでるようです。 むしろ一部の指示追従性は高いようすでした
- 検索の性能も大きくは変化しないようでした
- 337MBの日本語特化埋め込みはBM25と組み合わせると、ハイブリッド検索なら3072次元のAPIとほぼ同じ検索結果でした
- 速度については、Qwen3.8-27Bで懸念していた速度問題はそこまで深刻ではなさそうです。effortを低くしたりすると十分使えるように思います
6.2 正直なところ
- 今回は、回答品質については測定していないです。 採点は「正解チャンクがプロンプトに入ったか」だけなので、ローカル27Bが同等の回答文を書けるかは未測定です。「性能は同程度」ではなく「ツールを使って正解を検索する能力は同程度」という形です
- コーパスは自分のQiita記事20本 / 455チャンクで、話題も vLLM・ローカルLLM・RAG構築に偏っています