[[BigQuery]] の [`AI.IF()`](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-if) は、自然文で書かれた条件を LLM に判定させて真偽値を返す [[BigQuery マネージド AI 関数|マネージド AI 関数]] である。`WHERE` 句や `JOIN` の `ON` 条件に自然文フィルタを直接書けるのが中核となる設計。
マネージド AI 関数 3 兄弟 (`AI.IF()` / [[BigQuery AI.SCORE 関数|AI.SCORE()]] / `AI.CLASSIFY()`) の 1 つで、`BOOL` 戻り値の真偽判定を担う。モデルは自動選択され、プロンプトを内部で構造化して結果のブレを抑える。2025 年 11 月 Preview、2026 年 4 月 GA。
## シグネチャ
```sql
AI.IF(
[ prompt => ] PROMPT
[, examples => EXAMPLES ]
[, connection_id => 'CONNECTION' ]
[, endpoint => 'ENDPOINT' ]
[, embeddings => EMBEDDINGS ]
[, optimization_mode => 'OPTIMIZATION_MODE' ]
[, max_error_ratio => MAX_ERROR_RATIO ]
) -> BOOL
```
必須引数は `prompt` のみ。戻り値は `BOOL` (`TRUE` / `FALSE` / `NULL` の 3 値) で、[[Vertex AI]] の呼び出しに失敗したとき (quota 超過、モデル利用不可など) は `NULL` を返す。
## 基本的な使い方
```sql
WITH tickets AS (
SELECT * FROM UNNEST (ARRAY<STRUCT<id INT64, message STRING>> [
(1, '先週届いた商品が動作しません。至急返金対応お願いします'),
(2, 'パスワードのリセット方法を教えてください'),
(3, '料金プランの違いが知りたいです'),
(4, '3 回連続で配達遅延。もう解約したいです'),
(5, NULL),
(6, 'アカウントにログインできません。明日の会議で使う予定なので急ぎです'),
(7, 'A プランから B プランへの変更手順を教えてください'),
(8, '請求金額が二重に引き落とされています'),
(9, '新機能の追加を検討いただけると嬉しいです'),
(10, 'サーバーエラーで作業が止まっています。至急対応をお願いします')
])
)
SELECT
*,
AI.IF(('この問い合わせはクレームを含むか', message)) AS is_complaint
FROM tickets
```
実行結果:
| id | message | is_complaint |
| -- | ------------------------------------------------------------------ | ------------ |
| 1 | 先週届いた商品が動作しません。至急返金対応お願いします | true |
| 2 | パスワードのリセット方法を教えてください | false |
| 3 | 料金プランの違いが知りたいです | false |
| 4 | 3 回連続で配達遅延。もう解約したいです | true |
| 5 | NULL | NULL |
| 6 | アカウントにログインできません。明日の会議で使う予定なので急ぎです | true |
| 7 | A プランから B プランへの変更手順を教えてください | false |
| 8 | 請求金額が二重に引き落とされています | true |
| 9 | 新機能の追加を検討いただけると嬉しいです | false |
| 10 | サーバーエラーで作業が止まっています。至急対応をお願いします | true |
> [!note]
> - `id 5`: `message` が `NULL` のため、`AI.IF()` は LLM を呼ばずに `NULL` を返している
> - `id 6, 10`: 丁寧な表現でも「機能不全の報告」がクレーム扱いされている
> [!tip]
> 判定が安定しないときは、以下を組み合わせて精度を高められる:
> - **プロンプトに定義を埋め込む**: 「クレームとは『不満や非難の表明と、改善や補償の要求』を指し、機能不全の事実報告や質問は含まない」のように、判定基準をプロンプト内で固定する。曖昧なプロンプトほど効果が大きい
> - **`examples` 引数を使う**: 主観的な境界事例は `FALSE` / `TRUE` の例を示すと、汎化により判定が安定する
> [!info]
> 実運用の前に、[[BigQuery AI 関数の料金は二重構造になる]] で BigQuery 側と Vertex AI 側で二重に発生する課金の構造を押さえておく。
## NULL の扱い
`AI.IF()` は、入力プロンプトを実効文字列に正規化したうえで、それが `NULL` または空文字 (`''`) のときに LLM を呼ばずに `NULL` を返す。
この正規化は `STRUCT` で渡した場合にも適用され、フィールドを `CONCAT` で連結した結果に対して判定される。そのため入力形式 (`STRING` / `STRUCT`) やフィールド数による違いはない。
| 入力 | 実効プロンプト | 結果 |
| --- | --- | --- |
| `NULL` | `NULL` | `NULL` (LLM 呼び出しなし) |
| `''` (空文字) | `''` | `NULL` (LLM 呼び出しなし) |
| `' '` (空白のみ) | `' '` | LLM 呼び出し → `false` に倒れがち |
| `STRUCT('指示: ', NULL)` | `NULL` | `NULL` (LLM 呼び出しなし) |
| `STRUCT('', '', '')` | `''` | `NULL` (LLM 呼び出しなし) |
| `STRUCT('指示: ', '本文')` | `'指示: 本文'` | LLM 呼び出し → `true` / `false` |
> [!warning]
> `COALESCE(field, '')` で `NULL` を空文字に変換すると、LLM が常に呼び出されるようになる。
>
> この変換では、プロンプトテンプレート部分のフィールドが `NULL` だった場合、判定指示が欠落したままでも LLM は感情的シグナルから真偽を返してしまい、サイレントな誤判定を生む。
>
> `NULL` を「未判定」として保持したいなら、`CASE WHEN field IS NULL THEN NULL ELSE AI.IF(...) END` で SQL 層で分岐するか、`WHERE field IS NOT NULL` で先に除外する。
3 関数 (`AI.IF()` / `AI.SCORE()` / `AI.CLASSIFY()`) で `NULL` 入力時の挙動が異なる理由は [[BigQuery マネージド AI 関数は NULL 入力時の挙動が戻り値型で変わる]] で展開する。