※ この記事は生成AIを使用して作成しています。
対象読者: Azure OpenAI / Azure AI Search を使ったRAGチャットボットで「デプロイしたら遅い」に直面した人
使用モデル:gpt-5-mini→ 最終的にgpt-5.4-miniを採用(いずれもGPT-5系 reasoning model)/ Azure OpenAI Service
計測方法: Application Insights(classic)+自作の90問回帰テストハーネス
背景
- Azureにチャットボットアプリをデプロイし試してみたところ、AIからの応答が遅く実用的でなかった。
- 20~30秒ほど応答がかかってしまっていた
検証方法
- FAQを実データとしてAIに食わせている。そのため各FAQに対応した質問文を90個用意し、応答速度、回答の整合性を指標として測定
- 上記に加え、デプロイを行いE2Eで10回ほど質問を行いアプリ上の実際の挙動を確認
TL;DR(結論から)
結論 : 応答時間を 平均約20s → 9.7s まで縮められた。効いたのは以下の3つ
- スロットリング解消
- reasoning_effortの適正化
- 無駄なLLM呼び出しの削減
理由:
- ①APIの同時実行数上限(capacity)不足による429スロットリング
- ②reasoning modelの「見えない思考時間」の設定ミス
- ③本来不要なLLM呼び出しの3つだった。
最終的に効いた施策と数値は以下の通り。
| 施策 | Before | After | 効果 |
|---|---|---|---|
| デプロイのcapacity引き上げ(10→30) | 429エラーが散発 | 429ゼロ | 遅延の外れ値(9.6〜14.2s)を解消 |
reasoning_effort を low に設定 |
medium(初期値) |
low |
体感速度改善(後述、モデル依存に注意) |
| 履歴なし初回ターンのクエリリライトをスキップ+プロンプト重複ルール削減 | 15.8s | 10.5s | -34% |
| (上記すべて込みの最終計測) | ― | 平均9.7s | 初期状態比 約-39% |
以下に「なぜその結論に至ったか」を背景とともに調査結果を記載しています。(=外れた仮説も含む)
1. 原因調査
ログ調査には Azure Application Insights を使用。
- 主題と逸れるが、本環境は classic 型のため、テーブル名が
AppRequests/AppDependenciesではなくrequests/dependencies(小文字) になる点にハマったので注意。
ログを解析すると/chat/stream エンドポイントの体感レイテンシが大きく、原因を切り分ける必要があった。
App Serviceのリソース層
↓ シロ
API呼び出しのスロットリング
↓ クロ
reasoning_effortの設定
↓ クロ(一番効いた)
リクエスト設計・プロンプト
↓ クロ
2. 仮説①: App Serviceのスペック不足(結果: ほぼ空振り)
問題: 最初に疑ったのはApp Service Planのリソース不足、そのためApp ServiceのSKUをB1 → B2に変更したが大きな効果はなし。
仮説: Application Insightsで見ると、App Service Plan(B1 = 1vCPU/1.75GB)のメモリ使用率が常時80%前後、CPUも80〜90%まで跳ねることがあった。さらに GET /msi/token(マネージドID のトークン取得)が通常<100msのところ13秒以上かかっているケースを1件観測し、「これがボトルネックでは」と仮説を立てた。
試行: B1→B2(2vCPU/3.5GB)にスケールアップして再計測。
結果: 後日、Application Insightsで実測平均を取り直したところ、App Service層のレイテンシは全体の7.4s程度で、直接APIを叩いた場合の9.0sと同オーダーであり、App Service自体は主要因ではないと判明。最初の「MSIトークンが13秒詰まった」事象は一時的な外れ値だった可能性が高い。
学び: リソースメトリクス(CPU/メモリ使用率)が高いからといって、それが体感レイテンシの原因とは限らない。実際のリクエストのEnd-to-Endの内訳(
dependenciesテーブルをoperation_Idで追う)を見るまでは判断を保留すべきだった。
3. 仮説②: APIスロットリング(結果: 実際の原因の一つ)
問題: Azure OpenAIのデプロイに設定している capacity(同時実行数の枠)が不足しており、429エラー(レート制限)による再試行が遅延の一因になっていた。
仮説: dependencies テーブルを operation_Id でグルーピングし、STEP別(クエリリライト/embedding/検索/最終回答生成)に内訳を出したところ、最終回答生成(ストリーミング)が全体の**約72%**を占めていた。この工程で使っているデプロイのcapacityが 10 しかなく、他のデプロイ(30)より明らかに小さかった。
試行: 実測で確認したところ、約263件のAPI呼び出し中 9件が429エラー。そしてその429が発生した5件のリクエストが、まさに実測で最も遅かったリクエスト(9.6秒〜14.2秒)と一致していた。
対応: capacityを 10→30 に引き上げ。GlobalStandardは従量課金なので、capacityを上げてもコストは増えない(上限が上がるだけ)。
結果: 引き上げ後、90問の回帰テストで 429エラー・リトライともにゼロを確認。スロットリングは「疑いではなく実際に効いていた原因」と結論づけた。
4. 仮説③: reasoning_effortの設定
問題: GPT-5系reasoning modelの reasoning_effort(見えない思考にどれだけ時間を使うか)の設定が、体感速度に最も大きく影響していた。
仮説: 最終回答生成に使われるreasoning modelは、環境変数 AZURE_OPENAI_REASONING_EFFORT の値(mediumが初期値)に応じて、回答本文を出す前に内部で「思考トークン」を消費する。これが体感レイテンシの大半を占めていた。
試行: 90問の回帰テストハーネスで gpt-5-mini の reasoning_effort を比較。
結果: 速度、回答品質共にlowで十分
- mediumは遅くなってしまい、minimalは回答品質が目に見えて下がるため
| reasoning_effort | 平均レイテンシ | LLMジャッジ正答率 | 冒頭文フォーマット一致率 |
|---|---|---|---|
| medium(初期値) | 約17.9〜20s台 | 基準 | 基準 |
| low | 約15s | 100%(0件NG) | 98% |
| minimal | 12.3〜13.0s | 97〜98%(2〜3件NG) | 86〜87% |
重要な注意点(ここが一番の学び):
-
minimalは速くならず、むしろ ハルシネーションが新規発生した(FAQに存在しない具体例を勝手に補完する等)。2回の再現テストで再現したため、gpt-5-miniではminimalは使わないと判断。 -
lowが「速さと正確性の両立」という意味で最適。
P: reasoning_effort=low を採用。単体の設定変更としては最もコスパの良い施策だった。
5. 仮説④: リクエスト設計・プロンプトの無駄(結果: 効いた)
P: モデルやインフラの設定だけでなく、アプリ側のリクエスト設計にも無駄があった。ここを削るだけで追加のコストなしに速度改善できた。
R: コードレビューの中で2つの無駄を発見。
- 履歴なし初回ターンでも、検索クエリのリライト用にLLM呼び出しを行っていた。しかし初回ターンには畳み込むべき会話履歴がそもそも無いため、この呼び出しは意味がなく純粋なオーバーヘッドだった。
-
プロンプト(
.prompty)内に、同じ趣旨のフォーマットルールが7回重複して書かれていた。意味を変えずに3回まで削減。
E: 修正前後で90問回帰テストを実施。
| Before | After | |
|---|---|---|
| 平均レイテンシ | 15.8s | 10.5s(-34%) |
| LLMジャッジ正答率 | 98%(2件NG) | 97%(3件NG) |
| 冒頭文一致率 | 97% | 96% |
NGになった質問・冒頭文ミスの質問は毎回違う(同じ失敗パターンが固定化したわけではない)ため、品質を犠牲にしない純粋な高速化と判断した。
P: このコード修正は、モデルやインフラの設定変更と違って副作用がほぼゼロで再現性も高いため、真っ先に検討すべき施策だった(気づいたのは調査の後半だったが、本来は最初にやるべき)。
6. 最終結果とその後の判断
上記すべてを反映した最新の90問回帰テスト(gpt-5-mini / reasoning_effort=low / capacity 30)の結果:
- 平均 9.7s(中央値8.9s、p90は12.6s)
- LLMジャッジ正答率 100%(0件NG)
- 429エラー・リトライ 0件
6.1 モデルの変更は効果が高い
単発質問90問の回帰テストでは gpt-5-mini(平均9.7s)で十分と判断していたが、実際の利用を想定した「複数ターンの会話」で再計測したところ、単発テストでは見えていなかった速度差が明確に出たため、最終的に gpt-5.4-mini を採用した。
なぜ再計測したか: 単発質問のテストは初回ターン(クエリリライト無し)しか計測していない。実際にはユーザーが2〜3問続けて質問することも多く、2ターン目以降で発生するクエリリライト呼び出し分の遅延が単発テストの数値には反映されていなかった。そこで7会話・計15ターンの多ターン会話を両モデルに同一条件(同一プロンプト・検索設定・reasoning_effort=low、本番と同じRAG処理経路)で流し、測り直した。
| 指標 | gpt-5-mini | gpt-5.4-mini | 差 |
|---|---|---|---|
| 応答速度(平均) | 14.3s | 8.1s | 1.8倍速い |
| 出力トークン(平均) | 748 | 266 | うち推論トークンが508→93に減少 |
| 料金(1質問あたり) | 0.35円 | 0.47円 | 1.33倍 |
- 応答速度: 単発質問では気づかなかったが、2ターン目以降(クエリリライトが挟まる場面)で差が顕著に開いた(1ターン目 11.7s→7.4s、2ターン目 17.0s→8.7s、3ターン目 15.8s→8.7s)。
-
コスト: トークン単価は入力3倍・出力2.25倍だが、
gpt-5.4-miniは出力トークン量自体が3分の1以下になるため、実際のコスト増は1.33倍にとどまった(プロンプトキャッシュの割引は、質問間隔が空くとキャッシュが切れる想定のため加味していない=上振れしない前提の数字)。 - 回答の網羅性: 単発90問テストでは、引用FAQ数が平均1.52件→1.02件に減る傾向が見られた(複数FAQにまたがる回答より、最も近い1件だけで済ませる傾向)。LLMジャッジによる事実整合性チェックは今回の多ターン検証でも100%を維持しており、速度・コストのメリットに対して許容できるトレードオフと判断した。
学び: 単発質問だけのベンチマークは、実際の会話で発生する「2ターン目以降のクエリリライトによる遅延」を見落とす。モデル比較は本番に近い会話パターン(複数質問の連続)で行わないと、実際の差を過小評価する。
まとめ
| やったこと | 分類 | 効果 |
|---|---|---|
| App Serviceスペック増強 | インフラ | ほぼ効果なし(要検証) |
| デプロイcapacity引き上げ | インフラ設定 | 429解消、外れ値の遅延を解消 |
| reasoning_effortの適正化 | モデル設定 | 最も効果大(ただしモデル依存) |
| 不要なLLM呼び出しの削減/プロンプト整理 | アプリ実装 | 副作用なく-34% |
| gpt-5-mini → gpt-5.4-miniへの切り替え | モデル選定 | 採用。多ターン会話の実測で応答速度1.8倍(コストは1.33倍) |
コストが低いもの・副作用が小さいものから先に見るべき。reasoning modelを使う構成では、思考コストの設定(reasoning_effort)とAPIのスロットリングを先に疑うべき。またモデル比較は単発質問だけでなく、実際の利用に近い複数ターンの会話で行うこと。
今回はテストもAIに生成させていたが、テストの実施の仕方も視野に入れて考えておくこと。単発質問ではなく複数質問を同一セッション内で連続して行うなど、実際のユースケースを想定したテストを行うこと