はじめに
2026年9月30日のDatabricksブログで、AI Functionsに ai_decide が追加されたことが発表されました。現在はベータです。
テキストを受け取り、文章を生成する代わりに判定の結果を構造化して返す関数です。ブログでは、こうした判定専用のモデル (decision model) の例として TypeSafe AI の Jev が挙げられています。Jev については、これまで次の2本の記事で試してきました。
今回は、ワークスペースでベータを有効にして ai_decide を呼んでみました。ドキュメントの例を動かしたあと、日本語の入力で試し、同じクエリを2回流して結果を比べています。日本語での挙動や、実行するたびに値が変わる点も含めて書いていきます。
ai_decide とは
構文は次のとおりです。
ai_decide(state, questions [, options])
-
state: 判定の対象になるテキスト。STRING か VARIANT で渡し、JSON も渡せます -
questions: 質問を定義した JSON。キーが質問のID、値が質問の定義です -
options: 省略できます。いまのところ指定できるのはversion('1.0') だけです
質問には3つの型があります。
| 型 | 返すもの | 使いどころ |
|---|---|---|
noul |
0〜1の確率 | 「返品を希望しているか」のような、はい・いいえで答える質問 |
choice |
名前付きの選択肢から1つ | カテゴリ分け |
score |
順序のある段階のどこに当たるか | 満足度のような段階評価 |
1回の呼び出しで複数の質問をまとめて投げられます。戻り値は VARIANT で、response、metadata、error_message の3つのフィールドを持ちます。
ここが最初のハマりどころです。questions は定数の STRING でなければなりません。テーブルの列の値から質問を組み立てて渡すことはできないので、質問の内容を変えたいときは ai_decide の呼び出しを分けます。
ドキュメントに書かれている要件は次のとおりです。
- Databricks Runtime 15.4 LTS 以上が必要です。18.2 以上が推奨されています
- Databricks SQL Classic では使えません
- 提供されているリージョンは一部に限られます
- ベータなので、ワークスペースの管理者がプレビュー機能を有効にする必要があります
プレビュー機能の一覧で「AI Decide」をオンにしました。
ドキュメントには、現時点で使われる可能性のあるモデルは Apache 2.0 ライセンスである、という記載があります。具体的なモデル名は書かれていません。
ドキュメントの例を動かす
まずはドキュメントにある例をそのまま実行します。商品の説明文に対して、3つの型の質問を1回で投げています。
SELECT ai_decide(
'{"name": "TrailShell jacket", "description": "Lightweight waterproof hiking jacket made from recycled polyester. Packs into its own pocket."}',
'{
"category": {
"type": "choice",
"instructions": "Which product category best fits this item?",
"criteria": {
"outerwear": "Jackets, coats, and other protective outer layers",
"footwear": "Shoes, boots, and sandals",
"accessories": "Bags, hats, and other accessories"
}
},
"recycled_materials": {
"type": "noul",
"instructions": "Does the listing state that the product uses recycled materials?"
},
"hiking_suitability": {
"type": "score",
"instructions": "How suitable is this product for hiking in rainy weather?",
"criteria": [
"Not suitable for outdoor use in rain",
"Offers some protection from rain",
"Designed for hiking with waterproof protection"
]
}
}',
map('version', '1.0')
) AS decision;
返ってきた VARIANT の中身です。
{
"error_message": null,
"metadata": {"version": "1.0"},
"response": {
"answers": {
"category": {
"choice": "outerwear",
"confidence": 0.99,
"probabilities": {"accessories": 0, "footwear": 0, "outerwear": 1},
"type": "choice"
},
"hiking_suitability": {
"confidence": 0.99,
"legend": {
"0": "Not suitable for outdoor use in rain",
"1": "Offers some protection from rain",
"2": "Designed for hiking with waterproof protection"
},
"probabilities": {"0": 0, "1": 0, "2": 1},
"score": 2,
"type": "score"
},
"recycled_materials": {"probability": 0.99, "type": "noul"}
}
}
}
3問とも想定どおりの答えです。score の値は、criteria に並べた段階の何番目かを 0 から数えた番号です。legend を見ると、2 が一番上の「Designed for hiking with waterproof protection」に対応していることが分かります。
値は小数点以下2桁に丸めて返ってくるようです。ノートブックの結果表示では 0.99 が 1 と表示されることがあるので、細かい値を見たいときは列として取り出して確認してください。
レビューをまとめて判定する
ブログで紹介されている、レビューへのタグ付けを試します。テーブルの各行に対して ai_decide を呼び、VARIANT から必要な値を列として取り出します。
CREATE OR REPLACE TEMP VIEW reviews AS
SELECT * FROM VALUES
(1, 'Arrived with a cracked screen. I want my money back.'),
(2, 'Battery lasts all day, very happy with it.'),
(3, 'Shipping took three weeks. Product itself is fine.'),
(4, 'Size runs small, will exchange for a larger one.')
AS t(review_id, review_text);
WITH decided AS (
SELECT
review_id,
review_text,
ai_decide(
review_text,
-- questions は定数の文字列で渡す必要があります。列の値から組み立てることはできません
'{
"issue": {
"type": "choice",
"instructions": "What is the primary issue raised in this review?",
"criteria": {
"damaged": "Product arrived broken or defective",
"shipping": "Late or problematic delivery",
"sizing": "Wrong size or fit",
"none": "No issue; positive or neutral review"
}
},
"wants_return": {
"type": "noul",
"instructions": "Is the customer looking to return or exchange the product?"
},
"satisfaction": {
"type": "score",
"instructions": "How satisfied is the customer?",
"criteria": ["Very dissatisfied", "Neutral", "Very satisfied"]
}
}'
) AS d
FROM reviews
)
SELECT
review_id,
review_text,
d:response.answers.issue.choice::string AS issue,
d:response.answers.issue.confidence::double AS issue_conf,
d:response.answers.wants_return.probability::double AS p_return,
-- score は criteria の段階を 0 から数えた番号です (0 = Very dissatisfied, 2 = Very satisfied)
d:response.answers.satisfaction.score::double AS satisfaction,
d:error_message::string AS err
FROM decided;
結果は次のとおりです。
| review_id | issue | issue_conf | p_return | satisfaction |
|---|---|---|---|---|
| 1 (画面が割れて届いた、返金希望) | damaged | 1.0 | 1.0 | 0.0 |
| 2 (バッテリーが一日持つ、満足) | none | 0.99 | 0.01 | 2.0 |
| 3 (配送に3週間、製品は問題なし) | shipping | 0.95 | 0.1 | 0.8 |
| 4 (小さいので交換する) | sizing | 0.99 | 0.98 | 1.0 |
ID 3 の満足度は 0.8 です。score の値は、各段階の確率で重み付けした平均として計算されます。そのため 0 や 1 のような段階の番号ちょうどではなく、中間の値になることがあります。0.8 は「Very dissatisfied」と「Neutral」の間で、「Neutral」に近い位置です。不満と満足が混ざった文なので、妥当な値です。
ID 4 の p_return は 0.98 でした。質問の文に「return or exchange」と書いたので、交換も返品の希望として扱われています。どこまでを「はい」とするかは、質問の書き方で決まります。
ジャッジとして使う
state には JSON も渡せます。ポリシー、質問、回答をまとめて渡し、回答がポリシーに沿っているかを noul で判定します。
SELECT ai_decide(
to_json(named_struct(
'policy', 'Refunds are accepted within 30 days of purchase with a receipt.',
'question', 'Can I get a refund after 45 days?',
'answer', 'Yes, refunds are available at any time.'
)),
'{
"follows_policy": {
"type": "noul",
"instructions": "Is the answer consistent with the policy?",
"criteria": {
"true": "The answer agrees with the policy",
"false": "The answer contradicts or misstates the policy"
}
}
}'
):response.answers.follows_policy.probability::double AS p_follows_policy;
ポリシーに反する回答なので、p_follows_policy は 0.0 でした。エージェントの回答を評価するときに、LLM ジャッジの代わりに SQL から直接呼べる形です。
日本語で試す
ここからが本題です。ドキュメントの例はすべて英語なので、日本語の入力で試しました。指示文 (instructions と criteria) を日本語で書いた場合と英語で書いた場合を、同じ行に対して並べて比べています。
レビューは8件用意しました。
- ID 1〜4: 英語の例を日本語に訳したもの
- ID 5〜8: 判断が割れそうな文や、どのカテゴリにも当てはまらない文
CREATE OR REPLACE TEMP VIEW reviews_ja AS
SELECT * FROM VALUES
(1, '画面が割れた状態で届きました。返金してください。'),
(2, 'バッテリーが一日中持ちます。とても満足しています。'),
(3, '届くまで3週間かかりました。製品自体は問題ありません。'),
(4, 'サイズが小さめなので、大きいサイズに交換します。'),
(5, '悪くはないけど、この値段なら他のを選んだかもしれません。'),
(6, '箱は潰れていましたが、中身は無事でした。'),
(7, '色が写真と全然違う。正直がっかりです。'),
(8, 'もう二度と買いません。')
AS t(review_id, review_text);
WITH decided AS (
SELECT
review_id,
review_text,
-- 指示文を日本語で書いた場合
ai_decide(
review_text,
'{
"issue": {
"type": "choice",
"instructions": "このレビューで挙げられている主な問題は何ですか?",
"criteria": {
"damaged": "製品が壊れている、または不良品だった",
"shipping": "配送が遅い、または配送に問題があった",
"sizing": "サイズや着用感が合わない",
"none": "問題なし。肯定的または中立的なレビュー"
}
},
"wants_return": {
"type": "noul",
"instructions": "顧客は製品の返品または交換を希望していますか?"
},
"satisfaction": {
"type": "score",
"instructions": "顧客の満足度はどの程度ですか?",
"criteria": ["非常に不満", "どちらでもない", "非常に満足"]
}
}'
) AS d_ja,
-- 同じ質問を英語で書いた場合
ai_decide(
review_text,
'{
"issue": {
"type": "choice",
"instructions": "What is the primary issue raised in this review?",
"criteria": {
"damaged": "Product arrived broken or defective",
"shipping": "Late or problematic delivery",
"sizing": "Wrong size or fit",
"none": "No issue; positive or neutral review"
}
},
"wants_return": {
"type": "noul",
"instructions": "Is the customer looking to return or exchange the product?"
},
"satisfaction": {
"type": "score",
"instructions": "How satisfied is the customer?",
"criteria": ["Very dissatisfied", "Neutral", "Very satisfied"]
}
}'
) AS d_en
FROM reviews_ja
)
SELECT
review_id,
review_text,
d_ja:response.answers.issue.choice::string AS issue_ja,
d_en:response.answers.issue.choice::string AS issue_en,
d_ja:response.answers.issue.confidence::double AS conf_ja,
d_en:response.answers.issue.confidence::double AS conf_en,
d_ja:response.answers.wants_return.probability::double AS p_return_ja,
d_en:response.answers.wants_return.probability::double AS p_return_en,
d_ja:response.answers.satisfaction.score::double AS sat_ja,
d_en:response.answers.satisfaction.score::double AS sat_en,
coalesce(d_ja:error_message::string, d_en:error_message::string) AS err
FROM decided
ORDER BY review_id;
1回目の結果です。各列の左が日本語の指示文、右が英語の指示文の結果です。
| ID | issue | confidence | p_return | satisfaction |
|---|---|---|---|---|
| 1 | damaged / damaged | 1.0 / 1.0 | 1.0 / 1.0 | 0.0 / 0.0 |
| 2 | none / none | 1.0 / 0.99 | 0.0 / 0.0 | 2.0 / 1.98 |
| 3 | shipping / shipping | 0.95 / 0.9 | 0.02 / 0.05 | 0.95 / 1.0 |
| 4 | sizing / sizing | 1.0 / 0.99 | 1.0 / 1.0 | 0.9 / 0.4 |
| 5 | none / none | 0.9 / 0.9 | 0.1 / 0.05 | 1.0 / 0.9 |
| 6 | shipping / damaged | 0.95 / 0.95 | 0.0 / 0.1 | 0.9 / 0.9 |
| 7 | none / none | 0.6 / 0.3 | 0.3 / 0.6 | 0.25 / 0.15 |
| 8 | damaged / none | 0.2 / 0.4 | 0.5 / 0.1 | 0.3 / 0.0 |
satisfaction の列には 0.8999999999999999 のような値も返ってきました。丸めた確率から加重平均を計算しているためだと思われます。表では 0.9 のように丸めて載せています。
はっきりした文は言語に関係なく同じ答え
ID 1〜3 と ID 5 は、日本語の指示文でも英語の指示文でも同じ答えでした。ID 1〜3 は、英語のレビューで試したときともほぼ同じ値です。日本語の入力でも、内容がはっきりしていれば問題なく判定できています。
指示文の言語で答えが分かれた文
ID 6 (箱は潰れていたが中身は無事) は、日本語の指示文では shipping、英語の指示文では damaged になりました。どちらも confidence は 0.95 です。箱が潰れたことを配送の問題と見るか、商品の破損と見るかで、どちらにも取れる文です。
ID 7 (色が写真と違う) と ID 8 (もう二度と買わない) は、用意した4つのカテゴリのどれにも当てはまりません。どちらも confidence が低めに出ています。
ジャッジは中途半端な回答に 0.5
ジャッジも日本語で試しました。ポリシーは「返金は購入から30日以内、レシートがある場合に限り受け付けます」で、回答を4パターン用意しています。
CREATE OR REPLACE TEMP VIEW judge_ja AS
SELECT * FROM VALUES
(1, '返金できますか?購入から45日経っています。', 'はい、いつでも返金できます。'),
(2, '返金できますか?購入から45日経っています。', '申し訳ありません。返金は購入から30日以内に限られます。'),
(3, '返金できますか?購入から20日経っています。', 'はい、レシートがあれば返金できます。'),
(4, '返金できますか?購入から20日経っています。', 'はい、返金できます。')
AS t(case_id, question, answer);
SELECT
case_id,
question,
answer,
ai_decide(
to_json(named_struct(
'policy', '返金は購入から30日以内、レシートがある場合に限り受け付けます。',
'question', question,
'answer', answer
)),
'{
"follows_policy": {
"type": "noul",
"instructions": "回答はポリシーと矛盾していませんか?",
"criteria": {
"true": "回答はポリシーと一致している",
"false": "回答はポリシーと矛盾している、またはポリシーを誤って伝えている"
}
}
}'
):response.answers.follows_policy.probability::double AS p_follows_policy
FROM judge_ja
ORDER BY case_id;
| case_id | 回答 | p_follows_policy |
|---|---|---|
| 1 | (45日後に) はい、いつでも返金できます | 0.0 |
| 2 | (45日後に) 返金は30日以内に限られます | 1.0 |
| 3 | (20日後に) レシートがあれば返金できます | 1.0 |
| 4 | (20日後に) はい、返金できます | 0.5 |
ケース4は、期間の条件は満たしていますが、レシートの条件に触れていない回答です。間違いとは言い切れない回答に 0.5 が返っています。はっきり正しい回答とはっきり間違った回答は、0 と 1 にきれいに分かれました。
同じクエリを2回流すと値が変わる
日本語と英語の差が本当に言語によるものかを確かめるため、まったく同じクエリをもう一度流しました。
ジャッジの4ケースは、1回目と完全に同じ値でした。一方、レビューの判定は8行すべてで値が変わりました。
| ID | 1回目 (日本語 / 英語) | 2回目 (日本語 / 英語) |
|---|---|---|
| 1 | damaged / damaged (1.0 / 1.0) | damaged / damaged (0.99 / 1.0) |
| 3 | shipping / shipping (0.95 / 0.9) | shipping / shipping (0.95 / 0.99) |
| 4 | 満足度 0.9 / 0.4 | 満足度 1.0 / 0.7 |
| 6 | shipping / damaged (0.95 / 0.95) | shipping / damaged (0.96 / 0.99) |
| 7 | none / none (0.6 / 0.3) | damaged / damaged (0.4 / 0.9) |
| 8 | damaged / none (0.2 / 0.4) | damaged / none (0.4 / 0.9) |
括弧の中は issue の confidence です。
答えがはっきりした入力は安定している
ID 1〜3 と ID 5 は、issue の答えが2回とも同じでした。値の揺れは 0.1 程度に収まっています。
ID 6 は、2回とも「日本語の指示文なら shipping、英語なら damaged」という分かれ方でした。この差は実行ごとの揺れではなく、指示文の言語による差と見てよさそうです。
ID 4 の満足度は、1回目に日本語 0.9、英語 0.4 と大きく分かれました。2回目では 1.0 と 0.7 で、差が縮まっています。1回だけ流した結果で言語の差を判断していたら、読み違えていたところでした。
どのカテゴリにも当てはまらない入力は答えが変わる
ID 7 は、1回目は日英とも none、2回目は日英とも damaged でした。返品希望の確率 (日本語の指示文) も 0.3 から 0.8 に変わっています。用意した選択肢に合う答えがない入力では、実行するたびに答えが変わることがあります。
confidence は「入力が判定をどれだけ裏付けるか」
ID 7 の英語の指示文では、2回目の confidence が 0.9 でした。答えが変わったうえで、confidence が高く出ています。
ドキュメントでは、confidence は「state (入力) が判定をどれだけ裏付けているか」を 0〜1 で表す値と説明されています。答えが当たっている見込みを表す値ではありません。ID 8 の日本語の指示文の confidence が 0.2 だったのも、この定義なら説明がつきます。4択の確率の最大値は 0.25 を下回らないので、confidence が確率の最大値そのものではないことが分かります。
confidence で結果を絞り込む使い方を考えている場合は、この定義を前提に設計したほうが良さそうです。
まとめ
ai_decide を英語と日本語で試して分かったことをまとめます。
-
ai_decideは、テキストに対する判定の結果を VARIANT で返す AI Functions。質問の型はnoul(確率)、choice(選択肢)、score(段階評価) の3つ questionsは定数の文字列で渡す。列の値から組み立てることはできない- 値は小数点以下2桁に丸めて返ってくる。
scoreは確率で重み付けした平均なので、中間の値になることがある - 日本語の入力でも、内容がはっきりしていれば英語と同じ答えになった
- 指示文の言語によって、答えが分かれる文があった (箱は潰れていたが中身は無事、という文で
shippingとdamaged) - 同じクエリでも、実行するたびに値が変わる。とくに、用意した選択肢のどれにも当てはまらない入力では答えそのものが変わる
- confidence は答えが当たっている見込みではなく、入力が判定をどれだけ裏付けるかを表す値
- ジャッジの例は2回とも同じ値で、中途半端な回答には 0.5 が返った
一番の収穫は、1回の実行結果だけで判断してはいけないと分かったことでした。1回目の結果だけを見ていたら、日本語と英語の差をもっと大きく見積もっていたと思います。ベータの現時点でも、SQL の中からそのまま判定を呼べる手軽さは魅力です。本番で使うときは、選択肢に「どれにも当てはまらない」を明示的に用意することと、同じ入力を何回か流して答えが揃うかを確かめることから始めるのが良さそうです。
参考リンク
- Introducing ai_decide: Make fast decisions on your governed data
- ai_decide 関数
- Jev は LLM ジャッジに使えるか、紛らわしい誤答72件で確かめた


