Streamlitがついに画像・ドキュメント添付に対応!Claude 3.7 Sonnetと連携したマルチモーダルチャットの作り方 #AWS - Qiita

気がついたら、Streamlitのチャット入力が。添付ファイルに対応していました!
1.43.0からっぽいです。

出たらやるしかない!Bedrockの画像添付とドキュメント添付で実際に試しました!

これをもとに機能追加していきます

import boto3
import streamlit as st

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

st.title("Bedrock Chat")

# messagesをセッションに保存
if "messages" not in st.session_state:
    st.session_state.messages = []
messages = st.session_state.messages

# 会話のやり取りを表示
for message in messages:
    with st.chat_message(message["role"]):
        for content in message["content"]:
            if "text" in content:
                st.write(content["text"])

# ユーザー入力
if prompt := st.chat_input():
    # ユーザー入力の表示
    with st.chat_message("user"):
        st.write(prompt)

    # Converse APIに送信するメッセージを作成
    user_message = {"role": "user", "content": [{"text": prompt}]}

    messages.append(user_message)

    # Converse API呼び出し
    client = boto3.client("bedrock-runtime")
    response = client.converse(modelId=MODEL_ID, messages=messages)

    assistant_message = response["output"]["message"]
    messages.append(assistant_message)

    # Bedrockの返答を表示
    with st.chat_message("assistant"):
        st.write(assistant_message["content"][0]["text"])

画像のインプットに対応する

st.chat_input()accept_fileを指定すると、ファイルの添付が可能になります。さらに、file_typeで対応するファイルの形式を指定できます。

BedrockのConverse APIが対応している画像フォーマットに絞って添付できるようにします。

+ IMAGE_FORMAT = ["png", "jpeg", "gif", "webp"]

- if prompt := st.chat_input():
+ if prompt := st.chat_input(accept_file="multiple", file_type=IMAGE_FORMAT):

画面はこのように変わります。

  • accept_file指定なし

  • accept_file指定あり

ファイル添付を有効にするとpromptの型が変わります。ユーザー入力のテキストはprompt.textに変更なります。

    with st.chat_message("user"):
-         st.write(prompt)
+         st.write(prompt.text)

-   user_message = {"role": "user", "content": [{"text": prompt}]}
+   user_message = {"role": "user", "content": [{"text": prompt.text}]}

アップロードしたファイルはprompt.filesで取得できます。

for file in prompt.files:
    file.name # ファイル名
    file.type # ファイルのMimeType
    file.getvalue() # ファイルのバイナリ

st.file_uploaderのドキュメントが参考になります。

画面へ表示する際はfilest.image()に渡すだけでOKです。

    with st.chat_message("user"):
        st.write(prompt.text)

+       for file in prompt.files:
+           st.image(file)

添付された画像をConverse APIへ送信するmessagesに追加します。

file.typeにはMIMEタイプがセットされているので、/以降を切り取ってformatに指定します。
(例:「image/jpeg」→「jpeg」)

細かい点ですが、「jpeg」はOKですが「jpg」はバリデーションエラーになります。(大文字もだめ)
そのため、ファイル名のから拡張子を取るよりMIMEタイプの後ろを取ったほうが良さそうです

+   for file in prompt.files:
+       user_message["content"].append(
+           {
+               "image": {
+                   "format": file.type.split("/")[1],
+                   "source": {"bytes": file.getvalue()},
+               }
+           }
+       )

これで画像の添付ができるようになりました。

ソースの全体はこちらです。

import boto3
import streamlit as st

MODEL_ID = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"

st.title("Bedrock Chat")

# 対応画像フォーマット
IMAGE_FORMAT = ["png", "jpeg", "gif", "webp"]

# messagesをセッションに保存
if "messages" not in st.session_state:
    st.session_state.messages = []
messages = st.session_state.messages

# 会話のやり取りを表示
for message in messages:
    with st.chat_message(message["role"]):
        for content in message["content"]:
            if "text" in content:
                st.write(content["text"])
            elif "image" in content:
                st.image(content["image"]["source"]["bytes"])

# ユーザー入力
if prompt := st.chat_input(accept_file="multiple", file_type=IMAGE_FORMAT):
    # ユーザー入力の表示
    with st.chat_message("user"):
        st.write(prompt.text)

        for file in prompt.files:
            st.image(file)

    # Converse APIに送信するメッセージを作成
    user_message = {"role": "user", "content": [{"text": prompt.text}]}

    for file in prompt.files:
        user_message["content"].append(
            {
                "image": {
                    "format": file.type.split("/")[1],
                    "source": {"bytes": file.getvalue()},
                }
            }
        )

    messages.append(user_message)

    # Converse API呼び出し
    client = boto3.client("bedrock-runtime")
    response = client.converse(modelId=MODEL_ID, messages=messages)

    assistant_message = response["output"]["message"]
    messages.append(assistant_message)

    # Bedrockの返答を表示
    with st.chat_message("assistant"):
        st.write(assistant_message["content"][0]["text"])

ドキュメントのインプットに対応する

BedrockのConverse APIは画像だけでなく、PDFなどのファイルの入力にも対応しています。画像の入力と組み合わせることも可能です。

ドキュメントが対応しているフォーマットを定義し、st.chat_inputfile_typeに追加します。

+ # 対応ドキュメントフォーマット
+ DOCUMENT_FORMAT = ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]
- if prompt := st.chat_input(accept_file="multiple", file_type=IMAGE_FORMAT):
+ if prompt := st.chat_input(
+     accept_file="multiple", file_type=IMAGE_FORMAT + DOCUMENT_FORMAT
+ ):

ドキュメント部分のコンテンツをConverse APIへ送信するmessagesに追加します。
画像(image)と異なり、formatは拡張子を取得します。また、documentの場合はname属性があります。このnameは入力規則があり、日本語文字が指定できません(バリデーションエラーになります)。そのため、uuidで生成するようにしました。

    for file in prompt.files:
+       if file.type.split("/")[1] in IMAGE_FORMAT:
            user_message["content"].append(
                {
                    "image": {
                        "format": file.type.split("/")[1],
                        "source": {"bytes": file.getvalue()},
                    }
                }
            )
+        else:
+            user_message["content"].append(
+                {
+                    "document": {
+                        "format": os.path.splitext(file.name)[1][1:],
+                        "name": str(uuid.uuid4()),
+                        "source": {"bytes": file.getvalue()},
+                    }
+                }
+            )

最後に画面に表示する部分を追加します。ファイル名ぐらいしか出せなですが。

 # 会話のやり取りを表示
 for message in messages:
     with st.chat_message(message["role"]):
         for content in message["content"]:
             if "text" in content:
                 st.write(content["text"])
             elif "image" in content:
                 st.image(content["image"]["source"]["bytes"])
+            elif "document" in content:
+                st.write(content["document"]["name"])
    # ユーザー入力の表示
    with st.chat_message("user"):
        st.write(prompt.text)

        for file in prompt.files:
            if file.type.split("/")[1] in IMAGE_FORMAT:
                st.image(file)
+            elif file.type.split("/")[1] in DOCUMENT_FORMAT:
+                st.write(file.name)

できました。

import os
import uuid

import boto3
import streamlit as st

MODEL_ID = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"

st.title("Bedrock Chat")

# 対応画像フォーマット
IMAGE_FORMAT = ["png", "jpeg", "gif", "webp"]
# 対応ドキュメントフォーマット
DOCUMENT_FORMAT = ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]

# messagesをセッションに保存
if "messages" not in st.session_state:
    st.session_state.messages = []
messages = st.session_state.messages

# 会話のやり取りを表示
for message in messages:
    with st.chat_message(message["role"]):
        for content in message["content"]:
            if "text" in content:
                st.write(content["text"])
            elif "image" in content:
                st.image(content["image"]["source"]["bytes"])
            elif "document" in content:
                st.write(content["document"]["name"])

# ユーザー入力
if prompt := st.chat_input(
    accept_file="multiple", file_type=IMAGE_FORMAT + DOCUMENT_FORMAT
):
    # ユーザー入力の表示
    with st.chat_message("user"):
        st.write(prompt.text)

        for file in prompt.files:
            if file.type.split("/")[1] in IMAGE_FORMAT:
                st.image(file)
            elif file.type.split("/")[1] in DOCUMENT_FORMAT:
                st.write(file.name)

    # Converse APIに送信するメッセージを作成
    user_message = {"role": "user", "content": [{"text": prompt.text}]}

    for file in prompt.files:
        if file.type.split("/")[1] in IMAGE_FORMAT:
            user_message["content"].append(
                {
                    "image": {
                        "format": file.type.split("/")[1],
                        "source": {"bytes": file.getvalue()},
                    }
                }
            )
        else:
            user_message["content"].append(
                {
                    "document": {
                        "format": os.path.splitext(file.name)[1][1:],
                        "name": str(uuid.uuid4()),
                        "source": {"bytes": file.getvalue()},
                    }
                }
            )

    messages.append(user_message)

    # Converse API呼び出し
    client = boto3.client("bedrock-runtime")
    response = client.converse(modelId=MODEL_ID, messages=messages)

    assistant_message = response["output"]["message"]
    messages.append(assistant_message)

    # Bedrockの返答を表示
    with st.chat_message("assistant"):
        st.write(assistant_message["content"][0]["text"])

どうぞご活用ください!



フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link