CCCMKホールディングス TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

SnowflakeのCortex AISQLを一通り使ってみました!

こんにちは、CCCMKホールディングスAIエンジニアの三浦です。

先日サンフランシスコで開催されたSnowflakeの年次サミット"SNOWFLAKE SUMMIT25"のキーノートセッションの動画がアップされていて、最近そちらを閲覧しました。面白いアップデートが紹介されていたのですが、その中で特に印象に残ったのが"Cortex AISQL"です。

Cortex AISQLはSQLの中でLLMを呼び出し特定の処理を実行することが出来る関数セットです。LLMを使った特定の列の変換処理や、潜在的な意味に基づいたテーブルの結合処理を実現することが出来ます。

本記事では、現在利用できるAISQLを一通り触ってみて、それぞれどんなことが出来るのかをまとめてみました。今回ご紹介するCortex AISQLは現在パブリックプレビューでの提供になっています。また、一部のAISQLはテキストだけでなく画像も入力出来るのですが、今回試した日本リージョンの環境ではまだ対応していないようなのでテキストのみ検証しています。

AI_CLASSIFY

AI_CLASSIFYテキストを指定したカテゴリに分類することが出来ます。

docs.snowflake.com

たとえば季節に関するテキストを受け取り、それがどの季節に該当するのかを分類する例を試してみました。

こんなクエリを作って実行してみました。

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_CLASSIFYは"冬"に分類しました。しかし結果としてはいずれも正しいと言えます。

AI_COMPLETE

AI_COMPLETEプロンプトに対する応答を生成させることが出来ます。

docs.snowflake.com

任意のプロンプトを指定出来ることから汎用性が高い関数です。たとえば日本語を英語に変換する、といったことも可能です。 以下のような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_COMPLETE

AI_EMBED

AI_EMBEDテキストの埋め込みベクトルを生成することが出来ます。

docs.snowflake.com

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_EMBED

AI_FILTER

AI_FILTER与えられたプロンプトの正誤をbooleanで返します

docs.snowflake.com

個人的にはこの関数が一番面白い、と感じています

一番ベーシックな使い方は次のような感じです。テキストが"春"に関するものかを判定させています。

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と判定されました。

AI_FILTERを使ってみる

また、WHERE句と組み合わせることで条件に該当する行を取得することが出来ます。

WITH TWEET AS (
    SELECT value::string AS CONTENT
    FROM TABLE(FLATTEN(input => [
        '今日は花見に行きました。',
        '雪だるまをたくさん作りました。',
        '焼き芋を作って食べました。',
        '暑かったので、プールに行きました!'
    ]))
)
SELECT
    CONTENT,
FROM TWEET
WHERE AI_FILTER(
    PROMPT('これは春についての文章ですか?:{0}',CONTENT)
)

春に関するテキストだけ抽出されます。

WHERE句と組み合わせたAI_FILTER

さらにこの条件をテーブルの結合条件にすることも出来ます。

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と組み合わせたAI_FILTER

"焼き芋を作って食べました。"は"秋"に"冬"にも関連するので、JOINした結果は2行になりました。このように潜在的な意味に基づいてテーブルを結合できる、とても面白い機能だと思います!

AI_SIMILARITY

AI_SIMILARITYテキスト同士の類似度を、埋め込みベクトルのコサイン類似度に基づいて計算します。

docs.snowflake.com

たとえば"夏祭りに行きました。"というテキストとの類似度を計算し、類似度の高さで並び替えてみました。

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

結果は次のようになり、夏に関連するテキストが最も高い類似度になりました。

AI_SIMILARITY

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

別の埋め込みモデルを使ったAI_SIMILARITYの結果

AI_SENTIMENT

AI_SENTIMENT入力されたテキストの全体的な感情やカテゴリ別の感情を出力します。

docs.snowflake.com

判定結果は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

結果は次のようになりました。全体的な感情が判定され、出力出来ています。

AI_SENTIMENT

今度はカテゴリを指定してみました。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_AGGAI_SUMMARIZE_AGGテキスト列をLLMを使って集約することが出来ます。

docs.snowflake.com

docs.snowflake.com

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_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

結果です。

AI_SUMMARIZE_AGG

まとめ

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