Python ( Boto3 ) からBedrockのClaude3を実行する

Anthropic社はClaude3をリリースしました。
一部のモデル ( Claude 3 sonnet、Haiku )は既にBedrockで使用可能になっています。

https://www.anthropic.com/news/claude-3-family

https://qiita.com/minorun365/items/e6f3aa71f5e1bdf21139



というわけでBoto3から呼びだしてみます。

# AWS公式コードサンプル

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

色々変わっているので少し見てみます。

```Python
model_id = 'anthropic.claude-3-sonnet-20240229-v1:0'
```

モデルIDとしては上記を指定します。

:::note
上記はSonnetの例です。
Haikuを使用する場合は`anthropic.claude-3-haiku-20240307-v1:0`に変更してください
:::

```Python
    body=json.dumps(
        {
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": max_tokens,
            "system": system_prompt,
            "messages": messages
        }  
    )  
```

body部に、まず`anthropic_version`なるパラメータが必要になっています。
また、システムプロンプトについては`system`で指定する方式に変わっています。これは省略可能です。
最大トークン数は`max_tokens`という項目名に変更されています。
`messages`の指定の仕方は以下で掘り下げます。

```Python
        # Prompt with user turn only.
        user_message =  {"role": "user", "content": "Hello World"}
        messages = [user_message]
```
上記が`messages`の最小限の指定で、たとえ1件でも配列で指定する必要があります。
配列の中には`role`によって`user`と`assistant`で配列の別項目で分けて指定するように変わっています。

履歴無しでZeroShotのテキストのみの問い合わせをする場合は、上記の様にmessagesの中に`role`を`user`として1件設定し、`content`の中に実際の問い合わせ内容を設定します。

```Python
        # Prompt with both user turn and prefilled assistant response.
        #Anthropic Claude continues by using the prefilled assistant text.
        assistant_message =  {"role": "assistant", "content": "<emoji>"}
        messages = [user_message, assistant_message]
```

会話履歴を含めた問い合わせを行う際は、AI側の履歴を上記の様にmessagesの中に`role`を`assistant`として設定します。
チャット機能を構築する際は、チャット履歴をこの形式で設定するのだと思います。

```Python
        message = {"role": "user",
             "content": [
                {"type": "image", "source": {"type": "base64",
                    "media_type": "image/jpeg", "data": content_image}},
                {"type": "text", "text": input_text}
                ]}
```

画像を含む問い合わせを行う際は、上記の様に`content`を配列にし、その中に`image`と`text`を設定します。`image`については、`data`にBase64形式にエンコードして設定します。

:::note
Base64はバイナリを文字列で表現する為の記法で、6bit（1～64）のバイナリ毎に8bit（1byte）のASCIIコード1文字に置き換えます。ASCIIコードなので文字で表現可能です。バイナリに戻す際には逆の変換を実施すれば可逆に変換可能です
（Claudeの場合はClaude内部でデコードされるので気にする必要が無い）

※1～64のバイナリ値を、それぞれアルファベット26文字x2（大文字小文字）+10（数字）+"+/"（記号2文字）の64文字に割り当て、末尾の桁合わせで"="でパディングする
:::

# マネコン上のAPIリクエストサンプル

~~マネコン上のモデルの説明欄と見比べると、`messages`が配列になっているかどうかの差がある事が分かります。これマネコン上のAPIリクエストサンプルの方がおそらく間違いなので注意が必要です（そのうち直ると思います）。~~
2024/3/6 直ってました。これでドキュメントとマネコン上のサンプルが同じになりました。紛らわしいので間違ってる版は消しておきます。

# 自分で書いてみる

## まずはZeroShotのLLM

ZeroShotのテキストのみの問い合わせの場合、`messages`配列の1要素のみ設定するというのと、`content`は配列にする必要はありません。

```Python
import boto3
import json

# 指定可能なリージョンはバージニア北部（us-east-1）またはオレゴン（us-west-2）
# デフォルトリージョンで良い場合はリージョン指定省略可
bedrock = boto3.client('bedrock-runtime', region_name = "us-west-2")
body = json.dumps(
    {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1000,
        "messages": [
            {
                "role": "user",
                "content": "味噌汁の作り方を説明してください"
            }
        ]
    }
)
modelId = 'anthropic.claude-3-sonnet-20240229-v1:0'
# modelId = 'anthropic.claude-3-haiku-20240307-v1:0'
accept = 'application/json'
contentType = 'application/json'
response = bedrock.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType)
response_body = json.loads(response.get('body').read())
answer = response_body["content"][0]["text"]
print(answer)
```
```:実行結果
はい、基本的な味噌汁の作り方を説明します。

【材料】(4人分)
- 水 4カップ
- 味噌 4~5tablespoon
- 出し汁(昆布と鰹節で作るのが一般的)
- 具材(お好みで、ネギ、豆腐、わかめ、じゃがいも、キャベツなど)

【作り方】
1. 鍋に水を入れ、昆布と鰹節を入れて出し汁を取る。
2. 出し汁が煮立ってきたら、昆布と鰹節をこし取る。
3. 具材を鍋に入れ、火を通す。
4. 火を弱め、味噌を少しずつ溶かしながらよく混ぜる。
5. 味を見て、塩分が足りない場合は少し塩を加える。
6. 再び火を通し、味噌汁が滑らかになったら出来上がり。

ポイントは、最後に味噌を溶かし入れることです。味噌は直火で加熱すると、旨味が失われてしまうので注意が必要です。        

具材は何を入れても美味しいですが、ネギを入れると香りが良くなります。お好みで調整してみてください。
```
## 会話のサンプルを作ってみる

上記の`messages`配列に会話の履歴を入れてみます（該当箇所を差し替えます）
```Python
"messages": [
    {"role": "user", "content": "初めまして、私はアマゾン太郎です"},
    {"role": "assistant", "content": "私はジャン＝クロード・ヴァン・ダムです"},
    {"role": "user", "content": "私の名前を覚えましたか？"},
]
```
```:実行結果
はい、アマゾン太郎さんとおっしゃいましたね。
覚えました。
初めまして、アマゾン太郎さん。
```
質問を変えてみます。
```Python
"messages": [
    {"role": "user", "content": "初めまして、私はアマゾン太郎です"},
    {"role": "assistant", "content": "私はジャン＝クロード・ヴァン・ダムです"},
    {"role": "user", "content": "本当ですか？"},
]
```
```:実行結果
いいえ、冗談です。私は人工知能の対話エージェントであり、
ジャン=クロード・ヴァン・ダムではありません。
アマゾン太郎さん、初めまして。
どんな話題が好きですか?
```
会話履歴を参照して回答してくれているので、会話履歴を踏まえたチャットボットを作る際はこのスタイルで設定すれば出来そうです。多分。

## 画像とテキストの両方からのテキストを生成してみる

Wikipediaの東京タワーのページから画像を持ってきて、画像が何なのか問い合わせてみます。

https://ja.wikipedia.org/wiki/%E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC

Pythonを実行しているディレクトリに該当Jpegファイルを置いています。
```Python
import boto3
import json
import base64

# 画像の読み込みとBase64エンコード（UTF-8の文字列に変換される）
image = open("./TokyoTower.jpg", "rb").read()
b64 = base64.b64encode(image).decode("utf-8")

# リージョンは省略してデフォルトリージョンで実行
bedrock = boto3.client('bedrock-runtime')
body = json.dumps(
    {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1000,
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "image",
                        "source": {
                            "type": "base64",
                            "media_type": "image/jpeg",
                            "data": b64
                        }
                    },
                    {
                        "type": "text",
                        "text": "何が写っていますか？"
                    }
                ]
            }
        ]
    }
)
modelId = 'anthropic.claude-3-sonnet-20240229-v1:0'
# modelId = 'anthropic.claude-3-haiku-20240307-v1:0'
accept = 'application/json'
contentType = 'application/json'
response = bedrock.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType)
response_body = json.loads(response.get('body').read())
answer = response_body["content"][0]["text"]
print(answer)
```
`messages`としては`user`の1件で、そのコンテンツとして、画像と問い合わせテキストの両方を配列に入れています。

```:実行結果
この画像は、東京の代表的な観光名所である東京タワーを捉えています。
烈しい赤とさまざまなトーンの白で構成された、この陸標の特徴的なラティス構造と尖塔が青空に向かって伸びています。
周りには低層のビル群も見えますが、タワーの存在感が際立ています。
晴れ渡る空と対比されたこの鮮やかな色彩は、東京タワーの魅力をよく表現しているユニークな一枚です。
```

# 画面を付けたい場合
以下ご参照

https://qiita.com/cyberBOSE/items/89ff1e91f99eb79f83bc

https://qiita.com/cyberBOSE/items/fe66c2049aaa04568d61