
こんにちは、CCCMKホールディングスAIエンジニアの三浦です。
先日サンフランシスコで開催されたSnowflakeの年次サミット"SNOWFLAKE SUMMIT25"のキーノートセッションの動画がアップされていて、最近そちらを閲覧しました。面白いアップデートが紹介されていたのですが、その中で特に印象に残ったのが"Cortex AISQL"です。
Cortex AISQLはSQLの中でLLMを呼び出し特定の処理を実行することが出来る関数セットです。LLMを使った特定の列の変換処理や、潜在的な意味に基づいたテーブルの結合処理を実現することが出来ます。
本記事では、現在利用できるAISQLを一通り触ってみて、それぞれどんなことが出来るのかをまとめてみました。今回ご紹介するCortex AISQLは現在パブリックプレビューでの提供になっています。また、一部のAISQLはテキストだけでなく画像も入力出来るのですが、今回試した日本リージョンの環境ではまだ対応していないようなのでテキストのみ検証しています。
AI_CLASSIFY
AI_CLASSIFYはテキストを指定したカテゴリに分類することが出来ます。
たとえば季節に関するテキストを受け取り、それがどの季節に該当するのかを分類する例を試してみました。
こんなクエリを作って実行してみました。
WITH TWEET AS ( SELECT value::string AS CONTENT FROM TABLE(FLATTEN(input => [ '今日は花見に行きました。', '雪だるまをたくさん作りました。', '焼き芋を作って食べました。', '暑かったので、プールに行きました!' ])) ) SELECT CONTENT, AI_CLASSIFY( CONTENT, ['春','夏','秋','冬'] ):labels[0]::string AS SEASON FROM TWEET
結果は次のようになりました。

"焼き芋を作って食べました。"は個人的には"秋"なのですが、AI_CLASSIFYは"冬"に分類しました。しかし結果としてはいずれも正しいと言えます。
AI_COMPLETE
AI_COMPLETEはプロンプトに対する応答を生成させることが出来ます。
任意のプロンプトを指定出来ることから汎用性が高い関数です。たとえば日本語を英語に変換する、といったことも可能です。 以下のようなSQLになります。
WITH TWEET AS ( SELECT value::string AS CONTENT FROM TABLE(FLATTEN(input => [ '今日は花見に行きました。', '雪だるまをたくさん作りました。', '焼き芋を作って食べました。', '暑かったので、プールに行きました!' ])) ) SELECT CONTENT, AI_COMPLETE( 'llama3.1-8b', CONCAT('次のtextを英語に訳して。: <text>',CONTENT,'</text>') )::string AS EN_CONTENT FROM TWEET
AI_COMPLETEは第一引数で使用するLLMを指定することが出来ます。与えるプロンプトはテキストだけでなくSnowflakeのPROMPTオブジェクトでも与えることが出来ます。
実行すると、以下のような結果が得られました。プロンプト通り、日本語を英語に変換出来ました。

AI_EMBED
AI_EMBEDはテキストの埋め込みベクトルを生成することが出来ます。
WITH TWEET AS ( SELECT value::string AS CONTENT FROM TABLE(FLATTEN(input => [ '今日は花見に行きました。', '雪だるまをたくさん作りました。', '焼き芋を作って食べました。', '暑かったので、プールに行きました!' ])) ) SELECT CONTENT, AI_EMBED( 'multilingual-e5-large', CONTENT ) AS CONTENT_EMBED FROM TWEET
結果は次のようになりました。

AI_FILTER
AI_FILTERは与えられたプロンプトの正誤をbooleanで返します。
個人的にはこの関数が一番面白い、と感じています。
一番ベーシックな使い方は次のような感じです。テキストが"春"に関するものかを判定させています。
WITH TWEET AS ( SELECT value::string AS CONTENT FROM TABLE(FLATTEN(input => [ '今日は花見に行きました。', '雪だるまをたくさん作りました。', '焼き芋を作って食べました。', '暑かったので、プールに行きました!' ])) ) SELECT CONTENT, AI_FILTER( PROMPT('これは春についての文章ですか?:{0}',CONTENT) ) AS IS_SPRING FROM TWEET
結果は次のようになりました。春に関するテキストだけTrueと判定されました。

また、WHERE句と組み合わせることで条件に該当する行を取得することが出来ます。
WITH TWEET AS ( SELECT value::string AS CONTENT FROM TABLE(FLATTEN(input => [ '今日は花見に行きました。', '雪だるまをたくさん作りました。', '焼き芋を作って食べました。', '暑かったので、プールに行きました!' ])) ) SELECT CONTENT, FROM TWEET WHERE AI_FILTER( PROMPT('これは春についての文章ですか?:{0}',CONTENT) )
春に関するテキストだけ抽出されます。

さらにこの条件をテーブルの結合条件にすることも出来ます。
WITH TWEET AS ( SELECT value::string AS CONTENT FROM TABLE(FLATTEN(input => [ '今日は花見に行きました。', '雪だるまをたくさん作りました。', '焼き芋を作って食べました。', '暑かったので、プールに行きました!' ])) ),SEASONS AS ( SELECT value::string AS SEASON FROM TABLE(FLATTEN(input => [ '春','夏','秋','冬' ])) ) SELECT TWEET.CONTENT, SEASONS.SEASON FROM TWEET JOIN SEASONS ON AI_FILTER(PROMPT('この文章"{0}"と季節"{1}"がマッチしているか評価して。',TWEET.CONTENT,SEASONS.SEASON))
結果は次のようになりました。

"焼き芋を作って食べました。"は"秋"に"冬"にも関連するので、JOINした結果は2行になりました。このように潜在的な意味に基づいてテーブルを結合できる、とても面白い機能だと思います!
AI_SIMILARITY
AI_SIMILARITYはテキスト同士の類似度を、埋め込みベクトルのコサイン類似度に基づいて計算します。
たとえば"夏祭りに行きました。"というテキストとの類似度を計算し、類似度の高さで並び替えてみました。
WITH TWEET AS ( SELECT value::string AS CONTENT FROM TABLE(FLATTEN(input => [ '今日は花見に行きました。', '雪だるまをたくさん作りました。', '焼き芋を作って食べました。', '暑かったので、プールに行きました!' ])) ) SELECT CONTENT, AI_SIMILARITY( CONTENT, '夏祭りに行きました。', {'model':'multilingual-e5-large'} ) AS SUMMER_IDX FROM TWEET ORDER BY SUMMER_IDX DESC
結果は次のようになり、夏に関連するテキストが最も高い類似度になりました。

ちなみに埋め込みモデルによって結果は結構変わります。デフォルトのsnowflake-arctic-embed-l-v2を使うと次のような結果になり、今度は"今日は花見に行きました。"が最上位に来ました。

AI_SENTIMENT
AI_SENTIMENTは入力されたテキストの全体的な感情やカテゴリ別の感情を出力します。
判定結果はpositive, negative, neutral(ポジティブでもネガティブでもない),mixed(ポジティブとネガティブ両方含有), unknown(テキスト内にカテゴリについての言及がない)が出力されます。
カテゴリを指定しないと全体的な感情が出力されます。
WITH REVIEW AS ( SELECT value::string AS COMMENT FROM TABLE(FLATTEN(input => [ '使い心地がよく、最高です!。', 'まぁまぁかなぁと・・・', 'すぐ壊れてしまいました。残念です。', '使い心地はいいんだけど、ちょっとコスパが悪いです。' ])) ) SELECT COMMENT, AI_SENTIMENT( COMMENT ):categories AS COMMENT_TYPE FROM REVIEW
結果は次のようになりました。全体的な感情が判定され、出力出来ています。

今度はカテゴリを指定してみました。service,quality,costという3つの側面でポジティブネガティブ判定をさせてみました。
WITH REVIEW AS ( SELECT r.value:HOTEL_NAME::string AS HOTEL_NAME, r.value:COMMENT::string AS COMMENT FROM TABLE(FLATTEN(input => [ '料理の味もサービスも最高でした!ただ値段はちょっと高いかな・・・?' ])) ) SELECT COMMENT, AI_SENTIMENT( COMMENT, ['service','quality','cost'] ):categories AS COMMENT_TYPE FROM REVIEW
判定結果です。costだけネガティブな判定になっており、正しい結果と言えると思います。
[ { "name": "overall", "sentiment": "mixed" }, { "name": "cost", "sentiment": "negative" }, { "name": "quality", "sentiment": "positive" }, { "name": "service", "sentiment": "positive" } ]
AI_AGG/AI_SUMMARIZE_AGG
AI_AGGとAI_SUMMARIZE_AGGはテキスト列をLLMを使って集約することが出来ます。
AI_SUMMARIZE_AGGは要約に特化している一方、AI_AGGは任意の方法で集約することが出来ます。
たとえば宿泊施設に寄せられたユーザーレビューから、各宿泊施設のキャッチコピーを作る処理をAI_AGGで実行してみました。
WITH HOTEL_REVIEW AS ( SELECT r.value:HOTEL_NAME::string AS HOTEL_NAME, r.value:COMMENT::string AS COMMENT FROM TABLE(FLATTEN(input=>[ {'HOTEL_NAME':'xx旅館','COMMENT':'地域の食材を使った料理が魅力です。'}, {'HOTEL_NAME':'xx旅館','COMMENT':'温泉と夕食が良かった。'}, {'HOTEL_NAME':'xxホテル','COMMENT':'プライベートビーチがあって、景色が良かった。'}, {'HOTEL_NAME':'xxホテル','COMMENT':'駅からのアクセスが良く、ホテルの窓から見えるサンセットが最高でした。'} ])) AS r ) SELECT HOTEL_NAME, AI_AGG(COMMENT, '宿泊施設の魅力が伝わるキャッチコピーを日本語50文字以内で作成し、作成したキャッチコピーだけを出力してください') AS CATCH_COPY FROM HOTEL_REVIEW GROUP BY HOTEL_NAME
結果は次のようになりました。

何回かAI_AGGを使ってみたのですが、結構英語で結果を出力してしまう傾向が強いように感じました。"日本語で"といった指示を入れても英語で出力されてしまうことも結構あります。
AI_SUMMARIZE_AGGは要約に特化しているため、テキストを要約によって集約したい場合はAI_AGGよりももっと簡単に使うことが出来ます。
WITH HOTEL_REVIEW AS ( SELECT r.value:HOTEL_NAME::string AS HOTEL_NAME, r.value:COMMENT::string AS COMMENT FROM TABLE(FLATTEN(input=>[ {'HOTEL_NAME':'xx旅館','COMMENT':'地域の食材を使った料理が魅力です。'}, {'HOTEL_NAME':'xx旅館','COMMENT':'温泉と夕食が良かった。'}, {'HOTEL_NAME':'xxホテル','COMMENT':'プライベートビーチがあって、景色が良かった。'}, {'HOTEL_NAME':'xxホテル','COMMENT':'駅からのアクセスが良く、ホテルの窓から見えるサンセットが最高でした。'} ])) AS r ) SELECT HOTEL_NAME, AI_SUMMARIZE_AGG(COMMENT) AS SUMMARY FROM HOTEL_REVIEW GROUP BY HOTEL_NAME
結果です。

まとめ
今回はCortex AISQLを一通り使ってみて、それぞれどんなことが出来るのかをまとめてみました。それぞれ特色があり、組み合わせることでこれまでSQLでは実現が難しかった処理を簡単に実装することが出来そうです。今回は小さな規模のデータで試したのですが、大規模なデータでどれくらいのパフォーマンスが出るのかも今度検証してみたいと思いました。