HEROZ Tech Blog

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

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・軽量用途

S2S APIを比較して分かった実務的な選び方

はじめに

音声AIの実装では、これまで「音声 → テキスト → LLM → 音声」というパイプライン構成が一般的でした。 これに対して、音声入力から音声出力までを一気通貫で扱う S2S(Speech-to-Speech)API が登場し、リアルタイムな会話体験の実現が現実的になってきています。

一方で、各社のS2S APIはまだ新しい領域にあり、単純な性能比較だけでは整理しづらい状況です。応答の正確さだけでなく、会話の自然さ、レイテンシ、コスト、さらには実装時の扱いやすさまで含めて見ないと、実務的な判断は難しいと感じています。

そこで本記事では、主要なS2Sモデルを同一条件で比較し、まずは基本性能に絞って整理しました。RAGやTool Callingについては別記事で扱い、本記事では「素のS2S APIとしての違い」にフォーカスします。

比較対象

今回の比較対象は以下のモデルです。

  • GPT Realtime 1.5 (gpt-realtime-1.5)
  • GPT Realtime Mini (gpt-realtime-mini)
  • Nova 2 Sonic (amazon.nova-2-sonic-v1:0)
  • Gemini 2.5 Flash Live (gemini-2.5-flash-native-audio-preview-12-2025)
  • Gemini 3.1 Flash Live (gemini-3.1-flash-live-preview)

なお、Gemini 3.1 Flash Live は比較的新しいモデルであり、検証時点では Vertex AI 未対応で、Gemini API 経由での利用となりました。また、output token usage が取得できないケースがあり、コストの扱いには制約があります。

実験設計

今回の評価では、体験品質と知能性能を分けて評価しています。 S2Sでは「正しい答えを出すか」だけでなく、「会話として自然か」が重要になるため、この2軸を分離しました。

体験品質は人手評価で、会話自然度や感情表現、発話や割り込みといった観点を中心に3段階で評価しています。

一方で知能性能は自動評価とし、基本QA、指示追従、会話メモリ、言語混在耐性、構造制御などを対象に5段階で評価しました。評価には Claude 4.5 Sonnet を用いた LLM as a judge を利用しています。

総合スコアは単純平均ではなく、評価件数(体験30問、知能120問)を考慮して統合しています。

今回の評価に使用した対話テーマは、以下のような実務寄りのシナリオを中心に構成しています。

  • 一般的なQA(知識質問・業務FAQ)
  • 指示追従(条件付き回答、フォーマット指定)
  • 複数ターン会話(文脈保持・言い換え)
  • 言語混在(日本語+英語)

特定ドメインに強く依存しないように設計しつつ、実際の業務利用を想定したケースを中心に評価しています。

➡︎ 体験と知能を分離した上で統合することで、S2Sの特性をより正確に評価しています。

実験結果

今回の比較では、単純な精度や応答品質だけでなく、実際の利用シーンを想定した複数の観点から評価を行いました。

具体的には、以下の3つの軸で整理しています。

  • 体験品質(人手評価): 会話の自然さ、感情表現、発話品質、割り込みや停止の扱いやすさなど、実際に使った際の体験を評価しています。

  • 知能性能(自動評価): 基本QA、指示追従、会話メモリ、言語混在耐性、構造制御など、モデルとしての能力を評価しています。こちらは Claude 4.5 Sonnet を用いた LLM as a judge により採点しています。

  • システム特性: レイテンシやコストといった、実運用に直結する指標を評価しています。

これらを統合した総合スコアに加えて、各観点ごとの結果も併せて見ることで、モデルごとの特性をより立体的に把握できるようにしています。

➡︎ 本記事では「どのモデルが優れているか」ではなく、「どの観点で強みがあるか」に注目して比較しています。

総合比較

総合スコア (150問) 体験スコア (30問) 知能スコア (120問) レイテンシ (ms) コスト (円/分) 一言
Gemini 3.1 Live 2.9 3.6 3.2 N/A 高性能だがやや不安定(最新モデル枠)
Gemini 2.5 Live 2.7 3.3 3.1 × RAG・知識系に強いがレイテンシ重め
GPT Realtime 1.5 3.0 3.4 3.3 × 総合バランス最強・実運用向け
GPT Realtime Mini 2.4 2.8 2.8 低コストで十分使える軽量モデル
Nova 2 Sonic 2.1 1.9 2.7 超低遅延だが対話品質は控えめ

総合比較テーブル

総合スコアでは GPT Realtime 1.5 が最も高くなりました。 体験品質・知能性能ともに大きな弱点がありません。

個別項目で突出しているわけではありませんが、どの観点でも安定しており、実運用で扱いやすいバランスに収まっています。

Gemini 3.1 Flash Live は僅差で続き、特に体験スコアの高さが目立ちました。会話の自然さや感情表現といった部分では強さがあり、ユーザー体験という観点では魅力的です。

一方で、挙動の安定性や実装面では注意が必要な場面もあり、スコアだけでは評価しきれない側面があります。

Gemini 2.5 Flash Live は知能面では依然として高水準ですが、レイテンシの影響で総合スコアはやや下がっています。

GPT Realtime Mini は軽量モデルとしてバランスが良く、コストを抑えたい場合の現実的な選択肢です。 Nova 2 Sonic は総合スコアでは低めですが、低遅延という別軸の強みがあります。

整理すると、各モデルの位置づけは次の通りです。

  • GPT Realtime 1.5:バランス型で実運用向け
  • Gemini 3.1 Flash Live:体験品質が高い最新モデル
  • Gemini 2.5 Flash Live:知識系に強いがレイテンシ重め
  • GPT Realtime Mini:コスパ重視の軽量モデル
  • Nova 2 Sonic:低遅延特化

➡︎ 総合順位よりも「どの特性を持つか」で見るべき結果です。

体験品質と知能性能の関係

散布図

図を見ると、体験品質と知能性能は2軸上で右上に集中しており、いずれのモデルも一定以上の品質を持っていることが分かります。

そのため、この図は優劣を比較するというよりも、各モデルの「性格の違い」を見るものになります。

GPT Realtime 1.5 は体験・知能ともにバランスが良く、実用域の中心に位置しています。 特定の強みよりも、安定性が際立つモデルです。

Gemini 2.5 Flash Live および Gemini 3.1 Flash Live はやや知能寄りに分布しており、QAや会話メモリといった観点での強さが影響しています。

GPT Realtime Mini はその中間に位置し、軽量モデルとして性能とコストのバランスを取っています。

Nova 2 Sonic は他モデルより下側に位置しますが、これは今回の評価軸によるものであり、低遅延という特性はこの図には含まれていません。

整理すると、各モデルの関係は以下のようになります。

  • GPT Realtime 1.5:バランス型
  • Gemini系:知能寄り
  • GPT Realtime Mini:中間
  • Nova 2 Sonic:低遅延特化

➡︎ 性能差よりも「モデルの性格差」が支配的です。

能力分解(知能)

QA力 指示追従 メモリ 言語耐性 構造制御
Gemini 3.1 Live 3.6 3.0 4.5 2.9 2.7
Gemini 2.5 Live 4.5 2.8 4.0 2.9 3.2
GPT Realtime 1.5 3.6 3.1 3.0 2.3 3.4
GPT Realtime Mini 3.4 3.2 2.5 2.0 2.2
Nova 2 Sonic 3.4 2.7 2.5 1.8 2.2

能力分解テーブル

知能性能を分解すると、各モデルの得意分野がより明確になります。

Gemini 2.5 Flash Live は基本QAのスコアが高く、知識を引き出して回答する能力が際立っています。

Gemini 3.1 Flash Live は会話メモリが強く、複数ターンにわたる文脈保持に優れています。

一方で GPT Realtime 1.5 は構造制御の安定性が高く、形式に沿った出力や応答の整理といった点で優位です。 実務で扱う際に破綻しにくいという特徴は、この結果とも一致します。

GPT Realtime Mini は指示追従は比較的良好ですが、メモリや構造制御は控えめです。

Nova 2 Sonic はQA力自体は一定水準にあるものの、言語耐性が低く、言語混在への対応では弱さが見られました。

整理すると、モデルの特徴は次の通りです。

  • Gemini系:内容の強さ(QA・メモリ)
  • GPT Realtime系:出力の安定性
  • Nova 2 Sonic:知能面はやや弱い

➡︎ 「何を出すか」と「どう出すか」で強みが分かれています。

レイテンシとコスト

レイテンシ (ms) コスト (円/分) コメント
Gemini 3.1 Live 1202.5 N/A 高性能だがレイテンシやや重め(コスト不明)
Gemini 2.5 Live 2503.5 3.1 低コストだがレイテンシがボトルネック
GPT Realtime 1.5 738.5 14.0 低遅延だがコストが高い(トレードオフ型)
GPT Realtime Mini 764.0 4.2 低コスト・低遅延でバランス良好
Nova 2 Sonic 207.1 4.1 圧倒的低遅延だが品質とのトレードオフ

レイテンシ・コスト表

レイテンシでは Nova 2 Sonic が突出しており、約200msという値は体感的にも非常に軽快でした。 リアルタイム性を重視する用途では、この差は明確に効いてきます。

GPT Realtime 1.5 と GPT Realtime Mini はいずれも700ms台で、リアルタイム会話として十分成立する水準です。

特に GPT Realtime Mini はコストも抑えられており、速度と価格のバランスが良いモデルです。

Gemini 2.5 Flash Live はレイテンシが重く、応答開始までの待ち時間が長くなります。 Gemini 3.1 Flash Live は改善されているものの、依然としてGPT系より遅く、さらに output token usage が安定しないため、コスト見積もりには注意が必要です。

整理すると、次のような関係になります。

  • Nova 2 Sonic:最速(低遅延)
  • GPT Realtime系:バランス型
  • Gemini系:低コストだが遅い

➡︎ レイテンシとコストは明確なトレードオフです。

実装してみて分かったこと

実際にS2S APIを実装してみると、単純な性能比較では見えない差がはっきりと出てきます。 特に大きかったのは、音声対話における「ターン制御」の扱いです。

音声UIでは、「いつ話し終わったか」「途中で止めるか」「割り込んだ場合どうするか」といった制御が不可欠になります。これらは単なるAPI呼び出しでは解決できず、イベント処理・再生制御・状態管理が密接に絡みます。

➡︎ 精度より先に、ターン制御で実装が分かれます。

途中停止と割り込みの設計

途中停止(Stop / cancel)

音声UIでは、アシスタントの発話を途中で止められることが前提になります。ただし実装してみると、「止める」という処理は2つのレイヤーに分かれます。

  • モデル側の生成を止める
  • クライアント側の再生を止める

この違いを整理しないと、設計が破綻します。

GPT Realtime 1.5 / Mini では、response.cancelconversation.item.truncate により、生成そのものを停止できます。そのため、

  1. APIに停止イベントを送る
  2. 以降のトークン生成が止まる
  3. 再生も停止する

という一貫した制御が可能です。

一方で Nova 2 Sonic や Gemini Flash Live では、少なくとも今回の検証範囲では生成停止を前提としたAPI設計にはなっていません。そのため実装としては、

  1. 音声再生を停止する
  2. 受信済みのバッファを破棄する
  3. 以降の出力は無視する

という「聞き捨てる」設計になります。

この差は責任分担の違いとして整理できます。

  • GPT:停止制御をAPI側に委ねられる
  • Gemini / Nova:停止制御をクライアント側で持つ

➡︎ 途中停止は「止める」か「聞き捨てる」かで設計が分かれます。

割り込み(Barge-in)

割り込みは、ユーザーがアシスタント発話中に話し始めた際の挙動です。音声UIの自然さはここで決まります。

今回の実装ではローカルVADは使わず、API側の検知に委ねています。各モデルの挙動は以下の通りです。

  • GPT Realtime:server VAD による検知
  • Nova 2 Sonic:Bedrock側の turn detection / barge-in
  • Gemini Flash Live:activity detection

重要なのは、割り込みは単なる停止ではないという点です。実際には以下のような状態遷移になります(「話している → 止める → 次に切り替える」という流れ)。

  1. ユーザー発話を検知
  2. 現在の音声再生を停止
  3. 未処理の音声バッファを破棄
  4. 新しい入力として次ターンに遷移

この「再生停止 + 状態リセット + 次ターン移行」が一体となって初めて自然な挙動になります。

➡︎ 割り込みは停止ではなく「ターンの切り替え」です。

モデル別の実装特性

ここまでの挙動を踏まえると、各モデルの実装体験には明確な違いがあります。細かい仕様差に入る前に、直感的には以下のように整理できます。

  • GPT Realtime 1.5 / Mini:素直で完結している
  • Gemini Flash Live:柔軟だがイベント依存が強い
  • Nova 2 Sonic:仕様が厳格で入力にシビア

これは単なる使いやすさではなく、「どこに制御責任があるか」の違いです。

➡︎ モデルごとに「実装責任の置き場所」が異なります。

GPT Realtime 1.5 / Mini

GPT Realtime 系は、イベントの流れが明確で、ターン制御がAPI側で完結します。

  • server VAD によるターン検知
  • cancel / truncate による生成停止
  • 一貫したイベント構造(開始 → 生成 → 完了の流れが明確)

これにより、

  • いつ終了したか
  • いつ止めるか
  • いつ次に進むか

をすべてAPIで判断できます。

クライアント側は「イベントに従う」だけで成立するため、状態管理がシンプルです。結果として、Stop・割り込み・ターン制御を一貫した設計で扱えます。

Gemini Flash Live

Gemini Flash Live は柔軟ですが、その分イベント解釈の責任がクライアント側に寄ります。

特に注意が必要だったのは以下です。

  • activity detection に依存したターン確定
  • response.done のタイミングが一定でない
  • transcript / audio のイベント粒度の違い
  • usage情報が安定しないケース

例えば、response.done を終了判定に使うと、実際の音声出力より先に終了扱いになるケースがありました。このため、終了判定は複数イベントを組み合わせて判断する必要があります。

➡︎ イベントをどう解釈するかが実装の本質になります。

Nova 2 Sonic

Nova 2 Sonic は仕様が厳格で、入力フォーマットへの依存が強いモデルでした。

主な特徴は以下です。

  • promptの先頭がSYSTEMである必要がある
  • イベント構造が固定されている
  • フォーマット不備でValidationExceptionが発生しやすい
  • cancel前提ではなくturn detectionベース

このため、「自由に設計する」というよりも「仕様に正確に合わせる」実装が求められます。柔軟性は低いですが、その分動作は一貫しています。

➡︎ 仕様適合性がそのまま実装難易度になります。

まとめ

今回の比較から見えてきたのは、S2S APIは単純なランキングで選べるものではないという点です。

GPT Realtime 1.5 は体験品質・知能性能・レイテンシのバランスが良く、さらに実装も素直であるため、最も扱いやすいモデルでした。 汎用的なリアルタイム対話基盤としては第一候補になります。

Gemini 2.5 Flash Live と Gemini 3.1 Flash Live は知能面での強さがあり、特に知識系やRAG用途では有力です。ただしレイテンシや実装上の癖には注意が必要です。

GPT Realtime Mini はコストと性能のバランスが良く、軽量な選択肢として実用的です。 Nova 2 Sonic は低遅延という明確な強みがあり、速度が重要な場面では有効です。

整理すると、用途別の選択は以下の通りです。

  • 汎用用途:GPT Realtime 1.5
  • 知識・RAG用途:Gemini系
  • コスト重視:GPT Realtime Mini
  • 低遅延重視:Nova 2 Sonic

➡︎ 「どれが最強か」ではなく「どの用途に合うか」で選ぶのが重要です。

Agentic RAGの前に整えるべきもの:実務RAGの入口を設計する Router RAG

はじめに

前回の記事では、Agentic RAGの有効性について、社内実験と最新研究の両面から検証しました。

その結果、Agentic RAGは確かに強力なアプローチである一方、エンタープライズ検索のようなユースケースでは、必ずしも複雑化に見合う効果が得られるとは限らない、ということが分かりました。

では、なぜ期待したほどの改善が得られないのでしょうか。

この問いをシステム全体で分解していくと、次のような気づきに至ります。

💡 結論

問題は後段(検索・生成)ではなく、
前段(問い合わせ処理)にある

実際の業務システムでは、ユーザは必ずしも検索に適した形で質問してくれるわけではありません。

  • 「こんにちは」
  • 「売上を集計して」
  • 「この機種のエラー原因は?」
  • 「操作方法を教えて」

これらはすべて“質問”ですが、必要な処理はまったく異なります。

しかし従来のRAGでは、これらを十分に区別せず、そのまま検索に流してしまう構造になりがちです。

本記事で扱うのは、機器ごとに分かれたマニュアルや社内ドキュメントを対象とする検索システムです。

このような文書群では、似た説明が複数の機種やカテゴリにまたがって存在するため、単純な検索では別機種の情報が混ざりやすくなります。そのため、機種名やカテゴリーといった分類情報を前提とした設計が重要になります。

前回のAgentic RAG検証から見えてきたこと

前回の記事では、高難度な質問への対応として、Agentic RAGのようなより高度な構成を検証しました。

techblog.heroz.jp

Agentic RAGは柔軟で強力な一方で、

  • 挙動が不安定になりやすい
  • コストやレイテンシが増えやすい

という特徴もあります。

そのため、すべての問い合わせに対していきなりAgenticな構成を適用するよりも、

まずは問い合わせを整理し、適切な処理に振り分ける

という前段の設計の方が重要になります。

実際、複雑な質問に対しては、検索戦略の動的制御やツール利用が有効に働くケースもあります。

一方で、エンタープライズ検索では、すべての質問に対してそのような複雑な処理が必要なわけではありませんでした。 コストやレイテンシが増える一方で、効果が限定的なケースも少なくありませんでした。

そこで後段については、よりシンプルな構成、具体的には ReRanking を中心とした Enhanced RAG に整理しました。

しかし、ここでより重要な問題に気づきます。

そもそも、すべての質問を同じ検索フローに流してよいのか?

実際の入力を見てみると、

  • 雑談
  • 計算依頼
  • 条件不足の質問
  • 単純に答えられる質問
  • 丁寧な検索が必要な質問

が混在しています。

この状態で後段だけを高度化しても、入力が整理されていなければ、期待した効果は出ません。

つまり、Agentic RAGの前に、問い合わせを正しく振り分ける仕組みが必要だった

この気づきから設計したのが、今回の Router RAG です。

実務で起きていた問題

雑談が検索に流れ、事故的な回答が出る

⚠️ 問題: 関係ないチャンクを拾い、“それっぽい誤回答”を生成することがある

単純な挨拶であっても、検索に流してしまうと、偶然ヒットしたチャンクをもとに文脈と無関係な“それっぽい回答”が生成されることがあります。

場合によっては、まったく関係ない業務情報を返してしまうなど、事故に近い挙動になることもあります。

metadata不足による誤検索

マニュアル検索では、似た説明が複数の機種にまたがって存在します。

このとき、機種などの条件が曖昧なまま検索すると、

  • A機種の質問に対してB機種の情報が混ざる

といったことが起きます。

これは単なるノイズではなく、誤情報そのものになります。

検索ではなく計算を求められる

ユーザは普通にこう言います。

  • 「売上を集計して」
  • 「平均を出して」

これは検索ではなく計算です。

RAGに流すと、断片的な情報を組み合わせてもっともらしい答えを返す危険があります。

ユーザは「無駄には待ちたくない」が「間違いは避けたい」

ユーザはできるだけ早く答えがほしい一方で、間違うくらいなら少し待ってもいいという期待も持っています。

つまり、

  • 簡単な質問はすぐ答える
  • 難しい質問は丁寧に扱う

という切り分けが必要になります。

検索に流してはいけない入力が混ざっている

つまり、RAGの問題は検索精度ではなく、 「入力の整理不足」にあると言えます。

Router RAGという考え方

こうした問題に対して導入したのが、Router RAGです。

ここでのポイントは、RAGを改善するのではなく、

「そもそも検索に流すべきか」を先に判断する

という設計に変えたことです。

図1:まず何をすべきかを決めてから、必要な処理に進む

Before / After

Before(従来のRAG)

すべての質問を検索に流してしまう

After(Router RAG)

質問に応じて処理を分離する

この違いは、構造として見るとより分かりやすくなります。

図2:すべて検索に流す構造から、適切に処理を分離する構造へ

PreRetrieveは「判断材料を集める」ためにある

Router RAGでは、質問を受けると最初に少量の検索を行います(top_k=4)。

これは最終回答のための検索ではなく、次に何をすべきかを判断するための材料集めです。

ここで得られたチャンクは、主に次の用途に使われます。

  • 🔍 ナレッジに関係するかの判定
  • ❓ 情報が足りているかの判定
  • ✏️ クエリ書き換えのヒント

つまり、PreRetrieveは「検索の前段」ではなく、Routerのための観測ステップです。

Classifierは「次の動き」を決める

PreRetrieveの結果をもとに、Classifierが次の処理を決めます。

ここで重要なのは、Classifierは回答を作らないことです。 やるのは一つだけで、次にどの経路に進めるかを決めることです。

Classifierの役割:次にどの処理に進むかを決める

  • 挨拶 → 雑談として処理
  • 意味が不足 → 確認質問
  • 集計・計算 → Code Interpreter
  • 条件不足(metadata) → 追加質問
  • すぐ答えられる → 即答
  • それ以外 → 詳細検索

(具体的なプロンプト設計については、本記事では詳細には触れませんが、 事前取得したチャンクやスコアを含めて一度に判断させる形にしています)

metadataとRouter Query Engine

ここで少しだけ、検索の前提となる仕組みを説明します。

今回のシステムでは、文書ごとに

  • 機種名
  • カテゴリー
  • 種類

といった分類情報をLLMで自動付与し、metadataとして保存しています。

検索時には、質問文からこれらの値を推定し、フィルタとして使います。

ここで重要なのが、必須の項目(required metadata)です。

例えば機種が必須の場合:

  • 「エラー原因は?」 → 機種が不明
  • → そのまま検索すると危険
  • → 先に聞き返す

この処理を担当しているのが Router Query Engine です。

fast と slow の分離

今回の設計では、処理を2つに分けています。

fast

  • PreRetrieveの結果を使って即答
  • 低レイテンシ

slow

  • クエリ変換
  • 再検索
  • ReRanking

重要なのは、これは単なる最適化ではなく、

ユーザの期待に合わせた分離

であることです。

  • 簡単な質問 → 待たせない
  • 難しい質問 → 無理に急がない

ノード構成

全体の処理の流れをまとめると、次のようになります。

図3:Router RAGの全体構成

なお、確認質問に対するユーザの応答は、次のターンで会話履歴(history)として再度 Router 側に渡します。Router Query Engine はその履歴を見て、前のターンで不足していた項目が埋まったかを再評価します。

たとえば「どの機種ですか?」という確認質問に対して、次のターンで「A-120です」と返ってきた場合、その応答単体を新しい質問として扱うのではなく、直前の確認質問と合わせて metadata を補完する入力として解釈します。

設計上の知見

実装を進める中で感じたのは、

細かく調整するより、大まかに分けた方が安定する

という点です。

  • fast / slow の境界
  • rewriteの有無
  • rerankの強さ

こういった要素は、個別にチューニングすると全体のバランスが崩れやすくなります。

そのため今回は、

  • 雑談
  • 確認質問
  • 計算
  • 即答
  • 詳細検索

といった大きな分類を先に決める方針にしました。

おわりに

前回のAgentic RAGの検証を通じて見えてきたのは、

後段を強くする前に、前段を整える必要がある

ということでした。つまり、RAGを高度化する前に、

「入力を整える」

ことが重要だった、というのが今回の結論です。

Router RAGは、そのための仕組みです。

雑談は雑談として扱い、計算は計算に回し、条件が足りなければ確認し、必要なときだけ検索する。

こうした分離を行うことで、RAGは単なる検索付き生成から、実務で使えるシステムへと近づきます。