HEROZ Tech Blog

日本将棋連盟公認「将棋ウォーズ」や、AIを活用したシステム企画・開発を行う、AI企業HEROZの公式テックブログです。

A100 80GBでOSS VLMのマルチモーダルRAGを比較:A4000 16GBでは起動できなかった理由

はじめに

最近、オープンソースのLLM/VLMの選択肢がかなり増えてきました。

テキスト生成だけでなく、画像を入力できるVLMも増えており、PDFの図表やマニュアル画像をそのままRAGに使えるのではないか、という期待も高まっています。

前回の記事では、PDFに含まれる図表をRAGで扱うために、テキスト抽出、OCR、画像Embeddingなど複数の方法を比較しました。その中で、mode4gのようにページ全体を画像として扱う構成では、検索方式だけでなく、最終的に回答を生成するVLMの性能も重要になることが分かりました。

techblog.heroz.jp

また、別の記事では、A4000 16GB環境で日本語RAG用のOSS LLMを比較しました。テキストRAGでは、A4000 16GBのような比較的安価なGPUでも、gpt-oss 20Bのように実用的に動かせる候補が見えてきました。

(記事リンク)

では、同じようにOSS VLMを使って、マルチモーダルRAGも安価なGPUで動かせるのでしょうか。

結論から言うと、今回の条件ではかなり難しい結果でした。

テキストRAGではA4000 16GBでも現実的なOSS LLMがありましたが、今回試したOSS VLMは、A4000 16GBではどのモデルも起動できませんでした。そのため、今回は社内のA100 80GB環境を使い、vLLMでOSS VLMを起動して比較しています。

今回の記事では、前回と同じデータセットA〜C、同じmode4g構成を使い、回答生成側だけをOSS VLMに差し替えた場合に、どの程度マルチモーダルRAGが成立するのかを見ていきます。

評価条件

前回記事との関係

今回の検証は、前回のPDF図表RAG評価の続編です。

前回は、PDFに含まれる図表をRAGで扱うために、以下のような複数の方式を比較しました。

モード 概要
mode1 テキスト抽出のみ
mode2 OCRテキスト化
mode3 OCR+画像併用
mode4v 画像Embedding(Voyage)
mode4c 画像Embedding(Cohere)
mode4g 画像Embedding(Gemini)

今回は、その中からmode4gを固定して使います。

つまり、検索側は前回の高精度寄り構成に固定し、回答生成側のVLMだけを差し替えて比較します。

データセット

評価データセットは、前回と同じデータセットA〜Cを使用しました。

データセット 問題数 特徴
データセットA 24問 図解のみで構成される難易度の高いケース
データセットB 30問 テキストと図表が混在するケース
データセットC 20問 図とテキストが併記されたマニュアル形式のケース

評価方法

評価は前回と同様に、主に「情報の網羅性」の観点で行いました。

模範回答に含まれる情報がどの程度含まれているか、不要な情報が追加されていないかを1〜5点で評価しています。各質問に対する回答生成と評価は1回ずつ行い、評価には LLM-as-a-Judge を用いました。評価モデルは Claude Sonnet 4.5 です。

LLM-as-a-Judge の評価は回答の長さや表現、評価ごとのばらつきの影響を受ける可能性があるため、小さなスコア差だけで厳密な順位を判断するのではなく、応答時間、VRAM使用量、日本語遵守も合わせて見る方針にしました。

なお、応答時間も集計していますが、今回はデータセットごとの画像サイズの影響も大きいため、本文では主な判断軸を評価スコア、日本語安定性、VRAM使用量に絞っています。

実行環境

前回のテキストRAG評価では、Ollamaを使ってOSS LLMを起動しました。

一方で、今回はVLMを扱うため、vLLMのOpenAI-compatible APIを使っています。vLLMは、1つのプロセスで1つのベースモデルを起動し、OpenAI互換APIとして呼び出す形で利用しました。

A4000 16GBでも試しましたが、今回対象としたOSS VLMはどれも起動できませんでした。そのため、評価はA100 80GB環境で実施しています。

本記事は、同一条件に厳密にモデルサイズや実装条件を揃えたモデル研究というより、A100 80GB環境で実際にvLLMから利用できたOSS VLMを動かした実用寄りの比較として見てください。

なお、ここでのVRAM使用量は、今回実際にvLLMで起動した条件での値です。量子化版の利用、コンテキスト長、画像枚数、画像解像度を変えれば、より小さいGPUで動かせる可能性はあります。本記事では、今回のmode4g評価をそのまま回す実用寄りの条件として比較しています。

評価対象モデル

今回評価したモデルは以下です。

本文中では、読みやすさを優先して Qwen、Kimi、GLM のような表示名で統一します。正式なモデル名は、今回実際にvLLMで起動したHugging Faceのモデル名に合わせています。

表示名 正式モデル名
GPT-5.5 gpt-5.5
Qwen Qwen/Qwen3-VL-8B-Instruct
Kimi moonshotai/Kimi-VL-A3B-Thinking-2506
GLM zai-org/GLM-4.6V-Flash
Gemma google/gemma-4-E4B-it
Ministral mistralai/Ministral-3-14B-Reasoning-2512
Phi microsoft/Phi-4-reasoning-vision-15B

GPT-5.5は、OSS VLMではなく比較用のクラウドモデルとして入れています。今回の主眼は、OSS VLMを実務的なマルチモーダルRAGの回答生成に使えるかどうかです。

結果と考察

評価スコア

まず、データセットA〜Cにおける評価スコアです。

データセット GPT-5.5 Qwen Kimi GLM Gemma Ministral Phi
データセットA 2.71 2.25 1.58 1.96 1.38 2.13 1.67
データセットB 3.07 2.23 1.93 2.67 1.53 2.73 2.00
データセットC 4.00 3.50 2.30 3.25 2.55 3.00 2.15

データセットA〜Cの評価スコア

全体としては、Qwen、GLM、Ministral が上位グループに入りました。

その中で、今回まず試す候補として見やすかったのは Qwen です。データセットA〜Cを通して大きく崩れにくく、日本語回答も安定していました。

GLMやMinistralも上位グループに入っていますが、この記事では「どれを最初に試すか」という観点で、Qwenを中心に見ていきます。

データセットAは図解中心のため、OSS VLMにも難しかった

データセットAは、今回の中でも特に難しい結果になりました。

理由として大きいのは、データセットAが図解中心のタスクであることです。検索された画像の中から、手順、位置関係、部品の向き、矢印の意味などを読み取り、それをもとに回答する必要があります。

データセットBやデータセットCでは上位グループに入るモデルでも、データセットAではスコアが伸びにくくなっています。

前回の記事では、データセットAのような図解中心のタスクでは、Claude Sonnet 4.5 でもスコアが伸びにくい傾向がありました。今回の結果を見ると、OSS VLMでも、少なくとも一部のケースでは Claude Sonnet 4.5 を上回る結果が出ています。

もちろん、これはOSS VLMがクラウドVLM全般を上回るという意味ではありません。前回の比較でも、クラウドVLMの中で図解中心タスクへの向き不向きは分かれていました。今回見えてきたのは、図解中心のRAGではモデルごとの得意不得意が大きく、OSS VLMにも試す価値があるという点です。

非日本語文字の混入はKimiで目立った

日本語RAGでは、回答の主言語が日本語であることに加えて、回答中に不自然な非日本語文字が混じらないことも重要です。

今回は、回答の主言語が日本語であっても、中国語・簡体字・ハングルなどが不自然に混入した割合を集計しました。

モデル データセットA データセットB データセットC
Qwen 0.00% 0.00% 0.00%
Kimi 45.83% 26.67% 40.00%
GLM 25.00% 3.33% 0.00%
Gemma 0.00% 0.00% 0.00%
Ministral 0.00% 3.33% 5.00%
Phi 0.00% 3.33% 0.00%

非日本語文字の混入率

この観点で目立ったのは Kimi でした。複数のデータセットで非日本語文字の混入が多く、日本語RAGの回答としてそのまま使うには注意が必要です。

一方で、Qwen は今回の範囲では非日本語文字の混入が見られませんでした。Qwenを第一候補として見た理由は、評価スコアだけでなく、この日本語安定性も含めたものです。

なお、非日本語文字の集計では、最終回答としてユーザーに返す本文を対象にし、内部のthinking相当の出力は除外しました。

最大の壁はVRAMだった

今回もっとも大きかったのは、GPUメモリの壁です。

VRAM (GB)

テキストRAGでは、A4000 16GBでも評価、応答時間、日本語遵守、VRAM使用量を見ながら、安価なGPUでどこまで成立するかを比較できました。

一方で、VLMでは前提が変わります。画像入力を扱うため、モデル本体だけでなく、画像エンコーダや画像トークン、長いコンテキストを扱うためのメモリも必要になります。

そのため、OSS VLMによるマルチモーダルRAGは、精度だけでなく、利用可能なGPUに収まるかを最初に確認する必要があります。今回の結果では、ここがテキストRAGとの一番大きな違いでした。

まとめ

今回は、A100 80GB環境でOSS VLMを起動し、マルチモーダルRAGの回答生成モデルとして比較しました。

検索方式は前回の記事と同じmode4g固定、データセットも同じデータセットA〜Cを使用しています。前回との違いは、回答生成側をクラウドVLMではなくOSS VLMにした点です。

今回の条件でまず試す候補は Qwen でした。QwenはデータセットA〜Cを通して大きく崩れにくく、非日本語文字の混入も見られませんでした。

一方で、データセットAのような図解中心のタスクは、OSS VLMにとってもまだ難しい結果でした。画像を入力できることと、図解を正しく理解してRAG回答に使えることは別問題です。

また、今回もっとも大きかったのはGPUメモリの壁です。テキストRAGではA4000 16GBでも現実的なOSS LLMがありましたが、VLMでは同じ環境では起動できず、A100 80GBで評価する必要がありました。

OSS VLMによるマルチモーダルRAGは可能性が見えてきています。ただし、現時点ではGPUメモリや運用環境を含めて慎重に設計する必要がある、というのが今回の結論です。

1時間50円級GPUで日本語RAGはどこまで動くのか?A4000 16GBでgpt-oss 20Bを試した

はじめに

最近、オープンソースLLMの選択肢が急速に増えています。gpt-oss、Gemma、Phi、Ministral、Qwen、DeepSeek、LLM-jp など、ローカル環境やGPUクラウド上で利用できるモデルの候補もかなり広がってきました。

一方で、業務利用、特に日本語RAGで使うことを考えると、公開ベンチマークのスコアだけでは判断できません。日本語で安定して回答できるのか、検索文書に忠実に答えられるのか、英語や中国語が不自然に混じらないのか、推論時間は実用的なのか、といった観点が重要になります。

さらに実務では、GPUコストも無視できません。A100のような高性能GPUを使えば大きなモデルを動かしやすくなりますが、すべての用途で高価なGPUを使えるわけではありません。RAG評価、夜間バッチ、大量の社内文書処理のような用途では、「安価なGPUでどこまで成立するか」の方が重要になることもあります。

そこで今回は、GPUSOROBANで利用できる A4000 16GB 環境を使い、日本語RAGタスクで複数のOSS LLMを比較しました。GPUSOROBANは、ローカルPCからクラウド上のNVIDIA GPUインスタンスを利用できるGPUクラウドサービスで、LLMなどの機械学習用途にも利用できます。また、利用料金は1時間50円からで、停止中は課金されないと説明されています。

soroban.highreso.jp

結論から言うと、今回の条件でまず試す候補は gpt-oss 20B でした。

Allganize RAG Dataset では上位グループに入り、J-RAGBench ではクラウドLLM基準に匹敵する結果となりました。また、リトライもほぼ発生せず、日本語遵守も安定していました。

今回の記事では、1時間50円級のA4000 16GBで、日本語RAG用OSS LLMがどこまで実用になるのかを見ていきます。

評価条件

今回は、以下の2種類の日本語RAG評価データセットを使用しました。

データセット 問題数 特徴
Allganize RAG Dataset 207問 エンタープライズサーチ寄り
J-RAGBench 114問 難問寄り

Allganize RAG Dataset は、実務の社内文書検索やFAQに近い性質を持つ評価として見ています。一方で J-RAGBench は、より難しい読解・推論を含むRAG評価として扱いました。

回答生成プロンプトでは、検索文書のみを根拠に日本語で回答するよう明示しました。

与えられた検索文書だけを根拠に、質問に対して日本語で回答してください。

評価は主に「情報の網羅性」の観点で行いました。模範回答に含まれる情報がどの程度含まれているか、不要な情報が追加されていないかを1〜5点で評価しています。各質問に対する回答生成と評価は1回ずつ行い、評価には LLM-as-a-Judge を用いました。評価モデルは Claude Sonnet 4.5 です。

LLM-as-a-Judge の評価は回答の長さや表現の影響を受ける可能性があるため、小さなスコア差だけで厳密な順位を判断するのではなく、応答時間やリトライ回数、日本語遵守も合わせて見る方針にしました。

また、精度だけでなく、次の指標も集計しました。

指標 意味
時間(s) 1回答あたりの平均応答時間
トークン数 1回答あたりの平均出力トークン数
リトライ回数 5分タイムアウトまたは応答失敗による再実行回数
非日本語回答 回答の主言語が日本語以外だった割合
非日本語文字 主言語が日本語でも、中国語・簡体字・ハングルなどが不自然に混入した割合

英語の略語、製品名、技術用語、型番などは、非日本語文字としては扱わない方針にしました。たとえば APIPDFRAG のような表記は許容しています。

評価対象モデル

今回評価したモデルは以下です。

記事中の表示名 実行名 原産エリア
GPT-5.5 none API / reasoning none 米国
GPT-5.5 medium API / reasoning medium 米国
gpt-oss 20B gpt_oss_20b 米国
Gemma 4 E4B gemma4_e4b_it_q4_k_m 米国
Phi-4 Reasoning phi4_reasoning_14b_q4_k_m 米国
Ministral 3 14B ministral_3_14b_instruct_2512_q4_k_m 欧州
Qwen3 8B qwen3_8b 中国
Qwen3 14B qwen3_14b 中国
Qwen3.5 9B qwen3_5_9b 中国
DeepSeek-R1 14B deepseek_r1_14b 中国
LLM-jp 4 8B Instruct llm_jp_4_8b_instruct_gguf_q4_k_m 日本

評価対象モデル

本文中では、読みやすさを優先して gpt-oss 20B、Qwen3.5 9B、LLM-jp 4 8B Instruct のような表示名で統一します。

また、原産エリアは、モデル提供元・開発主体をもとに大まかに整理しています。実行名は、今回実際に利用したOllamaタグに合わせています。本記事は、同一条件に厳密に量子化方式を揃えたモデル研究というより、A4000 16GBで実際に使える形のモデルを動かした実用寄りの比較として見てください。

なお、LLM-jp 4 8B Instruct は、今回利用できるGGUF版を用いて評価しました。公式の配布形態や推奨実行環境そのものを評価したものではないため、LLM-jp 4 の結果は、今回使用したGGUF版での結果として見てください。

結果1:Allganize RAG Dataset

まず、Allganize RAG Dataset の結果です。

モデル 評価 時間(s) トークン数 リトライ
回数
VRAM
(MB)
非日本語
回答
非日本語
文字
GPT-5.5 none 2.81 2.9 2884.4 0 - 0.00% 0.00%
GPT-5.5 medium 2.79 7.3 3180.2 0 - 0.00% 0.00%
gpt-oss 20B 2.74 11.8 3615.3 1 13625 0.00% 0.00%
Gemma 4 E4B 2.73 11.1 3103.2 0 10196 0.00% 0.00%
Phi-4 Reasoning 2.62 69.0 5749.2 10 12990 2.42% 2.90%
Ministral 3 14B 2.75 8.5 3215.3 0 11131 0.00% 0.48%
Qwen3 8B 2.63 12.5 3327.6 2 6307 0.00% 0.00%
Qwen3 14B 2.67 21.2 3331.1 1 10350 0.00% 0.97%
Qwen3.5 9B 2.61 228.3 5697.5 146 8556 0.97% 1.93%
DeepSeek-R1 14B 2.49 13.9 3067.1 0 10360 0.00% 4.83%
LLM-jp 4 8B Instruct 2.28 4.2 2389.0 0 - 0.48% 0.48%

Allganize RAG Dataset の評価結果

Allganize:評価スコアと応答時間

Allganize RAG Dataset では、Ministral 3 14B、gpt-oss 20B、Gemma 4 E4B が上位に入りました。スコア差は小さく、このデータセットではこの3モデルがほぼ同程度の結果だったと見ています。

この時点では、どれか1つが明確に抜けているというより、A4000 16GBでも複数のOSS LLMが実務寄りRAGで十分戦えることが分かります。

一方で、表を見ると、平均スコア以外の指標には大きな差があります。特に応答時間、リトライ回数、日本語遵守にはモデルごとの特徴が出ているため、これらは後の節で詳しく見ます。

なお、Qwen3.5 9B は応答時間が大きく外れているため、散布図では表示範囲外としています。詳細は表の時間とリトライ回数を参照してください。

結果2:J-RAGBench

次に、J-RAGBench の結果です。

モデル 評価 時間(s) トークン数 リトライ
回数
VRAM
(MB)
非日本語
回答
非日本語
文字
GPT-5.5 none 2.32 2.1 2162.8 0 - 0.00% 0.00%
GPT-5.5 medium 2.61 5.9 2387.4 0 - 0.00% 0.00%
gpt-oss 20B 2.68 6.1 2625.2 0 13625 0.00% 0.00%
Gemma 4 E4B 2.55 9.3 2601.6 0 10196 0.00% 0.00%
Phi-4 Reasoning 2.11 8.9 3063.1 0 12990 0.00% 0.00%
Ministral 3 14B 2.44 3.2 2375.1 0 11131 0.00% 0.00%
Qwen3 8B 2.38 15.1 2998.0 5 6307 0.00% 0.00%
Qwen3 14B 2.46 22.0 2877.3 3 10350 0.00% 0.00%
Qwen3.5 9B 2.18 349.3 4070.7 117 8556 0.00% 0.88%
DeepSeek-R1 14B 2.22 9.7 2430.5 0 10360 0.00% 2.63%
LLM-jp 4 8B Instruct 2.36 1.1 1838.7 0 - 0.00% 0.00%

J-RAGBench の評価結果

J-RAGBench:評価スコアと応答時間

J-RAGBench では、gpt-oss 20B、GPT-5.5 medium、Gemma 4 E4B が上位に入りました。難問寄りのRAG評価でも、gpt-oss 20B はクラウドLLM基準に匹敵する結果となっています。

ここで重要なのは、gpt-oss 20B が単に高いスコアを出したことだけではありません。A4000 16GBに収まるモデルでありながら、難問寄りのデータセットでもクラウドLLMと同じ上位グループに入った点です。

一方で、J-RAGBenchでもモデルごとの運用上の差は大きく出ました。応答時間やリトライ回数、日本語遵守の違いは、平均スコアだけでは見えにくいため、次の節で整理します。

なぜ gpt-oss 20B を推すのか

ここまでの結果を踏まえると、今回の条件でまず試す候補は gpt-oss 20B だと感じました。

観点 Allganize J-RAGBench
評価 2.74 2.68
時間(s) 11.8 6.1
リトライ回数 1 0
非日本語回答 0.00% 0.00%
非日本語文字 0.00% 0.00%
VRAM(MB) 13625 13625

Allganize RAG Dataset では上位グループに入り、J-RAGBench でもクラウドLLM基準に匹敵する結果でした。さらに、リトライがほぼ発生せず、日本語遵守も安定していました。

また、A4000 16GBでVRAM使用量が約13.6GBに収まった点も重要です。1時間50円級のGPUで動かせる範囲に収まりつつ、日本語RAGで十分な品質を出せるなら、検証やバッチ処理用途ではかなり現実的な選択肢になります。

実務的には、最高スコアだけでなく、安定して返ってくること、日本語を守ること、GPUメモリに収まることが大事です。この観点で見ると、今回の範囲では gpt-oss 20B が最も「まず試しやすい」候補でした。

日本語RAGでは、日本語として安定して返ることも重要

日本語RAGでは、回答の内容だけでなく、日本語として安定して返ってくることも重要です。特に業務システムや顧客向け用途では、回答中に突然英語や中国語が混じると、内容以前に信頼感を損ないます。

今回、gpt-oss 20B は Allganize / J-RAGBench の両方で、非日本語回答・非日本語文字ともに 0.00% でした。一方で、DeepSeek-R1 14B は Allganizeで非日本語文字 4.83%、J-RAGBenchで 2.63% となりました。

平均スコアだけを見ると見落としやすいですが、日本語RAGの実運用では、この差はかなり重要です。

reasoningが過剰に走るモデルはRAGで扱いにくい

RAGでは、検索文書に含まれる情報をもとに、必要十分な回答を安定して返すことが重要です。一方で、モデルによっては推論機能が過剰に働き、回答生成が長引いたり、出力が膨らんだり、タイムアウトに到達したりするケースがあります。

今回その傾向が最も強く出たのが Qwen3.5 9B でした。今回のA4000環境では、応答が返らない、または5分タイムアウトに到達するケースが多く発生しました。Allganizeではリトライ146回、J-RAGBenchではリトライ117回となっており、通常のRAG用途にそのまま使うにはかなり厳しい挙動でした。

Phi-4 Reasoning も、Allganizeでは平均応答時間と出力トークン数が大きくなりました。こちらはQwen3.5 9Bほど極端ではありませんが、推論が重く出ると、質問によって応答時間が大きく伸びる可能性があります。

もちろん、推論機能そのものが悪いわけではありません。複雑な問題では有効に働く場面もあります。ただ、日本語RAGのように「検索文書を根拠に、必要な情報を簡潔に返す」用途では、推論が過剰に走ると、精度よりも先に応答時間や安定性が問題になります。

今回の条件では、Qwen3.5 9B のように推論が過剰に走るモデルは、スコア以前に応答時間とリトライ回数がボトルネックになりました。

なお、gpt-oss 20B も reasoning に対応したモデルですが、今回利用したOllamaタグでは、Qwen3.5 9B のような応答なしやタイムアウト多発は見られませんでした。今回の結果を見る限り、reasoning に対応しているかどうかよりも、実際の実行設定で推論が過剰に膨らまず、RAG回答として安定して返ってくるかが重要だと感じました。

まとめ

今回は、1時間50円級のA4000 16GB環境で、日本語RAG用OSS LLMを比較しました。

今回の条件でまず試す候補は gpt-oss 20B でした。Allganize RAG Dataset では上位グループに入り、J-RAGBench ではクラウドLLM基準に匹敵する結果となりました。リトライがほぼ発生せず、日本語遵守も安定しており、A4000 16GBでVRAM使用量が約13.6GBに収まった点も実用上大きいです。

一方で、平均スコアだけでは実用性は判断できません。Qwen3.5 9B のように、reasoningが過剰に走って応答時間やリトライ回数が大きくなるモデルもあります。また、DeepSeek-R1 14B のように、日本語回答中の非日本語文字混入が課題になるケースもありました。

今回の結果からは、安価なA4000 16GB環境でも、日本語RAG用OSS LLMは十分に検討できると感じました。ただし、選ぶときは平均スコアだけでなく、応答時間、リトライ回数、日本語遵守まで含めて見る必要があります。

Vertex AIのEmbedding TuningはRAGを改善するのか?検索精度・汎用性・運用コストで検証してみた

はじめに

RAG(Retrieval-Augmented Generation)は、LLMに外部知識を与えるための現実的な手法として広く使われるようになりました。弊社でも、社内外の文書検索、業務知識の活用、専門領域の問い合わせ対応などで検証を進めています。

一方で、最近はRAGの汎用的な精度向上がある程度打ち止めになってきた感覚があります。チャンク分割、クエリ書き換え、ハイブリッド検索、rerankingなどで着実な改善は可能ですが、既に一定水準に達したパイプラインをさらに大きく伸ばすのは簡単ではありません。

特に課題になるのが、顧客固有・業界固有の用語です。

  • 社内略語・製品コードネーム
  • 業界特有の言い回し
  • 一般語としては近くないが業務上は強く関連する語
  • 似ているが区別が必要な概念

ここで重要なのは、LLM本体が専門用語を知っていることと、Embeddingが適切に検索できることは別問題だという点です。

最近のフロンティアモデルは、KubernetesのようなIT系の用語や、ある程度一般化された業界知識をかなり知っています。そのため、最終的な回答だけを見ると、LLMの内蔵知識でそれなりに答えられることがあります。

しかしRAGでは、まずRetrieval段階で正しい文書を引ける必要があります。LLM本体が知っている用語であっても、Embeddingモデルがその用語の言い換え、略称、近い概念との差分をうまく表現できなければ、正しいチャンクに到達できません。

つまり、問題は「LLMが答えを知っているか」だけではなく、Retrieval段階で正しい文書に到達できるかにもあります。

この「検索で取りこぼす」問題を解決するために、Embedding自体をドメインに合わせてfine-tuningできないか、というのが今回の出発点です。

本記事では、Vertex AIの Tune text embeddings を使い、Embedding tuningがRAGの検索性能と最終回答品質に与える影響と、そのコスト面での現実を検証します。

結論から言うと、今回の検証では Embedding tuningは対象ドメインに対してbase比で改善しました。また、別データセットでのデグレも小さく、汎用性能が大きく壊れることもありませんでした。

一方で、最新のGemini Embeddings 2がチューニングなしで同等以上の性能を示すケースもありました。さらに、チューニング済みモデルの推論にはVertex AI Endpointでの推論リソース管理が必要であり、通常のEmbedding APIのようにそのままserverlessに呼び出せるものではありませんでした。

今回のまとめは次の通りです。

Embedding tuningは効く。ただし、軽くはない。
さらに、最新のフロンティアEmbeddingモデルがチューニングなしで同等以上になるケースもある。
そのため、まずは通常のRAG改善や最新Embeddingモデルを試し、それでも顧客固有語や業界用語の検索に課題が残る場合に検討するのが現実的です。

背景

基盤モデルのfine-tuningで感じていた難しさ

fine-tuning自体には以前から期待がありました。

ドメイン知識を追加する、顧客ごとの業務に合わせる、専門領域に強くする、といった話をすると、自然とfine-tuningという選択肢が出てきます。ビジネス面でも「顧客専用モデル」「業界特化モデル」という響きは分かりやすく、期待されやすい技術です。

ただ、基盤モデル、つまりLLM本体をfine-tuningする場合には、実務上いくつかの壁があります。

まず、fine-tuningはベースモデルの能力を大きく超える魔法ではありません。ベースモデルの知識量や推論力が十分でない場合、少量のドメインデータを追加しても、最新のフロンティアモデルに追いつくのは簡単ではありません。

次に、ドメイン知識を追加するだけでは、実用的なチャットモデルにはなりません。ユーザーの指示に従う、指定された形式で回答する、余計なことを言わない、といったinstruction followingの能力も必要になります。ドメイン知識のfine-tuningとinstruction tuningをどう両立させるかは、思った以上に大きな問題でした。

最後に、運用コストがあります。fine-tuningした基盤モデルを実運用するには、何らかのGPUリソースが必要になります。モデルサイズが大きくなるほど、学習だけでなく推論時のコストも重くなります。精度が多少上がっても、運用コストまで含めると見合わない、という判断になりがちです。

まとめると、基盤モデルのfine-tuningには次のような難しさがあります。

  • ベースモデルの能力に強く依存する
  • ドメイン知識だけでなくinstruction followingも考える必要がある
  • 学習後の推論コストが重い

このため、基盤モデルのfine-tuningは効果があったとしても、実用化まで考えるとかなり重い取り組みになります。

なぜEmbeddingのfine-tuningに注目したか

そこで発想を変えて、Embeddingだけをfine-tuningすることを考えました。

Embeddingであれば、基盤モデルのfine-tuningで問題になりやすい点をいくらか回避できるかもしれません。

Embeddingモデルは回答文を生成しないため、生成品質やinstruction followingには直接影響しません。学習対象も「このクエリとこの文書は近い」という関係であり、基盤モデル全体に新しい知識や振る舞いを覚えさせるよりも、問題を切り分けやすくなります。

また、RAGの性能を分解すると、RetrievalとGenerationに分けられます。Embeddingのfine-tuningは、このうちRetrievalだけをピンポイントで改善する手法です。効果測定もしやすく、RAG全体の改善施策として扱いやすいと考えました。

さらに、Vertex AIにはEmbeddingをチューニングできるmanagedサービスが用意されています。もし学習から推論までmanagedかつserverlessに近い形で使えるなら、基盤モデルfine-tuningで問題になった運用コストの壁を越えられるかもしれません。

この期待から、Vertex AIの Tune text embeddings を試すことにしました。

Vertex AI「Tune text embeddings」

Vertex AIには、テキストEmbeddingを教師ありでチューニングする Tune text embeddings という機能があります。

公式ドキュメントはこちらです。

https://cloud.google.com/vertex-ai/generative-ai/docs/models/tune-embeddings

今回使用したベースモデルは text-multilingual-embedding-002 です。日本語クエリで評価したかったため、多言語モデルを選びました。

また、比較対象としてOpenAIのtext-embedding-3-largeと、Gemini Embeddings 2 (gemini-embedding-2)も使用しました。

Google Cloudのドキュメントでは、公開ベンチマークにおいて、Embedding tuningにより最大41%、平均12%の品質改善があったと説明されています。

この数字だけを見ると、RAGの検索精度改善手法としてかなり魅力的です。

学習コスト

Tune text embeddingsの学習はVertex AI Pipelines上で実行され、GPUインスタンスの利用時間に応じた従量課金になります。

今回の検証では、学習時間はおよそ2時間で、コストは数ドル程度でした。少なくとも小規模な検証であれば、学習コスト自体はかなり軽い印象です。

この時点では、学習がこれだけ軽いなら、推論も通常のEmbedding APIのようにserverlessで使えて、トークン課金ではないかと期待していました。

この期待は、後半で少し裏切られます。

Embedding fine-tuningの学習データ

tripletの考え方

Embeddingのfine-tuningでは、クエリと文書の関係を学習します。

概念的にはtripletで考えると分かりやすいです。

  • anchor: 検索クエリ
  • positive: 正解文書
  • negative: 不正解文書

例えば、Kubernetesのドキュメントで考えると、次のようなイメージです。

  • anchor: 「コンテナが何度も落ちて再起動する原因を調べたい」
  • positive: anchorに関係するCrashLoopBackOffに関する説明チャンク
  • negative: anchorに関係しないDeploymentのreplica数に関する説明チャンク

このとき、Embedding空間上ではanchorとpositiveを近づけ、anchorとnegativeを遠ざけたいわけです。

ただし、Vertex AIのTune text embeddingsでは、明示的なtriplet形式そのものではなく、以下の3つのファイルを用意します。

corpus.jsonl

検索対象となる文書群です。

1行1JSONのJSONL形式で、各行に文書IDと本文を入れます。

{"_id": "doc_001", "text": "..."}

今回の実験では、対象文書をチャンク分割し、各チャンクを1つのcorpus itemとして扱いました。

queries.jsonl

検索クエリの一覧です。

{"_id": "query_001", "text": "..."}

ここが今回のデータ作成で一番重要な部分です。

単にチャンク本文をそのまま質問にすると、キーワード一致で解ける簡単な評価セットになってしまいます。それではEmbedding tuningの差が出にくくなります。

そこで今回は、各チャンクの内容からLLMでクエリを生成する際に、次のような特徴を持たせました。

  • 本文中の用語を別の自然な表現に言い換える
  • 具体的な用語を少し抽象化する
  • 似た概念と混同しやすい聞き方にする
  • 単語一致だけではなく、文脈理解が必要になるようにする

つまり、単なるQAデータではなく、Retrievalが少し難しくなる評価データを意図的に作っています。

例えば、本文にある専門用語をそのまま質問に含めるのではなく、実際のユーザーが曖昧に問い合わせそうな表現に寄せます。これにより、単純なlexical overlapではなく、意味的に正しいチャンクを引けるかを評価しやすくなります。

train_labels.tsv

クエリと文書の対応関係を表すラベルです。

query_id corpus_id score

scoreは、そのクエリと文書の関連度を表します。

今回は、各チャンクから生成したクエリに対して、そのチャンク自身を正例として扱いました。つまり、「このチャンクの内容から作ったクエリなら、このチャンクに戻ってきてほしい」という教師データです。

本格的には、似ているが正解ではない文書、いわゆるhard negativeも丁寧に設計した方がよいです。ただ、今回はまず実験基盤の構築と、Embedding tuningの効果確認を優先しました。

実験

実験設定

今回は、2つのデータセットで検証しました。

1つ目は、Kubernetes公式ドキュメントです。

Kubernetesを選んだ理由は、公式ドキュメントが整備されていて取得しやすく、用語や概念も多いためです。一方で、KubernetesはLLMやEmbeddingモデルにとって比較的得意な領域でもあります。そのため、今回の実験は「未知ドメインに対する最終検証」ではなく、「Embedding tuningの効果が出るかを確認する」位置づけです。

2つ目は、某損害保険会社の約款です。

こちらは、Kubernetesよりも業務ドメイン寄りのデータとして用意しました。約款は表現や条項の構造に独特さがあり、一般的なWeb知識やITドキュメントとは異なる性質があります。顧客固有・業界固有の検索課題に近い対象として、Embedding tuningの効果を確認するために使用しました。

なお、某損害保険会社の約款は非公開データを含むため、本文ではデータの詳細は記載せず、概要と評価結果のみを扱います。

対象 評価データ
Kubernetes公式ドキュメント 50件
某損害保険会社の約款 78件

比較したEmbeddingモデルは次の通りです。

表記 モデル
Vertex base text-multilingual-embedding-002
Vertex tuned text-multilingual-embedding-002 を対象データでチューニングしたもの
OpenAI text-embedding-3-large
Gemini Embeddings 2 gemini-embedding-2

RAGとしての最終回答品質も確認しました。

項目 内容
回答生成 gpt-5.4
評価方法 LLM as a judge
評価モデル Claude Sonnet 4.5
評価スケール 1〜5点

なお、RAG最終品質の評価にはLLM-as-a-Judgeを用いています。LLMによる評価にはスコアのばらつきや評価モデルのバイアスが含まれる可能性があるため、ここでのスコアは絶対的な性能値ではなく、同一条件内での相対比較として扱っています。

Retrieval評価の指標

Retrieval評価では、主にMRR(Mean Reciprocal Rank)を見ました。

MRRは、正解チャンクの順位の逆数を平均した指標です。正解が1位なら1.0、2位なら0.5、5位なら0.2になります。RAGでは、正解チャンクが単に検索候補に含まれるだけでなく、できるだけ上位に来ることが重要なので、MRRは実用上かなり重要な指標です。

結果

Retrieval性能

まず、Retrieval性能です。ここではMRRを比較します。

モデル Kubernetes
MRR
損害保険の約款
MRR
Vertex base 0.19 0.26
Vertex tuned 0.37 0.31
OpenAI 0.26 0.23
Gemini Embeddings 2 0.42 0.34

Retrieval性能

Kubernetesでは、Vertex tunedのMRRが0.19から0.37へ改善しました。ほぼ2倍です。

某損害保険会社の約款でも、Vertex tunedは0.26から0.31へ改善しました。改善幅はKubernetesほど大きくありませんが、base比では改善しています。

この結果から、少なくとも今回の条件では、Embedding tuningによって対象ドメインのRetrieval性能は改善することが分かりました。

一方で、Gemini Embeddings 2にも注目する必要があります。Kubernetesでは0.42、某損害保険会社の約款では0.34で、どちらもVertex tunedを上回りました。

つまり、Embedding tuningはbase比では効くものの、最新のフロンティアEmbeddingモデルがチューニングなしで同等以上の性能を示すケースがあります。

RAGとしての最終品質

次に、検索結果を使って実際にRAGで回答させた場合のスコアを比較します。

モデル Kubernetes
score
損害保険の約款
score
no RAG 2.24 2.15
Vertex base 2.42 2.78
Vertex tuned 3.04 3.06
OpenAI 2.68 2.83
Gemini Embeddings 2 3.02 3.21

RAGの品質

Kubernetesでは、Vertex tunedはVertex baseに対して2.42から3.04へ改善しました。RAGの最終回答品質でも、Embedding tuningの効果が確認できました。

某損害保険会社の約款でも、Vertex tunedは2.78から3.06へ改善しました。こちらも、Retrieval性能と同じくbase比で改善しています。

一方で、Gemini Embeddings 2はKubernetesでは3.02とVertex tunedにほぼ同等、某損害保険会社の約款では3.21と最も高いスコアになりました。

ここで見えてくるのは、次のような傾向です。

  • Embedding tuningは対象ドメインでbase比の改善を出す
  • その改善はRAG最終品質にも一定反映される
  • ただし、最新のGemini Embeddings 2がチューニングなしで同等以上になる場合がある

Retrieval評価ではMRRが大きく改善していても、RAGの最終スコアの改善幅はそれより小さくなることがあります。これは自然な結果だと思います。

RAGの最終回答は、検索だけで決まるわけではありません。

  • LLMが元々持っている知識
  • 検索結果の読み取り能力
  • プロンプトの設計
  • 評価データの難易度
  • top-kに入っていれば何位でも回答できるケース

などが絡みます。

特にKubernetesのような一般的なIT知識では、フロンティアモデルが内蔵知識だけである程度答えられるケースもあります。そのため、検索順位が大きく改善しても、最終回答の改善幅は圧縮されます。

汎用データでのデグレ確認

fine-tuningでは、対象ドメインに最適化される一方で、汎用性能が壊れる可能性があります。

そこで、チューニングしたEmbeddingモデルが、別のRAGデータセットで劣化しないかを確認しました。

デグレ確認には、Allganize RAG Evaluation Dataset JAを使用しました。

https://huggingface.co/datasets/allganize/RAG-Evaluation-Dataset-JA

結果は次の通りです。

モデル Kubernetes
tuned
損害保険の約款
tuned
no RAG 1.92 1.88
Vertex base 2.98 2.92
Vertex tuned 2.90 2.84
OpenAI 2.86 2.99
Gemini Embeddings 2 2.95 2.93

デグレ確認

Kubernetesでチューニングしたモデルは、Vertex baseに対して-2.4%でした。某損害保険会社の約款でチューニングしたモデルは、Vertex baseに対して-2.6%でした。

どちらも少し低下していますが、大きなデグレとは言いにくい結果です。少なくとも今回の範囲では、Embedding tuningしても、汎用的な日本語RAG評価セットで大きく性能が壊れることはありませんでした。

これは重要な結果です。

Embedding tuningが対象ドメインで効いたとしても、他ドメインで大きく壊れるなら、実用上はかなり扱いづらくなります。しかし今回の結果では、対象ドメインで改善しつつ、汎用データでは大きな劣化は見られませんでした。

考察

ここまでの結果は良好でした

ここまでの結果だけを見ると、Embedding tuningはかなり有望に見えます。

  • KubernetesドメインではRetrieval性能が大きく改善
  • 某損害保険会社の約款でもRetrieval性能が改善
  • RAGの最終回答品質も改善
  • 汎用データでの大きなデグレは見られない

基盤モデルのfine-tuningと比べると、Embedding tuningはかなり扱いやすい印象でした。

基盤モデルのfine-tuningでは、モデルの回答品質、instruction following、ハルシネーション、運用コストなど、考えることが多くなります。一方でEmbedding tuningは、検索対象との距離関係を改善する話なので、評価対象をRetrievalに切り分けやすいです。

RAGの改善手法としては、かなり素直です。

また、今回の追加検証で分かった重要な点として、某損害保険会社の約款のような業務ドメイン寄りのデータでも、base比では改善が見られました。これは、Embedding tuningがKubernetesのようなITドキュメントだけに効くわけではない、という意味で前向きな結果です。

ただし、最新のフロンティアEmbeddingモデルは強い

一方で、今回の結果ではGemini Embeddings 2が非常に強い結果を示しました。

Kubernetesでは、Gemini Embeddings 2がRetrieval性能で最も高く、RAG最終品質でもVertex tunedとほぼ同等でした。

某損害保険会社の約款でも、Gemini Embeddings 2がRetrieval性能とRAG最終品質の両方で最も高いスコアになりました。

この結果から、Embedding tuningを考えるときには、単に「baseモデルから改善するか」だけでは不十分だと分かります。

実務上は、次の比較が必要です。

  • Vertex baseと比べて改善するか
  • 最新のEmbeddingモデルと比べて優位があるか
  • 改善幅が運用コストに見合うか

今回の検証では、Vertex tunedはbase比では改善しました。しかし、Gemini Embeddings 2と比較すると、同等または下回る結果でした。

つまり、チューニング済みモデルを作って運用する前に、まず最新のフロンティアEmbeddingモデルを試す価値はかなり大きいと感じました。

さらに、推論時にGPU endpointが必要だった

実際に進めてみると、もう1つ大きな問題がありました。

Tune text embeddingsは、学習自体はVertex AIのmanagedな仕組みで実行できます。しかし、チューニング済みEmbeddingモデルは、通常のEmbedding APIのようにそのまま呼び出せるわけではありません。

チューニング済みモデルを使うには、Vertex AI Endpointにデプロイし、推論に使うmachine typeやacceleratorなどのリソースを管理する必要があります。

当初は、managed serviceという言葉から、学習後の推論も通常のEmbedding APIのようにserverlessに近い形で使えることを期待していました。

しかし実際には、チューニング済みEmbeddingモデルを運用するには、推論リソースをどこかで確保する必要があります。

これはかなり大きな違いです。

RAGのEmbeddingは、通常の検索クエリごとに呼ばれます。社内向けRAGや顧客向けRAGで常時GPU endpointを立てるとなると、利用頻度が十分に高くない限り、コストが見合わない可能性があります。

Embedding tuningによるRAG最終品質の改善に対して、GPUを含む推論リソースの運用コストを払うか、という判断になります。

ここはかなり悩ましいです。

Embedding tuningは「効くが軽くはない」

今回の実験から分かったことを整理します。

まず、Embedding tuningそのものには効果があります。

Kubernetesでも某損害保険会社の約款でも、Vertex baseと比較してRetrieval性能は改善しました。特にKubernetesではMRRがほぼ2倍になりました。正解チャンクがより上位に来るようになるため、RAGの検索品質改善としては素直に効いています。

また、RAGの最終回答品質も改善しました。

ただし、Retrievalの改善幅に比べると、最終回答の改善幅は小さくなります。これは、フロンティアモデルが元々ある程度の知識を持っていることや、検索結果の順位差を回答生成側がある程度吸収できることが理由だと思います。

次に、汎用性能のデグレは大きくありませんでした。

Kubernetesでチューニングしたモデルも、某損害保険会社の約款でチューニングしたモデルも、Allganize RAG Datasetで評価した範囲では、Vertex baseに対して-2.5%前後の低下に収まりました。この点は良い結果です。

一方で、最新のGemini Embeddings 2は非常に強い結果でした。今回の範囲では、Vertex tunedと同等またはそれ以上の性能を、チューニングなしで示しました。

さらに、最大の課題は運用コストです。

学習がmanagedで安価にできても、推論が通常のEmbedding APIのように使えないなら、実運用のハードルはかなり上がります。特にRAGの検索用途では、Embedding生成は高頻度に呼ばれる可能性があるため、GPU endpointのコストは無視できません。

つまり、今回の結論は次のようになります。

Embedding tuningはRetrievalを改善する。
RAGの最終回答品質にも一定の効果がある。
汎用性能も大きくは壊れない。
ただし、最新のフロンティアEmbeddingモデルがチューニングなしで同等以上になるケースがある。
さらに、推論時のGPU運用コストを考えると、適用できるケースは限られる。

おわりに

本記事では、Vertex AIのTune text embeddingsを使い、Embedding tuningがRAGを改善するのかを検証しました。

結果として、Kubernetesドキュメントと某損害保険会社の約款を対象にした評価では、Retrieval性能が改善し、RAGの最終回答品質も向上しました。また、Allganize RAG Datasetを使ったデグレ確認では、大きな汎用性能の劣化は見られませんでした。

一方で、Gemini Embeddings 2はチューニングなしでもVertex tunedと同等以上の性能を示しました。また、チューニング済みEmbeddingモデルの推論にはVertex AI Endpointでの推論リソース管理が必要であり、当初期待していたserverless managed serviceとは違いました。

今回のまとめは、次の一文に尽きます。

Embedding tuningは効く。ただし、軽くはない。

RAGの精度向上が頭打ちになってきたとき、Embedding tuningは有力な選択肢になり得ます。ただし、まずは通常のRAG改善やreranking、query rewriting、最新Embeddingモデルの利用などの軽量な手法を試した上で、それでも顧客固有用語や業界用語の検索に課題が残る場合に検討するのが現実的だと思います。

今後は、Vertex AI以外のオープンなEmbeddingモデルやオンプレGPU、クラウドGPUインスタンスを使った運用も含めて、実用的な構成を模索していきたいと思います。

Multi Query Retrieverから2年後:テキストRAG改善のフェーズは変わったのか

はじめに

2024年3月に、HEROZ Tech Blogで「Multi Query Retrieverを用いたRAGの精度向上」に関する記事を公開しました。

techblog.heroz.jp

この記事では、Naive RAGに対してMulti Query Retrieverを適用することで、社内Wikiを対象とした評価において正答数が19/25から24/25に改善しました。当時としては、クエリの表現揺れや検索漏れをMulti Queryで補うことに明確な価値があり、Enhanced RAGの効果が分かりやすく出た検証だったと思います。

一方で、この記事は今でも継続的にアクセスされています。RAG改善への関心は今も高いのだと思いますが、最近いろいろなRAG改善手法を試していると、以前ほど分かりやすく精度が伸びない感覚もありました。

Query Rewrite、Multi Query、HyDE、ReRank、Agentic RAG、Router RAG、Hybrid Search、チャンク設計など、RAG改善としてよく挙げられる手法を試しても、平均スコアが大きく動かないことが増えています。

そこで今回は、2024年の記事から約2年経った現在、あらためて次の問いを検証してみました。

今でもEnhanced RAGやAgentic RAGは、Naive RAGに対して平均精度を大きく押し上げるのか?

結論から言うと、今回の検証では、手法間の平均スコアの差はかなり限定的でした。

もちろん、特定の条件では改善する手法もあります。特にAgentic RAGの一部は、データセットによってはNaive RAGを上回りました。しかし、Query Rewrite、Multi Query、HyDE、ReRank、ReAct、Adaptive RAG、Deep Agentを横並びで比較しても、「これを入れれば平均的に大きく改善する」と言える手法は見つかりませんでした。

そこで本記事では、今回の横並び比較の結果を紹介したうえで、近年のRAG関連研究も参照しながら、この結果をどう解釈すべきかを考えます。

最近の研究を見ても、RAG改善の論点は、単体のEnhanced RAG手法で平均精度を押し上げる方向だけではなく、LLMの性能向上による複雑なRAG改善の限界効用、Agentic RAGのコストと探索制御、Long Contextとの使い分け、検索結果を増やしすぎることの副作用といった方向に広がっています。

本記事では、社内検証の結果と関連研究を踏まえて、テキストRAG改善のフェーズがどう変わってきているのかを考察します。

今回比較したRAG手法

今回は、大きくEnhanced RAG系とAgentic RAG系に分けて比較しました。

Enhanced RAG

手法 概要
Naive 通常のベクトル検索 + 回答生成
ReRank 取得文書を質問への有用度で並び替える
Query Rewrite 質問を検索しやすいクエリに書き換える
Multi Query 複数の検索クエリを生成し、検索漏れを減らす
HyDE 検索用の仮想文書を生成して検索する

2024年の記事で扱ったMulti Query Retrieverも、このEnhanced RAG系の一種です。

Agentic RAG

手法 概要
Naive 通常のベクトル検索 + 回答生成
ReRank 参考値として比較
ReAct 検索ツールを使うReAct型エージェント
Adaptive RAG 検索や回答可否を適応的に判断するRAG
Deep Agent より複雑な探索を行うAgent構成

Agentic RAGは、単発の検索で回答するのではなく、必要に応じて検索クエリを変えたり、複数回探索したりする構成です。うまく機能すれば、単純なRAGでは拾えない根拠を見つけられる可能性があります。一方で、LLM呼び出し回数、トークン数、コスト、レイテンシが増えやすいという難しさもあります。

評価データセットと評価方法

評価には、以下の2つの日本語RAG評価データセットを使用しました。

データセット 問題数 特徴
Allganize RAG Dataset 207問 エンタープライズサーチ寄り
J-RAGBench 114問 難問寄り

Allganize RAG Datasetは、業務文書検索に近い性質を持つデータセットとして扱いました。一方でJ-RAGBenchは、より難問寄りのRAG評価として扱っています。

評価は、独自プロンプトによるLLM as a Judgeで行いました。JudgeにはClaude Sonnet 4.5を使用し、1〜5点の5段階でスコアリングしています。

なお、LLM as a Judgeによる評価には、評価モデル固有のバイアスやスコアのばらつきが含まれる可能性があります。そのため、本記事ではスコアの絶対値や0.01〜0.1程度の小さな差を厳密な優劣として扱うのではなく、手法間の相対的な傾向を見るための参考値として扱います。

また、今回のスコアは多くの手法で3点未満となっています。これは、RAG全体として十分に高品質な回答ができている、というよりも、今回の評価プロンプト・回答生成プロンプト・データセットの難易度を含めた結果です。本記事では、絶対スコアの高さよりも、同一条件で比較したときに手法間でどれくらい差が出るのかに注目します。

実験条件

本文中のスコアは、手法間の傾向を見やすくするため、チューニング前の横並び比較結果を掲載しています。各手法のプロンプトやAgent制御を一定程度整えた追加実験も行いましたが、全体の結論は大きく変わらなかったため、本文では割愛します。

再現性に関わる主な条件は以下です。

項目 設定
生成LLM gpt-5.4
Embeddingモデル text-embedding-3-small
チャンクサイズ 1200文字
チャンクoverlap 100文字
retrieve_k 基本は4。ReRank系のみ候補取得は12
top_k 4
ベクトルDB / 検索方式 Weaviate / LangChain WeaviateVectorStore.as_retriever によるベクトル類似検索
ReRank LLM rerank。同じ生成LLMをtemperature=0で使用し、候補文書indexをJSONで返す方式。cross encoderは未使用
Agentの最大探索回数 チューニング前比較では明示的な検索回数上限なし。LangGraphのrecursion_limit=25
評価回数 各質問・各手法につき1回評価。複数回平均ではない

結果:Enhanced RAGでは平均改善が限定的だった

まず、Enhanced RAGの結果です。

データセット Naive ReRank Query Rewrite Multi Query HyDE
Allganize RAG Dataset 2.80 2.79 2.72 2.82 2.80
J-RAGBench 2.48 2.57 2.48 2.50 2.42

Enhanced RAGの結果

Allganize RAG Datasetでは、Multi Queryが2.82で最も高く、Naiveの2.80に対して+0.02でした。J-RAGBenchでは、ReRankが2.57で最も高く、Naiveの2.48に対して+0.09でした。

どちらも改善はしていますが、5点満点の平均スコアとしてはかなり小さい差です。

ここで重要なのは、Multi QueryやReRankが無意味だった、ということではありません。これらの手法は、検索漏れや表現揺れ、検索結果のノイズが問題になるケースでは有効に働く可能性があります。

ただし、今回のようにデータセット全体の平均で見ると、Naive RAGを大きく押し上げる結果にはなりませんでした。

2024年の記事では、Multi Query Retrieverによる改善がかなり分かりやすく出ました。しかし今回の検証では、少なくとも平均スコアを見る限り、同じような大きな改善は確認できませんでした。

結果:Agentic RAGも安定した万能薬ではなかった

次に、Agentic RAGの結果です。

データセット Naive ReRank ReAct Adaptive RAG Deep Agent
Allganize RAG Dataset 2.77 2.83 2.79 3.13 2.88
J-RAGBench 2.51 2.55 2.52 2.28 2.58

Agentic RAGの結果

Allganize RAG Datasetでは、Adaptive RAGが3.13で最も高く、Naiveの2.77に対して+0.36でした。これは今回の結果の中では比較的大きな改善です。

一方で、J-RAGBenchではAdaptive RAGは2.28となり、Naiveの2.51を下回りました。J-RAGBenchで最も高かったのはDeep Agentの2.58ですが、Naiveとの差は+0.07にとどまりました。

つまり、Agentic RAGは特定の条件では有効に働く可能性があります。しかし、少なくとも今回の範囲では、データセットをまたいで安定して大きく改善する手法とは言えませんでした。

Naiveとの差分だけをまとめると、次のようになります。

データセット 系統 最良手法 Naive 最良スコア 差分
Allganize RAG Dataset Enhanced Multi Query 2.80 2.82 +0.02
J-RAGBench Enhanced ReRank 2.48 2.57 +0.09
Allganize RAG Dataset Agentic Adaptive RAG 2.77 3.13 +0.36
J-RAGBench Agentic Deep Agent 2.51 2.58 +0.07

Naiveとの差

Enhanced RAGでは、最良手法でもNaiveとの差は+0.02〜+0.09でした。一方、Agentic RAGではAllganize RAG DatasetのAdaptive RAGだけが+0.36と目立ちましたが、J-RAGBenchでは同様の改善は見られませんでした。

コストやレイテンシも含めると、さらに悩ましい結果になります。

データセット 手法 評価 時間(ms) LLM呼出回数 コスト(円)
Allganize RAG Dataset Naive 2.77 3019.4 1.0 1.40
Allganize RAG Dataset Adaptive RAG 3.13 8368.9 7.0 3.46
J-RAGBench Naive 2.51 1394.6 1.0 0.86
J-RAGBench Deep Agent 2.58 3158.8 2.1 5.05

コストとレイテンシ

Allganize RAG DatasetのAdaptive RAGは、Naiveに対して+0.36改善しています。一方で、時間は約2.8倍、LLM呼び出し回数は7倍、コストは約2.5倍です。

J-RAGBenchのDeep Agentは、Naiveに対して+0.07の改善に対して、コストは約5.9倍です。

この結果を見ると、Agentic RAGを常に使うというより、複数回探索が必要な質問や、回答可否判定が重要な質問に限定して使う方が自然だと感じました。

また、Adaptive RAGがAllganize RAG Datasetでは改善し、J-RAGBenchでは悪化した点も重要です。Allganize RAG Datasetはエンタープライズサーチ寄りであり、質問に対して「根拠があるか」「追加検索すべきか」「回答できないか」を判断するAdaptive RAGの制御が噛み合いやすかった可能性があります。一方で、J-RAGBenchのような難問寄りのデータセットでは、回答可否判定や追加検索判断が過剰に働き、必要な推論に進む前に回答を控えたり、余計な文脈を取り込んだりした可能性があります。

なお、本記事でのAdaptive RAGは、前回の記事で扱ったLangGraphの実装例を参考にした構成です。詳細な考え方は前回記事も参照してください。

この点はログ分析を深掘りしないと断定できませんが、Agentic RAGがタスク依存であることを示す重要な結果だと考えています。

関連研究から結果を読み解く

今回の検証では、Enhanced RAGやAgentic RAGを追加しても、平均スコアの改善は限定的でした。

この結果を解釈するうえで参考になるのが、近年のRAG関連研究です。ここでは、今回の結果と特に関係が深い4本に絞って紹介します。

強いLLM時代には、複雑なRAG改善の限界効用が下がる可能性がある

今回の結果に最も近いと感じたのが、2025年の “Revisiting Robust RAG: Do We Still Need Complex Robust Training in the Era of Powerful LLMs?” です。

この論文は、RAGがノイズ文書や無関係文書に弱いことを背景に、複雑なdocument selectionやadversarial trainingのようなrobust trainingが、強力なLLM時代でも必要なのかを検証しています。複数のモデルアーキテクチャ、モデルサイズ、データセットで評価した結果、モデルが強力になるほど、複雑なrobust RAG trainingによる性能向上は大きく低下する傾向が報告されています。

これは、今回の「Naive RAGにさまざまな手法を足しても平均スコアが大きく動かなかった」という結果とかなり整合的です。

もちろん、この論文が扱っているのはrobust trainingであり、今回のQuery RewriteやMulti Queryそのものではありません。しかし、より強いLLMでは複雑なRAG周辺手法の限界効用が小さくなる、という大きな方向性は、今回の実験結果を解釈するうえで重要な補助線になります。

Agentic RAGは、性能だけでなくコストとのトレードオフで見る必要がある

Agentic RAGについては、以前の記事 “Is Agentic RAG worth it? An experimental comparison of RAG approaches” を紹介しました。

この論文では、Enhanced RAGとAgentic RAGを複数シナリオ・複数観点で経験的に比較し、どちらが常に優れているというより、性能とコストのトレードオフを踏まえてRAG設計を選ぶ必要があると整理しています。Agentic RAGはLLMがどの行動をいつ行うかを判断する柔軟性を持つ一方で、その分、計算資源やコストの問題が生じます。

今回の結果でも、Agentic RAGはAllganize RAG Datasetでは改善した一方で、J-RAGBenchでは安定した改善にはなりませんでした。また、改善したケースでもLLM呼び出し回数やコストは増えています。

この意味で、Agentic RAGは「導入すれば精度が上がる万能手法」というより、タスクや失敗モード、コスト制約に応じて使うべき構成だと考えています。

検索結果やコンテキストは、増やせばよいわけではない

Multi QueryやAgentic RAGは、検索回数や投入文脈を増やす方向に働きやすい手法です。しかし、検索結果やコンテキストは増やせば増やすほどよいわけではありません。

“In Defense of RAG in the Era of Long-Context Language Models” では、Long Context LLMの時代におけるRAGの価値を再検討し、OP-RAGという手法を提案しています。この論文では、取得チャンク数を増やすと回答品質が一度上がってから下がる、逆U字型の挙動が報告されています。つまり、適切な取得量にはスイートスポットがあり、文脈を増やしすぎると品質が下がりうるということです。

これは、今回の結果を考えるうえでも重要です。Multi QueryやAgentic RAGによって取得文書が増えても、それが必要な根拠であれば有効ですが、不要な文脈やノイズが増えるだけであれば、改善にはつながりません。

RAGかLong Contextかの選択も、条件依存になっている

2025年のLaRAは、RAGとLong Context LLMを体系的に比較するベンチマークです。2,326のテストケース、4つの実用QAカテゴリ、3種類の長文テキストを対象に、7つのOSSモデルと4つの商用モデルを評価しています。その結果、RAGとLong Contextのどちらが適しているかは、モデルサイズ、長文処理能力、context length、タスクタイプ、retrieved chunkの性質などに依存すると報告されています。

つまり、外部知識を扱う方式そのものも、単純な優劣ではなく条件依存です。

これらの研究を合わせると、最近のRAG研究は「単体手法を足せば平均的に伸びる」というより、「タスクや失敗モードに応じて、検索・文脈・Agent化・Long Context利用の度合いを選ぶ」方向に進んでいるように見えます。

なぜ差がつきにくくなったのか

ここからは、今回の実験結果と関連研究を踏まえた考察です。

Naive RAGのベースラインが上がっている可能性

一番大きいのは、Naive RAGのベースラインが以前より上がっている可能性です。

2024年頃は、検索クエリと文書中の表現が少しずれるだけで検索漏れが起きたり、取得文書にノイズが混じるとLLMがうまく回答できなかったりしました。そのため、Multi Queryのように検索クエリを増やすだけでも、平均精度が分かりやすく改善する余地がありました。

しかし現在は、EmbeddingモデルやLLMの性能向上により、単純なベクトル検索でも必要な文書を拾えるケースが増えている可能性があります。また、LLMの文脈読解能力が上がったことで、検索結果に多少ノイズが含まれていても、回答に必要な部分を抽出できるケースも増えているのではないかと考えています。

その結果、Query RewriteやMulti Queryを追加しても、「もともと解ける問題を、より高コストに解いているだけ」になりやすいのかもしれません。

検索や文脈を増やすことが、常に改善につながるわけではない

Multi Query、HyDE、Agentic RAGは、いずれも検索や文脈を増やす方向に働きやすい手法です。

しかし、関連研究でも示唆されているように、文脈は増やせば増やすほどよいわけではありません。必要な根拠が増える場合は有効ですが、不要な文脈が増えると、LLMの焦点がぼやけたり、矛盾した情報が混ざったり、コストだけが増えたりします。

今回の結果でも、Enhanced RAGやAgentic RAGを足したからといって、平均スコアが安定して伸びるわけではありませんでした。

これは、RAG改善が「検索を増やす」だけではなく、「必要な情報だけを、必要以上にノイズを増やさず渡す」問題になっていることを示しているように思います。

残る課題は平均改善ではなく、失敗モード別対応になっている

今回の検証と最近の研究動向を合わせて見ると、テキストRAGの改善は、平均点を上げる汎用手法の追加から、失敗モード別の対応へ移っているように見えます。

たとえば、型番や価格、日付のような厳密一致が重要なケースでは、ベクトル検索よりもキーワード検索やmetadata、DB検索が重要になります。

表やPDFレイアウトに情報が埋まっているケースでは、テキストRAGではなくOCR、table parser、マルチモーダルRAGが必要になります。

複数文書の関係性やmulti-hopが重要なケースでは、GraphRAGやAgentic RAGのような構成が効く可能性があります。

一方で、単純なFAQや文書検索では、Naive RAGで十分な場合もあります。

つまり、「どのRAG手法が最強か」ではなく、「どの失敗モードに、どの処方を当てるか」が重要になってきているのだと思います。

まとめ:RAG改善は「手法追加」から「失敗モード別対応」へ

今回、2024年のMulti Query Retriever記事から約2年後の再検証として、Enhanced RAGとAgentic RAGを横並びで比較しました。

Allganize RAG DatasetとJ-RAGBenchで評価したところ、Query Rewrite、Multi Query、HyDE、ReRankといったEnhanced RAGは、Naive RAGに対して平均スコアを大きく押し上げる結果にはなりませんでした。

Agentic RAGについても、Allganize RAG DatasetではAdaptive RAGが改善した一方で、J-RAGBenchでは安定した改善は見られませんでした。少なくとも今回の範囲では、単にAgent化すればRAGが強くなる、とは言えなさそうです。

関連研究を見ても、強力なLLM時代における複雑なRAG改善の限界効用、Agentic RAGの性能とコストのトレードオフ、取得チャンク数のスイートスポット、RAGとLong Contextの条件依存な使い分けが議論されています。今回の結果は、そうした流れとも整合的に見えます。

実務で見るなら、全質問に同じEnhanced RAGを常時適用するより、失敗モードごとに処方を選ぶ方が自然です。

失敗モード 対応候補
検索意図が曖昧 Query Rewrite
表現揺れ・検索漏れ Multi Query / HyDE
取得文書にノイズが多い ReRank / Context Compression
multi-hop・関係性が重要 GraphRAG / Agentic RAG
型番・価格・日付が重要 Keyword Search / Metadata / DB検索
PDF・表・図に情報がある OCR / Table Parser / Multimodal RAG
回答可否判定が重要 Adaptive RAG / Abstention設計

テキストRAGが終わったわけではありません。 ただし、汎用手法を足して平均精度を押し上げるフェーズから、失敗モードを見極めて適切な処方を当てるフェーズへ移っている。今回の検証は、その変化を実感する結果になりました。

IoTゲートウェイの「設定リモート注入」で難航した記録

ハードウェア制約のあるIoTゲートウェイで「設定リモート注入」をどう設計したか — Greengrass を見送り、S3 + MQTT に着地するまでの判断軸

想定読者: AWS IoT を使ってエッジゲートウェイを組もうとしている開発者の方、特にベンダー提供の制約が強いハードウェアを扱われる方

キーメッセージ: ベストプラクティスらしきもの(Greengrass、Device Shadow など)を一通り検討しましたが、どれも完全には当てはまりませんでした。初期構築の構成として S3 + MQTT + 自作中継 に落ち着くまでの、判断の記録です。Greengrass / Shadow / S3 + MQTT の優劣ではなく、「何が判断軸だったか」を共有することを目的としています。


はじめに

IoTゲートウェイの開発をやっていると、ある時期から決まって同じ問題に当たります。

「現地に置いた機器の 設定 を、どうやって遠隔から更新するのか」

ファームウェアの更新(OTA)はベンダー側の純正機構があることが多く、そこは比較的素直に解けます。本記事で扱うのは、OTAでは粒度が粗すぎる、もっと細かい「設定」の遠隔更新のほうです。

具体的には、センサー較正値・ポーリング間隔・接続先機器の認証情報・AWS の一時クレデンシャルといった、「OS の世代を上げるほどではないが、現地派遣せずに変えたい」情報 のことです。

AWS IoT の世界には、このユースケース向けに見える機能が一通り揃っています。Greengrass V2 の Component UpdateDevice ShadowSSM Parameter Store / AppConfig といったところです。Webで「IoT 設定 リモート 更新」を検索すれば、これらを使った構成例がたくさん見つかります。

ところが、いざ本プロジェクトのハードウェア(マイクロプロセッサ搭載のIoTゲートウェイ機)に当てはめようとすると、どれもピタッとはハマりませんでした

動かないわけではありません。動かせることは確認した上で、運用に乗せたときの歪みが看過できなかった、というのが実態に近いです。

なぜそんなことになるかというと、エッジ側のハードウェア制約とAWS側のサービス前提が、思ったより細かいレイヤーで噛み合わないから です。

具体的には、ベンダー純正OTAの更新粒度が ルートFS丸ごと差し替え だったり、ホストOSのルートFSが 揮発(再起動で初期化) だったり、RAM が潤沢でなかったり、といった事情です。これらは AWS のドキュメントを読んでいる側からは想像しにくく、現場でハードウェアを触ってはじめて見えてきます。

本記事は、その 「ベストプラクティスがそのままハマらなかった現場での、見送り判断と着地点の記録」 です。

Greengrass / Shadow / S3 + MQTT のどれが優れているかという話ではありません。「ハードウェア制約と運用制約のもとで、何を判断軸にして選んだか」 を共有することが目的です。同じような制約を持つ現場の方の判断材料になれば幸いです。


TL;DR

  • マイクロプロセッサ搭載のIoTゲートウェイ機を使った案件で、設定・認証情報のリモート更新をどう実現するかで試行錯誤しました
  • 本命候補だった AWS IoT Greengrass V2 は、ベンダー純正OTA(SWU)との役割重複・揮発ストレージとの相性・SWU という粒度の粗いイメージ媒体との噛み合わせの悪さで見送りました
  • Device Shadow / SSM Parameter Store / AppConfig も候補に挙がりましたが、要件と合わず他手法に流れました
  • 初期構築の構成としては、S3 にJSON設定ファイルを置く → IoT Core MQTT で更新通知 → デバイス側の中継エージェントがダウンロードして配置 → フロー実行基盤がリロード という自作構成に着地しました(フロー実行基盤の現状は Node-RED。次期開発で別基盤への移行を再検討する想定)
  • 決まったベストプラクティスがなく、ハードウェアとクラウドサービスの板挟みの中で組み合わせを探すのが、エッジ設計の現実でした

図1: 検討した4候補の評価一覧です。Greengrass / Shadow / SSM はそれぞれ妥当な選択肢ながら、本機の制約(粗粒度OTA・揮発ルートFS)と噛み合いませんでした。本記事は左の3枚を順に外し、右の採用構成に着地するまでを追いかけます。


背景: なぜ「設定リモート注入」が要るのか

IoTゲートウェイは現場設置型ですので、一度設置したら簡単に触れません。ですが設定は変わります。

本プロジェクトで遠隔更新したい情報は次のとおりです。

  • センサー較正値(スケール下限/上限、換算係数)
  • ポーリング間隔(cron式)
  • 接続先デバイスの接続情報(多数のセンサー機器をぶら下げるため、IPアドレスや認証情報の集合が大きくなりがちです)
  • AWS一時クレデンシャル(S3アップロード用に定期更新が必要)
  • 機器種別のマッピング(同じGWでも接続される機器の構成が変わるケースがあります)

現地派遣なしでこれらを更新する仕組みが要件です。ここまでは普通の話で、問題はどう実装するかでした。


候補1: AWS IoT Greengrass V2

Greengrass V2 は、まさにこれをやるためのサービスに見えます。

  • Component Update: 設定やコードをクラウドから配信、段階的デプロイ
  • Token Exchange Service (TES): X.509証明書で一時AWS認証情報を取得
  • ローカル MQTT ブローカー: Component間のIPC
  • Shadow 連携: 状態同期
  • CloudWatch Logs 連携: ログ集約

設計書の初期版ではこれを前提に書いていました。「Component Update で OTA、TES で認証、Shadow で状態同期」という筋書きです。

見送りの決め手1: ベンダー純正 OTA との二重構造

結論から言いますと、ハードウェアベンダーが純正で提供しているOTA/監視サービスと Greengrass Component Update の役割が重なるのが大きな決め手でした。

具体的にはこうなります。

  • ベンダー純正: OS・コンテナ構成・アプリをA/Bパーティション方式の署名付きイメージで配信。失敗時は自動ロールバック。ベンダー提供の監視コンソールから状態が見える
  • Greengrass: Component 単位でクラウドから配信。バージョン管理・段階的デプロイ・ロールバックを Greengrass 側で持つ

この2つを併用すると、「OS更新はベンダー純正、アプリ更新は Greengrass」という役割分担を厳密に管理する必要があります。しかもどちらも管理コンソールが別物で、オペレーションが二重になってしまいます。

そもそもベンダー側の署名付きOTAが堅牢なため、Greengrass 側の段階的デプロイを使う動機が薄い、という事情もありました。

見送りの決め手2: 揮発ストレージとの相性

もう一つ大きかったのが、ホストOSのルートファイルシステムが揮発(read-only + tmpfs、再起動で初期化)である点との相性でした。

Greengrass は /greengrass/v2 配下にログ・デプロイ情報・アーティファクトを書き続ける前提で動作します。本機ではこれらを 再起動で消えない永続領域 にマッピングし続ける必要があり、Greengrass の想定するファイルレイアウトとはズレが生じます。

  • アーティファクト保管領域とログ書き込み先を別々に永続側へ逃がす設計が要る
  • 再起動のたびに揮発側から永続側へ復元するブートストラップを書く必要がある
  • Greengrass 自体の自動更新が、揮発側の差分を期待して挙動するケースで予測しづらい

見送りの決め手3: SWU という粒度の粗いイメージ媒体との噛み合わせ

ここまで「純正OTAとの重複」「揮発ストレージとの相性」を挙げましたが、ベンダー純正OTAの実体が SWU(SWUpdate 形式の署名付きアーカイブ)である点も、見送り判断に効いてきました。

SWU は OS・カーネル・ルートFS・コンテナイメージ・アプリケーションを 1つのアーカイブにまとめて A/B パーティションへ書き込む タイプの、配信単位がきわめて粗い更新媒体です。これは Greengrass の Component(クラウドから細粒度に配るアーティファクト)と、配信単位の粒度が根本的にズレます。

図2: SWU は「OS〜アプリまで丸ごと積み重なった一枚岩」、Greengrass Component は「クラウドから細かく配られる多数のブロック」を表現しています。面積で粒度の違いを見ていただくのが意図です。両者を併走させると、下段の3つの不整合(消える / 二重ソース / 世代割れ)が運用負担として効いてきます。

具体的に何が困るかと言いますと、

  • 更新粒度が「アプリ部品単位」と「ルートFS丸ごと」で 2系統になる Greengrass は Component 単位の差分配信を前提としますが、SWU は実質「全部入りイメージを差し替える」運用です。「軽い変更は Greengrass、重い変更は SWU」という二系統運用を許容する必要があり、どちらの経路でいまの状態に至ったかが追いづらくなります
  • SWU 更新で Greengrass のローカル状態が吹き飛ぶ/古いまま固まる ルートFSを丸ごと差し替える性質上、/greengrass/v2 配下を永続ボリュームへ逃がしておかないと、SWU 適用のたびに Greengrass のデプロイ状態が消えることになります。逆に永続側へ逃がした場合は、SWU 側で更新したはずの Greengrass 本体・nucleus バージョンが、永続側の古いデータと不整合になる懸念が出てきます
  • Greengrass 本体やデプロイ済み Component を SWU に焼き込むかどうかの判断が要る SWU に焼き込めばオフラインでも初期状態が再現されますが、Greengrass のクラウド側デプロイ管理と二重ソースになります。焼き込まずクラウドからの再取得に任せれば、SWU 適用直後はネットワーク復旧 + Greengrass 同期完了までフルに機能しない期間が発生します。どちらを選んでも歪みが出ます
  • ロールバックの主導権が SWU 側にある A/B パーティションの自動ロールバックが効くため、「SWU は前世代に戻ったが、Greengrass のクラウド側デプロイは新世代のまま」という状態が容易に起こり得ます。ロールバック時の整合性は SWU 側だけで完結させたい運用要件にとって、Greengrass のクラウド側状態を別途巻き戻す手間は割に合いませんでした

つまり、SWU は「粗いがその分シンプルで、署名・A/B・ロールバックまで一体化した強い更新媒体」であり、ここに Component 単位の細かい配信レイヤー(Greengrass)を重ねると、二つの異なる更新粒度を同期させるオーバーヘッドが支配的になります。

動かせることは確認した上で、次の3点を総合して見送りました。

  1. 純正OTA(SWU)と役割が重複し、運用が二重化する
  2. 揮発ルートFS という前提と Greengrass の想定ファイルレイアウトが合わず、永続側へのマッピング設計が窮屈になる
  3. 更新媒体の粒度が SWU(ルートFS丸ごと)と Greengrass(Component単位)で二重化し、状態管理のオーバーヘッドが運用負担として効いてくる

副次的に効いた要因として Java ランタイム(12〜60 MB 規模の常駐プロセス)が増えることも、RAM が潤沢ではない環境では無視できませんでした。

なお、机上判断ではなく実機での nucleus インストール・最小Component デプロイまで動作確認した上での、運用面での見送りです。


候補2: AWS IoT Device Shadow

Shadow は desired / reported で状態同期する仕組みで、設定リモート注入のユースケースに使われる代表例です。本プロジェクトでも、設計初期にかなり真剣に載せ替えを検討しました。

検討の過程で見えてきた、外す決め手になった点を順に挙げます。

  • secrets を載せていいものか問題 対象データに secrets(パスワード)や接続先機器の認証情報が含まれます。Shadow ドキュメントは IAM ポリシーで保護されるとはいえ、「状態同期」のための領域に認証情報が常駐するモデル自体に心理的抵抗がありました
  • ペイロード上限の頭打ち Shadow のペイロード上限は Classic で 8KB、Named でも 30KB です。本プロジェクトは 1台のGWに多数のセンサー機器をぶら下げる前提のため、機器ごとの接続情報・較正値・マッピングを束ねると、近い将来に上限に当たるリスクが見えました
  • Node-RED 連携の現実 Shadow を Node-RED から扱うには node-red-contrib-aws 系か自作ノードが必要です。既存の汎用ノードは更新頻度・メンテナ状況にバラツキがあり、長期運用で塩漬けになるリスクがありました

総じて「Shadow でやれる部分もありますが、Shadow だけで完結しないし、Shadow を入れる動機が決定打にならない」という結論でした。

全体を一つのパターン(後述の S3 + MQTT)で統一したほうが、運用も実装もシンプルにまとまる、というのが最終的な判断です。


候補3: SSM Parameter Store / AppConfig

一般論として挙がりますが、本プロジェクトでは軽く検討して外れました。

  • SSM Parameter Store:
    • エッジからの読み取りに AWS SDK + IAM認証が要り、結局 Greengrass TES 相当の仕組みが必要になります
    • 加えて、本プロジェクトでは 拠点ごとに複数のGWが存在する前提のため、少なくとも /{site}/{gw_id}/...二階層は欲しくなります。SSM Parameter Store の / 区切りでも階層は表現できますが、サイト×GW×設定種別の3次元を一覧・差分管理しようとすると IAM ポリシーやネーミングが膨らみがちで、設定全体を扱うストアとしては窮屈に感じました
  • AppConfig: 設定の段階的配信に強い反面、IoTデバイスからのネイティブサポートがないため、HTTPで取りに行くコードを自前で書く必要があります。S3 直読みと手間が変わりません

採用(初期構築): S3 + MQTT + 自作中継エージェント

結局のところ、既存マネージドサービスの部品を最小限に組み合わせる形に落ち着きました。これは初期構築フェーズで採用した構成です。

フロー実行基盤としては今のところ Node-RED を使っていますが、ここは次期開発で再検討する余地があると考えています(後述「次にやるなら」参照)。

全体フロー

図3: 採用構成の全体フローです。クラウド層(青)と現場層(橙)を上下に物理分離し、5ステップで「アップロード → 通知 → 受信 → 配置 → 反映」の一本道を表現しています。永続領域(🔒)に着地することで、再起動を跨いで設定が生き残ります。詳細なトピック名・パス・補助経路(ack / クレデンシャル取得)は本文を参照ください。

各ステップで使っているのは S3 / IoT Core / MQTT という標準的な部品だけです。マネージドサービスの固有機能に依存していません。

フロー実行基盤の差し替えが起きても、上流(S3 / IoT Core / 中継エージェント)はそのまま流用できる切り分けにしてあります。

設計上の山場1: S3 → MQTT → 中継エージェント → フロー実行基盤 の伝搬経路

「どこからどこまでを一つの"設定更新"として扱うか」が設計の肝でした。

  • S3 だけ更新して通知しない → デバイスは気づきません。ポーリングするとクラウド通信が無駄になります
  • MQTT だけ通知して S3 を更新しない → 何を取りに行けばよいか分かりません
  • 両方を同期的にやる仕組みはAWSにない → ユーザー側で順序を守る必要があります

ここは割り切って、「S3 にアップロードした後、管理者が MQTT 通知を投げる」を手順として明示化しました。

将来的には Lambda で S3 PutObject トリガーから MQTT publish する自動化も選択肢にありますが、初期構成では手動で十分でした。

通知トピックは GW単位(config/update/{site_id}/{gw_id})で切っています。全デバイス一斉更新は避け、GW 単位で個別に当てていける構造にしてあります。

本格的なステージング配信(カナリアロールアウト等)を組むなら、Lambda + 配信スケジューラを別途立てる前提です。

設計上の山場2: IoT Credential Provider を直叩きする認証情報管理

Greengrass TES を使わないということは、デバイスが AWS サービスに直接アクセスするための一時認証情報を自分で取りに行く必要があります。

ここは AWS IoT の Credential Provider + Role Alias を直接叩く実装にしました。流れは次のとおりです。

  1. 事前準備: AWS IoT で Role Alias を作成し、IAM Role をアタッチします(S3 アップロード権限など)
  2. デバイス側: Thing 証明書を使って Credential Provider エンドポイントに HTTPS GET します
    • URL: https://{account-specific-prefix}.credentials.iot.{region}.amazonaws.com/role-aliases/{role-alias}/credentials
    • ヘッダ: x-amzn-iot-thingname: {thing-name}
    • クライアント証明書: 秘密鍵 + デバイス証明書 + AmazonRootCA1
  3. レスポンス: アクセスキー / シークレット / セッショントークン / 有効期限
  4. デバイス側: 10分の安全マージンを取って、期限切れ前にリフレッシュします

取得した認証情報はフロー実行基盤側のメモリ上(現状は Node-RED の global 変数)に保持し、S3 アップロードなど AWS SDK 的な処理をするときに取り出して使います。

この実装は小さなユーティリティですが、Greengrass を見送った以上、すべてのコンポーネントがここを通るため、落ちると連鎖的に影響します。

単体テストを厚めに書きました(credentials の有効性判定、期限切れ判定、リフレッシュ要否判定)。

設計上の山場3: 設定ファイルの配置とロードタイミングの設計

設定リモート注入を「現地で壊さず」「想定どおりのタイミングで反映させる」ためには、結局のところ 「設定ファイルをデバイス上のどこに置き、いつ・どう読み直すか」 という、デバイス側の設定ライフサイクル設計で勝負がつきます。

S3 / MQTT / 中継エージェントといった経路の話は前の山場1〜2で整理しましたが、経路がいくら正しくても、着地後のファイル取り扱いが雑だと、リロードのたびにランタイム状態が壊れたり、古い処理と新しい設定が混ざったりします

ここはエッジ側の品質をそのまま規定する論点でした。

図A: 3つのトリガー(起動・reload通知・障害復旧)はすべて Loading に集約され、Waiting(走行中ジョブの完了待ち) → Swap(入替) を経てはじめて Active に戻ります。設定が動的に差し替わる系で必ず効いてくる骨格です。

どこに置くか(配置)

  • 配置先: 再起動で消えない永続領域配下に、環境別サブディレクトリで置きます
    • 例: {永続領域}/config/{env}/設定ファイル{env} は dev / stg / prod 等)
  • 揮発するルートFS側には置きません(再起動で消えます)
  • 書き換えはアトミック書き込みを徹底します。*.tmp に書いてから rename(2) で差し替える、というよくある手順です
    • 中途半端な状態の JSON を読み手側が読みに行ってパースエラーを起こすと、リロードのたびにプロセスが unhealthy になるため、ここはケチりませんでした
  • 中継エージェントとフロー実行基盤の両方が同じファイルを参照します。書く責務は中継エージェント側だけ、フロー実行基盤は読み専として、書き手を一本化しました

どのタイミングでロードするか(タイミング)

ロードのトリガーは次の3つに整理しました。

  1. 起動時の初期ロード: 起動時に最新の設定ファイルを読み、ランタイムに展開します。ファイルが無い場合は ack を返さずに安全側(既存設定で起動しない)に倒します
  2. reload 通知契機の再ロード: 中継エージェントから flow/config/reload を受信した時点で、ファイルを読み直してランタイムを入れ替えます。OS のファイル監視機能には頼りません。理由は、書き手と読み手が別プロセスである以上、書き込み中の中間状態をファイル監視側が拾ってしまうリスクがあるためです。「書き手が書き終わった」ことを MQTT 通知の発火点で表明する方が、責務として素直です
  3. 障害復旧時のフォールバック: MQTT 切断中に設定が更新されていた場合に備え、再接続直後に最新版の取得確認を行います

どう反映するか(反映の二段構え)

ロードした設定の中身は、性質の異なる2種類に分けて扱いました。

  • データとしての設定値(接続先IP、認証情報、較正値、機器マッピング、AWS一時クレデンシャル など)
    • これはランタイムのデータ領域に上書きするだけで反映されます
    • 次に値を参照する処理から自然に新しい値が使われます
  • ランタイム状態に紐づく設定(スケジュール、コネクションセッション、購読中のトピック など)
    • これは値を上書きしただけでは反映されません。いま走っている状態を一度落として、新しい設定で立て直す必要があります
    • 例として、スケジュールの差し替えは「既存ジョブを止める → 新しい cron 式で再登録する」という二段階処理になります
    • この種の差し替えは順序を間違えると状態が消えっぱなしになるため、リロード手順を関数として一本化し、単体テストで担保しました
    • さらに、reload 通知を受けた瞬間に前のポーリング・送信処理がまだ走っている可能性があります。リロード処理の入口でフラグを立て、走行中のジョブが完了するのを待ってからランタイムを入れ替える仕組みを入れています。ここは「Node-RED だから」というより、設定を動的に差し替える系一般で必ず効いてくる論点です

結果: 動いたけれど

落ち着いたところを振り返ると、次のような構造になっていました。

本来 Greengrass が提供する機能 本プロジェクトの代替
Component Update による OTA SWU(A/B パーティションの署名付きイメージ)
Token Exchange Service IoT Credential Provider + Role Alias を自前で直叩き
Component 間 IPC ローカル Mosquitto (MQTT)
Shadow 連携(状態同期) 不採用(候補2 参照)。設定リモート注入は S3 + MQTT で代替
ローカルデバッグコンソール フロー実行基盤のエディタ(現状: Node-RED)
設定配信(OTA は別行で SWU がカバー) S3 + IoT Core MQTT publish + ack(GW 単位での個別配信)
設定ライフサイクル管理 中継エージェントによるアトミック書き込み + 通知駆動リロード
ログ集約 podman logs + ベンダー純正監視コンソール
バージョン管理・ロールバック S3 オブジェクトバージョニング(設定)/SWU の世代管理(OS・アプリ)

機能単位で見れば、だいたいカバーできています。ただし、Greengrass が提供する「統一された管理レイヤー」は失われています。

トラブルシュートは各層(S3 / IoT Core / 中継エージェント / フロー実行基盤 / OS)で個別にログを見る必要があります。

これが正解だとは思っていません。ただ、ハードウェア制約と運用制約(粒度の粗い純正OTAとの共存・揮発ストレージ)を満たすのがこの形だった、という事例として残しておきたいと考えています。


学び(テクニカルな反省)

  • 「AWS IoTのベストプラクティス」はハードウェアを選びます。Greengrass V2 は aarch64 + 1GB RAM 以上の環境なら素直に効きます。armv7 + 512MB + ベンダー純正OTAあり + 揮発ルートFS、という組み合わせでは合わないことがあります
  • ベンダー側が提供している機構(OTA、監視)と AWS 側のサービスで機能重複が起きるとき、AWS 側を入れるコストのほうが大きくなる場合があります
  • 更新媒体の「粒度」が合うかどうかは、機能の有無よりも実は支配的です。SWU のようなルートFS丸ごと差し替え型の更新と、Greengrass の Component 単位の細粒度配信を併走させると、状態管理のオーバーヘッドが運用負担として現れます
  • 設定リモート注入は「経路」より「着地後のライフサイクル」で勝負がつくことがあります。S3 / MQTT / 中継エージェントの経路設計だけでなく、書き込みのアトミック性・ロードのトリガー設計・ランタイム状態の差し替え順序まで含めて初めて、現地で壊れない仕組みになります
  • 「設定をJSONファイルとしてS3に置く」は原始的ですが、関与するレイヤーが少なく、各層で独立にテスト可能です。スケールしない規模では十分強い選択肢でした
  • IoT Credential Provider は地味ですが便利です。Greengrass を使わない場合でも、これを直接叩けば AWS SDK 的な処理は回せます
  • Device Shadow は万能ではありません。サイズ制限・連携ノードの品質を考えると、候補から外れるケースがあります
  • Node-RED は立ち上げ初期に強い反面、長期運用での限界(フロー JSON の差分レビュー困難・水平スケーラビリティ・観測性・contrib 品質)が顕在化しやすいです。初期構築では割り切って使い、規模が大きくなる前に別基盤への移行を検討する余地はあります

特筆すべき T&E 事例を一つ挙げておきます。Greengrass 検証中の一時期、ローカル開発環境のビルドに非常に時間がかかったことがありました。

クロスアーキのマルチステージビルド、gdk CLI による Component 作成、Docker-in-Docker の検証を並行すると、ビルド環境自体のメンテナンスが主業務化する現象が発生します。これも「Greengrass 寄りの開発は軽くない」と実感した瞬間でした。


次にやるなら(次期開発に向けて)

初期構築で割り切った部分のうち、次期開発で再検討したい論点を並べておきます。

運用面の改善(短中期)

  • S3 PutObject → Lambda → IoT MQTT publish の自動化(手動通知の撤廃)
  • 設定スキーマの JSON Schema 化と CI 検証(不正な設定を配布して現地で壊さないため)
  • AWS IoT Jobs の再検討。Shadow より大きなペイロードを扱えるジョブキュー型で、OTA 以外の用途(設定更新)でも使える可能性があります

基盤の差し替え(中長期)

  • フロー実行基盤の差し替え検討。Node-RED から、コード表現可能で観測性の高いランタイムへ。候補としては、Python asyncio / Rust / Go 製の小型ワーカ、メッセージング層の NATS / Redis Streams 化、変換ロジックのクラウド側ストリーム処理(Kinesis / MSK + Flink)への寄せ、などです
  • 段階的移行の前提: 初期構築で動いている Node-RED フローはそのまま「仕様」として活かし、新基盤に対する受け入れテストの参照とする。一斉切り替えではなく、役割を切り分けて段階的に移行する

おわりに

ここまで、Greengrass / Device Shadow / SSM Parameter Store / AppConfig という4つの候補を順に外し、S3 + MQTT + 自作中継エージェントに着地するまでをたどってきました。最後に、本記事から持ち帰っていただきたいことを3点に絞ります。

1. ベストプラクティスの優劣ではなく、「制約と噛み合うか」が判断軸です

Greengrass V2 は優れたサービスですし、Device Shadow も SSM Parameter Store も、それぞれ素直にハマる現場では強力です。本記事はこれらを否定するものではありません。

重要なのは、「自分の現場のハードウェア制約・運用制約と、サービス側の前提が噛み合うか」を、ドキュメント上ではなく実機で確かめることです。

Greengrass を見送った最大の理由は機能の不足ではなく、SWU との更新粒度のズレでした。これはAWSのベストプラクティス資料からは見えてきません。

2. エッジ設計では「機能があるか」より「更新粒度・永続化・運用責務が噛み合うか」が支配的です

クラウド単独のシステムなら「必要な機能があるか」が最初の問いになります。

一方、エッジを含むシステムでは、機能の有無は満たせても、それを支える前提(ファイルレイアウト、永続化方針、更新粒度、運用責務の分担)が噛み合わないと、運用に乗せた瞬間に歪むことが多いです。

設計初期の評価軸に、「更新の粒度」「永続化境界の位置」「責務分担の重複」を必ず入れることをおすすめします。

3. 同じような制約を持つ現場では、今回の試行錯誤がそのまま判断材料になります

「ベンダー純正OTA + 揮発ルートFS + RAM 制約」という組み合わせは、本プロジェクト固有の話ではありません。産業用エッジ機器ではむしろ普通の構成です。

同じ制約に当たった方が、「Greengrass を載せようとしてうまくいかなかったが、なぜダメだったか言語化できない」という状態から一歩進むための、判断材料になれば嬉しいです。

S3 + MQTT という素朴な答えに着地したこと自体が結論ではなく、「制約を直視して、足りる範囲で組む」というスタンスのほうが、本記事の本当の中身だと思っています。

決まったベストプラクティスがない領域で、ハードウェアとクラウドの板挟みの中から組み合わせを選び取るのが、エッジ設計の現実です。本記事がその実例の一つとして、読んでいただいた方の次の一手に少しでも役立てば幸いです。


参考資料

S2S APIでどこまで作れるのか? 〜gpt-realtime 1.5 と System Prompt / Tool Callingだけで試した3つの音声アプリ

はじめに

前回の記事では、S2S(Speech-to-Speech)APIを比較し、体験品質・知能性能・レイテンシといった観点から各モデルの違いを整理しました。 またRAG編では、Tool Callingを含めた実務的な観点での選び方を扱いました。

今回は少し方向を変えて、実際にどこまで「アプリケーションとして成立するのか」を試した内容を紹介します。

S2Sはここ1年で急速に進化していますが、「実際に業務として使えるのか」という点はまだ見えづらい部分もあります。

そこで今回は、できるだけシンプルな構成に限定し、どこまで成立するのかを検証しました。

  • モデルは gpt-realtime-1.5 のみ
  • 構成は System Prompt + Tool Calling
  • いわゆる「複雑なエージェント構成」は使っていません

この条件で、以下の3つの音声アプリを試作しました。

  • App1: 電話応対
  • App2: 通訳
  • App3: AI面接官

結論から言うと、想像していたよりもかなり実用に近いところまで成立しました。

全体として見えたこと

今回の3アプリはいずれもシンプルな構成ですが、共通して次のような特徴がありました。

  • 会話の進行はほぼ System Promptだけで制御可能
  • 「確定的な出力」だけを Tool Callingで外に出すと安定する
  • モデルの知能というより、責務分離の設計で体験が大きく変わる

特に印象的だったのは、 「どこまでを会話に任せ、どこからを構造化するか」の切り分けでした。

この点を踏まえつつ、それぞれのアプリを見ていきます。

App1: 電話応対

最初に作ったのは、いわゆる一次受電の電話応対です。

役割はシンプルで、

  • 宛先
  • 名前
  • 折り返し先電話番号

を聞き出し、電話メモとして残す、というものです。

構成としては、

  • 会話進行 → System Prompt
  • メモ確定 → save_call_memo(Tool)

という分離にしています。

実際の出力イメージ

電話メモ

やっていることはかなり単純

  • 1ターン1質問
  • 必須項目が揃ったらToolを呼ぶ
  • 名前と電話番号は復唱

いわゆる新人研修レベルの電話応対ルールを、そのままプロンプトに書いています。

想像以上だった点

実際に動かしてみると、かなり自然に成立しました。

  • 相手が話した内容を踏まえて質問順を調整する
  • すでに出た情報を重複して聞かない
  • 電話番号を正確に聞き取り、復唱する

特に印象的だったのは、電話番号の扱いの安定性です。 多少聞き取りが曖昧でも、無理に補完せずに聞き直す動きが自然に出ます。

また、1回の通話コストも数円程度で、 単純な一次受電の一部は置き換え可能な感触がありました。

設計上のポイント

このアプリで重要だったのは、次の分離です。

  • 会話 → プロンプトに任せる
  • 確定出力 → Toolで外に出す

これにより、

  • 会話は柔軟に
  • 出力は安定して構造化

という状態を作れます。

App2: 通訳

次に試したのが、音声通訳です。

最初は単純な逐次通訳を想定していましたが、最終的には3セッション構成にしています。

  • Channel A: 日本語 → 対象言語
  • Channel B: キャラクター
  • Channel C: 対象言語 → 日本語

UIイメージ

3チャンネルUI

2セッションではうまくいかなかった

当初は「通訳+キャラ」の2セッションで試しましたが、

  • 入力と出力の向きが混線する
  • モデル同士が会話し始める

といった問題が発生しました。

結果として、

➡︎ 双方向通訳を1セッションに持たせない

という方針に変えています。

特に、同一セッション内で双方向の通訳を担わせると、入力と出力の向きの解釈が不安定になり、結果としてモデル同士が会話を継続してしまうような挙動が発生しやすい状態でした。

同時通訳的な体験

途中から逐次通訳ではなく、ストリーミング寄りの同時通訳にすると体験が大きく変わりました。

  • こちらが話している途中に通訳が始まる
  • それに対してキャラクターが反応する
  • その応答がまた通訳されて戻ってくる

従来の「順番に待つ」通訳ではなく、 被りながら会話が進む感覚になります。

これはかなり新鮮でした。

設計上のポイント

このアプリのポイントはシンプルです。

  • 通訳は「忠実変換」に徹する
  • 会話はキャラクター側に寄せる
  • 双方向を分離する

結果として、

➡︎ セッション分割がそのまま品質に効く

という構造になっていました。

App3: AI面接官

最後に作ったのが、AI面接官です。

これは、

  • App1のTool(構造化出力)
  • App2の複数セッション

を組み合わせた形になっています。

UIイメージ

面接フィードバック

構成

  • 面接官(AI)
  • 応募者(AI or 人間)

に加えて、

  • 求人票
  • 職務経歴書

をプロンプトに差し込んで面接を進めます。

最後に、

➡︎ submit_interview_feedback

を呼び出して評価を出します。

想像以上だった点

これもかなり驚きがありました。

  • 面接の流れを自然に進行する
  • 回答に応じて深掘りする
  • 最後に構造化された評価を出す

特に面接官側は、かなり“ちゃんとした面接官”として振る舞います。

一方で、あまりにも正確に評価してくるため、 体感としてはやや厳しめに感じる場面もありました。

AI応募者の挙動

応募者もいくつかパターンを用意しましたが、

  • 優秀な候補者
  • やや弱い候補者

の差がかなり自然に出ます。

特に「書類は通るが決め手に欠ける」ような微妙なラインも再現されており、 このあたりは実務的にも興味深い結果でした。

設計上のポイント

このアプリでは、

  • 会話(面接進行) → プロンプト
  • 評価(最終出力) → Tool

という分離に加えて、

➡︎ コンテキスト(求人票・職務経歴書)をそのまま渡す

ことが効いています。

まとめ

今回の3つの試作から見えてきたのは、

S2Sはまだ万能ではないものの、 「型のある会話業務」はかなりの精度で成立するということでした。

特に重要なのは、

  • モデルの性能そのもの
  • よりも
  • どの責務をどこに置くか

です。

整理すると、

  • 会話 → プロンプト
  • 確定出力 → Tool
  • 複雑な役割 → セッション分割

この3点で、多くのユースケースは一定の形になります。

少なくとも今回の範囲では、「複雑なエージェント構成を組まなくても、ここまでは作れる」というラインは見えたように思います。

おわりに

今回の内容はあくまで試作レベルですが、 S2Sが「デモ」から「実装」に近づいてきている感触はありました。

一方で、

  • 安定性
  • 長時間運用
  • エッジケース対応

といった点はまだ検証が必要です。

今後もこのあたりは継続的に試していきたいと思います。

S2S API比較:RAG編 〜Speech-to-SpeechでRAGを使うなら、何を選ぶべきか

はじめに

前回の記事では、S2S(Speech-to-Speech)APIを単体で比較し、体験品質・知能性能・レイテンシといった観点から各モデルの違いを整理しました。素のS2S APIとして見ると、GPT Realtime 1.5 はバランスが良く、Gemini 3.1 Flash Live は体験品質が高く、Gemini 2.5 Flash Live は知識系に強く、Nova 2 Sonic は低遅延が目立つ、という構図でした。

techblog.heroz.jp

ただ、実際のシステム開発では、単体の対話性能だけでモデルを選ぶ場面はそれほど多くありません。業務システムや社内向けアシスタントを考えると、外部知識を参照するRAG(Retrieval-Augmented Generation)を前提にした設計になることがほとんどです。すると、見るべきポイントは少し変わります。S2S単体では「自然に話せるか」「賢く答えられるか」が中心でしたが、RAGではそれに加えて「正しい文書に到達できるか」「検索結果を会話の中でうまく扱えるか」が重要になります。

さらに、RAGではモデルの素の能力だけでなく、実装のしやすさも無視できません。Tool Callingの仕様、ツール結果の返し方、イベント処理、再開制御などがモデルごとにかなり異なるため、表のスコアだけでは実務上の扱いやすさまでは見えてきません。実際に組み込んでみると、同じ「Tool Calling対応」でもかなり性格が違い、その差がそのまま開発コストや安定性に跳ね返ってきます。

本記事では、S2SでRAGを使う前提で各モデルを比較し、どのモデルがどの用途に向くのかを整理します。前回の基本編の続きとして、今回は「RAGを入れると評価軸がどう変わるか」を中心に見ていきます。

比較対象と評価設計

今回の比較対象は、前回と同じく以下の5モデルです。

  • GPT Realtime 1.5
  • GPT Realtime Mini
  • Nova 2 Sonic
  • Gemini 2.5 Flash Live
  • Gemini 3.1 Flash Live

評価は、Tool Calling 40問、RAG 35問で実施しました。採点には Claude 4.5 Sonnet を用いた LLM as a judge を利用し、各項目を 1〜5点の5段階評価(5点満点) で採点しています。前回と同様、単に「答えられたか」だけではなく、実際の利用場面を意識して複数の観点に分解して見ています。

今回のRAG評価で主に見たのは、次の3つです。

  • 検索性能 検索精度、正答文書到達、根拠提示、multi-hop推論
  • 会話型RAG 文脈依存、曖昧質問対応、ハルシネーション耐性、結果統合
  • システム特性 レイテンシ、コスト、実装時の扱いやすさ

ここで重要なのは、RAGでは単体性能の延長で順位が決まるわけではない、という点です。S2S単体で好印象だったモデルが、RAGを入れるとそのまま有力とは限りません。逆に、基本編では不利だったモデルが、RAGでは十分候補に入ることもあります。今回はその差がかなりはっきり出ました。

結論

先に結論をまとめると、今回の位置づけは次の通りです。

  • 第一候補:Gemini 2.5 Flash Live 検索精度、根拠提示、会話型RAGのいずれも高く、RAG性能が最も高かった
  • 安定性重視:GPT Realtime 1.5 極端な弱点が少なく、実装も含めて扱いやすい
  • 速度重視:Nova 2 Sonic 基本編では厳しかったが、RAGでは実用圏まで健闘した
  • 今回の用途では弱い:GPT Realtime Mini 軽量さはあるが、RAG性能はかなり厳しい

要するに、RAGでは「最強モデル」を一つ決めるというより、何を優先するかで選び方が変わるということです。今回の比較では、知識性能なら Gemini 2.5 Flash Live、安定性なら GPT Realtime 1.5、速度なら Nova 2 Sonic という整理が最もしっくりきました。

RAG性能(検索能力)

まずは、RAGの土台になる検索性能を見ます。今回は、検索精度(基本1hop)、正答文書到達、根拠提示、multi-hop推論を中心に評価しました。

検索精度 (基本1hop) 正答文書到達 (類似誤参照耐性) 根拠提示 (暗黙評価) Multi-hop 推論 検索性能 (平均)
Gemini 3.1 Live 2.6 2.4 2.6 2.8 2.6
Gemini 2.5 Live 4.3 3.5 4.1 3.1 3.7
GPT Realtime 1.5 3.4 2.1 3.4 2.8 2.8
GPT Realtime Mini 1.5 2.1 1.5 1.0 1.4
Nova 2 Sonic 3.3 2.4 3.3 3.2 3.1

検索性能表

まず目立つのは、Gemini 2.5 Flash Live が検索性能全体で一歩抜けている点です。検索精度だけでなく、正答文書到達や根拠提示も高く、RAGで重要な指標がきれいに揃っています。単にそれらしい答えを返すのではなく、検索した文書を比較的うまく使えている、という印象です。

一方で、他モデルはそれぞれ強みと弱みがはっきり出ました。ここは表をそのまま読み上げるより、要点だけ押さえた方が分かりやすいと思います。

  • Gemini 2.5 Flash Live 検索精度、正答文書到達、根拠提示のバランスが最も良く、今回の比較では最もRAG向きでした。似た文書が複数ある状況でも比較的正しい文書に着地できており、知識アクセスを伴う用途では第一候補です。

  • GPT Realtime 1.5 検索精度と根拠提示は一定水準でしたが、正答文書到達はやや弱く、類似文書への誤参照耐性には課題が残りました。検索自体はできても、似た情報が並ぶ状況で安定して正しい文書を使えるかという点では、Gemini 2.5 Flash Live に一歩譲る結果です。

  • Gemini 3.1 Flash Live 基本編では体験品質の面で好印象だったモデルですが、RAG性能だけを見ると今回は伸び切りませんでした。Gemini系だからRAGにも強いだろうと見たくなるところですが、少なくとも今回の条件では Gemini 2.5 Flash Live の方が明確に上でした。

  • GPT Realtime Mini 軽量モデルとしての魅力はありますが、検索精度、根拠提示、multi-hop推論のいずれも低めで、RAGの検索基盤としては力不足でした。今回の用途ではかなり厳しい印象です。

  • Nova 2 Sonic 基本編の印象より健闘しました。トップではありませんが、検索性能としては十分実用圏で、少なくとも「低遅延だがRAGには弱い」と単純には言えない結果でした。

今回の結果から見えてくるのは、RAGでは単体の知能性能と検索性能が必ずしも一致しないということです。素の会話で賢く見えるモデルが、検索文書を挟んだ途端に不安定になることがありますし、その逆もあります。RAGでは「何を知っているか」よりも、「必要な知識にどう到達するか」の比重が大きくなります。

会話型RAG

ただし、RAGは検索性能だけでは決まりません。S2Sで重要なのは、検索結果を会話の中にどう統合できるかです。そこで今回は、会話型RAG(文脈依存・参照解決)、曖昧質問対応、ハルシネーション耐性、結果統合、ツール選択も合わせて見ました。

会話型RAG (文脈依存・参照解決) 曖昧質問対応 ハルシネーション耐性 (負例) 結果統合 ツール選択
Gemini 3.1 Live 3.2 2.8 3.0 2.8 3.0
Gemini 2.5 Live 4.2 4.5 4.2 2.2 2.0
GPT Realtime 1.5 3.3 3.8 2.3 3.0 2.4
GPT Realtime Mini 2.7 2.5 1.2 2.7 2.4
Nova 2 Sonic 3.8 4.0 3.0 2.3 2.4

会話型RAG表

ここでも最も強かったのは Gemini 2.5 Flash Live でした。会話型RAG、曖昧質問対応、ハルシネーション耐性が全体に高く、検索結果を会話の流れの中で使う能力まで含めて強さが出ています。音声対話では、ユーザーが毎回きれいに検索語を話してくれるわけではなく、「あれ」「さっきの件」「前の資料」のような曖昧な参照が普通に出てきます。そのときに、会話履歴と検索をつなげて扱えるかどうかは、体験を大きく左右します。

各モデルの傾向を整理すると、次のようになります。

  • Gemini 2.5 Flash Live 会話型RAGでも最も強く、曖昧な問いや文脈依存の参照に比較的安定して対応できました。今回のRAG評価では、検索性能だけでなく会話としての扱いやすさまで含めてトップです。

  • GPT Realtime 1.5 突出はないものの、全体的に安定していました。曖昧質問対応や結果統合は大崩れせず、会話として破綻しにくい印象があります。ピーク性能ではなく、バランスの良さが強みです。

  • Gemini 3.1 Flash Live 基本編の体験品質ほどの強さは今回は出ませんでした。会話型RAGでは中位に収まり、素のS2Sとしての良さと、RAG込みでの強さは別物だということがよく分かる結果です。

  • GPT Realtime Mini 会話型RAG、曖昧質問対応、ハルシネーション耐性のいずれも低く、今回の用途では厳しいという評価を補強する結果でした。

  • Nova 2 Sonic 会話型RAGでも一定の健闘を見せました。トップではないものの、基本編からの印象よりはかなり実用寄りで、速度重視の選択肢としては十分に検討できます。

この結果から見えてくるのは、RAGでは単発の検索精度だけでなく、会話として扱えるかどうかがかなり重要だということです。検索で上位文書を取れても、その結果を文脈に接続できなければ、ユーザー体験としては弱いままです。S2SでRAGを使うなら、ここは外せない観点です。

レイテンシ(重要な注意点)

RAGを導入すると、レイテンシは必ず増加します。これはどのモデルでも避けられません。 ここでのレイテンシは、問い合わせから最初のトークンが返ってくるまでの時間(TTFT相当)で測定しています。RAGではこの値が、検索処理の影響を受けて増加します。

S2S単体レイテンシ (ms) RAGレイテンシ (ms) 増加量 (ms) 増加率 (%) コメント
Gemini 3.1 Live 1202.5 2153.5 950.9 79.1% 高性能だがRAGで遅延が顕著
Gemini 2.5 Live 2503.5 3859.8 1356.2 54.2% RAG性能は高いが構造的に重い
GPT Realtime 1.5 738.5 1401.5 663.0 89.8% バランス型(増加も標準的)
GPT Realtime Mini 764.0 796.8 32.8 4.3% ほぼ増加なし(RAGが機能していない)
Nova 2 Sonic 207.1 740.5 533.4 257.6% 低遅延だがRAGで相対的に崩れる

レイテンシ比較表

今回の比較では、Gemini 2.5 Flash Live は 2503.5ms から 3859.8ms、GPT Realtime 1.5 は 738.5ms から 1401.5ms、Nova 2 Sonic は 207.1ms から 740.5ms へと伸びました。増加率だけを見ると Nova 2 Sonic がかなり大きく見えますが、最終的なレイテンシ自体はまだ十分速い水準にあります。

一方で、GPT Realtime Mini は増加がほとんど見られませんでした。ただし、これは単純に強みと見るより、検索性能や会話型RAGの結果と合わせて解釈する必要があります。レイテンシが小さいこと自体は魅力ですが、RAGとして十分に機能していないのであれば評価は変わります。

ここで押さえておきたいのは、RAGでは遅くなること自体が例外ではなく前提だということです。検索処理が入る以上、S2S単体より速くなることはありません。したがって、レイテンシは「どのモデルが遅いか」を競うというより、RAG込みでどこまで許容できるかで見る方が実務的です。

RAGの流れ

S2SにRAGを組み込むと、処理の流れは概ね次のようになります。 S2S単体の対話に検索処理が追加されることで、RAGでは応答までの流れが一段増えます。

RAGシーケンス図

この図で見ておきたいのは、S2S単体の対話に検索処理が追加される、という点です。音声対話ではこの追加処理がそのまま待ち時間や応答テンポに影響するため、RAGではレイテンシが一つの注意点になります。

ただし、本記事の主眼はあくまで「どのモデルがRAGに向くか」です。レイテンシは重要ですが、それだけでモデル選定が決まるわけではありません。検索性能、会話型RAG、実装の安定性と合わせて見る必要があります。

実装してみて分かったこと(Tool Calling編)

ここからは、表の数字だけでは分かりにくい実装上の差です。前回の記事では、S2S一般の実装差、割り込みや停止、ターン制御なども扱いました。今回はそこには踏み込まず、Tool Callingを使ったRAG実装で実際に効いた差に絞って整理します。

GPT Realtime 1.5

GPT Realtime 1.5 は、Tool Calling自体は比較的素直でした。function calling の流れは追いやすく、イベント構造も理解しやすいため、今回の5モデルの中では最も見通しが立てやすい部類です。

特に重要だったのは次の点です。

  • tool実行後に明示的な再開処理が必要だった GPT Realtime 1.5 では、tool call を受けて検索を実行し、function_call_output を返しただけでは、そのまま最終回答が生成されないケースがありました。ここで response.create を送って会話を再開しないと、ツールは呼ばれたのに最終応答が返らない状態になります。 これは実装してみるまで見落としやすく、評価系でもかなり影響が大きいポイントでした。tool call 前後の response.done を区別せずに処理すると、「無応答だった」と誤判定しやすくなります。Specでも、OpenAI Realtime は function_call_output 投入後に追加の response.create が必要だったことが明記されています。

このモデルは、実装上の癖がまったくないわけではありませんが、癖の出方が比較的分かりやすく、修正方針も立てやすいのが強みでした。RAG性能そのものでは Gemini 2.5 Flash Live に譲る場面があっても、実務で「まず崩れにくいものを選びたい」ときにはかなり有力です。

GPT Realtime Mini

GPT Realtime Mini も系統としては GPT Realtime 1.5 に近く、Tool Callingの考え方も大きくは変わりません。ただし、今回のRAG用途では肝心の性能面が伸びませんでした。

実装面で特別大きな落とし穴があるというより、ちゃんと実装しても、RAGとしての見返りが小さいというのが率直な印象です。軽量モデルとしての魅力はありますが、S2SでRAGをしっかり使いたいという前提では、選びにくい結果でした。

Gemini 2.5 Flash Live / Gemini 3.1 Flash Live

Gemini系は、性能面では特に Gemini 2.5 Flash Live が強かった一方で、Tool Calling実装では最も気を使う部分が多いグループでした。OpenAI互換の実装をそのまま持ち込めず、Gemini向けに合わせ直す必要があります。

特に効いたのは次の点です。

  • OpenAI互換のschemaをそのまま受け付けなかった Gemini 2.5 Flash Live / Gemini 3.1 Flash Live では、OpenAI向けの tool schema をそのまま渡しても受理されず、Gemini向けに変換する必要がありました。たとえば additionalProperties などを落とさないと通らず、共通のツール定義をそのまま各モデルに流す設計では破綻しやすいです。 Tool Callingをマルチプロバイダで共通化したい場合、この差分吸収レイヤはほぼ必須でした。Specでも、Gemini向け schema への変換が必要だったことが明記されています。

  • FunctionResponseにtool callのidが必要だった Gemini系では、nameresponse だけ返せばよいわけではなく、対応する tool call の id を明示的に返す必要がありました。ここが欠けると、見た目上は正しいレスポンスを返していても受理されません。 OpenAI系と同じ感覚で実装するとハマりやすい点で、Tool Callingのレスポンス形式そのものが微妙に違うことを意識する必要があります。

  • tool call と他イベントが同居し、取りこぼしやすかった Gemini 3.1 Flash Live では、usage_metadatatool_call が同一 response に同居するケースがあり、usageを先に処理すると tool call 自体を取りこぼして無応答に見えることがありました。 また、Gemini系は全体としてイベント粒度がやや扱いづらく、Tool Callingだけを独立して雑に処理すると不安定になりやすい印象があります。イベントを逐次1件ずつ単純に返す設計ではなく、どの情報を優先して拾うかを考えて受信ループを組む必要がありました。

Gemini系は、RAG性能だけ見れば今回かなり有力です。ただし、Tool Calling実装まで含めると、強いモデルであるほど丁寧な受け側が必要になる、という印象でした。特に Gemini 2.5 Flash Live を使うなら、schema変換とレスポンス整形は最初から前提にしておいた方がよさそうです。

Nova 2 Sonic

Nova 2 Sonic は、Tool Callingまわりの仕様が最も厳格でした。自由に設計するというより、仕様に正確に合わせること自体が実装になります。

特に重要だったのは次の点です。

  • tool schemaの渡し方がかなり特殊だった Nova 2 Sonic では、inputSchema.json を JSON文字列で渡す必要があり、一般的なJSONオブジェクトとしてそのまま載せる実装だと通りませんでした。また、toolUseOutputConfiguration も必要で、ツール利用時の周辺設定まで含めて揃えないと動きません。 ぱっと見では小さな差に見えますが、共通実装を組もうとするとここが大きな分岐になります。OpenAI系やGemini系と同じ前提では作れません。

  • tool resultの返却形式が専用で、少しでもずれると落ちた Nova 2 Sonic では、tool result を単純なテキスト応答として返すのではなく、contentStart(type=TOOL) -> toolResult -> contentEnd のような専用構造で返す必要がありました。これを単発イベントで返したり、誤って textInput 的に返すと受理されません。 しかもエラーは必ずしも親切ではなく、ValidationExceptionModelStreamErrorException のような抽象的な形で返るため、原因の切り分けに時間がかかりました。Specでも、Nova 2 Sonic は tool use まわりが最も癖が強かったと整理されています。

Nova 2 Sonic は、性能評価だけ見ると今回かなり健闘しています。ただし、その裏側では「仕様に正確に合わせる」実装負荷が相応にあります。速度を優先して選ぶ価値はありますが、Tool Calling部分は雑に共通化せず、専用分岐を用意した方が安定します。

まとめ

今回の比較で見えてきたのは、S2SでRAGを使うと評価軸がかなり変わるということです。基本編では、体験品質や素の知能性能、低遅延性が前面に出ていましたが、RAGを入れると「正しい文書に到達できるか」「その結果を会話の中で扱えるか」がより重要になります。

今回の結論をあらためて整理すると、次の通りです。

  • 知識性能を重視するなら Gemini 2.5 Flash Live
  • 安定性を重視するなら GPT Realtime 1.5
  • 速度を重視するなら Nova 2 Sonic

逆に、GPT Realtime Mini は今回の用途ではかなり厳しいというのが率直な印象でした。

RAGでは、単純なランキングよりも、何を優先するかで選ぶ方が実務的です。知識性能、安定性、速度のどれを取りにいくかで、最適なモデルは変わります。今回の比較が、S2SでRAGを組み込む際のモデル選定の参考になればありがたいです。

モデル別の使い分け(まとめ)

モデル 総合評価 強み 弱み 向いている用途
Gemini 2.5 Flash Live ★★★★☆ RAG性能が最も高い 実装がやや複雑 知識検索・RAG中心の対話
GPT Realtime 1.5 ★★★☆☆ 安定性が高い RAG精度は中程度 安定重視の音声対話
Nova 2 Sonic ★★★☆☆ 低レイテンシ Tool周りが厳格 速度重視のRAG
GPT Realtime Mini ★★☆☆☆ 軽量・低コスト RAG性能が低い 非RAG・軽量用途