Bedrock の Converse API を少しだけ掘り下げる

Converse API の登場でモデル毎のインターフェースがある程度統一されたので、（langchainのように）以下のように全モデル同じように呼び出す事が出来るようになりました。

https://qiita.com/cyberBOSE/items/59fd50a5a33849512e65

とはいえ細かな差もあるので、Converse API の動作をもう少し確認します。

# サポートされていないパラメーターを設定するとどうなるのか？

Converse API はモデルが変わっても同じ呼び方が出来ると言いつつ、モデル毎にパラメーターのサポート有無が異なります。

https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/e2f6e4e4-becf-678d-1a04-1141a94b5b52.png)

システムプロンプトに対応していないAmazon Titanに対してシステムプロンプトを指定してみます。

```Python
import boto3

bedrock = boto3.client("bedrock-runtime")
modelId="amazon.titan-text-premier-v1:0"

messages = [
    {"role": "user", "content": [{"text": "こんにちは！"}]},
    ]
inferenceConfig = ({"maxTokens": 1000,"temperature": 0.0})
system = [{"text": "あなたは聞かれたことにネコ語で答えます"}]
response = bedrock.converse(modelId=modelId, messages=messages, inferenceConfig=inferenceConfig,system=system)
print("```:" + modelId + ": \n" + str(response["output"]["message"]["content"][0]["text"]) + "\n```\n")
```
```:実行結果
botocore.errorfactory.ValidationException: 
An error occurred (ValidationException) when calling the Converse operation:
This model doesn't support system messages.
Try again without a system message or use a model that supports system messages.
```
エラーになりました。

# モデルとしては対応していないはずのパラメーターを指定するとどうなるのか？

Converse API としてはシステムプロンプトに対応しているものの、モデルとしてはシステムプロンプトに対応していないはずの Claude Instant に対してシステムプロンプトを指定してみます。

```Python
import boto3

bedrock = boto3.client("bedrock-runtime")
modelId="anthropic.claude-instant-v1"

messages = [
    {"role": "user", "content": [{"text": "こんにちは！"}]},
    ]
inferenceConfig = ({"maxTokens": 1000,"temperature": 0.0})
system = [{"text": "あなたは聞かれたことにネコ語で答えます"}]
response = bedrock.converse(modelId=modelId, messages=messages, inferenceConfig=inferenceConfig,system=system)
print("```:" + modelId + ": \n" + str(response["output"]["message"]["content"][0]["text"]) + "\n```\n")
```
```:anthropic.claude-instant-v1: 
にゃんにゃん!
```
ちゃんと機能しますね。そりゃそうなんですが。
ログを見てみます。
```JSON
    "operation": "Converse",
    "modelId": "anthropic.claude-instant-v1",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "こんにちは！"
                        }
                    ]
                }
            ],
            "system": [
                {
                    "text": "あなたは聞かれたことにネコ語で答えます"
                }
            ],
            "inferenceConfig": {
                "maxTokens": 1000,
                "temperature": 0
            }
        },
        "inputTokenCount": 36
    },
```
ログ自体は Converse API の独自のものが出力される為、最終的にモデルに対して何がプロンプトとして渡されているのかは分かりませんでした。

# 画像を読み込ませる

以前までのAPIでは画像をBase64エンコードして渡す必要がありましたが、API仕様を見るとバイト配列（バイナリ）で渡せば良いと書いてあるように見えます。

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html#

本当か？という事で試してみます。
画像ファイルとしては、「あ」という文字だけが書いてある「a.jpg」というファイルをバイナリで渡してみます。

```Python
import boto3

bedrock = boto3.client("bedrock-runtime")
with open("a.jpg", "rb") as data:
    image = data.read()

modelId="anthropic.claude-3-haiku-20240307-v1:0"

messages = [
    {"role": "user", "content": [{"text": "何が書いてありますか？"}, {"image": {"format": "jpeg", "source": {"bytes": image}}}]},
    ]
inferenceConfig = ({"maxTokens": 1000,"temperature": 0.0})
response = bedrock.converse(modelId=modelId, messages=messages, inferenceConfig=inferenceConfig)
print("```:" + modelId + ": \n" + str(response["output"]["message"]["content"][0]["text"]) + "\n```\n")
```
```:anthropic.claude-3-haiku-20240307-v1:0: 
この画像には「あ」という1つの漢字が書かれています。これは日本語の平仮名の1文字で、発音は「あ」です。平仮名は日本語の基本的 
な文字の1つで、主に語彙の表記に使われます。この1文字だけの簡単な画像ですが、日本語の文字の基本的な形態を示しています。     
```
本当でした。そりゃそうなんですが。
ログを見てみます。

```JSON
    "operation": "Converse",
    "modelId": "anthropic.claude-3-haiku-20240307-v1:0",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "何が書いてありますか？"
                        },
                        {
                            "image": {
                                "format": "jpeg",
                                "source": {
                                    "bytes": "/9j/4AAQSkZJRgABAQEAeAB4AAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAyADIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9/KKKKACisWLx9oU/jqbwuutaS3iaCwTVJNIF5Gb6O0eRo0uGgz5giaRHQORtLIwByDW1mjpcAooooAKKKKAGkc9KxfH2gXnirwdqGm2PiDVPCt5eQNFDq2mR2sl5p7H/AJaRLcwzQFh28yJ19VNbZ9q8P/4KNfCfxd8cv2KvH3hTwOjXHiDWrFIBZpf/AGCTVbUTxteWUdxkCGS4tVngSRmVVaZSzKoLCJ35dCo25j85tB+LGteKv+CgnxK8U/Df4xftN/GDw/oPw90qC78Z+ErP4eWkPkw6lrHnL5+rWNnY3VpHJHLtuLJZf3i3CPKdgRPsX/gm1+1Rp3xh1CG31P4l/HbVPEnifQLfxDpnhb4qeHNC0e8XTXwyXtnJpWn28F2jLLD5nl3Fx5HmRrKsMjFa8k+L37NusfFL4sf8Jx4u+HXiz4M/APT/AAfZeBNf8D2mjJ4m8SeMLO3vBPa2cdj4fN+ltpimZ45GjkkleMzRmKCM+c3s3hmLxP8Atk/tcfC/xtp/gHxR8PPhj8IU1S6sdT8T2A0rUvFdzd20lgkFtprMLu0tI498rtexW8jsLcJCykyLdGyjyvz+Wr1vtro7bmdaTlNyStt89F9/Vfd6H11RRRQUFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf//Z"
                                }
                            }
                        }
                    ]
                }
            ],
            "inferenceConfig": {
                "maxTokens": 1000,
                "temperature": 0
            }
        },
        "inputTokenCount": 25
    },
```
`bytes`には`/9j/4…`が設定されています。
「JpegをBase64エンコードすると必ず/9j/4で始まる」というBase64界隈の常識により（？）、ログに出力する際にはBase64エンコードされているのが分かります。
ログ出力時だけBase64エンコードしているのかBase64エンコードした形で処理しているのかまでは読み取れません。

# まとめ

モデル毎に指定できるパラメーターが異なるので、「常にモデル名だけを変えれば他のモデルを試せる」とまではいかないと思いますが、モデル毎のパラメーター名や指定方法を毎回調べるの結構煩わしかったので（特に新規モデル追加時）、少し使いやすくなりました。