Amazon Bedrock でマルチモーダルAI分析アプリを作成

# はじめに

最近こちらの本を購入し、Amazon Bedrock の学習に励んでおります。まだ読み始めたばかりですが、とてもおすすめです。

https://www.amazon.co.jp/Amazon-Bedrock-%E7%94%9F%E6%88%90AI%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E5%85%A5%E9%96%80-%EF%BC%BBAWS%E6%B7%B1%E6%8E%98%E3%82%8A%E3%82%AC%E3%82%A4%E3%83%89%EF%BC%BD-%E5%BE%A1%E7%94%B0-ebook/dp/B0CY28Z13G

そういえば、マルチモーダル生成AIアプリを作成したことがなかったので、以下の Git コードを元に（ありがとうございます） Streamlit でアプリを作成してみました。

https://github.com/minorun365/bedrock-book/blob/main/chapter2/4_multimodal.py

# ドキュメント

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html#api-inference-examples-claude-multimodal-code-example-invoke-model


# サンプルコード

```python
import streamlit as st
import base64
import json
import boto3

def main():
    st.title("マルチモーダルAI分析アプリ")
    
    # サイドバーでプロンプトのテンプレートを選択
    prompt_template = st.sidebar.selectbox(
        "プロンプトテンプレートを選択",
        ["画像の説明", "画像の分析", "カスタム"],
        index=0
    )
    
    # テンプレートに基づいてデフォルトのプロンプトを設定
    default_prompts = {
        "画像の説明": "この画像は何？日本語で説明して",
        "画像の分析": "この画像を詳しく分析して、以下の点について日本語で説明してください：\n1. 主な被写体\n2. 色使いや構図\n3. 画像から感じる雰囲気や印象",
        "カスタム": ""
    }
    
    # プロンプト入力エリア
    user_prompt = st.text_area(
        "プロンプトを入力してください",
        value=default_prompts[prompt_template],
        height=150
    )
    
    # ファイルアップローダーの作成
    uploaded_file = st.file_uploader("画像をアップロードしてください", type=['png', 'jpg', 'jpeg'])
    
    if uploaded_file is not None:
        # 画像の表示
        st.image(uploaded_file, caption='アップロードされた画像', use_column_width=True)
        
        # 詳細設定の展開オプション
        with st.expander("詳細設定"):
            max_tokens = st.slider("最大トークン数", 
                                 min_value=1000, 
                                 max_value=4096, 
                                 value=4096, 
                                 step=100)
            temperature = st.slider("温度 (創造性)", 
                                  min_value=0.0, 
                                  max_value=1.0, 
                                  value=0.7, 
                                  step=0.1)
        
        # 分析ボタン
        if st.button('分析開始'):
            if not user_prompt.strip():
                st.warning("プロンプトを入力してください")
                return
                
            with st.spinner('分析中...'):
                try:
                    # 画像データの変換
                    image_data = base64.b64encode(uploaded_file.getvalue()).decode("utf-8")
                    
                    # Bedrockクライアントの作成
                    bedrock_runtime = boto3.client("bedrock-runtime")
                    
                    # プロンプトの設定
                    prompt_config = {
                        "anthropic_version": "bedrock-2023-05-31",
                        "max_tokens": max_tokens,
                        "temperature": temperature,
                        "messages": [
                            {
                                "role": "user",
                                "content": [
                                    {
                                        "type": "image",
                                        "source": {
                                            "type": "base64",
                                            "media_type": f"image/{uploaded_file.type.split('/')[-1]}",
                                            "data": image_data,
                                        },
                                    },
                                    {"type": "text", "text": user_prompt},
                                ],
                            }
                        ],
                    }
                    
                    # Bedrock API呼び出し
                    response = bedrock_runtime.invoke_model(
                        body=json.dumps(prompt_config),
                        modelId="anthropic.claude-3-5-sonnet-20240620-v1:0",
                        accept="application/json",
                        contentType="application/json"
                    )
                    
                    # レスポンスの処理
                    response_body = json.loads(response.get("body").read())
                    results = response_body.get("content")[0].get("text")
                    
                    # 結果の表示
                    st.success("分析が完了しました！")
                    st.write("### 分析結果")
                    st.write(results)
                    
                except Exception as e:
                    st.error(f"エラーが発生しました: {str(e)}")

if __name__ == "__main__":
    main()
```

`content`配列の中に複数の要素を含めることができ、画像は`type": "image"`として、テキストは`type": "text"`として渡しているんですね。


# 実行結果

画像は前の記事で利用した[こちら](https://qiita.com/leomaro7/items/cef6bed9591ad18a6032)の記事で作成したものを利用しました。

![スクリーンショット 2025-02-07 20.36.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/2ad96c3a-98a9-6eb6-bd0f-998157b13ab1.png)
![スクリーンショット 2025-02-07 20.36.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/36f660c9-7ef4-574f-04b6-db50469d07bb.png)
![スクリーンショット 2025-02-07 20.41.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/2f91acf2-8e9c-aa3b-d9c8-df63dc6e8567.png)
![スクリーンショット 2025-02-07 20.43.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/384e466d-6f95-b8cf-58b5-ff35f5738dfa.png)

おーいいい感じです。