HEROZ Tech Blog

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

AIエージェントの「ハーネス」とは何か:Cowork時代の事務作業エージェントを3問だけ試して見えたこと

はじめに

最近、AIエージェントという言葉を聞く機会が増えました。

エンジニア向けには、Codex CLI、Claude Code、Gemini CLI のようなコーディングエージェントが広がっています。一方で、最近はこの流れがエンジニアだけのものではなくなってきているように感じます。

ファイルを読み、内容を整理し、表を加工し、レポートを作り、必要に応じて何度かやり直して成果物を作る。こうした「作業相手」としてのAI、いわゆる Cowork 的な使い方が広がりつつあります。

この文脈では、利用者はそれを「AI」と呼ぶこともあれば、「AIエージェント」と呼ぶこともあります。さらに、Claude Code や Codex のような具体的な製品名が、そのまま「賢く作業してくれるもの」の代表名のように使われることもあります。

しかし、実際に仕組みを見ていくと、AI、LLM、AIエージェント、ハーネスは少し分けて考えた方が理解しやすいです。

今回の記事では、AIエージェントを支える「ハーネス」という概念を整理しつつ、いくつかのハーネスを使って、事務作業系・表計算系・コーディング系のタスクを3問だけ試してみた結果を紹介します。

先に結論を言うと、今回の実験は3問だけの予備実験なので、ハーネスやモデルの優劣を決めるものではありません。統計的な有意性もありません。また、評価用の集計ロジックにも、まだデバッグしきれていない部分があります。

それでも、少なくとも今回の範囲では、次のような示唆が得られました。

  • 現代的なハーネスは、コーディングだけでなく事務作業にも使えそう
  • AIエージェントを「LLMにツールを渡してループを回すもの」と捉えるだけでは、現代的なハーネスの振る舞いを説明しにくくなってきた
  • 賢く作業できるようになった一方で、裏側ではかなり大量のトークンを消費している
  • Cowork的な利用を考えるなら、「できるか」だけでなく「いくらで、どの作業なら割に合うか」も重要になる

AI、AIエージェント、ハーネスを分けて考える

まず、この記事での用語を整理します。

用語 この記事でのざっくりした意味
AI 機械学習、LLM、画像認識、推薦システムなども含む広い概念
LLM / モデル 文章を理解・生成する中核部分。GPT、Claude、Gemini など
AIエージェント 目標に向かって、考える、道具を使う、結果を見る、やり直す、というループでタスクを進める仕組み
ハーネス AIエージェントを実際に動かすための実行基盤。ツール、ファイル操作、コマンド実行、ループ制御、ログ、成果物管理などを含む

Cowork文脈では、これらの言葉がかなり混ざって使われがちです。

たとえば、ファイルを渡して「このExcelを整理して」「この資料を要約して」「このCSVからグラフを作って」と依頼できるものがあったとします。利用者から見ると、それは単に「AIが作業してくれた」と見えます。ある人はそれを「AI」と呼び、ある人は「エージェント」と呼び、また別の人は「Claude Codeみたいなもの」と呼ぶかもしれません。

しかし技術的には、そこにはいくつかの層があります。

モデルは、文章を読み、指示を理解し、次に何をすべきかを考える頭脳に近い部分です。AIエージェントは、そのモデルがツールや環境を使いながらタスクを進める仕組みです。そしてハーネスは、そのエージェントを実際に動かすための作業環境です。

人間にたとえるなら、モデルは頭脳、ハーネスは作業机、道具、手順、作業ログ、ファイル置き場を含む環境のようなものです。

ここで重要なのは、最近のAIエージェントの能力はモデルだけで決まっているわけではない、という点です。

もちろんモデル性能は重要です。しかし、ファイルをどう読むか、どのタイミングでコマンドを実行するか、失敗したときにどう戻るか、途中結果をどう保持するか、どこまで探索するか、といった部分はハーネス側の設計にも大きく依存します。

「ツールを渡せばエージェント」から、その先へ

一昔前のAIエージェント理解は、「LLMにツールを渡して、考える・実行する・観察するループを回すもの」という捉え方が中心だったように思います。ReAct はその代表的な考え方の一つで、推論と行動を組み合わせる枠組みとして提案されました。

この考え方は今でも重要です。AIエージェントの基本形を理解するうえでは、ReAct的なループは分かりやすい入口です。

一方で、実務でAIエージェントを使う段階になると、「ツールを渡せばよい」という理解だけでは足りなくなってきました。

最近は、MCPのようにツールやデータソースを接続するための仕組みも整備されつつあります。つまり、ツール接続そのものは以前より標準化されつつあります。

しかし、ツールがつながることと、AIエージェントが賢く作業できることは同じではありません。

実際の作業では、どのファイルを先に読むか、どこまで探索するか、中間結果をどう保持するか、コマンド実行結果をどう解釈するか、失敗したときにやり直すか、別案に切り替えるか、といった判断が大量に発生します。

このあたりが、現代的なハーネスの強さになっているように感じます。

つまり、「LLMにツールを渡してループを回せばエージェントになる」という理解と、Codex CLI、Claude Code、Gemini CLI のような現代的なハーネスの間には、かなり大きな距離があります。

なぜコーディング用ハーネスが事務作業にも使えそうなのか

多くの現代的なハーネスは、もともとコーディング用途から発展してきました。

コードベースを読む、ファイルを編集する、テストを実行する、エラーを見て修正する、差分を作る。これはソフトウェア開発では自然な作業です。

しかし、よく考えると、非エンジニアの事務作業にも似た構造があります。

  • ExcelやCSVを読む
  • 必要なデータを抽出する
  • 表を整形する
  • グラフを作る
  • 複数ファイルを比較する
  • レポートを生成する
  • 出力ファイルを所定の形式で保存する

これは「コードを書く」作業ではありませんが、ファイルを読み、加工し、成果物を作るという意味では、コーディングエージェントの作業環境と近いものがあります。

そこで今回は、いくつかのハーネスを使って、コーディング系だけでなく、事務作業系・表計算系のタスクも試してみました。

実験条件

今回の目的は、モデル単体の優劣を決めることではありません。

モデルとハーネスの組み合わせによって、どの程度タスクを進められるのか。コーディング用に見えるハーネスが、事務作業にも使えるのか。素朴なツール利用型の実装と、現代的なハーネスの間にどれくらい差がありそうか。そして、その裏側でどれくらいトークンやコストを使うのか。

そのあたりを見るために、モデル、ハーネス、評価セットを組み合わせて実験しました。

なお、今回の比較では、Codex CLI、Claude Code、Gemini CLI、OpenCode、mini-SWE-agent、AutoGen、ReActを同じ枠組みで比較し、コーディング評価と事務作業評価を同じ実行基盤で扱えるようにしています。これは実験用の内部構成であり、記事では内部基盤の詳細には踏み込みません。

評価セット

評価セットは以下の3つです。

評価セット 説明
SpreadsheetBench V1 Verified 実世界由来のスプレッドシート操作を評価するベンチマークです。SpreadsheetBench全体は912問で、400問の人手検証済みサブセットが公開されています。本記事では、SpreadsheetBench 2と区別するため、今回使った評価セットを SpreadsheetBench V1 Verified と表記します。
OfficeBench 複数アプリケーションをまたぐ現実的なオフィス自動化ワークフローを評価するベンチマークです。
SWE-bench Verified 実際のGitHub issueをもとに、コードベースを編集して問題を解決するソフトウェアエンジニアリング評価です。Verifiedは人手検証済み500問のsubsetです。

今回は各評価セットからランダムに50問のsubsetを用意し、予備実験としてその先頭3問だけを実行しました。3問はランダムsubset由来ではありますが、サンプル数が非常に少ないため、評価セット全体の難易度分布を代表しているとは限りません。

モデル

今回比較したモデルは、以下の4系統です。

記事内表記 位置づけ
GPT-5.5 OpenAI系モデル
Claude Sonnet 4.6 Claude系モデル
Gemini 3.5 Flash Gemini系モデル
gpt-oss:20b ローカルLLM枠

ハーネス

比較したハーネスは以下です。

記事内分類 実体 説明
純正ハーネス Codex CLI / Claude Code / Gemini CLI 各モデル系列で代表的に使われるベンダー系CLIハーネスです。Codex CLIはOpenAIのローカル実行型coding agent、Claude CodeはAnthropicのagentic coding tool、Gemini CLIはGoogleのオープンソースAI agentです。
AgentCore Amazon Bedrock AgentCore AWSのエージェント実行・運用基盤です。今回はマネージド参考枠として扱います。
OpenCode OpenCode OSS系の現代的なcoding agentです。
mini-SWE-agent mini-SWE-agent SWE-agent系の軽量ハーネスです。
AutoGen AutoGen MicrosoftによるAI agents / applications構築フレームワークです。
ReAct ReAct Reasoning and Actingを組み合わせる古典的なエージェント枠組みです。今回は素朴なツール利用型の比較対象として扱います。

以降の結果表では、行方向をおおむね「純正ハーネス・マネージド参考枠・OSS系現代ハーネス・汎用フレームワーク・素朴なツール利用型」の順に並べています。列方向は、左から GPT-5.5、Claude Sonnet 4.6、Gemini 3.5 Flash、gpt-oss:20b としました。

表中の「純正ハーネス」は、GPT-5.5列では Codex CLI、Claude Sonnet 4.6列では Claude Code、Gemini 3.5 Flash列では Gemini CLI を指します。これは同じハーネスを複数モデルで動かした結果ではなく、各モデル系列で代表的に使われるベンダー系CLIハーネスをまとめて表示したものです。gpt-oss:20b には対応する純正ハーネスを設定していないため、空欄にしています。

実行と評価の単位

今回の予備実験では、各タスクを1回ずつ実行しました。したがって、スコアは pass@k のように複数回試行した成功率ではなく、今回の1回の実行結果に基づく値です。

スコアは、各評価セットの判定方法に従って集計しました。SWE-bench Verified では、生成された修正が対象issueを解決できたかをテストベースで判定します。SpreadsheetBench V1 Verified と OfficeBench では、タスクごとに期待される出力や評価条件に対する達成度を集計しています。

ただし、この記事では評価基盤の内部実装には踏み込まず、各ハーネス・モデルの組み合わせで得られた予備的な傾向を見ることを目的とします。

注意点

今回の実験には、強い制約があります。

注意: 本実験は各評価セット3問だけの予備実験であり、統計的な有意性はありません。また、評価・集計ロジックにはまだデバッグしきれていない部分があり、一部の数値には怪しい箇所が残っている可能性があります。以降の結果はランキングではなく、傾向や論点を見るための予備結果として読んでください。

また、AgentCore は他のCLI / SDK型ハーネスとは実行環境が異なるため、厳密な横並び比較ではなく、参考枠として扱います。

実験結果

スコア

まず、各評価セットのスコアを見ます。

以下の表では、行方向をおおむね「現代的なハーネス → 素朴なツール利用型」の順に並べています。また、列方向は「クラウドモデル → ローカルLLM」の順に並べています。

ハーネス GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
純正ハーネス 66.7% 0.0% 33.3% -
AgentCore - 16.7% - -
OpenCode 100.0% 16.7% 33.3% 0.0%
mini-SWE-agent 33.3% 16.7% 0.0% 33.3%
AutoGen 33.3% 16.7% 66.7% 33.3%
ReAct 0.0% 0.0% 33.3% 0.0%

SpreadsheetBench V1 Verified:スコア

ハーネス GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
純正ハーネス 100.0% 100.0% 100.0% -
AgentCore - 100.0% - -
OpenCode 100.0% 100.0% 66.7% 0.0%
mini-SWE-agent 100.0% 100.0% 100.0% 100.0%
AutoGen 100.0% 66.7% 33.3% 66.7%
ReAct 66.7% 66.7% 33.3% 0.0%

OfficeBench:スコア

ハーネス GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
純正ハーネス 66.7% 33.3% 66.7% -
AgentCore - 33.3% - -
OpenCode 66.7% 33.3% 66.7% 0.0%
mini-SWE-agent 66.7% 33.3% 33.3% 0.0%
AutoGen 66.7% 33.3% 33.3% 0.0%
ReAct 0.0% 0.0% 0.0% 0.0%

SWE-bench Verified:スコア

個別の数値を細かく読むのではなく、表全体の傾向として見ると、いくつかの特徴が見えます。

まず、行方向では、上側に置いた純正ハーネス、AgentCore、OpenCode、mini-SWE-agentのような現代的なハーネスの方が、下側に置いたAutoGenやReActよりスコアが出やすいように見えます。特にReActは、今回の範囲ではSWE-bench Verifiedで苦戦していました。

次に、列方向では、左側に置いたクラウドモデルの方が、右側に置いたローカルLLM枠よりスコアが出やすいように見えます。もちろん、これはOSSモデル一般の限界という意味ではなく、今回使ったモデルサイズ、ハーネス実装、tool callingとの相性、評価タスクとの相性を含んだ結果です。

また、評価セットごとの違いも見えます。今回の3問では、OfficeBenchは比較的スコアが出やすく、SpreadsheetBench V1 Verifiedはばらつきがあり、SWE-bench Verifiedはより難しいタスクに見えました。これは、事務作業系タスクの方が今回の範囲では解きやすく、実コードベースを修正するSWE-bench Verifiedは難度が高い、という感触と合っています。

トークン消費

次に、トークン消費を見ます。

以下の表は、3問平均の消費トークンを k tokens 単位で示しています。たとえば 392.2 は約392,200 tokensです。スコア表と同じく、行方向を「現代的なハーネス → 素朴なツール利用型」、列方向を「クラウドモデル → ローカルLLM」の順に並べています。

なお、トークン消費が 0.0 になっているセルがあります。これは実際に無コストで解けたという意味ではなく、実行失敗、ログ欠損、または集計上の未デバッグ箇所を含んでいる可能性があります。そのため、0.0 のセルは低コストな成功例として解釈しないでください。

ハーネス GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
純正ハーネス 123.7 174.9 879.2 -
AgentCore - 364.0 - -
OpenCode 170.5 95.3 863.4 16.7
mini-SWE-agent 15.1 139.6 992.1 17.6
AutoGen 17.1 47.2 23.4 11.6
ReAct 4.6 52.1 163.8 12.0

SpreadsheetBench V1 Verified:トークン消費(3問平均、k tokens)

ハーネス GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
純正ハーネス 252.5 359.8 579.3 -
AgentCore - 834.7 - -
OpenCode 314.3 205.2 362.8 37.8
mini-SWE-agent 29.7 133.7 506.0 31.5
AutoGen 34.1 55.7 24.4 29.0
ReAct 18.2 63.3 60.2 0.0*

OfficeBench:トークン消費(3問平均、k tokens)

ハーネス GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
純正ハーネス 392.2 2175.3 3586.7 -
AgentCore - 1172.8 - -
OpenCode 322.1 878.2 3191.0 53.9
mini-SWE-agent 441.3 1512.0 2015.7 127.8
AutoGen 645.4 904.1 105.4 8.8
ReAct 0.0* 449.7 173.1 68.7

SWE-bench Verified:トークン消費(3問平均、k tokens)

* は実行失敗・ログ欠損・集計上の未デバッグ箇所を含む可能性がある値です。

こちらも個別の値を厳密に比較するのではなく、表全体の傾向として見るのがよさそうです。

大まかには、スコアと同じく、上側に置いた現代的なハーネスほどトークン消費が大きく、下側に置いた素朴なツール利用型ほどトークン消費が小さくなる傾向が見えます。また、列方向でも、左側のクラウドモデルではトークン消費が大きく、右側のローカルLLM枠では小さく見えます。

これはある意味で自然です。現代的なハーネスは、単に一度回答して終わるのではなく、ファイルを読み、探索し、コマンドを実行し、結果を見て、必要に応じてやり直します。そのぶん、スコアが出やすくなる一方で、トークン消費も大きくなります。

特に目立つのは、SWE-bench Verifiedです。SpreadsheetBench V1 VerifiedやOfficeBenchと比べて、SWE-bench Verifiedではトークン消費が一段大きくなっています。実コードベースを読み、問題箇所を探し、修正し、検証する必要があるため、事務作業系タスクよりもトークンが膨らみやすいと考えられます。

コスト

トークン消費は、そのままコストにも効いてきます。

ただし、ここで示すコストは「各モデルで実行した全ハーネス分の合計」です。モデルごとに実行したハーネスの数や種類は完全には揃っていません。特に AgentCore は Claude Sonnet 4.6 の参考枠として追加しているため、Claude Sonnet 4.6 列には他モデルにはない実行分が含まれます。

そのため、以下のコスト表はモデル同士の厳密な価格比較ではありません。今回の予備実験を実際に回したときの費用感、および50問評価へ広げた場合の概算規模を見るためのものです。

3問だけの実績コストは次のようになりました。

評価セット GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
SpreadsheetBench
V1 Verified
¥899.9 ¥1,308.8 ¥2,250.9 ¥0.0
OfficeBench ¥1,648.4 ¥2,365.5 ¥1,153.1 ¥0.0
SWE-bench
Verified
¥4,341.7 ¥9,855.6 ¥6,297.1 ¥0.0
合計 ¥6,890.1 ¥13,529.9 ¥9,701.2 ¥0.0

3問実行時の実績コスト

商用モデル3系統の合計だけを見ると、3問ずつの予備実験でも約3万円です。

さらに、これを50問に単純換算すると次のようになります。

評価セット GPT-5.5 Claude
Sonnet 4.6
Gemini
3.5 Flash
gpt-oss:20b
SpreadsheetBench
V1 Verified
¥14,998.9 ¥21,812.9 ¥37,515.5 ¥0.0
OfficeBench ¥27,473.2 ¥39,425.3 ¥19,219.0 ¥0.0
SWE-bench
Verified
¥72,362.3 ¥164,259.5 ¥104,952.2 ¥0.0
合計 ¥114,834.3 ¥225,497.7 ¥161,686.8 ¥0.0

50問実行時の推定コスト

ベンチマーク別・モデル別のコスト

商用モデル3系統を50問ずつ実行すると、単純換算で約50万円規模になります。

もちろん、これは3問の結果を単純に50問へ引き伸ばしただけです。問題の難易度分布、キャッシュ、実行設定、レート、モデル単価、集計方法、実行対象ハーネスの数によって変わります。

それでも、予備実験の段階で「これはフル実験を軽々しく回せない」と分かりました。

考察

スコアとトークン消費はセットで見る必要がある

今回の予備実験で見えた一番大きな傾向は、スコアとトークン消費を別々に見るのではなく、セットで見る必要があるという点です。

表全体を見ると、現代的なハーネスやクラウドモデル側では、スコアが出やすい一方で、トークン消費も大きくなる傾向がありました。逆に、素朴なツール利用型やローカルLLM枠では、トークン消費は小さく見えるものの、スコアも伸びにくい場面がありました。

ただし、これは個別セルごとに「トークンを多く使えば必ずスコアが上がる」という意味ではありません。実際には、少ないトークンで高めのスコアが出ている組み合わせもあれば、多くのトークンを使ってもスコアが伸びていない組み合わせもあります。

したがって、今回の結果は「トークンを燃やせばよい」という話ではなく、現代的なハーネスは大量に読み、探索し、実行し、やり直すことでタスクを進める傾向があり、その結果としてスコアとコストの両方に影響が出る、という読み方がよさそうです。

特にCowork的な事務作業では、ユーザーからは単に「AIがファイルを処理してくれた」と見えます。しかし裏側では、現代的なハーネスがかなり多くのトークンを使って作業している可能性があります。

そのため、実務でAIエージェントを使うときは、「できるか」だけでなく、「その作業をそのコストで任せる価値があるか」を見る必要があります。

事務作業にも一定の適用可能性がありそう

OfficeBenchでは、今回の3問に限ると比較的スコアが出やすい傾向がありました。これだけで「事務作業は十分に解ける」とは言えません。問題がたまたま簡単だった可能性もありますし、評価ロジックに怪しい部分が残っている可能性もあります。

ただし、ファイルを読み、加工し、成果物を作るような作業に対して、コーディング用に発展してきたハーネスが一定の力を発揮しそうだ、という感触はあります。

Cowork的な使い方を考えるうえでは、これは重要です。

非エンジニアの作業をAIが手伝うとき、必要なのは必ずしもコードを書く能力だけではありません。むしろ、ファイルを読み、内容を理解し、必要な操作を行い、最終成果物を作る能力です。

その意味では、コーディングハーネスで発展してきた作業能力は、事務作業にも転用できる可能性があります。

素朴なツール利用型だけでは厳しそう

今回の結果では、下側に置いた素朴なツール利用型ほど、スコアが伸びにくい傾向が見えました。特にSWE-bench Verifiedのように、実際のコードベースを読み、編集し、検証するタスクでは、その傾向が目立ちます。

もちろん、これは今回の実装・プロンプト・評価対象に依存します。ReActそのものが不要という話ではありません。ReActは、AIエージェントの基本構造を理解するうえでは今でも重要です。

ただし、今から実務用のAIエージェントを作るときに、素朴なツール利用型の実装だけで、Codex CLIやClaude Codeのような現代的ハーネスと同じ振る舞いを目指すのは、かなり厳しいのではないかと感じました。

以前は「LLMにツールを渡せばエージェントになる」という理解でも、ある程度は話が通じました。

しかし現在は、探索、編集、実行、検証、リトライ、コンテキスト管理を含むループ全体の作り込みが性能に効いているように見えます。

実務的には、ゼロから手組みで頑張るよりも、既存の現代的ハーネスの能力と制約を理解し、どこで使うべきかを考える方が現実的かもしれません。

ローカルLLMはどう見るか

今回、gpt-oss:20b もローカルLLM枠として試しました。

結果だけを見ると、商用ベンダーモデルと比べて苦戦する場面が多く見えました。特にSWE-bench Verifiedでは、今回の3問ではスコアが伸びませんでした。

ただし、これをもって「OSSモデルは使えない」と言うつもりはありません。

今回の結果には、モデルサイズ、tool callingへの適性、プロンプト、ハーネス側の実装、評価タスクとの相性がすべて含まれています。また、ローカルLLMはAPI課金が0円として扱われていますが、実際には計算資源、運用、チューニング、セットアップのコストがあります。

一方で、データ管理や閉域環境での利用を考えると、ローカルLLMやOSSモデルには別の価値があります。

そのため、今回の結果は「OSSモデル一般の限界」というより、「今回の設定では、現代的な商用モデルの方がタスク遂行には有利に見えた」と解釈するのが妥当だと思います。

まとめ

今回は、AIエージェントを支える「ハーネス」という概念を整理し、いくつかのハーネスを使って、事務作業系・表計算系・コーディング系のタスクを3問だけ試してみました。

繰り返しになりますが、これは3問だけの予備実験です。統計的な有意性はありませんし、評価用の集計ロジックにもデバッグしきれていない部分があります。そのため、ハーネスやモデルの優劣を決めるものではありません。

それでも、いくつかの示唆は得られました。

Codex CLIやClaude Codeのような現代的なハーネスは、もはやコーディングだけの道具ではなく、ファイルを読み、加工し、成果物を作る Cowork 的な作業相手になりつつあります。

一方で、その賢さは無料ではありません。裏側では大量のトークンを使い、SWE-bench Verifiedのような重いタスクでは、3問だけでも無視できないコストになります。

また、AIエージェントを「LLMにツールを渡してループを回すもの」と捉えるだけでは、現代的なハーネスの振る舞いを説明しにくくなってきました。ツール接続は重要ですが、それだけでは差別化になりにくく、探索、編集、実行、検証、リトライを含むループ全体の作り込みが性能に効いているように見えます。

次にやるなら、評価セットや集計ロジックをもう少し安定させたうえで、予算を確保して50問評価まで広げたいです。そのうえで、タスク種別ごとに「どの作業を、どのハーネスに、どれくらいのコストで任せると割に合うのか」を見ていきたいと思います。

ハーネスをサービスとしてどう提供するかは、今回は扱いませんでした。ただ、Cowork的なAI活用を考えるうえでも、ハーネスの性能とコスト構造を理解しておく必要はありそうです。

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が「デモ」から「実装」に近づいてきている感触はありました。

一方で、

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

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

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