生成AIを使ったアプリケーションをGoogle Cloud App Engineで作る

## 動機
Google Cloudの資格取得を行おうとしており、App Engineのイメージがつかなかったので利用してみようと思いました。
せっかくなので、今流行りの生成AIとやらを私もつかってみむとてするなり

## アプリをローカルで作る
アプリの作成については素人なのですが、Flaskで作成しました。
HTML、CSS、JavaScriptについてはChatGPTに作ってもらいました。
HTMLとPythonだけ置いておきます。

```python:app.py
from flask import request, render_template
##generate.pyができたらコメントアウトを外す
##from generate import generate

app = Flask(__name__)

@app.route("/", methods=['GET'])
def index():
    return render_template('index.html')

@app.route("/lyric-post", methods=['POST'])
def create():
    reference = request.form.get('reference-lyrics', '')
    theme = request.form.get('theme', '')

    prompt = f"「{theme}」\nというテーマで、Aメロ、Bメロ、サビを作詞してください。 \n「{reference}」\nという歌詞の文体や表現、一人称を参考に作詞してください。内容はテーマを遵守してください。"
    
    ## generate関数の結果を取得
    ## ここはまだコメントアウト。generate.pyができたら差し替え
    ##generate_lyric = generate(prompt)
    generate_lyric = "test"

    return render_template('index.html', generate_lyric=generate_lyric, reference=reference, theme=theme)
```
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>天才作詞家</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <h1>天才作詞家</h1>
        <form id="lyrics-form" action="/lyric-post" method="post">
            <div class="form-group">
                <label for="reference-lyrics">参考にしてほしい歌詞を入力:</label>
                <textarea id="reference-lyrics" name="reference-lyrics"rows="6" placeholder="ここに参考歌詞を入力してください">{{reference}}</textarea>
            </div>
            <div class="form-group">
                <label for="theme">作詞する詞のテーマを入力:</label>
                <textarea id="theme" name="theme" rows="6" placeholder="ここにテーマを入力してください">{{theme}}</textarea>
            </div>
            <button type="submit">送信</button>
        </form>
        <div id="output" class="output">
            <pre>{{ generate_lyric }}</pre>
        </div>
    </div>
    <script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>
```
フォルダ構成は下記です。
```
venv
│  app.py
│
├─static
│      script.js
│      styles.css
│      
├─templates
       index.html
```

## 生成AIに質問するためのコードを取得する
Vertex AI Studioの「チャット」で質問を投げてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759447/24dfa7a0-9689-46c6-94fe-8e628c9ff23b.png)
投げたら、右側の「コードを取得」を押すと、同じ質問をするためのコードが表示されますので、これを基にして「generate.py」を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759447/149ea661-4081-b40a-be96-a95b6c6236c7.png)
```python:generate.py
import base64
import vertexai
from vertexai.generative_models import GenerativeModel, Part, SafetySetting


def generate(prompt):
    vertexai.init(project="[project name]", location="us-central1")
    model = GenerativeModel(
        "gemini-1.0-pro-002",
    )
    responses = model.generate_content(
        [prompt],
        generation_config=generation_config,
        safety_settings=safety_settings,
        stream=True,
    )

    answer = ""
    for response in responses:
        print(response.text, end="")
        answer = answer + "\n" + response.text
        
    return answer

generation_config = {
...(この先は変更特になし)
```
```
venv
│  app.py
|  generate.py
│
├─static
│      script.js
│      styles.css
│      
├─templates
       index.html
```
ローカルで動くのかは試していませんが、今考えれば動かせたかもしれません。

## App Engineで動かすために少しつくりを変える
Webで情報を探すと、App EngineでFlaskアプリケーションを動かすためには、guricornというWebサーバを利用することが多いようです。
そのために、下記のようにコードを変えます。
```python:server.py(新規作成)
from flask import Flask
from app import app as blueprint_app 

# Flaskアプリケーションを作成
app = Flask(__name__)

# Blueprintを登録
app.register_blueprint(blueprint_app)

if __name__ == "__main__":
    app.run(debug=False) 
```
```python:app.py(冒頭を変更)
from flask import Blueprint, request, render_template
from generate import generate

# Blueprintの作成
app = Blueprint('index', __name__, url_prefix='/')

@app.route("/", methods=['GET'])
...
```

## App Engineにデプロイするための下準備
App Engineにデプロイの指示を与えるために、「app.yaml」を作成します。
```yaml:app.yaml
runtime: python312
env: standard
instance_class: F1
service: lyric
entrypoint: gunicorn --workers 1 --threads 8 server:app
automatic_scaling:
  max_instances: 1
  max_idle_instances: 0
  min_instances: 0
  min_idle_instances: 0
  min_pending_latency: 3000ms
  max_pending_latency: automatic
```
https://cloud.google.com/appengine/docs/flexible/reference/app-yaml?hl=ja&tab=python
- env
    - できるだけインスタンスを上げないために、スタンダード環境とします
- service
    - serviceについては、最初は「default」で作成する必要があります
    - 私は最初にミスって作成したものがあるため、自由な名前を付けました
- service_account
    - 指定していませんが、デフォルトのサービスアカウントが使われてしまうので、サービスアカウントを事前に定義して指定したほうが良いと思います

必要なパッケージ等をrequirements.txtで指定します。
```requirements.txt
flask==3.0.3
gunicorn==23.0.0
Jinja2==3.1.4
google-cloud-aiplatform==1.66.0
```
```
venv
│  app.py
|  generate.py
|  app.yaml
|  requirements.txt
│
├─static
│      script.js
│      styles.css
│      
├─templates
       index.html
```

## デプロイする
app.yamlがあるディレクトリに移動し、下記コマンドを実行します。
```
gcloud app deploy
```
するとデプロイが成功します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759447/a58ca98b-58fa-eafa-25cc-c0bce8055c41.png)

```
gcloud app browse -s (サービス名)
```
を実行すると、画面が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759447/967910b4-d660-015d-e952-99c2974e7db4.png)
今回はイメージする詞とテーマを入れたら作詞をしてくれるようにVertex AIに指示するアプリケーションなので、さっそく作詞してもらいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3759447/58730349-7d64-7aa3-443d-1719590017c1.png)

```
(Aメロ)
サーバ室の冷気が骨まで沁みる
ホットスペア
なしのストレージは不安定
サポート切れのOS、脆弱性だらけ

バグだらけのアプリ、納期は目前

(Bメロ)
どれだけ頑張っても 報われるとは限らない
誰かのために頑張ってるのに
 虚しい気持ちになる
わかっているのに 諦められない
決意は揺らぐ 未来は不確か

(サビ)
結末ばかりに
気を取られ この瞬間を楽しめない
頭がクラクラする 思考が停止する
夢じゃない あれもこれも 全部自分の手で掴み取る
祝福が欲しいなら 悲しみを知って 1人で泣く
そして輝く
 輝く ウルトラソウル
```
絶対に盗作です。お疲れさまでした。

## サービスアカウントの権限
デフォルトのサービスアカウントはかなりの権限が与えられているのですが、今回で言えば下記のロールで十分でした。
- Cloud Build サービスアカウント
- カスタムロール
    - aiplatform.endpoints.predict

## 参考にさせていただいた記事
下記を参考にさせていただきました。
- App Engineについて
    - https://qiita.com/zuotianzhiye/items/4c847caff2ccb98e58f2
    - https://qiita.com/kai_kou/items/775bcc058aaabbdff4e7
- Vertex AIについて
    - https://zenn.dev/google_cloud_jp/articles/google-cloud-generative-ai