BedrockのClaude3にマルチモーダルな問い合わせが可能なWebアプリをイチから作る

BedrockのClaude3がなんとなく分かってきたので、画像を含む問い合わせが行えるWebアプリを作ってみます。PythonとBoto3とStreamlitで作ります。

※LangChain版は以下

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

:::note
Haikuを使用する場合はモデルIDを`anthropic.claude-3-haiku-20240307-v1:0`に変更してください
:::

# 動作イメージ

申し訳程度の認証画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/df60d0ce-5038-e7d7-b2ce-a311e2afd3dd.png)

画像アップロード欄とテキスト入力欄

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/bcae1fe2-08a8-cb75-3937-f183db247de3.png)

テキストのみで問い合わせ可能
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/3ab088ce-1bfc-d5d3-3847-44bc0855e6b9.png)

こんな写真について聞いてみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/a397e033-92b2-1892-99f9-b208147ba98f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/89da49c4-26bf-40a5-828c-1c98502e1403.png)


こんな画像（というかPDFを画像変換したもの）について聞いてみる
![miraisouhatsu-report_vol10_202312_page-0009.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/002c5ccf-372b-8991-23e3-9458577b3d7d.jpeg)
引用：https://www.mhlw.go.jp/content/11601000/001125241.pdf

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/3acf7e0b-6c41-859a-1a27-73e2f1181044.png)

という感じです。
チャット機能を持たせるとプログラムが煩雑になるのでシンプルに一回の問い合わせで完結させています。また、画像も1ファイルのみです。
PDFファイル自体をアップロードできるようにしようかとも思ったのですが、画像変換を含む作り込みが必要そうだったので見送りました。作れば出来ると思います。

# 必要ライブラリ
特に変わったものは使っていません（LangChainも使っていません）
```
pip install -U boto3 streamlit streamlit_authenticator numpy
```

# 作ったもの

```Python:hogehoge.py
import datetime
import base64
import copy
from PIL import Image
import numpy as np
import boto3
import json
import streamlit as st
import streamlit_authenticator as sa

# 無くても良いけどシステムプロンプト
now = str(datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))).isoformat())
system_prompt="The assistant is Claude, created by Anthropic. The current date and time is" + now + ". Claude's knowledge base was last updated in August 2023 and it answers user questions about events before August 2023 and after August 2023 the same way a highly informed individual from August 2023 would if they were talking to someone from " + now + ". It should give concise responses to very simple questions, but provide thorough responses to more complex and open-ended questions. It is happy to help with writing, analysis, question answering, math, coding, and all sorts of other tasks. It uses markdown for coding. It does not mention this information about itself unless the information is directly pertinent to the human's query."

# bedrock/claude 固定で認証情報を作成
authenticator = sa.Authenticate(
    credentials={"usernames":{"admin":{"name":"bedrock","password":"claude"}}},
    cookie_name="streamlit_cookie",
    key="signature_key",
    cookie_expiry_days=1
)

# ログイン画面描画
authenticator.login()

if st.session_state["authentication_status"]: # ログイン成功

    # ログイン後 画面表示
    authenticator.logout()  # ログアウトボタン
    st.title("Claude3 Sonnetに聞いてみよう")
    img_file = st.file_uploader("Jpegファイルアップロード", type='jpg')
    thumbnail_space = st.empty()  # サムネイル表示用の欄を作っておく
    input_text = st.text_input("何でも聞いてね")
    send_button = st.button("送信")

    if img_file: # 画像がアップロードされている場合
        img_file_copy = copy.copy(img_file) # コピーしてからサムネイル表示
        image = Image.open(img_file_copy)
        img_array = np.array(image)
        thumbnail_space.image(img_array,width = 200)

    # 送信ボタン押下
    if send_button and input_text:

        # content配列の初期化
        content=[]

        if img_file: # 画像がアップロードされている場合
            image = img_file.read() # 読み込み
            b64 = base64.b64encode(image).decode("utf-8") # Base64変換

            # プロンプトの形式にしてcontent配列に追加
            content_image={"type": "image", "source": {"type": "base64","media_type": "image/jpeg","data": b64}}
            content.append(content_image)

        # 入力テキストをcontent配列に追加
        content_text = {"type": "text", "text": input_text}
        content.append(content_text)

        bedrock = boto3.client('bedrock-runtime')
        body = json.dumps(
            {
                "anthropic_version": "bedrock-2023-05-31",
                "max_tokens": 1000,
                "system": system_prompt,
                "messages": [{"role": "user", "content": content}]
            }   
        )
        response = bedrock.invoke_model(body=body, modelId='anthropic.claude-3-sonnet-20240229-v1:0')
        response_body = json.loads(response.get('body').read())
        answer = response_body["content"][0]["text"]
        st.write(answer)

elif st.session_state["authentication_status"] is False: #ログイン失敗
    st.error('Username/password is incorrect')
elif st.session_state["authentication_status"] is None: #未入力
    st.warning('Please enter your username and password')
```

システムプロンプトとして、インターネットで拾ってきたClaudeアプリのシステムプロンプトをほぼそのまま与えてみてます。（無くても特に問題ありません）

慣れて感覚がマヒしてきていますが、改行やコメント込みで七十数行のプログラムでマルチモーダル生成AIアプリが作れるのだからなかなかのものです。

# 起動する
```
python -m streamlit run hogehoge.py
```

# （無料版）Claudeアプリよりも機能が少ないけどどんな意味があるの？
デモ環境・検証環境のベースとして使う想定です。
個人で使う分には大して意味はありませんが、Claudeアプリからシステムプロンプトを変える事が出来るというのと、その他のContextやDocument等の情報をプロンプトに（自力で）追加可能です。Claudeに送るよりもAWSに送った方が各社の規定的に幾分やりやすいことも多いかなと思います。
また、残りのClaude3モデルが来たときはモデルIDのみ変更すれば対応可能な想定です（もしかしたら`anthropic_version`も）。

# インターネットに公開したい場合

自己責任で以下

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

# 元ネタ
画面無しの元ネタはこちら

https://qiita.com/cyberBOSE/items/0e41fb7bcee6b7965d09

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