アップロードしたAWS構成図をClaude V3に解説してもらうマルチモーダルなサンプルコードを書く必要があったのだが、とても苦労したので、後のどなたかのために貼っておく。
なお一番苦戦したHumanMessage
の部分は、同僚S氏のサンプルコードを利用させていただいた。この場を借りて御礼申し上げます。
最終作成物
適当に作ったアーキテクチャー図を読み込ませ、Claudeに解説させる。
使ったもの
モデル/ライブラリ | バージョン |
---|---|
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
一部動作しない箇所があったため、修正しました。
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の進化を待つ必要があるが、また時間を見つけて試してみたい。