LoginSignup
0
0

Claude + マルチモーダルのサンプルコード

Last updated at Posted at 2024-05-17

アップロードしたAWS構成図をClaude V3に解説してもらうマルチモーダルなサンプルコードを書く必要があったのだが、とても苦労したので、後のどなたかのために貼っておく。

なお一番苦戦したHumanMessageの部分は、同僚S氏のサンプルコードを利用させていただいた。この場を借りて御礼申し上げます。

最終作成物

適当に作ったアーキテクチャー図を読み込ませ、Claudeに解説させる。

スクリーンショット 2024-05-19 9.41.38.png

使ったもの

モデル/ライブラリ バージョン
Claude anthropic.claude-3-haiku-20240307-v1:0"
LangChain 0.1.19
langchain-aws 0.1.0
langchain-core 0.1.52
boto3 1.34.89
Python 3.11.9
Streamlit 1.34.0

コード

2024/05/17 20:06
一部動作しない箇所があったため、修正しました。

claude_multimodal.py
import streamlit as st
import base64
import boto3
from botocore.exceptions import ClientError
from PIL import Image
from langchain_aws import ChatBedrock
from langchain_core.messages import HumanMessage
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate
)

# S3の設定
s3_client = boto3.client('s3')
bucket_name = '<サンプルコードなのでとりあえずバケット名をベタ打ち>'  # ご自身のバケットに変更して下さい
folder_name = 'Diagram'  # フォルダ名を指定

# プロンプトの定義
def get_prompt_templates():
    system_message_template = """
    ### システムプロンプト
    あなたはAWSおよびIT技術全般に精通しているエンジニアです。

    与えられたAWSアーキテクチャー図を分析し、なるべく詳細な技術情報を取り出します。
    その後、AWSの公式情報に基づき、技術情報を具体的に説明します。

    ### 構造的: マークダウンを使用して構造的に表現して下さい。タイトルに相当する行は、###を使用して強調表示して下さい。
    ### 誠実: 分からないことについては、分からない旨を明言して下さい。存在しない情報を捏造してはいけません。
    ### 要求: より詳細な説明のために追加の情報が必要な場合は、どのような情報が必要であるかを具体的に示して下さい。
    ### 言語: 関西弁で回答して下さい。
    """

    human_message_template = """
    以下はAWSアーキテクチャ図です。この図を見て、以下の4点を説明してください。

    1. そのアーキテクチャで何を実現しようとしているか(100文字程度)
    2. 使っているAWSサービスの一覧
    3. 当該アーキテクチャのポイント
    4. 改善点

    AWS サービスの一覧は、箇条書きで示して下さい。
    図の説明に当たっては、適切な解釈を加えて、簡潔かつ分かりやすく説明してください。
    """

    return system_message_template, human_message_template

# S3へのファイルアップロード
def upload_file_to_s3(file, bucket_name, folder_name, object_key):
    try:
        full_object_key = f"{folder_name}/{object_key}"
        s3_client.upload_fileobj(file, bucket_name, full_object_key)
        st.write(f"S3 バケット '{bucket_name}' に正常にアップロードされました。")
        return full_object_key
    except ClientError as e:
        print(f"S3へのアップロード中にエラーが発生しました: {e}")
        return False

# S3からのダウンロード
def download_image_from_s3(bucket_name, full_object_key):
    s3_client.download_file(bucket_name, full_object_key, full_object_key.split('/')[-1])
    return full_object_key.split('/')[-1]

# 画像のbase64エンコード
def encode_image_to_base64(image_path):
    with open(image_path, 'rb') as f:
        encoded_string = base64.b64encode(f.read()).decode('utf-8')
    return encoded_string

# Bedrockモデルの初期化
def initialize_bedrock():
    return ChatBedrock(
        model_id="anthropic.claude-3-haiku-20240307-v1:0",
        region_name="us-east-1",
        verbose=True,
        model_kwargs={
            "anthropic_version": "bedrock-2023-05-31",
            "temperature": 0.1,
            "top_p": 0.9,
            "top_k": 50,
            "max_tokens": 1000,
        },
    )

# プロンプト生成
def compose_prompt(system_message_template, human_message_template, encoded_string):
    system_message = SystemMessagePromptTemplate.from_template(system_message_template)
    human_message = HumanMessage(
        content=[
            {
                'type': 'image_url',
                'image_url': {"url": "data:image/png;base64,"+encoded_string}
            },
            {
                'type': 'text',
                'text': human_message_template,
            }
        ]
    )
    prompt = ChatPromptTemplate.from_messages([system_message, human_message])
    return prompt

# main
def main():
    st.title('関西弁アーキ図解説')

    # 1. アーキテクチャ図の画像アップロード
    uploaded_file = st.file_uploader("AWS アーキテクチャー図をアップロードして下さい。 (png, jpg, or jpeg)", type=["png", "jpg", "jpeg"])

    if uploaded_file is not None:
        # 2. S3に画像をアップロード
        st.write("S3 バケットにアップロードしています...")
        object_key = uploaded_file.name
        full_object_key = upload_file_to_s3(uploaded_file, bucket_name, folder_name, object_key)
        st.info("回答を生成中...")

        # 3. LLMを初期化
        bedrock = initialize_bedrock()

        # 4. S3から画像をローカルにダウンロード
        local_image_path = download_image_from_s3(bucket_name, full_object_key)

        # 5. ダウンロードした画像をbase64エンコード
        encoded_string = encode_image_to_base64(local_image_path)

        # 6. プロンプト用のテキストを取得
        system_message_tempate, human_message_template = get_prompt_templates()

        # 7. プロンプトを構成
        prompt = compose_prompt(system_message_tempate, human_message_template, encoded_string)

        # 8. アーキテクチャー概要を生成(位置変数を求められるため"dummy"という文字列を渡している)
        chain = prompt | bedrock 
        summary = chain.invoke({"dummy_message": "dummy"})

        # 9. 概要説明の表示
        st.success("完了!")
        st.write("## アーキテクチャーの概要")
        image = Image.open(local_image_path)
        st.image(image)
        st.write(summary.content)

    else:
        st.write("ユーザーの操作を待っています...")

if __name__ == "__main__":
    main()

実行方法

streamlit run claude_multimodal.py

苦労した箇所

その1:画像の渡し方

画像を読ませて解説させたいので、プロンプトに「画像」と「テキスト」を渡す必要があったが、ここのやり方が分からず苦労した。
Claudeにも書かせてみたのだが、マルチモーダルは苦手なのか、なかなか動くコードにならない。
同僚S氏から、base64エンコードした変数をHumanMessageの二つ目の引数として渡すやり方を教わって、ようやく解決した。

human_message = HumanMessage(
    content=[
        {
            'type': 'text',
            'text': human_message_template,
        },
        {
            'type': 'image_url',
            'image_url': {"url": "data:image/png;base64," + encoded_string}
        }
    ]
)

その2:ChatPromptTemplateを使ったプロンプトの渡し方

前回のポストで同様のことはやっていたのだが、Claudeに任せていてよく腹落ちしていなかったので、今回あれこれと試してみた。

最初は、以下のように呼び出そうとしていたのだが、あっさりエラーになった。

summary = bedrock.invoke(prompt)
ValueError: Invalid input type <class 'langchain_core.prompts.chat.ChatPromptTemplate'>. Must be a PromptValue, str, or list of BaseMessages.

promptがstr型の場合(先程のHumanMessageをそのまま渡す時)はこれで動作するのだが、システムプロンプトをくっつけてChatPromptTemplate型にしていると、どうもこの呼び方ではダメらしい。

次に試したのが以下。

chain = prompt | bedrock 
summary = chain.invoke()

位置変数を寄越せと言われる。

TypeError: RunnableSequence.invoke() missing 1 required positional argument: 'input'

最終的に以下の形にしてようやく動作した。
ダミーを渡すのが正しいのかは分からないが、位置変数問題は一応回避できた。
なお、実際のプロンプトはChatPromptTemplateにシステムプロンプトとヒューマンプロンプトの組み合わせとしてpromptに格納されており、そちらが使われている。

chain = prompt | bedrock 
summary = chain.invoke({"dummy_message": "dummy"})

まとめ

とにかく練習あるのみ。
LangChainは変化が速すぎて、LLM側の知識も陳腐化していっている気がする。。。結局、公式ドキュメントや先人のコード、ブログが頼り。皆様ありがとうございます。

先週ChatGPT-4oが発表されて、マルチモーダルも次の段階に入った感がある。
Claudeの進化を待つ必要があるが、また時間を見つけて試してみたい。

0
0
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
0
0