[[BigQuery]] の [`AI.SCORE()`](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-ai-score) は、自然文で書かれた採点基準を LLM に渡して `FLOAT64` のスコアを返す [[BigQuery マネージド AI 関数|マネージド AI 関数]] である。`ORDER BY` 句に直接組み込んで上位 N 件を取り出すランキング用途が中核となる設計。
マネージド AI 関数 3 兄弟 ([[BigQuery AI.IF 関数|AI.IF()]] / `AI.SCORE()` / `AI.CLASSIFY()`) の 1 つで、`FLOAT64` 戻り値の連続値ランキングを担う。ルーブリックの自動生成で、曖昧な指示でも結果がブレにくい。2025 年 11 月 Preview、2026 年 4 月 GA。
## シグネチャ
```sql
AI.SCORE(
[ prompt => ] PROMPT
[, connection_id => 'CONNECTION' ]
[, endpoint => 'ENDPOINT' ]
[, max_error_ratio => MAX_ERROR_RATIO ]
) -> FLOAT64
```
必須引数は `prompt` のみ。`AI.IF()` と比べて `examples` / `embeddings` / `optimization_mode` を持たない (蒸留モデルによる LLM 呼び出しの省略が連続値スコアに馴染まないため)。
戻り値は `FLOAT64` で、[[Vertex AI]] の呼び出しに失敗したとき (quota 超過、モデル利用不可など) は `NULL` を返す。
`prompt` を `STRUCT` で渡す場合、少なくとも 1 つのフィールドは文字列リテラル (採点指示として SQL に静的に書いた文字列) である必要がある。すべてカラム参照だと validation エラーになる。これは行ごとに採点指示が動的に変わって尺度がぶれるのを防ぐ設計。
## 基本的な使い方
```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.SCORE(('対応の緊急度を 1 (低) から 10 (高) で採点してください', message)) AS urgency
FROM tickets
ORDER BY urgency DESC
```
実行結果:
| id | message | urgency |
| -- | ------------------------------------------------------------------ | ------- |
| 6 | アカウントにログインできません。明日の会議で使う予定なので急ぎです | 10.0 |
| 10 | サーバーエラーで作業が止まっています。至急対応をお願いします | 10.0 |
| 1 | 先週届いた商品が動作しません。至急返金対応お願いします | 8.0 |
| 4 | 3 回連続で配達遅延。もう解約したいです | 8.0 |
| 8 | 請求金額が二重に引き落とされています | 8.0 |
| 2 | パスワードのリセット方法を教えてください | 5.0 |
| 5 | NULL | 5.0 |
| 7 | A プランから B プランへの変更手順を教えてください | 5.0 |
| 3 | 料金プランの違いが知りたいです | 2.0 |
| 9 | 新機能の追加を検討いただけると嬉しいです | 2.0 |
> [!note]
> - `id 5`: `message` が `NULL` でも `AI.SCORE()` は LLM を呼び出し、デフォルトとしてスケール中央値の `5.0` が返っている。`AI.IF()` と異なり LLM 呼び出しを省略しない
> - スコア値そのものは絶対的な意味を持たない。同じクエリ内で「相対順位を付けるための尺度」として使う
> [!tip]
> 採点の範囲はプロンプトに必ず明示する (「1 から 10」「0.0 から 1.0」など)。範囲を書かないと LLM が独自に決めてしまい、行ごとに尺度がぶれる。両端の意味 (例: 「1 = 緊急性が低い、10 = 即対応が必要」) も 1 文で書いておくと、中間判断の軸が安定する。
> [!info]
> 実運用の前に、[[BigQuery AI 関数の料金は二重構造になる]] で BigQuery 側と Vertex AI 側で二重に発生する課金の構造を押さえておく。
## NULL の扱い
`AI.SCORE()` は入力プロンプトに `NULL` フィールドが含まれていても LLM を呼び出し、`FLOAT64` の数値を返す。`AI.IF()` のように `NULL` 入力で LLM を呼ばずに `NULL` を返す挙動は持たない。
この挙動の差は戻り値型から逆算された設計に由来する (詳細は [[BigQuery マネージド AI 関数は NULL 入力時の挙動が戻り値型で変わる]])。
LLM が返す値は採点スケールの最小値・中央値などのデフォルトに倒れる傾向があり、ランキング結果に静かに混入する。`NULL` を「未採点」として保持したい場合は SQL 層で防御する。
```sql
SELECT
CASE
WHEN message IS NULL THEN NULL
ELSE AI.SCORE(('対応の緊急度を 1 から 10 で採点: ', message))
END AS urgency
FROM tickets
```
> [!warning]
> `COALESCE(field, '')` で `NULL` を空文字に倒すと、判定基準が壊れたまま LLM が呼ばれて何らかの数値が返る。意味のない採点が下流に流れるリスクがあるため、入力欠損は `WHERE` または `CASE` で先に弾く。