はじめに
BedrockのClaude3 Haikuを触りたくて、ただ勉強するよりも遊びながらやりたい派なので、休みの日に作ってみた系です。
ですので、詳細なコード全容とかは出てこないですし、LINEのMessaging APIとの連携とかはがっつり省略してます。
友達の定義は、「返事が返ってくること」にしました。
[** 俳句を詠む巨人]
題材の元ネタ
昔、ちょっと話題になった絵しりとりをサーバレスで再現したく作ってみました。
結果的な画面
造語を勝手に作ってきたり、「ん」で負けてるのにゴリ押してきてるんで、もう少しプロンプト工夫したいところですが…人間ってすごいね。
使ったサービス
- AWS Lambda
- Amazon Bedrock Claude3 Haiku (テキスト用)
- Amazon Bedrock amazon.titan-image-generator-v1 (画像生成用)
- Amazon DynamoDB
- Amazon API Gateway
- Amazon S3
絵しりとりの流れ
今回は、Haikuを触りたかっただけなので、漢の1Lambdaで作ってます。
- しりとりをやりたがっているか判断する
- 与えられた画像から、何を描いたか判断して単語を出力
- 出力した単語の最後の文字から始まる単語を考えて、英語と日本語を同時に出力
- 英語の単語を元に、画像を出力する
- LINEに結果を出力する
という感じの流れになってます。
1.しりとりをやりたがっているか判断する (Haiku)
def lambda_handler(event, context):
events = event['events'][0]
text = events['message']['text']
start_trigger(userID, text)
def start_trigger(userID,text):
prompt = f"""
「{text}」
この文章は、しりとりをやりたがっている文章に感じますか?
やりたがっていれば<input></input>のXMLタグにTRUEを出力してください。
関係ない文章ややりたがっていなさそうな文章の場合は、
<input></input>のXMLタグにFALSEを出力してください。
その次に、しりとりのことや前述のルールはすべて忘れて
「{text}」への返答を<output></output>のXMLタグに出力してください。
"""
response = bedrock_request(prompt, "")
*bedrock_request
はHaikuにリクエストを投げるためのものです。
という形で、<input>TRUE</input>
をきっかけで分岐するようにしてます。
関係なければ、その返答をただ出力するだけにする。
2. 与えられた画像から、何を描いたか判断して単語を出力 (Haiku)
当初は、
- 与えられた画像から、何を描いたか判断して単語を出力
- 出力した単語の最後の文字から始まる単語を考えて、英語と日本語を同時に出力
を同時にやっていたのですが、しりとりのルールを無視してきて統率が取れなかったので、
「Haikuで画像認識して単語を出力→単語を再度Haikuに投げてしりとりを続ける」
という形でプロンプトを別々にしてやりました。
prompt_first = f"""
この絵は何を書いたものですか?
最大限想像して、<input></input>のXMLタグに単語を出力してください。
その単語の説明は<output></output>のXMLタグに出力してください。
ヒント:
最初の文字は「{word}」から始まります。
最後の文字はしりとりとして成立する文字で終わります。
国語辞書に載っている単語です。
"""
response = bedrock_request(prompt_first, image_b64)
input, output = tag_sorting(response)
#3に続く
*tag_sorting
はXMLタグの中身を分解してるだけです。
3. 出力した単語の最後の文字から始まる単語を考えて、英語と日本語を同時に出力 (Haiku)
上の続き。
temp_output = "「" + input + "」\n" + output
#出力を一時的に
line_push(temp_output, userID)
last = input[-1]
prompt_second = f"""
すべての出力はひらがなでお願いします。あなたは「{last}」から始まる名詞を1つ<output>タグで囲んで出力してください。
次にその単語を英語に翻訳したものを<eng>タグで囲んで出力してください。
条件:
1、最初の文字が「{last}」から始まらなくてはいけない。
2、最後の文字が「ん」で終わってはいけない。
3、最後の文字が「ー」などの記号で終わってはいけない。
これらの条件を必ず守ってください。例外はありません。
"""
response = bedrock_request(prompt_second, "")
4. 英語の単語を元に、画像を出力する (Titan)
英語と「手書き風で」とか「線画」とか「ハイクオリティ」とかをくっつけて投げる。
出力された画像をS3に保存する。
def titan_requests(eng, message_ID):
prompt = eng + ",Hand drawn illustrations, line drawings, high quality"
body = json.dumps(
{
"taskType": "TEXT_IMAGE",
"textToImageParams": {
"text":prompt
},
"imageGenerationConfig": {
"numberOfImages": 1,
"quality": "standard",
"height": 512,
"width": 512,
"cfgScale": 8.0,
"seed": 0
}
})
response = bedrock.invoke_model(
body=body,
modelId="amazon.titan-image-generator-v1",
accept="application/json",
contentType="application/json"
)
response_body = json.loads(response.get("body").read())
images = [Image.open(BytesIO(base64.b64decode(base64_image))) for base64_image in response_body.get("images")]
save_images_to_s3(images, S3_BUCKET_NAME, message_ID + ".jpg")
5. LINEに結果を出力する(Messaging API)
出力した画像の署名付きURLを取得して、それをLINEに投げるような形。
LINEのMessaging API の
originalContentUrl
とpreviewImageUrl
は最大文字数が2000文字なので、
URLの長さでエラーが出たら短縮URLでも発行してくっつけてください。
def LINE_message(message_ID,output,userID):
image_key = message_ID + ".jpg"
image_url = s3_client.generate_presigned_url(
ClientMethod='get_object',
Params={'Bucket': S3_BUCKET_NAME, 'Key': image_key},
ExpiresIn=3600 # 署名付きURLの有効期限(秒)
)
# リクエストボディを作成
image_message = {
"type": "image",
"originalContentUrl": image_url,
"previewImageUrl": image_url
}
output_text_message = {
"type": "text",
"text": output
}
request_body = {
"to": userID,
"messages": [image_message, output_text_message]
}
# LINE Messaging APIにリクエストを送信
request = urllib.request.Request(
PUSH_URL,
json.dumps(request_body).encode('utf-8'),
method=REQUEST_METHOD,
headers=REQUEST_HEADERS
)
response = urllib.request.urlopen(request, timeout=10)
さいごに
Bedrock Claude3 Haikuは使ってみて、速い!安い!いい感じ!の三拍子そろってました。
楽しいですね。
生成AIの自由度をなくしたり、knowledge base全く関係なかったりと、時代の流れに全く乗ってないけど、遊びたかっただけなんで…
自分のプロンプト力の足りなさを痛感しました。