Amazon Bedrockアプリケーションの実行

演習の概要

このモジュールでは、Amazon Bedrockを利用するためのサンプルアプリケーションを実行します。


このモジュールは、次の2つのAWSブログを元ネタにしています。 これらのブログを参照しつつ、ROSA HCPクラスターで動かすための差分となる情報を紹介していきます。

東京リージョン(ap-northeast-1)のAmazon Bedrockで、Claude 3の基盤モデルへのアクセスが許可されている状態を前提とします。Amazon Bedrock基盤モデルへのアクセス管理ガイドは、 こちらのWebページをご参照ください。本演習を自習している時以外、Amazon Bedrock基盤モデルへのアクセス許可の設定は必要ありません。

[デモ] Amazon Bedrockを利用するためのサンプルアプリケーション

このコンテンツを実行する必要はありません。

上記の1つめのブログで紹介されているアプリケーションを変更します。 このブログでは、Amazon BedrockのClaude 1モデルを利用するためのサンプルコード(app.py)を 紹介していますが、最新の Claude 3モデルを利用するためのコードに変更してみます。

  • Amazon BedrockのClaude 3 Haikuモデルを利用するアプリケーションのコードの例 (app.py)

import streamlit as st
import boto3
import json

st.title("Bedrock Chat Application using Claude 3 model on ROSA HCP")

# Bedrock Runtimeサービス用のクライアント
bedrock = boto3.client(
    service_name='bedrock-runtime',
    region_name='ap-northeast-1')

def format_chat_history(messages):
    formatted_history = ""
    for message in messages:
        # ユーザーかアシスタントかに応じてロールを設定
        role = "Human:" if message["role"] == "user" else "Assistant:"
        # メッセージを整形して追加
        formatted_history += f"{role} {message['content']}\n"
    return formatted_history

# チャット履歴の初期化
if "messages" not in st.session_state:
    st.session_state.messages = []

# アプリ再実行時にチャットメッセージを表示
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# ユーザー入力の受け取り
if prompt := st.chat_input("このテキストボックスに文章を入力して下さい"):
    # ユーザーメッセージをチャットメッセージコンテナに表示
    with st.chat_message("user"):
        st.markdown(prompt)
    # ユーザーメッセージをチャット履歴に追加
    st.session_state.messages.append({"role": "user", "content": prompt})

    # 対話履歴を整形してモデルの入力用に準備
    chat_history = format_chat_history(st.session_state.messages)

    # アシスタントの応答をチャットメッセージコンテナに表示
    with st.chat_message("assistant"):
        message_placeholder = st.empty()
        full_response = ""

        # 整形された対話履歴と新しいユーザー入力をモデルに送信
        body = json.dumps({
            'max_tokens': 1000,
            "messages": [{"role": "user", "content": prompt}],
            "anthropic_version": "bedrock-2023-05-31"
        })
        accept = 'application/json'
        contentType = 'application/json'
        model_id = 'anthropic.claude-3-haiku-20240307-v1:0'
        #model_id = 'anthropic.claude-3-5-sonnet-20240620-v1:0'

        response = bedrock.invoke_model_with_response_stream(
            body=body, modelId=model_id, accept=accept, contentType=contentType)
        stream = response.get('body')

        for event in stream:
            chunk = event.get('chunk')
            if chunk:
                # 取得したチャンクを追加し、Streamlitに表示
                chunk_json = json.loads(chunk.get("bytes").decode())
                if chunk_json["type"]=="content_block_delta":
                    full_response += str(chunk_json["delta"]["text"])
            message_placeholder.markdown(full_response + "▌")
        message_placeholder.markdown(full_response)
        # アシスタントの応答をチャット履歴に追加
        st.session_state.messages.append(
            {"role": "assistant", "content": full_response})

元のブログで紹介していたコードとの差分は下記です。

--- app.py.org	2024-08-08 12:14:31
+++ app.py	2024-08-08 12:13:57
@@ -2,7 +2,7 @@
 import boto3
 import json

-st.title("Bedrock Chat")
+st.title("Bedrock Chat Application using Claude 3 model on ROSA HCP")

 # Bedrock Runtimeサービス用のクライアント
 bedrock = boto3.client(
@@ -28,7 +28,7 @@
         st.markdown(message["content"])

 # ユーザー入力の受け取り
-if prompt := st.chat_input("What is up?"):
+if prompt := st.chat_input("このテキストボックスに文章を入力して下さい"):
     # ユーザーメッセージをチャットメッセージコンテナに表示
     with st.chat_message("user"):
         st.markdown(prompt)
@@ -45,14 +45,14 @@

         # 整形された対話履歴と新しいユーザー入力をモデルに送信
         body = json.dumps({
-            'prompt': chat_history + 'Human: ' + prompt + '\n\nAssistant:',
-            'max_tokens_to_sample': 1000,
-            "stop_sequences": ["\n\nHuman:"],
+            'max_tokens': 1000,
+            "messages": [{"role": "user", "content": prompt}],
             "anthropic_version": "bedrock-2023-05-31"
         })
         accept = 'application/json'
         contentType = 'application/json'
-        model_id = 'anthropic.claude-instant-v1'
+        model_id = 'anthropic.claude-3-haiku-20240307-v1:0'
+        #model_id = 'anthropic.claude-3-5-sonnet-20240620-v1:0'

         response = bedrock.invoke_model_with_response_stream(
             body=body, modelId=model_id, accept=accept, contentType=contentType)
@@ -62,8 +62,9 @@
             chunk = event.get('chunk')
             if chunk:
                 # 取得したチャンクを追加し、Streamlitに表示
-                full_response += json.loads(chunk.get('bytes').decode()
-                                            )["completion"]
+                chunk_json = json.loads(chunk.get("bytes").decode())
+                if chunk_json["type"]=="content_block_delta":
+                    full_response += str(chunk_json["delta"]["text"])
             message_placeholder.markdown(full_response + "▌")
         message_placeholder.markdown(full_response)
         # アシスタントの応答をチャット履歴に追加

上記の差分を解説します。

  • サンプルアプリケーションのタイトルを変更

  • ユーザー入力を受け取るテキストボックスに表示される文字を変更

  • Claude 3の Message API を利用するように変更

  • モデルIDをClaude 3 Haikuに変更 (Claude 3.5 SonnetのモデルIDも参考として記載)

  • Claude 3の出力をJSON形式にデコードした情報からメッセージを抽出するように変更

Dockerfileとrequirements.txtは、元のブログに記載されていたものと同じです。 これらのソースコードを、下記のGitHubリポジトリにアップロードしています。

サンプルコードからアプリケーションをデプロイ

上記の変更を適用したサンプルコードを、ROSA HCPクラスターにデプロイします。 OpenShiftコンソールにログインして、「開発者」→「+追加」メニューから 「Gitからのインポート」をクリックします。

本演習をワークショップ形式で実施している場合、 サンプルアプリケーションをデプロイするプロジェクトには 「test」から始まる名前を付けてください。
dev console menu

「GitリポジトリーのURL」に、下記のURLをコピーして貼り付けます。

https://github.com/h-kojima/sample-llm-aws/

すると、次のような画面が表示されます。

git import

この画面を下にスクロールしていき、アプリケーション名に「sample-llm-aws-app」、名前に「sample-llm-aws」 ターゲットポートに「8501」を入力します。 このターゲットポートは、前述したコードのDockerfileでアプリケーション起動時に指定されています。

port

その他のパラメータは全てデフォルトのままで「作成」をクリックします。 これによって、Dockerfileの内容に従ってコンテナイメージの自動ビルドと、 ビルドされたコンテナイメージをベースにしたコンテナの自動デプロイが実行されます。

数分待つと、「開発者」→「トポロジー」メニューから、 サンプルアプリケーションのPodが「Running」状態になっていて、 正常に実行されたことを確認できるようになります。 赤四角で囲んである所をクリックして、サンプルアプリケーションのURLにアクセスします。

topology

すると、次のようなサンプルアプリケーションの画面が表示されます。 この段階では、Bedrockを利用するための認証情報が無いため、テキストボックスに何か入力しても、 画面のように NoCredentialsError: Unable to locate credentials などが表示されるはずです。

error

[デモ] Amazon Bedrockを利用するためのIAMロールの作成

本演習を自習している時以外、この項目は実行しないで下さい。

デプロイしたアプリケーションがAmazon Bedrockを利用するための認証情報を追加します。 STS を用いた ROSA ワークロードのためのきめ細かい IAM ロール に記載してあるステップ1, 2, 4, 5を順番に実行して、次の「サービスアカウントの作成」で利用するIAMロールを作成します。これらのステップ実行時の注意点を記載します。

  • ステップ1で設定してある変数は、ご自身の利用している環境に応じて、適宜変更します。 APP_NAMESPACE 変数は、前述の手順でデプロイしたアプリケーションのプロジェクト名(例: test-llm-project20 など)を指定します。 また AWS_REGION 変数は必要ありません。

  • ステップ2で実行する oc CLIは、ROSA HCPクラスターの管理者アカウント( cluster-admin など)で実行します。

  • ステップ3を実行する必要はありません。

  • ステップ5にあるIAMポリシーについて、元のブログではAmazon S3にアクセスするための IAMポリシーをアタッチしていますが、ここではAmazon BedrockにアクセスするためのIAMポリシーとして、 AmazonBedrockFullAccess を指定するようにします。

サービスアカウントの作成

アプリケーションをデプロイしているプロジェクトにサービスアカウントを作成します。 OpenShiftのコンソールに戻って、「管理者向け表示」→「ユーザー管理」→「ServiceAccounts」から、 「ServiceAccount の作成」をクリックします。

下記をコピペして貼り付けます。「XXXXX」となっているところは、 AWSアカウントIDに置き換えて「作成」をクリックします。 これで、Amazon Bedrockの認証情報として利用するための、 「bedrock-sa」という名前のサービスアカウントが作成されます。

本演習をワークショップ形式で実施している場合、 インストラクターがAWSのARNをご案内します。 本演習を自習している場合、前の手順で作成したIAMロールのARNに置き換えてください。
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bedrock-sa
  annotations:
    eks.amazonaws.com/role-arn: 'arn:aws:iam::XXXXXXXXXXXXXX:role/iam-app-bedrock-role-YYYYYYYYY'
serviceaccount

Deploymentにサービスアカウントの追加

OpenShiftのDeploymentリソースに、作成したサービスアカウントを追加します。 これで、既存のサンプルアプリケーションが、Amazon Bedrockの認証情報を利用できるようになります。

「Deployments」メニューから「sample-llm-aws」デプロイメントを選択して、 「YAML」タブから下記を「spec:」と「containers:」の間に挿入して「保存」をクリックします。

serviceAccount: bedrock-sa
deployment

すると、Podが自動的に再デプロイされて、 Amazon Bedrockにアクセスするためのサービスアカウントを利用するための設定が追加されます。

Amazon Bedrockのテスト

ここまでの手順によって、チャットアプリケーション経由でAmazon Bedrockを利用できるようになっています。 色々な質問を入力してみてください。Claude 3 Haikuの標準モデルによる回答が返されます。 もし日本語の回答が微妙だと思う場合は、英語で同じ質問をすると、より精度が高い回答が返ってくる場合があります。

bedrock