# 通知設計書 87-AIエンドポイント呼び出し

## 概要

本ドキュメントは、SQL Server First Responder Kitのsp_BlitzCache.sqlにおける「AIエンドポイント呼び出し」通知の設計を定義する。この通知は、クエリプラン分析においてAI（人工知能）サービスのREST APIエンドポイントへの呼び出しを開始することを示す情報メッセージである。

### 本通知の処理概要

この通知は、sp_BlitzCacheストアドプロシージャがAIプロンプト構築後、実際にOpenAIやGoogle GeminiなどのAIサービスのエンドポイントを呼び出す処理の開始を示す。@AIパラメータが1に設定されている場合にのみ、実際のAPI呼び出しが行われる。

**業務上の目的・背景**：SQL Server 2022以降とAzure SQL Databaseでは、sp_invoke_external_rest_endpoint関数を使用して外部REST APIを呼び出すことが可能になった。この機能を活用して、クエリプランとパフォーマンスメトリクスをAIサービスに送信し、クエリ最適化のアドバイスを自動生成する。この通知は、各クエリに対するAI分析のためのAPI呼び出しが開始されたことをユーザーに通知する。

**通知の送信タイミング**：sp_BlitzCacheプロシージャの実行中、@AI = 1の場合に、AIエンドポイント呼び出しのループ処理開始時および各クエリ分析時に送信される。カーソルループ内で各クエリごとにAPI呼び出し前のメッセージが出力される。

**通知の受信者**：この通知はRAISERROR関数によりSQL Server Management Studio（SSMS）のメッセージウィンドウ、またはsqlcmdなどのコマンドラインツールの標準出力に表示される。主な受信者は、sp_BlitzCacheを@AI=1で実行しているDBA、データベース開発者、システム管理者である。

**通知内容の概要**：「Calling AI endpoint for query plan analysis - starting loop」または「Calling AI endpoint for query plan analysis on query: {クエリ抜粋}」という英語メッセージが表示され、AIエンドポイントへの呼び出しが実行中であることを示す。

**期待されるアクション**：通知の受信者は、AI分析のためのAPI呼び出しが進行していることを確認し、処理完了を待つ。外部APIへの呼び出しにはネットワーク遅延が伴うため、分析対象クエリ数に応じて処理時間が延長される可能性がある。

## 通知種別

コンソール出力（RAISERROR WITH NOWAIT）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（WITH NOWAITオプションにより即座に出力） |
| 優先度 | 低（情報レベル、重大度0） |
| リトライ | なし（通知メッセージ自体はリトライなし） |

### 送信先決定ロジック

sp_BlitzCacheを実行しているセッションのクライアント接続に対して直接出力される。送信先の動的な決定ロジックは存在せず、実行元のセッションに対して常に通知される。

## 通知テンプレート

### メール通知の場合

該当なし（コンソール出力のみ）

### 本文テンプレート

```
Calling AI endpoint for query plan analysis - starting loop
```

または（各クエリ処理時）

```
Calling AI endpoint for query plan analysis on query:
{QueryType} - {QueryText（先頭100文字）}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @CurrentQueryClipped | クエリの種別と先頭100文字 | COALESCE(QueryType, N'') + N' - ' + LEFT(QueryText, 100) | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| プロシージャ実行 | sp_BlitzCache内のAIエンドポイント呼び出しループ開始 | @AI = 1 | AI API呼び出しが有効な場合のみ通知 |
| プロシージャ実行 | 各クエリのAI分析前 | @AI = 1かつカーソルループ内 | 各クエリ処理時に通知 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| @AI = 0または未指定 | AI機能が無効の場合は通知されない |
| @AI = 2 | プロンプト生成のみで実際のAPI呼び出しは行わない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[AIプロンプト構築完了] --> B{@AI = 1?}
    B -->|No| I[スキップまたはプロンプトのみ]
    B -->|Yes| C[RAISERROR - starting loop]
    C --> D[カーソル ai_cursor オープン]
    D --> E[FETCH NEXT]
    E --> F{@@FETCH_STATUS = 0?}
    F -->|Yes| G[RAISERROR - on query]
    F -->|No| K[カーソルクローズ]
    G --> H[sp_invoke_external_rest_endpoint呼び出し]
    H --> J[結果パース・保存]
    J --> E
    K --> L[AI analysis complete]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| ##BlitzCacheProcs | プランキャッシュ分析結果の格納 | グローバル一時テーブル |

### テーブル別参照項目詳細

#### ##BlitzCacheProcs

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| SqlHandle | カーソルでの一意識別 | DISTINCT, WHERE QueryPlan IS NOT NULL |
| QueryHash | カーソルでの一意識別 | 同上 |
| PlanHandle | カーソルでの一意識別 | 同上 |
| ai_prompt | AIへ送信するプロンプト | カーソルで取得 |
| QueryType | クエリ種別 | 進捗メッセージ用 |
| QueryText | クエリテキスト | 進捗メッセージ用（先頭100文字） |
| ai_advice | AI分析結果の格納先 | SET句で更新 |
| ai_raw_response | APIレスポンスJSON | SET句で更新 |
| ai_payload | 送信ペイロード | SET句で更新 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| ##BlitzCacheProcs | UPDATE | ai_advice、ai_raw_response、ai_payloadカラムを更新 |

#### ##BlitzCacheProcs更新

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| UPDATE | ai_advice | AIからのアドバイステキスト | パース後の内容 |
| UPDATE | ai_raw_response | APIレスポンスJSON | デバッグ用 |
| UPDATE | ai_payload | 送信したペイロード | デバッグ用 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| API呼び出しエラー | ネットワーク障害、認証エラー | TRY-CATCHでエラーメッセージをai_adviceに格納 |
| レスポンスパースエラー | 想定外のJSON形式 | 「Unable to parse API response」メッセージを設定 |
| タイムアウト | @AITimeoutSeconds超過 | エラーメッセージをai_adviceに格納 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（API呼び出し自体のリトライは実装されていない） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（AIサービス側のレート制限に依存） |
| 1日あたり上限 | 制限なし（AIサービス側のクォータに依存） |

### 配信時間帯

制限なし（sp_BlitzCache実行時に即座に出力）

## セキュリティ考慮事項

- 通知内容にはクエリの先頭100文字が含まれる可能性がある
- 外部AIサービスへの送信時はデータの機密性を考慮する必要がある
- @AICredentialパラメータで認証情報を指定する必要がある
- VIEW SERVER STATE権限が必要（sp_BlitzCache実行の前提条件）
- sp_invoke_external_rest_endpointの実行権限が必要

## 備考

- この通知はsp_BlitzCacheのAI機能の一部であり、@AI = 1の場合にのみ実際のAPI呼び出しが行われる
- @AI = 2の場合はプロンプト生成のみで、API呼び出しは行われない
- WITH NOWAITオプションにより、バッファリングなしで即座にクライアントに送信される
- 重大度0は情報メッセージを意味し、エラーではない
- OpenAI形式とGoogle Gemini形式の両方のレスポンスパースに対応している
- 各クエリごとにループで処理するため、対象クエリ数に応じて処理時間が延長される

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、プロシージャで使用されるAI関連カラムとパラメータの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | sp_BlitzCache.sql | `sp_BlitzCache.sql` | @AI、@AIURL、@AICredential、@AIModel、@AITimeoutSecondsパラメータの定義を確認 |
| 1-2 | sp_BlitzCache.sql | `sp_BlitzCache.sql` | ##BlitzCacheProcsテーブルのai_advice、ai_raw_response、ai_payloadカラム定義を確認 |

**読解のコツ**: @AI = 1の場合のみsp_invoke_external_rest_endpointが呼び出される。@AI = 2はプロンプト生成のみ。

#### Step 2: エントリーポイントを理解する

処理の起点となる条件分岐を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | sp_BlitzCache.sql | `sp_BlitzCache.sql` | @AI = 1による条件分岐とカーソルループの開始 |

**主要処理フロー**:
1. **行5317**: IF @AI = 1 による条件分岐
2. **行5319**: RAISERROR('Calling AI endpoint for query plan analysis - starting loop', 0, 1) WITH NOWAITでループ開始メッセージ出力
3. **行5330-5335**: ai_cursorカーソル定義（SqlHandle, QueryHash, PlanHandle, ai_prompt, クエリ抜粋を取得）
4. **行5341**: WHILE @@FETCH_STATUS = 0 でループ
5. **行5359**: 各クエリのRAISERROR('Calling AI endpoint for query plan analysis on query: ', 0, 1)

#### Step 3: API呼び出しと結果処理を理解する

実際のAPI呼び出しと結果パース処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | sp_BlitzCache.sql | `sp_BlitzCache.sql` | sp_invoke_external_rest_endpointの呼び出しとレスポンス処理 |

**主要処理フロー**:
- **行5362-5369**: sp_invoke_external_rest_endpoint呼び出し（@url, @method, @payload, @headers, @credential, @timeout, @response）
- **行5379-5409**: レスポンスJSONのパース（OpenAI形式、Google Gemini形式対応）
- **行5417-5426**: ai_advice、ai_raw_response、ai_payloadの更新
- **行5429-5448**: TRY-CATCHによるエラー処理

### プログラム呼び出し階層図

```
sp_BlitzCache
    |
    +-- RAISERROR('Building AI prompts for query plans')
    |
    +-- IF @AI = 1
            |
            +-- RAISERROR('Calling AI endpoint... - starting loop')  <-- 本通知(1)
            |
            +-- CURSOR ai_cursor
            |       |
            |       +-- WHILE @@FETCH_STATUS = 0
            |               |
            |               +-- TRY
            |               |       |
            |               |       +-- RAISERROR('Calling AI endpoint... on query:')  <-- 本通知(2)
            |               |       |
            |               |       +-- sp_invoke_external_rest_endpoint
            |               |       |       @url = @AIURL
            |               |       |       @method = 'POST'
            |               |       |       @payload = @AIPayload
            |               |       |       @credential = @AICredential
            |               |       |
            |               |       +-- OPENJSON(@AIResponseJSON) -- レスポンスパース
            |               |       |
            |               |       +-- UPDATE ##BlitzCacheProcs SET ai_advice, ai_raw_response
            |               |
            |               +-- CATCH
            |                       |
            |                       +-- エラーメッセージをai_adviceに設定
            |
            +-- RAISERROR('AI analysis complete')
```

### データフロー図

```
[入力]                          [処理]                              [出力]

##BlitzCacheProcs.ai_prompt --> @AIPayloadテンプレート展開      --> @AIPayload
                                    |
                                    v
@AIURL, @AICredential      --> sp_invoke_external_rest_endpoint --> @AIResponseJSON
                                    |
                                    v
@AIResponseJSON            --> OPENJSON パース                  --> @AIAdviceText
                                - OpenAI: $.result.choices[].message.content
                                - Gemini: $.result.candidates[].content.parts[].text
                                    |
                                    v
                           UPDATE ##BlitzCacheProcs
                                - ai_advice = @AIAdviceText
                                - ai_raw_response = @AIResponseJSON
                                - ai_payload = @AIPayload
                                    |
                                    v
                           コンソール出力 <-- RAISERROR
                           "Calling AI endpoint..."
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| sp_BlitzCache.sql | `sp_BlitzCache.sql` | ソース | AIエンドポイント呼び出し処理を含むメインストアドプロシージャ |
