31
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-04-07

気がついたら、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_format := f.type.split("/")[1]) in IMAGE_FORMAT:
            user_message["content"].append(
                {
                    "image": {
                        "format": file_format,
                        "source": {"bytes": file.getvalue()},
                    }
                }
            )
+        elif (ext := os.path.splitext(f.name)[1][1:]) in DOCUMENT_FORMAT:
+            user_message["content"].append(
+                {
+                    "document": {
+                        "format": ext,
+                        "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 os.path.splitext(f.name)[1][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 os.path.splitext(f.name)[1][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_format := f.type.split("/")[1]) in IMAGE_FORMAT:
            user_message["content"].append(
                {
                    "image": {
                        "format": file_format,
                        "source": {"bytes": file.getvalue()},
                    }
                }
            )
        elif (ext := os.path.splitext(f.name)[1][1:]) in DOCUMENT_FORMAT:
            user_message["content"].append(
                {
                    "document": {
                        "format": ext,
                        "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"])

どうぞご活用ください!

31
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?