祝🎉東京リージョンにClaude3ファミリーが登場！早速バージニア北部と対決させてみた！

## はじめに
AWS Summit Tokyoでアナウンスのあった通り、東京リージョンにAnthropicのClaude３ファミリーの``Haiku``と``3.5 Sonnet``が登場しました！！

![スクリーンショット 2024-07-30 21.48.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/83fe284d-7636-1623-895c-c3bf68e0a87d.png)

![スクリーンショット_2024-08-06_5_54_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/bb83f134-98be-e7cc-82b2-76b24cf5fe7f.png)


当初7月予定とアナウンスされていましたが、予定通りにいかないのが世の常。
そんな中、7月リリースを信じ続けたBedrockerもこれで漸く8月を迎えることができます。。。

https://x.com/hedgehog051/status/1818662946245111912

https://x.com/minorun365/status/1818664200740520322

https://x.com/cyber__BOSE/status/1818951907991552100

早速ですが、東京リージョンとバージニア北部とで出力対決をやってみようと思います！

## コード
東京リージョンのBedrockコンソールからモデルアクセスを有効化したら、``profile_name``にご自身のAWSアカウントのプロフィールを入力してください。

このコードでは下記の3つの質問を2つのリージョンで2つのモデルを使ってそれぞれ10回実行して平均応答時間を計測させています。

- 質問
    - AWSについて教えて下さい
    - 日本一高い山はなんですか？
    - 太陽系の8つの惑星を、太陽からの距離が近い順にリストアップしてください

```python
import streamlit as st
import boto3
import json
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from statistics import mean

st.set_page_config(layout="wide")

# カスタムCSSを追加
st.markdown("""
<style>
    .output-container {
        display: flex;
        flex-direction: column;
    }
    .fixed-height-output {
        min-height: 200px;
        height: 400px;
        overflow-y: auto;
        border: 1px solid #ccc;
        padding: 10px;
        margin-bottom: 10px;
        flex-grow: 1;
    }
</style>
""", unsafe_allow_html=True)

models = {
    "東京 - Claude 3 Haiku": {
        "model_id": "anthropic.claude-3-haiku-20240307-v1:0",
        "region": "ap-northeast-1"
    },
    "東京 - Claude 3.5 Sonnet": {
        "model_id": "anthropic.claude-3-5-sonnet-20240620-v1:0",
        "region": "ap-northeast-1"
    },
    "バージニア北部 - Claude 3 Haiku": {
        "model_id": "anthropic.claude-3-haiku-20240307-v1:0",
        "region": "us-east-1"
    },
    "バージニア北部 - Claude 3.5 Sonnet": {
        "model_id": "anthropic.claude-3-5-sonnet-20240620-v1:0",
        "region": "us-east-1"
    },
}

session = boto3.Session(profile_name="プロフィール名を入力")

st.title("Bedrock Claude 3: 東京 vs バージニア北部")

FIXED_PROMPTS = [
    "AWSについて教えて下さい",
    "日本一高い山はなんですか？",
    "太陽系の8つの惑星を、太陽からの距離が近い順にリストアップしてください"
]

col1, col2 = st.columns(2)
with col1:
    num_tests = st.slider("テスト実行回数を決めてください", min_value=1, max_value=10, value=5, step=1)

def invoke_model(model_name, model_info, prompt):
    try:
        bedrock = session.client(service_name='bedrock-runtime', region_name=model_info['region'])

        body = json.dumps({
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": 300,
            "messages": [
                {"role": "user", "content": prompt}
            ]
        })

        start_time = time.time()

        response = bedrock.invoke_model(
            body=body,
            modelId=model_info['model_id'],
            accept='application/json',
            contentType='application/json'
        )
        
        end_time = time.time()

        response_body = json.loads(response.get('body').read())
        
        response_text = response_body.get('content')[0].get('text')

        return {
            "response": response_text,
            "time_taken": round(end_time - start_time, 2)
        }
        
    except Exception as e:
        return {"response": f"Error: {str(e)}", "time_taken": 0}

def run_test(model_name, model_info, prompt):
    results = []
    for _ in range(num_tests):
        result = invoke_model(model_name, model_info, prompt)
        results.append(result)
    
    avg_time = mean([r["time_taken"] for r in results])
    return {
        "response": results[0]["response"],
        "time_taken": avg_time,
        "all_times": [r["time_taken"] for r in results]
    }

if st.button("テスト実行"):
    progress_bar = st.progress(0)
    status_text = st.empty()
    status_text.text("テストを開始します...")

    all_results = {}
    
    for prompt in FIXED_PROMPTS:
        all_results[prompt] = {}
        with ThreadPoolExecutor(max_workers=4) as executor:
            future_to_model = {executor.submit(run_test, model_name, model_info, prompt): model_name 
                               for model_name, model_info in models.items()}
            
            for i, future in enumerate(as_completed(future_to_model)):
                model_name = future_to_model[future]
                all_results[prompt][model_name] = future.result()
                progress = (len(all_results) - 1 + (i + 1) / len(models)) / len(FIXED_PROMPTS)
                progress_bar.progress(progress)
                status_text.text(f"{prompt} - {model_name} のテストが完了しました。")

    status_text.text("すべてのテストが完了しました。")
    progress_bar.empty()

    st.subheader("テスト結果")
    
    for prompt in FIXED_PROMPTS:
        st.markdown(f"## プロンプト: {prompt}")
        col1, col2 = st.columns(2)
        
        def display_test_results(model_name):
            data = all_results[prompt][model_name]
            st.markdown(f"**{model_name}** (Model ID: {models[model_name]['model_id']})")
            st.markdown(f"平均応答時間: {data['time_taken']:.2f} 秒")
            st.markdown("応答例:")
            st.markdown('<div class="output-container">', unsafe_allow_html=True)
            st.markdown(f'<div class="fixed-height-output">{data["response"]}</div>', unsafe_allow_html=True)
            st.markdown('</div>', unsafe_allow_html=True)
            st.markdown("---")

        with col1:
            st.subheader("東京リージョン (ap-northeast-1)")
            for model_name in ["東京 - Claude 3 Haiku", "東京 - Claude 3.5 Sonnet"]:
                display_test_results(model_name)

        with col2:
            st.subheader("バージニア北部リージョン (us-east-1)")
            for model_name in ["バージニア北部 - Claude 3 Haiku", "バージニア北部 - Claude 3.5 Sonnet"]:
                display_test_results(model_name)

else:
    st.info("「テスト実行」ボタンを押してテストを開始してください。")
```

## 東京リージョン vs バージニア北部

下記のコマンドを実行するとブラウザが開きます。
```sh
streamlit run app.py
```

デフォルトではテスト実行回数のスライドバーが5なので、10に合わせて「テスト実行」ボタンを押すとテスト開始です。

![スクリーンショット 2024-08-06 5.58.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/3bc7be4a-01b3-f0ad-304f-778d99932e10.png)

## 結果

|平均応答時間|東京 Haiku|東京 Sonnet|バージニア北部 Haiku|バージニア北部ン Sonnet|
|---|---|---|---|---|
|AWSについて教えて下さい|2.68秒|5.01秒|4.39秒|7.20秒|
|日本一高い山はなんですか？|1.55秒|5.14秒|3.27秒 |6.95秒|
|太陽系の8つの惑星を、太陽からの距離が近い順にリストアップしてください|1.31秒|4.14秒|2.12秒|5.95秒|

![スクリーンショット 2024-08-06 6.01.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/98d660f1-1a6d-f844-5377-158c20dca74e.png)

![スクリーンショット 2024-08-06 6.02.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/d3ddf89f-bd5f-67aa-e5bb-30a2eaa74b59.png)

![スクリーンショット 2024-08-06 6.02.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/ff5e7ce8-9675-f482-50b6-9efbad3a6e55.png)

![スクリーンショット 2024-08-06 6.02.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/2723eccd-1bd9-9a7f-4ca4-54b6bd2ddfb6.png)

![スクリーンショット 2024-08-06 6.03.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/84a7606a-20f6-2e22-b57b-526bf5137115.png)

![スクリーンショット 2024-08-06 6.03.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/e2d71bfd-3257-40d4-8031-e986526b6b07.png)


今回のケースでは東京リージョンの方がバージニア北部リージョンと比較して、平均応答時間がHaikuで約1.8倍、Sonnetで約1.4倍速く返ってきてることが確認できました！！

## 終わりに
東京リージョンに強力なClaude３ファミリーがGAしたことで、レスポンス速度が向上したことは勿論ですが、特にAPIアクセスであっても国内のインフラストラクチャで完結させたいという企業ニーズに応えられるようになったので、日本市場におけるAIサービスの適用範囲が広がりましたね。

なんでも、使用頻度の多いリージョンは要望が強いと判断されるらしいので、今後のモデル拡充に一役買うためにも東京リージョンでガンガン使っていきましょう！！

## おまけ
Bedrockを活用した生成AIアプリ開発入門本を共著がお陰様で重版になりました。
ハンズオンも豊富でBedrockの入門に最適な本となっておりますので是非興味のある方はお手にとってみてください！

[![Amazon Bedrock 生成AIアプリ開発入門](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/ad8a3f71-46fb-4be3-ad18-8f1980c58bf9.png)](https://prtimes.jp/main/html/rd/p/000000303.000016318.html)