SnowflakeからClaude3(Bedrock)を呼び出して、文章作成や画像認識させてみた。

# SnowflakeからClaude3を呼び出して、文章作成やさせてみる
今回は**SnowflakeのUDFでClaude3(Amazon Bedrock)を呼び出して、文章作成や画像認識させる方法**について紹介したいと思います。

最初は文章作成させる方法だけで記事を作成しようと思ったのですが、[クイックスタート](https://quickstarts.snowflake.com/guide/getting_started_with_bedrock_streamlit_and_snowflake/index.html#0)をなぞるだけの記事になりそうだし、もう[Snowflake Cortex](https://docs.snowflake.com/en/user-guide/snowflake-cortex/overview)でも出来ることなので、ステージ上の画像ファイルを認識する機能も追加してみました。

https://quickstarts.snowflake.com/guide/getting_started_with_bedrock_streamlit_and_snowflake/index.html#0

ちなみに、以下はステージに配置したとある画像をClaude3に説明させた結果です。何だと思いますか。Snowflakeヘビーユーザにはお馴染みのあいつです。
>この画像には、**可愛らしい白いぬいぐるみのクマ**が写っています。クマの体はふわふわと柔らかそうに見え、**青いニットのマフラー**を巻いて寒さから身を守っているように見えます。クマの顔は丸く優しい表情で、ブラックの目とクマのアイコンが印刷されたパーツが特徴的です。全体としては、温かみのある冬の雰囲気を感じさせるキュートなぬいぐるみの画像です。

# 使用ツール
### [Amazon Bedrock](https://aws.amazon.com/jp/bedrock/)
AWSが提供する生成系AIサービスです。Amazon, Meta, Stability AIに加えて今話題のClaude3(Anthropic)を利用できます。

### [Snowflake](https://www.snowflake.com/ja/)
クラウドベースのデータウェアハウスであり、大量のデータを高速かつスケーラブルに処理できます。[30日間の無料トライアル](https://signup.snowflake.com/?_l=ja)もあります。

# 作成手順
コードはモックとして簡単な書き方で書いています。また、一部コードはSnowflakeの[クイックスタート](https://quickstarts.snowflake.com/guide/getting_started_with_bedrock_streamlit_and_snowflake/index.html#0)を流用しています。

### 1. Claude3のモデルアクセスリクエスト
Amazon Bedrockのモデルアクセスページにアクセスし、Claude 3 SonnetとClaude 3 Haikuのユースケースを記載、アクセスをリクエストします。記事作成時点では、ap-northeast-1(東日本)ではClaude3が提供されていないため、AWSコンソール上でリージョンをus-east-1(バージニア北部)等に変更する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3342101/272d53aa-0526-7c35-4b58-6b23c2237898.png)

数分でリクエストが承認されました。

### 2. Claude3のmodel arnを確認
[**基盤モデルに関する情報の取得**](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/models-get-info.html)に従い、IAMポリシーを作成するためにmodel arnを確認します。
AWS CLIでリージョンをus-east-1に変更し、以下のコマンドを実行します。
```
aws bedrock list-foundation-models
```
すると、以下の出力があるのでmodel arnを控えておきます。記事作成時点ではこのような結果となっています。
```
MODELSUMMARIES  arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0     anthropic.claude-3-sonnet-20240229-v1:0 Claude 3 Sonnet Anthropic       True
INFERENCETYPESSUPPORTED ON_DEMAND
INPUTMODALITIES TEXT
INPUTMODALITIES IMAGE
MODELLIFECYCLE  ACTIVE
OUTPUTMODALITIES        TEXT
MODELSUMMARIES  arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0      anthropic.claude-3-haiku-20240307-v1:0  Claude 3 Haiku  Anthropic       True
INFERENCETYPESSUPPORTED ON_DEMAND
INPUTMODALITIES TEXT
INPUTMODALITIES IMAGE
MODELLIFECYCLE  ACTIVE
OUTPUTMODALITIES        TEXT
```

### 3. IAMポリシーの作成
先ほど取得したarnを用いてIAMポリシーを作成します。今回はhaikuとsonnetどちらも利用したいため、以下のように記載します。
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "bedrock:InvokeModel",
                "bedrock:InvokeModelWithResponseStream"
            ],
            "Resource": [
                "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0",
                "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0"
            ]
        }
    ]
}
```

### 4. IAMユーザの作成
先ほど作成したIAMポリシーを適用するIAMユーザを作成します。このIAMユーザを使ってSnowflakeからClaude3にリクエストを投げます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3342101/5398a187-3a58-2308-86fa-1bec3978b98f.png)
作成したIAMユーザのアクセスキーを作成(ユースケースにサードパーティーサービスを選択)し、アクセスキーとシークレットアクセスキーを控えておきます。
### 5. UDFを作成する環境作成
UDFで利用する**ウェアハウス**を作成します。
```SQL
USE ROLE ACCOUNTADMIN;
CREATE OR REPLACE WAREHOUSE CLAUDE3_WH WITH WAREHOUSE_SIZE='X-SMALL', AUTO_SUSPEND = 60;
```
UDFを保管する**データベース・スキーマ**を作成します。
```SQL
CREATE OR REPLACE DATABASE CLAUDE3_DB;
USE DATABASE CLAUDE3_DB;
USE SCHEMA PUBLIC;
```
画像認識に使用する画像を保存する**外部ステージ**を作成します。（ストレージ統合作成は割愛）
```SQL
CREATE OR REPLACE STAGE CLAUDE3_IMAGE_STAGE
STORAGE_INTEGRATION = integration_inoue
URL = 's3://***/unstructured/';
```
UDFからCluade3に接続するには、**外部アクセス統合**を作成する必要があります。そのため、[**Amazon bedrockのランタイムAPIエンドポイント**](https://docs.aws.amazon.com/ja_jp/general/latest/gr/bedrock.html)に対する**ネットワークルール**を作成します。
```SQL
CREATE OR REPLACE NETWORK RULE claude3_network_rule
mode = EGRESS
type = HOST_PORT
value_list = ('bedrock-runtime.us-east-1.amazonaws.com','bedrock-runtime-fips.us-east-1.amazonaws.com');
```
AWSのアクセスキーやセッションキーを保管する**シークレット**を作成します。シークレットを用いることでキー情報をセキュアに保管することができます。
```SQL
CREATE OR REPLACE SECRET claude3_aws_keys
    TYPE = password
    USERNAME = '<アクセスキー>'
    PASSWORD = '<シークレットアクセスキー>';

CREATE OR REPLACE SECRET claude3_session_key
    TYPE = password
    USERNAME = 'session'
    PASSWORD = ''; -- if not using a session key, leave this blank and it won't be used later
```
上記の**ネットワークルール**と**シークレット**を含めた**外部アクセス統合**を作成します。
```SQL
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION claude3_integration
ALLOWED_NETWORK_RULES = (claude3_network_rule)
ALLOWED_AUTHENTICATION_SECRETS = (claude3_aws_keys, claude3_session_key)
enabled=true;
```
### 6. UDFの作成
文章作作成部分のコードは、ほぼほぼ[クイックスタート](https://quickstarts.snowflake.com/guide/getting_started_with_bedrock_streamlit_and_snowflake/index.html#0)の流用であるため、画像認識部分について説明します。
まず、Claude3に画像認識させる際はどのようにするか確認しましょう。Amazon BedrockのAPIリクエスト例を見てみると・・・
```JSON
{
  "modelId": "anthropic.claude-3-sonnet-20240229-v1:0",
  "contentType": "application/json",
  "accept": "application/json",
  "body": {
    "anthropic_version": "bedrock-2023-05-31",
    "max_tokens": 1000,
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "image",
            "source": {
              "type": "base64",
              "media_type": "image/jpeg",
              "data": "iVBORw..."
            }
          },
          {
            "type": "text",
            "text": "What's in this image?"
          }
        ]
      }
    ]
  }
}
```
ほうほう、**画像データをbase64形式**で投げれば良さそうですね。

[**BUILD_SCOPED_FILE_URL**](https://docs.snowflake.com/ja/sql-reference/functions/build_scoped_file_url)を使えば、呼び出し元のみからアクセスできる画像URLを作成しUDFに渡すことができるので、**画像URLからバイナリデータを読み込み、base64に変換するPython関数**を作成します。
```Python
# encode image file to base64 data
def to_base64(image_file):
    data = io.BytesIO(SnowflakeFile.open(image_file, 'rb').read())
    base64_data = base64.b64encode(data.getvalue())
    return base64_data.decode('utf-8')
```
上記を含めて作成したUDFは以下になります。
```SQL
CREATE OR REPLACE FUNCTION ask_claude3(image_file string, user_context string, model string)
returns string
language python
runtime_version=3.8
handler = 'ask_claude3'
external_access_integrations=(claude3_integration)
SECRETS = ('aws_keys' = claude3_aws_keys, 'session_key' = claude3_session_key)
PACKAGES = ('boto3','snowflake-snowpark-python')
as
$$
--可読性のため、Pythonコード部分を以下のコードブロックに分離
$$;
```
```Python
import json
import io
import base64
import boto3
from snowflake.snowpark.files import SnowflakeFile
import _snowflake

# Function to get AWS credentials
def get_aws_credentials():
    aws_key_object = _snowflake.get_username_password('aws_keys')
    session_key_object = _snowflake.get_username_password('session_key')
    region = 'us-east-1'

    boto3_session_args = {
        'aws_access_key_id': aws_key_object.username,
        'aws_secret_access_key': aws_key_object.password,
        'region_name': region
    }

    if session_key_object.password != '':
        boto3_session_args['aws_session_token'] = session_key_object.password

    return boto3_session_args, region

# Function to prepare the request body based on the model with text and image
def prepare_request_body_image(model_id, user_context, base64_image_data):
    default_max_tokens = 4096

    if model_id == 'anthropic.claude-3-sonnet-20240229-v1:0' or model_id == 'anthropic.claude-3-haiku-20240307-v1:0':
        body = {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": default_max_tokens,
            "messages": [
              {
                "role": "user",
                "content":[
                  {
                    "type": "image",
                    "source": {
                      "type": "base64",
                      "media_type": "image/jpeg",
                      "data": f"{base64_image_data}"
                    }
                  },
                  {
                    "type": "text",
                    "text": f"{user_context}"
                  }
                ]
              }
            ]
        }
    else:
        raise ValueError("Unsupported model ID")

    return json.dumps(body)
    
# Function to prepare the request body based on the model with only text
def prepare_request_body_text(model_id, user_context):
    default_max_tokens = 4096

    if model_id == 'anthropic.claude-3-sonnet-20240229-v1:0' or model_id == 'anthropic.claude-3-haiku-20240307-v1:0':
        body = {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": default_max_tokens,
            "messages": [
              {
                "role": "user",
                "content":[
                  {
                    "type": "text",
                    "text": f"{user_context}"
                  }
                ]
              }
            ]
        }
    else:
        raise ValueError("Unsupported model ID")

    return json.dumps(body)

# parse API response format from different model families in Bedrock
def get_completion_from_response(response_body, model_id):
    if model_id == 'anthropic.claude-3-sonnet-20240229-v1:0' or model_id == 'anthropic.claude-3-haiku-20240307-v1:0':
        output_text = response_body.get('content')[0].get('text')
    else:
        raise ValueError("Unsupported model ID")
    return output_text

# encode image file to base64 data
def to_base64(image_file):
    data = io.BytesIO(SnowflakeFile.open(image_file, 'rb').read())
    base64_data = base64.b64encode(data.getvalue())
    return base64_data.decode('utf-8')

# Main function to call bedrock
def ask_claude3(image_file, user_context, model):
    boto3_session_args, region = get_aws_credentials()

    # Create a session using the provided credentials
    session = boto3.Session(**boto3_session_args)

    # Create a bedrock client from session
    client = session.client('bedrock-runtime', region_name=region)

    # Prepare the request body based on the model
    if image_file != '':
        body = prepare_request_body_image(model, user_context, to_base64(image_file))
    else:
        body = prepare_request_body_text(model, user_context)

    response = client.invoke_model(modelId=model, body=body)
    response_body = json.loads(response.get('body').read())

    output_text = get_completion_from_response(response_body, model)
    return output_text
```

# 実際に動かしてみた。
今回作成したUDFの使い方は以下になります。
```SQL
ask_claude3('画像(使わない場合は空文字)','テキストプロンプト','モデル名');
```

### 文章作成
こちらはSnowflake Cortexでも可能な機能ですね。

実行クエリ：
```SQL
SELECT ask_claude3('','NTTDATAについて教えて','anthropic.claude-3-haiku-20240307-v1:0');
```
回答：
```
NTT DATAについて以下のようなことが主な特徴です:

- NTT DATAは日本の大手ITサービス企業です。1988年に設立され、NTTグループの一部となっています。

- 主な事業内容は、ITコンサルティング、システムインテグレーション、クラウド、BPOサービスなどのITソリューションの提供です。

- 顧客は主に大企業や公共機関で、幅広い業界にサービスを提供しています。主な強みはシステムインテグレーションの能力と金融・公共分野での実績です。

- 世界30か国以上に拠点を持ち、グローバルに事業展開しています。従業員数は約12万人と大規模な企業です。

- 技術力、専門性、サービス品質などでは業界でトップクラスの評価を得ています。データセンター、クラウド、AIなどの最新テクノロジーにも積極的に取り組んでいます。

- 持続可能な社会の実現に向けた取り組みにも力を入れており、ESG経営にも注力しています。

以上が、NTT DATAの主な特徴や事業概要となります。大手IT企業としての規模と実績を持つ企業です。
```
パッと見た感じ間違っていなそうです。Claude3素晴らしいです。

### 画像認識
事前に以下の画像を外部ステージ上に配置しておきます。
![test_image.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3342101/b2ba0383-d1a8-c458-afde-33b5f376a8fc.jpeg)
ちなみに、こちらはSnowflakeのオフラインイベントで配られているクマ太郎のぬいぐるみです。**機会があれば是非イベントに参加して、手に入れてみてください。**

listを実行し、画像ファイルがあることを確認します。
```SQL
list @CLAUDE3_IMAGE_STAGE;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3342101/691ae4c8-900c-45b2-b60c-1d14343960b5.png)

[BUILD_SCOPED_FILE_URL](https://docs.snowflake.com/ja/sql-reference/functions/build_scoped_file_url)を使って、先程の画像を指定して、UDFを実行します。
実行：
```SQL
SELECT ask_claude3(build_scoped_file_url(@CLAUDE3_IMAGE_STAGE, 'test_image.jpg'),'この画像について教えて。','anthropic.claude-3-haiku-20240307-v1:0');
```
回答：
```
この画像には、可愛らしい白いぬいぐるみのクマが写っています。
クマの体はふわふわと柔らかそうに見え、青いニットのマフラーを巻いて寒さから身を守っているように見えます。
クマの顔は丸く優しい表情で、ブラックの目とクマのアイコンが印刷されたパーツが特徴的です。
全体としては、温かみのある冬の雰囲気を感じさせるキュートなぬいぐるみの画像です。
```
「クマのアイコンが印刷されたパーツ」ってどういうことでしょうかね。
もしかして、Snowflakeのロゴが手足を広げたクマに見えてるのでしょうか。Claude3、流石の着眼点に脱帽です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3342101/1ca5311b-149e-7d54-91a0-edc7c2133da9.png)


# まとめ
今回は、SnowflakeからClaude3を呼び出して、文章作成や画像認識を行うUDFを作ってみました。
**ステージ上の画像認識**・・・この機能は何かに使えないかな。


# 仲間募集

NTTデータ テクノロジーコンサルティング事業本部 では、以下の職種を募集しています。

<details><summary>1. クラウド技術を活用したデータ分析プラットフォームの開発・構築(ITアーキテクト/クラウドエンジニア)</summary>

クラウド／プラットフォーム技術の知見に基づき、DWH、BI、ETL領域におけるソリューション開発を推進します。
https://enterprise-aiiot.nttdata.com/recruitment/career_sp/cloud_engineer
</details>


<details><summary>2. データサイエンス領域（データサイエンティスト／データアナリスト）</summary>

データ活用／情報処理／AI／BI／統計学などの情報科学を活用し、よりデータサイエンスの観点から、データ分析プロジェクトのリーダーとしてお客様のDX／デジタルサクセスを推進します。
https://enterprise-aiiot.nttdata.com/recruitment/career_sp/datascientist
</details>


<details><summary>3. お客様のAI活用の成功を推進するAIサクセスマネージャー</summary>

DataRobotをはじめとしたAIソリューションやサービスを使って、
お客様のAIプロジェクトを成功させ、ビジネス価値を創出するための活動を実施し、
お客様内でのAI活用を拡大、NTTデータが提供するAIソリューションの利用継続を推進していただく人材を募集しています。
https://nttdata.jposting.net/u/job.phtml?job_code=804
</details>


<details><summary>4. DX／デジタルサクセスを推進するデータサイエンティスト《管理職/管理職候補》</summary>

データ分析プロジェクトのリーダとして、正確な課題の把握、適切な評価指標の設定、分析計画策定や適切な分析手法や技術の評価・選定といったデータ活用の具現化、高度化を行い分析結果の見える化・お客様の納得感醸成を行うことで、ビジネス成果・価値を出すアクションへとつなげることができるデータサイエンティスト人材を募集しています。
https://nttdata.jposting.net/u/job.phtml?job_code=898
</details>


# ソリューション紹介

<details><summary>Trusted Data Foundationについて</summary>

～データ資産を分析活用するための環境をオールインワンで提供するソリューション～
https://enterprise-aiiot.nttdata.com/tdf/
最新のクラウド技術を採用して弊社が独自に設計したリファレンスアーキテクチャ（Datalake+DWH+AI/BI）を顧客要件に合わせてカスタマイズして提供します。
可視化、機械学習、DeepLearningなどデータ資産を分析活用するための環境がオールインワンで用意されており、これまでとは別次元の量と質のデータを用いてアジリティ高くDX推進を実現できます。
</details>


<details><summary>TDFⓇ-AM（Trusted Data Foundation - Analytics Managed Service）について</summary>

～データ活用基盤の段階的な拡張支援（Quick Start) と保守運用のマネジメント（Analytics Managed）をご提供することでお客様のDXを成功に導く、データ活用プラットフォームサービス～
https://enterprise-aiiot.nttdata.com/service/tdf/tdf_am
TDFⓇ-AMは、データ活用をQuickに始めることができ、データ活用の成熟度に応じて段階的に環境を拡張します。プラットフォームの保守運用はNTTデータが一括で実施し、お客様は成果創出に専念することが可能です。また、日々最新のテクノロジーをキャッチアップし、常に活用しやすい環境を提供します。なお、ご要望に応じて上流のコンサルティングフェーズからAI/BIなどのデータ活用支援に至るまで、End to Endで課題解決に向けて伴走することも可能です。
</details>


<details><summary>NTTデータとTableauについて</summary>

ビジュアル分析プラットフォームのTableauと2014年にパートナー契約を締結し、自社の経営ダッシュボード基盤への採用や独自のコンピテンシーセンターの設置などの取り組みを進めてきました。さらに2019年度にはSalesforceとワンストップでのサービスを提供開始するなど、積極的にビジネスを展開しています。

これまでPartner of the Year, Japanを4年連続で受賞しており、2021年にはアジア太平洋地域で最もビジネスに貢献したパートナーとして表彰されました。
また、2020年度からは、Tableauを活用したデータ活用促進のコンサルティングや導入サービスの他、AI活用やデータマネジメント整備など、お客さまの企業全体のデータ活用民主化を成功させるためのノウハウ・方法論を体系化した「デジタルサクセス」プログラムを提供開始しています。
https://enterprise-aiiot.nttdata.com/service/tableau
</details>


<details><summary>NTTデータとAlteryxについて</summary>

Alteryxは、業務ユーザーからIT部門まで誰でも使えるセルフサービス分析プラットフォームです。
Alteryx導入の豊富な実績を持つNTTデータは、最高位にあたるAlteryx Premiumパートナーとしてお客さまをご支援します。

導入時のプロフェッショナル支援など独自メニューを整備し、特定の業種によらない多くのお客さまに、Alteryxを活用したサービスの強化・拡充を提供します。

https://enterprise-aiiot.nttdata.com/service/alteryx
</details>


<details><summary>NTTデータとDataRobotについて</summary>

DataRobotは、包括的なAIライフサイクルプラットフォームです。
NTTデータはDataRobot社と戦略的資本業務提携を行い、経験豊富なデータサイエンティストがAI・データ活用を起点にお客様のビジネスにおける価値創出をご支援します。

https://enterprise-aiiot.nttdata.com/service/datarobot
</details>


<details><summary>NTTデータとInformaticaについて</summary>

データ連携や処理方式を専門領域として10年以上取り組んできたプロ集団であるNTTデータは、データマネジメント領域でグローバルでの高い評価を得ているInformatica社とパートナーシップを結び、サービス強化を推進しています。
https://enterprise-aiiot.nttdata.com/service/informatica
</details>


<details><summary>NTTデータとSnowflakeについて</summary>

NTTデータでは、Snowflake Inc.とソリューションパートナー契約を締結し、クラウド・データプラットフォーム「Snowflake」の導入・構築、および活用支援を開始しています。
NTTデータではこれまでも、独自ノウハウに基づき、ビッグデータ・AIなど領域に係る市場競争力のあるさまざまなソリューションパートナーとともにエコシステムを形成し、お客さまのビジネス変革を導いてきました。
Snowflakeは、これら先端テクノロジーとのエコシステムの形成に強みがあり、NTTデータはこれらを組み合わせることでお客さまに最適なインテグレーションをご提供いたします。

https://enterprise-aiiot.nttdata.com/service/snowflake
</details>