AWSのEC2でGPTsのActions用のAPIサーバーを動作させるまで

### AWSのEC2でGPTsのActions用のAPIサーバーを動作させるまで

標題の「AWSのEC2でGPTsのAPIサーバーを動作させる」までに様々な難関があります。それらの難関をなんとかすり抜けてめでたく、GPTsとAPIサーバーが交信できるようになるまでのメモです。

GPTsはGPT4のインストラクション（プロンプト）を予(あらかじ）め作成し、設定しておいて、その、いわば、カスタマイズされたGPT4で、ユーザーが特定の目的のためにすぐ使えるようしたオーダーメイドGPTです。例えば、野菜や卵などの料理の材料を写真で撮影し、それをGPT4にアップロードして、「写真に映っている材料で作れる料理のレシピと完成した時の写真を生成せよ」というインストラクションをインプットしておけば、誰でもこのGPTsにアクセスすれば、簡単に材料の映った写真⇒レシピ⇒完成写真が簡単に生成できます。

　GPTsにはinstruction（プロンプト）knowledge（知識源) Actionsという三つ要素を組み合わせて役に立つ、または楽しめるカスタムGPTを作成し、配布、販売できるという画期的なOpenAIのサービスです。

 ### AWS入門
  AWS上にGPTsのActionsと交信するAPIサーバーを構築するためには、先ず、AWSのアカウントをセットアップします。
  
  次に、基本的なEC2というバーチャルサーバーを設定しますが、
  YouTubeにあるクライン[Klein]さんの動画が参考になります。
  https://www.bing.com/videos/riverview/relatedvideo?q=aws%E3%81%AB%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E6%A7%8B%E7%AF%89&mid=34E779814471D730363634E779814471D7303636&FORM=VIRE

  これで基本的な設定ができますので、次にインスタンス（実際に動いているサーバー）を設定します。
  コンソールのホームから、EC2を選択し、インスタンスを起動のボタンをクリックします。ここで、画面右上に都市の名前（ムンバイとか東京とか）が表示されていますので注意してください。これはサーバーの物理的な場所ですので、ユーザーが実際にいる近くの都市を選択します。インスタンスを起動させても、この都市の指定が、最初に設定した時と違っていると起動中のインスタンスが認識されません。
  インスタンスを起動のボタンを押すと、
  ##### アプリケーションおよび OS イメージ (Amazon マシンイメージ)
  のページが表示されますので、このページの中のクイックスタートのOSのアイコンの中から、自分が使いやすいOSを選択します。本稿では、ubuntuをクリックして選択します。
  　続いて、キーペア (ログイン)   で、キーペアを作成します。このキーはダウンロードホルダに自動的にダウンロードされます。
  　続いて　セキュリティグループを作成します。
   HTTP HTTPS のチェックボックスにチェックを入れ、
、ポート22番への接続は、任意の場所を自分のIPに変更します。これは後で、
オンプレミスからAWSにリモートログインするために使うポートです。

  ストレージを設定は実験用には16GiBもあれば十分だと思います。
　実験したいことの内容に合わせて決定してください。

 　ページの右下の　インスタンス起動ボタンをクリックすれば、インスタンスが
  起動し、サーバーが使える状態になります。

  ここでEC2に戻り、リソースの欄を見ると、インスタンスが実行中になっているのが
  わかります。ここで、インスタンス（起動中）をクリックすると、起動中の
  インスタンスの状態を確認したり、停止したり、終了したりできます。
  ここで、起動中のインスタンスのインスタンスIDをクリックすると、その
  起動中のインスタンスの概要が表示されます。
  （注意　インスタンスを終了するとIPが変わりますの中止してください。）

ここで、自動的に割り当てられたIPアドレスの下に表示されている
xx.xx.xx.xx（パブリックIP)の数字をコピーしてメモにペーストしておきます。

#### リモートログイン
次にWindowsの場合は、Powershell かwsl2のlinuxを起動し、そのウィンドウの中で、
次のコマンドを打ちます。そのディレクトリの中に先ほどのキーを置いておきます。

ssh -i MyTestKeyPare.pem ubuntu@xx.xx.xx.xx 

DNSでxx.xx.xx.xxをドメインネームにアサインしておけば、例えば<ドメイン名>.com　がドメイン名に設定されていれば、

ssh -i MyTestKeyPare.pem ubuntu@<ドメイン名>　でサーバーに接続できます。

ここで、 MyTestKeyPare.pem　は、上でダウンロードしたキーです。
MyTestKeyPareは自分が作成した時のキーペア名です。また、このキーファイル
は、上の起動コマンドのウィンドウと同じフォルダが、別のファルダにある場合
はフォルダ名を含めなければなりません。

　上のsshを起動するときこのキーファイルを以下のように他者が見られないようにしておかないとエラーになります。
```
chmod 400 MyTestPare
```
scpコマンドで、AWS上のサーバーにアップロードできます。
```
scp -i /path/key-pair-name.pem /path/my-file.txt ec2-user@instance-public-dns-name:path/
```

### Nginxとcertbotのインストール
(参考　https://qiita.com/shin4488/items/8738e13b92143c88aab6)

　nginxはApacheのオールターナティヴです。
 以下の手順でインストールします。
 ```
sudo apt update
sudo apt install nginx
sudo apt install certbot python3-certbot-nginx
sudo ufw allow 443  // port 443の解放
```
インストール後、
/etc/nginx/sites-enabled/sample.conf
を見ると、👇のようになっています。
```
server {
    server_name <ドメイン名>;

    location / {
        root /home/sample;
        index index.html;
    }
}
```
上でインストールしたcertbotはssl用のキーを取得するためのもので、これによって
キーファイルを取得しないと https ～のアドレスは使えません。
 /etc/letsencrypt/live/<ドメイン名>/にストアされます。
そして、GPTsのActionのスキーマに、apiのアドレスを入れる場合、httpsでないとエラーになります。
```
sudo certbot --nginx -d <ドメイン名>
```
無事終了後、/etc/nginx/sites-enabled/sample.conf を見てみると
```
server {
            server_name <ドメイン名>;

                location / {
                                root /home/sample;
                                index index.html;
                           }


    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/xrights.net/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/xrights.net/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = xrights.net) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


            server_name xrights.net;
    listen 80;
    return 404; # managed by Certbot


}
```
となっています。これを 以下のように/etc/nginx/conf.dにコピーします。

```
sudo cp /etc/nginx/sites-enabled/sample.conf  /etc/nginx/conf.d/
```
さらにコピーした　/etc/nginx/conf.d/sample.conf を以下のように
編集します。
```
server {
            server_name <ドメイン名>;

                location / {
                                #root /home/ubuntu;
                                #index index.html;
                                include uwsgi_params;
                                uwsgi_pass unix:///tmp/uwsgi.sock;
                           }


    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/xrights.net/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/xrights.net/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = xrights.net) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


            server_name xrights.net;
    listen 80;
    return 404; # managed by Certbot
```
 ポイントは👇の部分です。
 ```
                                #root /home/ubuntu;
                                #index index.html;
                                include uwsgi_params;
                                uwsgi_pass unix:///tmp/uwsgi.sock;
```
上のように準備ができたら、構文エラーをチェック後
nginxをリスタートします。

```
sudo nginx -t                     #nginx 構文チェック
sudo systemctl restart nginx      #nginx リスタート
```

次に、/etc/nginx/sites-enabled/defualtの設定を無効化するために
以下のコマンドでエディターでnginx.confを読み込み
sudo vi /etc/nginx/nginx.conf
#を先頭につけて、一行コメントアウトします。
```
#include /etc/nginx/sites-enabled/*;
```

### Flaskのインストール

次に以下のようにFlaskの環境をセットアップします。
```
sudo apt-get update
sudo apt install python3.10-venv
python3 -m venv api
```
python3 -m venv apiは、これ以降のインストールの内容がapiに集約され
様々なライブラリーやプログラムがコンフリクトしないようにするための
設定です。
```
source venv/bin/api
```
これによって、プロンプトの前に(api)が付き、これ以降のインストールが
apiフォルダにの中に集約されます。
 先ず、flaskをインストールします。

```
pip3 install flask jsonify
```
次に、vi app.pyで起動して以下のファイルを作成します。
```
from flask import Flask, jsonify
app = Flask(__name__)

@app.route('/hello')
def hello_world():
        return jsonify({'message': 'Hello world!'})
```
flask run で起動します。以下が起動画面です。
```
(api) ubuntu@ip-172-31-46-53:~$ flask run
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
```
ctrl+G -> bg でバックグランドで起動し
```
curl http://localhost:5000/hello

127.0.0.1 - - [23/Feb/2024 06:18:03] "GET /hello HTTP/1.1" 200 -
{"message":"Hello world!"}

```
で、動作確認できます。

### uWSGIのインストール

 uWSGIは、https->nginx-><uWSGI> ->flask
 と、https経由でflaskとGPTsをパイプする仕組みです。
 以下のようにインストールします。
```
sudo apt install build-essential
pip3 install wheel
pip3 install uwsgi
```
うまくインストールされていると、

 curl https://<ドメイン名>/
```
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>
(api) ubuntu@ip-172-31-46-53:~$ curl https://xrights.net/
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>
```
と出力されます。要するに、"Bad Gateway"、つまりパイプがうまくいってい
ないということですが、これはその役目を果たすuwsgiが起動していないからです。

そこで、uwsgiを起動してAPIサーバーを動作させます。以下のコマンドです。

```
uwsgi --socket=/tmp/uwsgi.sock --wsgi-file=app.py --callable=app --chmod-socket=666
```
ここで、ブラウザのアドレス入力欄にhttps://<ドメイン名>/hello
を入力してリターンすすると、
```
{"message":"Hello world!"}
```
が表示されれば、ここまですべてOKです。


次に、GPTsのActionsのスキーマに、以下をコピペします。
https://mydomain.com のところにご自分のドメイン名に変更します。
このスキーマは、app.pyをActionsGPTに渡して生成することができます。
以下の記事を参考にしてください。
https://qiita.com/quittardis/items/7a5a3679d332050762b2

```
openapi: 3.0.0
info:
  title: Flask Hello World API
  description: A simple API that returns a Hello World message.
  version: 1.0.0
servers:
  - url: https://<ドメイン名>
    description: Local Flask server
paths:
  /hello:
    get:
      operationId: getHelloWorld
      summary: Returns a Hello World message
      responses:
        '200':
          description: A JSON object with a Hello World message
          content:
            application/json:
              schema: 
                type: object
                properties:
                  message:
                    type: text
                    example: 'Hello world!'

```
これでtestボタンを押してみてください。
「API経由でメッセージを受け取って」というインストラクションで
を入れると、
'Hello world!'がもどってきたら無事開通です。