Amazon Novaをboto3のconverseまたはconverse_stream APIで実行する

# はじめに
Amazon Bedrockでは、生成AIの基盤モデルをConverse (Stream) APIで呼び出して実行しますが、Amazon Nova系のモデルを呼び出した時の考慮点について、本記事で備忘録としてまとめます。

# Amazon Novaの推論タイプはON_DEMANDではない
Amazon Bedrockがサポートするモデルの中で、特に`inferenceTypesSupported`が`ON_DEMAND`のモデルを利用するケースが非常に多いと思いますが、以下のコードで、フィルタしつつ全てリストすることが可能です。

```Python:boto3
# Bedrockがサポートするモデル一覧を取得
def fetch_on_demand_models():
    try:
        response = bedrock_client.list_foundation_models()

        # 'modelSummaries'からON_DEMANDモデルのみ抽出
        models = [
            {
                "modelId": model['modelId'],
                "modelName": model['modelName'],
                "providerName": model['providerName'],
                "inferenceType": ", ".join(model.get('inferenceTypesSupported', [])),
                "shortModelId": model['modelId'].split(":")[0],
                "inputModalities": model.get("inputModalities", []),  # 入力モダリティ
                "outputModalities": model.get("outputModalities", []),  # 出力モダリティ
            }
            for model in response.get('modelSummaries', [])
            if "ON_DEMAND" in model.get('inferenceTypesSupported', [])
        ]
        return models

    except Exception as e:
        print(f"モデル一覧の取得中にエラーが発生しました: {e}")
        return []
```

簡単に書くと`list_foundation_models()`のAPIを呼び出すことでモデル一覧が取得できるため、その中から`inferenceTypesSupported`が`ON_DEMAND`のモデルをフィルタしているだけです。

ところが、Amazon Nova系のモデルは`inferenceTypesSupported`が`ON_DEMAND`ではなく`INFERENCE_PROFILE`であり、このコードではリストされないことが分かりました。
そこで、`ON_DEMAND`のモデルに加えて`INFERENCE_PROFILE`のモデルもリストするよう、コードの変更が必要となりました。

# INFERENCE_PROFILEの推論タイプに対応する

変更後のコードは以下になります。

```Python:boto3
# Bedrockがサポートするモデル一覧を取得
def fetch_on_demand_models():
    try:
        response = bedrock_client.list_foundation_models()
        
        # 'modelSummaries'からON_DEMANDモデルのみ抽出
        models = []
        for model in response.get('modelSummaries', []):
            inference_types = model.get('inferenceTypesSupported', [])
            
            # INFERENCE_PROFILEタイプの場合は推論プロファイルを取得
            if "INFERENCE_PROFILE" in inference_types:
                try:
                    # 推論プロファイルの一覧を取得
                    profiles_response = bedrock_client.list_inference_profiles()
                    for profile in profiles_response.get('inferenceProfileSummaries', []):
                        # モデルのARNが一致する推論プロファイルを探す
                        for profile_model in profile.get('models', []):
                            if profile_model.get('modelArn') == model.get('modelArn'):
                                models.append({
                                    "modelId": profile['inferenceProfileId'],  # 推論プロファイルIDを使用
                                    "modelName": f"{model['modelName']} (Profile: {profile['inferenceProfileName']})",
                                    "providerName": model['providerName'],
                                    "inferenceType": ", ".join(inference_types),
                                    "shortModelId": model['modelId'].split(":")[0],
                                    "inputModalities": model.get("inputModalities", []),
                                    "outputModalities": model.get("outputModalities", []),
                                })
                                break

                except Exception as e:
                    print(f"推論プロファイルの取得中にエラーが発生しました: {e}")
            
            # ON_DEMANDタイプの場合は通常のmodelIdを使用
            elif "ON_DEMAND" in inference_types:
                models.append({
                    "modelId": model['modelId'],
                    "modelName": model['modelName'],
                    "providerName": model['providerName'],
                    "inferenceType": ", ".join(inference_types),
                    "shortModelId": model['modelId'].split(":")[0],
                    "inputModalities": model.get("inputModalities", []),
                    "outputModalities": model.get("outputModalities", []),
                })
        return models
 
    except Exception as e:
        print(f"モデル一覧の取得中にエラーが発生しました: {e}")
        return []
```
先ほどのコードとの違いは、`list_foundation_models()`のAPIを呼び出した結果を走査しつつ、`inferenceTypesSupported`が`INFERENCE_PROFILE`のモデルの場合に`list_inference_profiles()`のAPIを呼び出して、推論プロファイルID (`inferenceProfileId`) を従来のモデルIDの属性に代入している点です。

このような処理が必要となる理由は、`converse()`または`converse_stream()`のAPIで`inferenceTypesSupported`が`INFERENCE_PROFILE`のモデル (= Amazon Nova系) を呼び出そうとすると、以下のエラーが出力されて失敗するためです。

> エラーが発生しました: An error occurred (ValidationException) when calling the ConverseStream operation: Invocation of model ID amazon.nova-pro-v1:0 with on-demand throughput isn’t supported. **Retry your request with the ID or ARN of an inference profile that contains this model.**

上のコードにおける`modelId`に`inferenceProfileId`の値を代入することによる違和感は拭えませんが、この変更によってモデル一覧のスキーマを統一したままにでき、`inferenceTypesSupported`属性の違いを全く意識することなく、 以下画像の例のようにアプリケーションのユーザにモデルを選ばせることが可能となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/d7016e6f-5dd8-0e98-056e-0b208b085bf9.png)

`converse_stream()`のAPIでモデルを呼び出すときに指定する属性も`modelId`であり、`converse_stream()`を呼び出すコードを変えるのではなく、`modelId`を渡すか`inferenceProfileId`を渡すかの違いとなります。

以下はboto3の公式ドキュメントから、リクエスト仕様を引用したものですが、この`modelId`に与える値が、Amazon Nova系の場合は`inferenceProfileId`になるということです。(大変ややこしい)

```Python:boto3
response = client.converse_stream(
    modelId='string',
    messages=[
        {
            'role': 'user'|'assistant',
            'content': [
                {
                    'text': 'string',
                    'image': {
                        'format': 'png'|'jpeg'|'gif'|'webp',
                        'source': {
                            'bytes': b'bytes'
                        }
                    },
                    (以下略)
```

この仕様に沿ったコードの変更後にAmazon Nova系のモデルを呼び出せていることが、ログからも確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/a269d261-f028-f633-6563-1bea5db43c87.png)


# 参考文献
* [converse_stream - Boto3 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse_stream.html)
* [list_foundation_models - Boto3 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock/client/list_foundation_models.html)
* [list_inference_profiles - Boto3 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock/client/list_inference_profiles.html)
* [Supported foundation models in Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html)
* [Supported Regions and models for inference profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html)