LoginSignup
4
5

サーバレスな友達が欲しかったので、BedrockのHaikuとAmazon Titanを使って、LINEで「絵しりとり」を出来る友達を作る。

Posted at

はじめに

BedrockのClaude3 Haikuを触りたくて、ただ勉強するよりも遊びながらやりたい派なので、休みの日に作ってみた系です。
ですので、詳細なコード全容とかは出てこないですし、LINEのMessaging APIとの連携とかはがっつり省略してます。

友達の定義は、「返事が返ってくること」にしました。

[** 俳句を詠む巨人]

題材の元ネタ

昔、ちょっと話題になった絵しりとりをサーバレスで再現したく作ってみました。

結果的な画面

名称未設定のデザイン.png
造語を勝手に作ってきたり、「ん」で負けてるのにゴリ押してきてるんで、もう少しプロンプト工夫したいところですが…人間ってすごいね。

使ったサービス

  • AWS Lambda
  • Amazon Bedrock Claude3 Haiku (テキスト用)
  • Amazon Bedrock amazon.titan-image-generator-v1 (画像生成用)
  • Amazon DynamoDB
  • Amazon API Gateway
  • Amazon S3

絵しりとりの流れ

今回は、Haikuを触りたかっただけなので、漢の1Lambdaで作ってます。

  1. しりとりをやりたがっているか判断する
  2. 与えられた画像から、何を描いたか判断して単語を出力
  3. 出力した単語の最後の文字から始まる単語を考えて、英語と日本語を同時に出力
  4. 英語の単語を元に、画像を出力する
  5. LINEに結果を出力する

という感じの流れになってます。

1.しりとりをやりたがっているか判断する (Haiku)

lambda_handler
def lambda_handler(event, context):

    events = event['events'][0]
    text = events['message']['text']
    
    start_trigger(userID, text)
start_trigger

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> をきっかけで分岐するようにしてます。
関係なければ、その返答をただ出力するだけにする。
Screenshot_20240327-182002.png

2. 与えられた画像から、何を描いたか判断して単語を出力 (Haiku)

当初は、

  1. 与えられた画像から、何を描いたか判断して単語を出力
  2. 出力した単語の最後の文字から始まる単語を考えて、英語と日本語を同時に出力

を同時にやっていたのですが、しりとりのルールを無視してきて統率が取れなかったので、
「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 の
originalContentUrlpreviewImageUrlは最大文字数が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全く関係なかったりと、時代の流れに全く乗ってないけど、遊びたかっただけなんで…

自分のプロンプト力の足りなさを痛感しました。

ちなみに、ChatGPTだと、
名称未設定のデザイン (1).png

image.png
こんな感じなんで、絵しりとりって難しいんですかね?

4
5
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
4
5