LambdaでBedrockのClaude 3を呼び出してみた

[Supership](https://supership.jp/)の名畑です。[プリティーリズム・レインボーライブ10周年展](https://www.0101.co.jp/086/event/detail.html?article_seq=123730&article_type=sto)おめでとうございます。とても楽しい展示でした。

## はじめに

[Anthropic](https://www.anthropic.com/)がリリースした[Claude 3](https://www.anthropic.com/news/claude-3-family)が話題です。

- [Anthropic、AIチャット「Claude 3」を3サイズで　日本語力も向上 - ITmedia NEWS](https://www.itmedia.co.jp/news/articles/2403/05/news089.html)
- [GPT-4超えをアピールするClaude-3がAIで初めてIQ100超えを達成したという報告 - GIGAZINE](https://gigazine.net/news/20240308-claude-3-chat-gpt-iq-test/)
- [GPT-4を上回るAIチャット「Claude 3」向けの公式プロンプト集が無料公開 - 窓の杜](https://forest.watch.impress.co.jp/docs/news/1576357.html)

ということで今回はタイトルの通り、[AWS(Amazon Web Services)](https://aws.amazon.com/jp/)上において[Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html)から[Bedrock](https://aws.amazon.com/jp/bedrock/)の**Claude 3**を呼び出してみます。

前に書いた記事「[AWS Lambda(Python)からOpenAIのGPT4のAPIを呼び出してみた](https://qiita.com/nabata/items/903a2ebff8e44516598e)」の「**Claude-3版、しかもAWS上で完結**」といったところです。
また、前の記事と違って、今回はひとまず動作を試すことを最優先とし、ブラウザ上のコンソールのみで進めます。

## Bedrockとは

> Amazon Bedrock は、AI21 Labs、Anthropic、Cohere、Meta、Stability AI、Amazon などの大手 AI 企業が提供する高性能な基盤モデル (FM) を単一の API で選択できるフルマネージド型サービスです。
> 
> 参考：[Amazon Bedrock](https://aws.amazon.com/jp/bedrock/) 

## モデルのアクセスリクエスト

現在、**Bedrock**上で**Anthropic**のモデルを使用するにはユースケースを送信して許可をもらう必要があります。その手順を進めます。

> Amazon Bedrock の Anthropic の Claude 3 Sonnet は、米国東部 (バージニア北部) および米国西部 (オレゴン) の各 AWS リージョンでご利用いただけます。
>
> 参考：[Anthropic の Claude 3 Sonnet モデルが Amazon Bedrock で利用可能に](https://aws.amazon.com/jp/about-aws/whats-new/2024/03/anthropics-claude-3-sonnet-model-amazon-bedrock/)

とのことですので、まず**リージョン**を **ua-east-1 米国東部 (バージニア北部)** としました。

その上で**Bedrock**のサイドメニューから**モデルアクセス**のページを開きます。

![clause-3-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/13454125-28f2-9864-e03d-a9f6b59c2d47.png)

右上の「**モデルアクセスを管理**」を押します。

![clause-3-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/7131a35d-4f44-d63f-5378-9dc9368d8dd9.png)

**Claude 3**には3種類のモデルがあるのですが、今回は**Sonnet**を使うことにします。ですので、**Claude 3 Sonnet**を選んで「**ユースケースの詳細を送信**」を押します。

遷移先の画面で会社名などの項目を入力して送信します。

![clause-3-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/bb0c3b1f-fa5c-b6d9-977d-ee071ffc4160.png)

すると**リクエスト可能**という状態になりますので、希望するモデルを選択して画面下部の「**モデルアクセスをリクエスト**」を押します。

![clause-3-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/cc4d53bb-d900-e8a8-c11e-10c6ca7736b1.png)

ステイタスが**進行中**に変わります。

> モデルにアクセスするには数分かかる場合があります。その間、Bedrock コンソールの他のページを使用できます。アクセスが付与されると、Bedrock でモデルを使用できるようになります。ベースモデルテーブルを更新すると、更新されたステータスが表示されます。

とのことなので、少し待ってからリロードします。

![clause-3-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/3d363c2a-6dd6-4f80-2101-09e8dc6d25e1.png)

「**アクセスが付与されました**」となりました。これで使用可能です。

余談ですが「**ユースケースの送信**」は**Anthropic**共通で1度だけで良いですが、「**モデルアクセスをリクエスト**」はモデル毎に必要です。

## IAMでロールの作成

**Lambda**から**Bedrock**を呼び出せるようにするために**IAM**でロールを作成します。

> AWS Identity and Access Management (IAM) は、AWS リソースへのアクセスを安全に管理するためのウェブサービスです。IAM を使用すると、ユーザーがアクセスできる AWS のリソースを制御するアクセス許可を集中管理できます。IAM を使用して、誰を認証 (サインイン) し、誰にリソースの使用を認可する (アクセス許可を付与する) かを制御します。
> 
> 参考：[IAM とは - AWS Identity and Access Management](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/introduction.html)

まず「**ロールを作成**」のページに行きます。

![clause-3-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/2e7d5eda-5279-f017-b7aa-aff3251003a2.png)

「**信頼されたエンティティタイプ**」として「**AWSのサービス**」を、**ユースケース**として**Lambda**を選択して**次へ**を押します。

![clause-3-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/4c5d2d99-bd55-9dcf-1e76-1bbda158f3c6.png)

許可ポリシーとして**AmazonBedrockFullAccess**を選択して**次へ**を押します。**AmazonBedrockReadOnly**だとこの後の処理実行時に**AccessDeniedException**が起きます。

![clause-3-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/f9525510-7bfa-2f43-33cd-c726c755f934.png)

**ロール名**をつけます。私はわかりやすく**LambdaToBedrockAccessRole**としました。

これで「**ロールを作成**」を押して作成完了です。

## Lambda関数の作成

次に**Lambda**の設定に移ります。
**Labmda**は他の**リージョン**(たとえばアジアパシフィック (東京) ap-northeast-1)でも動きます。

**Lambda**で**関数の作成**のページを開きます。

今回は下記の設定としました。

- **関数名**：LambdaToBedrockAccess
- **ランタイム**：Python3.12
- **アーキテクチャ**：arm64
- **デフォルトの実行ロールの変更**
	- 「既存のロールを使用する」を選択して先ほど作成したLambdaToBedrockAccessRoleを選択

アーキテクチャについて詳しくは「[Lambda 命令セットアーキテクチャ (ARM/x86) - AWS Lambda](
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/foundation-arch.html)」をご覧ください。

ランタイムの注意点としては、**Python**のバージョンによって**Boto3**（AWSのサービスを扱うライブラリ）のバージョンも変わります。**Boto3**のバージョンによっては**Bedrock**へのアクセスに対応していないため、ご注意ください。仮に古いバージョンの**Python**が必須の場合は、[過去記事](https://qiita.com/nabata/items/903a2ebff8e44516598e)で**OpenAI**のライブラリをアップロードしたのと同じように、**Boto3**をアップロードする必要があります。

バージョンについて詳しくは「[Lambda ランタイム - AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html)」を、**Boto3**から**Bedrock**へのアクセスについて詳しくは「[BedrockRuntime - Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime.html)」をご覧ください。


## Lambda関数の設定

![clause-3-9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/350bf21a-6b4a-49de-e064-92e406e2fa2e.png)

基本的にはほぼデフォルト設定で動くのですが、タイムアウトだけはデフォルトの3秒のままだとレスポンスが間に合わないので**30秒**に伸ばしました。

## コード

[Anthropic Claude Messages API](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html)の**Code examples**を参考にして書きました。
**Lambda**に渡されてきた**json**の**message**を参照して、それを**claude 3**に投げるように実装しています。必要最低限のコードだとかなりシンプルです。

```python
import json
import boto3

def lambda_handler(event, context):
    bedrock_runtime = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')

    model_id = 'anthropic.claude-3-sonnet-20240229-v1:0'
    system_prompt = "必ず日本語で答えてください"
    max_tokens = 1000

    user_message = {"role": "user", "content": event.get("message")}
    messages = [user_message]
    
    body = json.dumps(
        {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": max_tokens,
            "system": system_prompt,
            "messages": messages
        }  
    )  

    response = bedrock_runtime.invoke_model(body=body, modelId=model_id)
    response_body = json.loads(response.get('body').read())

    return {
        'statusCode': 200,
        'body': response_body
    }
```

## テスト

![clause-3-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/1a96b832-26d0-6187-515d-70140fc315d3.png)

テストのページに移動して、以下の**json**を記述してみます。

```json
{
  "message": "東京都23区を教えてください"
}
```

そして**テスト**を押すと、数秒で以下のようなレスポンスが返ってきました。

```json
{
  "statusCode": 200,
  "body": {
    "id": "msg_hogehoge",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": "東京都23区は以下の通りです。\n\n千代田区\n中央区\n港区\n新宿区\n文京区\n台東区\n墨田区\n江東区\n品川区\n目黒区\n大田区\n世田谷区\n渋谷区\n中野区\n杉並区\n豊島区\n北区\n荒川区\n板橋区\n練馬区\n足立区\n葛飾区\n江戸川区\n\n1947年に東京都内の旧区が統合されて誕生した23の特別区で、人口が密集し、商業や業務が集中する都心部を形成しています。"
      }
    ],
    "model": "claude-3-sonnet-28k-20240229",
    "stop_reason": "end_turn",
    "stop_sequence": null,
    "usage": {
      "input_tokens": 29,
      "output_tokens": 178
    }
  }
}
```

> 東京都23区は以下の通りです。\n\n千代田区\n中央区\n港区\n新宿区\n文京区\n台東区\n墨田区\n江東区\n品川区\n目黒区\n大田区\n世田谷区\n渋谷区\n中野区\n杉並区\n豊島区\n北区\n荒川区\n板橋区\n練馬区\n足立区\n葛飾区\n江戸川区\n\n1947年に東京都内の旧区が統合されて誕生した23の特別区で、人口が密集し、商業や業務が集中する都心部を形成しています。

バッチリな回答に思えます。

## 画像の解析

せっかくなので画像の解析もしてみましょう。**画像url**と**指示**を渡すこととします。いわゆる**マルチモーダル**です。

> 用語「マルチモーダルAI」について説明。テキスト／画像／音声／数値など複数の種類のモダリティー（データ種別）を一度に処理できる統合されたAIモデルを指す。
>
> 参考：[マルチモーダルAI（Multimodal AI）とは？：AI・機械学習の用語辞典 - ＠IT](https://atmarkit.itmedia.co.jp/ait/articles/2207/04/news016.html)

**Lambda**に渡されてきた**json**の**message**を**指示**、**image**を**画像url**として扱うコードとします。

```python
import json
import boto3
import urllib
import base64

def lambda_handler(event, context):
    bedrock_runtime = boto3.client(service_name='bedrock-runtime', region_name='us-east-1')

    model_id = 'anthropic.claude-3-sonnet-20240229-v1:0'
    system_prompt = "必ず日本語で答えてください"
    max_tokens = 1000
    
    with urllib.request.urlopen(event.get("image")) as image_file:
        content_image = base64.b64encode(image_file.read()).decode('utf8')

    user_message = {
        "role": "user",
        "content": [
            {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": content_image}},
            {"type": "text", "text": event.get("message")}
        ]
    }
            
    messages = [user_message]
    
    body = json.dumps(
        {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": max_tokens,
            "system": system_prompt,
            "messages": messages
        }  
    )  

    response = bedrock_runtime.invoke_model(body=body, modelId=model_id)
    response_body = json.loads(response.get('body').read())

    return {
        'statusCode': 200,
        'body': response_body
    }
```

先ほどのコードと多くは同じですが、**画像の読み込み**と、モデルに渡す**messages**の生成が異なっています。

テストで渡すjsonは以下としました。

```json
{
  "image": "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/467c6358-084c-6ce1-d684-cad6dbfe501a.png",
  "message": "これはどんな絵ですか"
}
```

ちなみにurlの示す画像は過去記事で生成したこちらです。

![gen-2-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/467c6358-084c-6ce1-d684-cad6dbfe501a.png)

テストしたところ、レスポンスは以下のようなものでした。

```json
{
  "statusCode": 200,
  "body": {
    "id": "msg_hogehoge",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": "この画像は東京の繁華街、渋谷のスクランブル交差点を捉えたものです。大きな電光掲示板や高層ビルが映っており、にぎやかな雰囲気が伝わってきます。そして、前景には個性的なアニメキャラクターのバッグを持った明るい笑顔の若い女性が写っています。都会的で活気に満ちた渋谷の風景と、それを楽しむような女性の様子が見事に描かれた1枚の写真です。個人の特定は避けましたが、都会的で賑やかな雰囲気が伝わる魅力的な写真だと思います。"
      }
    ],
    "model": "claude-3-sonnet-28k-20240229",
    "stop_reason": "end_turn",
    "stop_sequence": null,
    "usage": {
      "input_tokens": 423,
      "output_tokens": 208
    }
  }
}
```

> この画像は東京の繁華街、渋谷のスクランブル交差点を捉えたものです。大きな電光掲示板や高層ビルが映っており、にぎやかな雰囲気が伝わってきます。そして、前景には個性的なアニメキャラクターのバッグを持った明るい笑顔の若い女性が写っています。都会的で活気に満ちた渋谷の風景と、それを楽しむような女性の様子が見事に描かれた1枚の写真です。個人の特定は避けましたが、都会的で賑やかな雰囲気が伝わる魅力的な写真だと思います。

正確にはスクランブル交差点ではないですが、かなり高精度な分析だと感じます。

## 最後に

**Claude 3**は評判通り、かなり優秀と感じます。
そしてなにより、**AWS**のみで完結するのが本当に魅力ですね。

[ナレッジベース](https://aws.amazon.com/jp/bedrock/knowledge-bases/)を使った**RAG**(検索拡張生成)も試したので記事にする予定です。まだ現時点では**Claude 3**は対応はしていないようですが。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。