Amazon Bedrockに画像生成のプロンプトを作ってもらうPythonコードを書く

## はじめに

Amazon Bedrock(以下Bedrock)使って画像生成するアプリを作ろうとしてます。(画像生成AI好きなので趣味として)

以前「Stable Diffusion XL 1.0のAPIを叩く->作成された画像をS3に格納する」部分をAWS Lambda(以下Lambda)で作ったので、今回は与えたキーワードをもとにプロンプトを生成する部分のコードを書きたいと思います。

前回の記事はこちら
[Amazon Bedrockで画像生成&S3保存するPythonコードを書く](https://qiita.com/Pretzelisa/items/61c4d3838bb6ac7ff561)

## 1. 構成
今回はキーワードからTitan Text G1 - Expressに画像生成用のプロンプトを作ってもらいます。返却されたプロンプトを引数に、Stable Diffusion XL 1.0のAPIを叩くLambdaを呼び出します。


今Amazon API Gateway - Lambda間の連携はしていませんが、イメージとしてはこんな感じです。
![Bedrock_StableAI2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/3be6cd08-1713-0c45-96ed-0b578041cd9a.png)



## 2. 画像生成のプロンプトを作ってもらうプロンプト
AWSのマネジメントコンソールのPlaygroundで、画像生成のプロンプトを作ってもらうプロンプトを試してみました。

`Temperature`は回答のランダム性を調整します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/b21c0c87-74e5-b7f8-a46e-c3a2ce06f498.png)

色々なプロンプト試してみたいので、コードでは`Temperature`をMaxの1に設定してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/c9d1ebbb-3e60-e931-70a9-4a50382eb0f5.png)

`Top P`は可能性の低い回答を無視したいとき -> 低い値を設定するとよいようです。色々試してみましたが、値が大きいと思ってたのと違う画像が出てくることが多かったのでコードでは0にしています。

また、入れるキーワードによっては穴埋め形式だったり物語形式だったりで長々と返してくるので、1行で返して(in a sentence)もつけることにします。

"in a sentence"をつけなかった場合(Response lengthの値を初期値の2048のままにしてたのもあるけど、長い……)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/5373349e-01ab-3fd2-e28e-a203f9754dfd.png)


## 3. コード

引数`keyword`に生成したい画像のざっくりとしたキーワードを入れて実行します。

```python
import boto3
import json

def lambda_handler(event, context):
  bedrock_runtime = boto3.client(service_name="bedrock-runtime")
  keyword = event['keyword']
  prompt = "write a prompt to create" + keyword + "in a sentence"

  body = json.dumps({
      "inputText": prompt, 
      "textGenerationConfig":{  
        "temperature": 1,  
        "topP": 0.1,
        "maxTokenCount": 100,
        "stopSequences": []
      }
  })
  
  #Titan Text G1 - Expressを呼び出し
  response = bedrock_runtime.invoke_model(
      body=body,
  	  modelId="amazon.titan-text-express-v1",
      accept="application/json", 
      contentType="application/json"
  )

  #レスポンスを整形
  response_body = json.loads(response.get('body').read())
  outputText = response_body.get('results')[0].get('outputText')
  text = outputText[outputText.index('\n')+1:]
  image_prompt = text.strip('.')

  #画像生成のLambdaに渡す引数
  input_event = {
      "input": image_prompt
  }
  payload = json.dumps(input_event)

  #画像生成のLambdaをキック
  response = boto3.client('lambda').invoke(
        #<function_name>には画像生成のLambda名を入れる
        FunctionName = '<function_name>',
        InvocationType='RequestResponse',
        Payload = payload
  )
        
```


参考サイト

LambdaからLambdaを呼び出す方法

https://qiita.com/ume1126/items/8170a10fad6b21f0f54a

Amazon Titanのパラメータについて

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-parameters-titan.html

Amazon Titanのレスポンスの整形の部分を参考にさせていただきました。

https://community.aws/content/2ZUYD0un7dMmCcVRUMjRSsZ7Smw/family-of-titan-text-models---boto3-demo

<br>

コードでは消していますが、printで整形中のレスポンスをログに吐いてみました。
`outputText`は最初と最後に`\n`が入ってるので本文だけ抽出します。

画像生成のプロンプトを生成された画像のファイル名にしているのでピリオドも消してます。(ピリオド残しておくと画像のファイル名が"hoge..png"になってちょっと気持ち悪い)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/7bb18730-6322-da70-e995-4d7bb57efba3.png)

## 3. 動かしてみる

"beautiful sea"を`keyword`に入れて実行してみましょう。
返ってきたプロンプトを引数に画像生成のLambdaをキックしてくれます。

こんな感じで引数が渡ってます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/7af01926-ae1d-b83a-c25e-ed6cbf13fc2d.png)

画像ができました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/789ed074-ad1f-54a7-6c51-8f027678448d.png)

こんな感じ。きれい。
![A boat is traveling on the sea with the sun shining on it.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/c982fb29-601b-7526-18a6-68b79dcb589b.png)

<br>

ちなみに`keyword`によっては回答に余計な文がくっついてきます。
keywordに"in"を含めると起こりやすい気がします。(in a sentenceのところ上手く汲んでくれなくなるのかな…)

`keyword`にfortess in the skyを入れて実行するとこんな感じ。(Here is a prompt to~が邪魔)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/17fedb58-0830-b96c-207e-32f2952d7232.png)

ただ"Here is a prompt to~"がくっついてても画像はいい感じに生成されます。
![Here is a prompt to create a fortress in the sky in a sentence___Buildings are soaring into the sky like fortresses, with glass walls and steel frames that reach for the clouds.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/2b458738-8cc2-4a73-3e55-e88c3014c645.png)

`keyword`にsky fortessを入れて実行してみます。"Here is a prompt to~"はついてません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/752e44e1-7fab-2f7e-f65a-0d7de4998d6d.png)

これもいい感じに画像が生成されました。
![A stone fortress is perched high on a hill overlooking a beautiful sky filled with clouds.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458266/8779c07c-4e61-0243-d3e4-f5b6b199a337.png)

## おわりに
とりあえず動くようになったレベルですが、プロンプトを作って画像生成するコードはできました。
今はLambdaのテストから実行しているので、Amplifyでホストしたアプリからコードを実行できるようにしたりとか、プロンプトや画像の精度を高めるためのチューニングとか、エラー処理だとかアプリとしての体裁を整えていきたいです。