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

[Supership](https://supership.jp/)の名畑です。[ツイステッドワンダーランド](https://twisted-wonderland.aniplex.co.jp/)の第7章は、私がやってきた歴代ゲームの中でも最高レベルに熱いストーリーでした。

## はじめに

> これまでで最もインテリジェントなモデルである Anthropic の Claude 3.7 Sonnet ハイブリッド推論モデルを Amazon Bedrock でご利用いただけるようになりました。Claude 3.7 Sonnet では AI 機能の大幅な進化を実現し、迅速な応答とユーザーに可視化されるステップバイステップの拡張思考の両方を提供します。この新しいモデルでは、コーディングが大幅に改善され、指示実行、数学、物理などのさまざまなタスクのパフォーマンスが向上しています。
> 
> 参考：[Anthropic の Claude 3.7 Sonnet が Amazon Bedrock で利用可能に - AWS](https://aws.amazon.com/jp/about-aws/whats-new/2025/02/anthropics-claude-3-7-sonnet-amazon-bedrock/)

ということで、リリースからすでに一月経っており目新しいものではないですが、備忘も兼ねてAWSの**Lambda** + **Bedrock** + **Claude 3.7 Sonnet**の実装を残します。

過去記事「[LambdaでBedrockのClaude 3を呼び出してみた](https://qiita.com/nabata/items/5bcc3beb76182f626040)」のアップデート版です。

## Claudeとは

> Claudeは、Anthropicが開発した高性能で知的なAIモデル群です。Claudeは強力で拡張性が高いだけでなく、利用可能な中で最も信頼性の高いAIでもあります。重要なプロトコルに従い、ミスが少なく、ジェイルブレイクに対する耐性があるため、エンタープライズのお客様は大規模な環境で最も安全なAI駆動アプリケーションを構築できます。
> 
> 参考：[Claudeの紹介 - Anthropic](https://docs.anthropic.com/ja/docs/intro-to-claude)

## Bedrockとは

> Amazon Bedrock は、単一の API を通じて AI21 Labs、Anthropic、Cohere、Luma、Meta、Mistral AI、poolside (近日リリース予定)、Stability AI、および Amazon などの先駆的な AI 企業からの高性能な基盤モデル (FM) の幅広い選択肢を提供するフルマネージドサービスであり、セキュリティ、プライバシー、責任ある AI を備えた生成 AI アプリケーションを構築するために必要な一連の幅広い機能を提供します。
> 
> 参考：[基盤モデルによる生成 AI アプリケーションの構築 - Amazon Bedrock - AWS](https://aws.amazon.com/jp/bedrock/) 

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

**Bedrock**上で**Anthropic**のモデルを使用するには許可をもらう必要があります。その手順を進めます。

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


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

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

**Anthropic**のモデルの使用が初だと「**ユースケースの詳細を送信**」が求められ、遷移先の画面で会社名などの項目の入力が求められます。しかし今回は[過去記事](https://qiita.com/nabata/items/5bcc3beb76182f626040)ですでに完了しているため、省きます。

![claude_3_7_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/c06ef610-ded2-4834-ae00-f41bb0059c9e.png)

**Claude 3.7 Sonnet** の「**リクエスト可能**」を押し、次に吹き出しから「**モデルアクセスをリクエスト**」を押します。

![claude_3_7_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/d91b4c93-a313-450c-8b66-f5677a91ec41.png)

すると遷移先のページで **Claude 3.7 Sonnet** にチェックが入った状態となりますので、このままページ最下部にある「**次へ**」を押します。

![claude_3_7_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/84b9aeaa-73d3-4e93-883a-3dd38b466834.png)

「**送信**」を押します。

![claude_3_7_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/799296d6-4d47-447f-a055-17262cb4ee5c.png)

![claude_3_7_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/bcaa6f48-9d67-4148-be80-5a92c0188b3b.png)

「**進行中**」になりますので、しばらく待ちます。

![claude_3_7_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/dbcf4914-f5e4-4eb5-9c4c-f8590580c341.png)

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

## IAMでロールの作成

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

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

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

![claude_3_7_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/8cf2ff5b-576c-47fa-a7c0-f332e9c2fa94.png)

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

許可ポリシーとして**AmazonBedrockFullAccess**を選択して**次へ**を押します。

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

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

## Lambda関数の作成

次に**Lambda**の設定に移ります。

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

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

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

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

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

## Lambda関数の設定

![claude_3_7_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/dbfb65b5-e5d1-4f91-b432-37c7d14295fd.png)

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

## コード

[*NEW* Anthropic Claude 3.7 Sonnet - Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-37.html)を参考にして書きました。

**Lambda**に渡されてきた**json**の**message**を参照して、それを**claude 3.7 Sonnet**に投げるように実装しています。必要最低限のコードだとかなりシンプルです。

```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 = 'us.anthropic.claude-3-7-sonnet-20250219-v1:0'  # モデル名の先頭にリージョンを示すus.を書く
    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
    }
```

## テスト

![claude_3_7_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/ff216950-bae4-4524-8bd0-ce0a136c3f59.png)

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

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

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

```json
{
  "statusCode": 200,
  "body": {
    "id": "msg_hogehoge",
    "type": "message",
    "role": "assistant",
    "model": "claude-3-7-sonnet-20250219",
    "content": [
      {
        "type": "text",
        "text": "東京都23区は以下の通りです：\n\n1. 千代田区\n2. 中央区\n3. 港区\n4. 新宿区\n5. 文京区\n6. 台東区\n7. 墨田区\n8. 江東区\n9. 品川区\n10. 目黒区\n11. 大田区\n12. 世田谷区\n13. 渋谷区\n14. 中野区\n15. 杉並区\n16. 豊島区\n17. 北区\n18. 荒川区\n19. 板橋区\n20. 練馬区\n21. 足立区\n22. 葛飾区\n23. 江戸川区\n\nこれらの区は東京都の特別区と呼ばれ、それぞれが基礎自治体としての機能を持っています。"
      }
    ],
    "stop_reason": "end_turn",
    "stop_sequence": null,
    "usage": {
      "input_tokens": 29,
      "cache_creation_input_tokens": 0,
      "cache_read_input_tokens": 0,
      "output_tokens": 234
    }
  }
}
```

> 東京都23区は以下の通りです：\n\n1. 千代田区\n2. 中央区\n3. 港区\n4. 新宿区\n5. 文京区\n6. 台東区\n7. 墨田区\n8. 江東区\n9. 品川区\n10. 目黒区\n11. 大田区\n12. 世田谷区\n13. 渋谷区\n14. 中野区\n15. 杉並区\n16. 豊島区\n17. 北区\n18. 荒川区\n19. 板橋区\n20. 練馬区\n21. 足立区\n22. 葛飾区\n23. 江戸川区\n\nこれらの区は東京都の特別区と呼ばれ、それぞれが基礎自治体としての機能を持っています。

適切な回答に思えます。

## 画像の解析

せっかくなので画像の解析もしてみましょう。**画像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 = 'us.anthropic.claude-3-7-sonnet-20250219-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",
    "model": "claude-3-7-sonnet-20250219",
    "content": [
      {
        "type": "text",
        "text": "この画像は日本の都会（おそらく渋谷や新宿などの繁華街）の様子です。背景には大きな看板やデジタルサイネージがある典型的な都市の交差点があり、多くの人々が行き交っています。\n\n手前には、カラフルなアニメキャラクターのバッジやピンがたくさん付いたバッグを持った若い女性が写っています。彼女はグレーのトップスを着て、都市の景色を見上げながら微笑んでいます。\n\nこの写真は日本のポップカルチャーとアニメファッションが都市環境と融合している様子を捉えています。バッグに付いているのはアニメキャラクターのバッジやピンで、日本のオタク文化やアニメ・漫画文化を表現しています。"
      }
    ],
    "stop_reason": "end_turn",
    "stop_sequence": null,
    "usage": {
      "input_tokens": 393,
      "cache_creation_input_tokens": 0,
      "cache_read_input_tokens": 0,
      "output_tokens": 253
    }
  }
}
```

> この画像は日本の都会（おそらく渋谷や新宿などの繁華街）の様子です。背景には大きな看板やデジタルサイネージがある典型的な都市の交差点があり、多くの人々が行き交っています。\n\n手前には、カラフルなアニメキャラクターのバッジやピンがたくさん付いたバッグを持った若い女性が写っています。彼女はグレーのトップスを着て、都市の景色を見上げながら微笑んでいます。\n\nこの写真は日本のポップカルチャーとアニメファッションが都市環境と融合している様子を捉えています。バッグに付いているのはアニメキャラクターのバッジやピンで、日本のオタク文化やアニメ・漫画文化を表現しています。

かなり正確な分析です。

## 最後に

活用が当たり前になってきた生成AIを、より活用していきたい。

## 宣伝

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

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