Converse API と Streamlit で質問回答アプリを作ってみる

長らく ToDo リストに入りっぱなしだった Amazon Bedrock の Converse API を試してみる。

## いきなりコード
<details><summary>Streamlit と boto3 で Converse するアプリのコード</summary>

```python:converse.py
import boto3
import streamlit as st


def bedrock_converse(modelId, messages, system, inferenceConfig):
    '''
    Amazon Bedrock の Converse API を呼び出します。
    '''
    bedrock = boto3.client(
        service_name='bedrock-runtime',
        region_name='us-east-1',
    )

    # Converse API の実行
    try:
        converse_response = bedrock.converse(
            modelId=modelId,
            messages=messages,
            system=system,
            inferenceConfig=inferenceConfig
        )
    except Exception as e:
        st.error(f"例外が発生しました: {e}")
    else:
        st.success("呼び出しが成功しました!")
    finally:
        return converse_response


def set_modelId():
    '''
    モデルを選択します。
    '''

    # サイドバーでmodelIdを選択できるようにする
    st.sidebar.header("モデルの選択")

    # モデルの選択
    # Titan、Corere Command、および Large 以前の Mistral はシステムプロンプトをサポートしていないため、ここでは除外
    # また、Opus はオンデマンドスループットをサポートしていないため同じく除外
    modelId = st.sidebar.selectbox(
        "Select a model",
        [
            "anthropic.claude-v2:1",
            "anthropic.claude-3-haiku-20240307-v1:0",
            "anthropic.claude-3-sonnet-20240229-v1:0",
            "anthropic.claude-3-5-sonnet-20240620-v1:0",
            "cohere.command-r-v1:0",
            "cohere.command-r-plus-v1:0",
            "meta.llama2-13b-chat-v1",
            "meta.llama2-70b-chat-v1",
            "meta.llama3-8b-instruct-v1:0",
            "meta.llama3-70b-instruct-v1:0",
            "mistral.mistral-small-2402-v1:0",
            "mistral.mistral-large-2402-v1:0"
        ],
        index=1
    )
    return modelId


def set_inferenceConfig():
    '''
    推論パラメーターを設定します。
    '''

    # maxTokens, temperature, topP を設定できるようにする
    st.sidebar.header("パラメーターの設定")

    maxTokens = st.sidebar.selectbox(
        "最大トークン",
        [
            1024,
            2048,
            4096
        ]
    )
    temperature = st.sidebar.slider(
        "創造性（temperature）",
        min_value=0.0,
        max_value=1.0,
        value=0.5,
        step=0.1
    )
    topP = st.sidebar.slider(
        "多様性（Top P）",
        min_value=0.0,
        max_value=1.0,
        value=0.9,
        step=0.01
    )

    return maxTokens, temperature, topP


def set_otherSettings():
    '''
    その他の設定を行います。
    '''

    # 回答文字数など、その他の設定を行えるようにする
    st.sidebar.header("その他の設定")

    # 回答文字数の設定
    # max_response_lengthを複数選択できるようにする
    max_response_length = st.sidebar.selectbox(
        "回答の最大文字数",
        (100,200,300,400,500,1000,2000)
    )

    return max_response_length


def main():
    '''
    メイン処理です。
    '''

    st.title("Amazon Bedrock Converse API Test")
    st.write("[Converse API](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html) を使用すると、複数のモデルで共通の呼び出し方法を使うことができます。")
    sample_code = '''
        converse_response = bedrock.converse(
            modelId=modelId,
            messages=messages,
            system=system,
            inferenceConfig=inferenceConfig
        )    
    '''
    st.code(sample_code, language='python')

    # modelIdの設定
    modelId = set_modelId()

    # 高度な設定
    if st.sidebar.toggle("高度な設定"):
        # inferenceConfigの設定
        maxTokens, temperature, topP = set_inferenceConfig()

        # その他の設定
        max_response_length = set_otherSettings()
    else:
        # 高度な設定を選択しない場合のデフォルト値
        maxTokens = 1024
        temperature = 0.5
        topP = 0.9
        max_response_length = 100

    # inferenceConfigの設定
    inferenceConfig = {
        "maxTokens": maxTokens,
        "temperature": temperature,
        "topP": topP,
        "stopSequences":[]
    }

    # システムプロンプトを構築
    system_prompt = f'''
    あなたは古今東西の知識に通じた一流のAIアシスタントです。
    以下の規則に従って、ユーザーの質問に回答して下さい。

    - ユーザーが問い合わせた際の言語で回答して下さい。
    - 知識の範囲で回答できないことは、捏造せず、「知識がないので回答できない」と回答して下さい。
    - 回答は{max_response_length}文字以内に収めて下さい。
    - 参照元を示して下さい。ただし、存在しないリンク先を捏造してはいけません。また、参照元のリンクは、クリックにより辿れるようにしておいて下さい。
    - 回答はマークダウン記法で構造化して下さい。以下二例を示します。
    #### （回答タイトル）
    （概要）
    - （要点1）
    - （要点2）
    '''

    system = [
        {
            "text": system_prompt
        }
    ]

    # ユーザープロンプトを構築
    user_prompt = st.text_input("質問を入力して下さい:", "")

    # Bedrock の呼び出し
    if st.button("送信") and user_prompt:
        messages = [
            {
                "role": "user",
                "content": [
                    {
                        "text": user_prompt
                    }
                ]
            }
        ]
        converse_response = bedrock_converse(modelId, messages, system, inferenceConfig)
        st.write(converse_response["output"]["message"]["content"][0]["text"])

    else:
        st.write("Waiting for user input...")

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

</details>

## テスト

![スクリーンショット 2024-09-15 1.07.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/f94c8222-e9be-7b72-d31b-09175d440292.png)

いい感じ。

## 所感
モデルごとの書き方の差異を考える必要がないので、単純に楽。モデルごとに出力を比較することも容易になった。

あと、Bedrock 関係ないけどやっぱり Streamlit は便利。シンプルなコードでそこそこの UI が作れるし、触ってて楽しい。