GPT4-Vで構成図からIaCコードを生成させる

# はじめに
GPT-4-Turbo with Visionが楽しくて色々試しています。[以前の記事](https://qiita.com/shyamagu/items/6abf9d511a447a92258c)ではフロー図からプログラムを生成させてみましたが、今回は雑な構成図からIaCコード(今回はARM template)を生成させてみたいと思います。

ARMテンプレートにした理由は特にないです。別にterraformでもbicepでもいいんですけどね。

# 構成図
せっかくなので、手書きです。PCにえいやで書き込んだのでとても文字が汚いですねw
App ServiceにStagingスロットつけて、あとはSQLDBがあるだけの単純構成です。

![handwritten.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/969bd2fc-08af-57b7-0a4c-702200c2d429.png)

# GPT4-V のプロンプト
今回も英語のプロンプトで試しました。
日本語だと「
**あなたはAzureのインフラストラクチャの専門家です。描画されたAzureリソースを作成するためのARMテンプレートを作成してください。
各リソース名には、"shtest-"という文字列に続けて、リソースを表す文字列と、都度ランダムな文字列も含めてください。
例：shtest-appservice-dfiweofsdf**
」
という感じです。

ポイントとしては、リソース名が一意なものを要求されることが多いので、ランダムな文字列をつけくわえさせているところです。

```python
system_prompt = f"""\
You are an Azure infrastructure expert. Please create an ARM template to create the depicted Azure resources. 
Please add the resource-representing string to each resource name followed by "shtest-", and also include a random string of characters each time.
For example: shtest-appservice-dfiweofsdf
"""
```

# 生成されたARMテンプレート

冒頭の画像と、上のプロンプトで以下のARMテンプレートが生成されました。
なお補足として、SQLDBのadministratorLoginとPasswordは適切に変えろという説明文がついてきます。確かに、このままのパスワードだとポリシー次第では強度不足でデプロイエラーになったりしますね。

<font color="red">その上で、1個だけエラーがあります。</font>

```json
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "randomString": {
      "type": "string",
      "metadata": {
        "description": "Random string of characters for unique resource naming"
      }
    }
  },
  "variables": {
    "appServicePlanName": "[concat('shtest-appserviceplan-', parameters('randomString'))]",
    "webAppName": "[concat('shtest-appservice-', parameters('randomString'))]",
    "sqlServerName": "[concat('shtest-sqlserver-', parameters('randomString'))]",
    "sqlDatabaseName": "[concat('shtest-sqldb-', parameters('randomString'))]",
    "location": "Japan East"
  },
  "resources": [
    {
      "type": "Microsoft.Web/serverfarms",
      "apiVersion": "2020-06-01",
      "name": "[variables('appServicePlanName')]",
      "location": "[variables('location')]",
      "sku": {
        "name": "S1",
        "tier": "Standard",
        "size": "S1",
        "family": "S",
        "capacity": 1
      },
      "properties": {
        "name": "[variables('appServicePlanName')]"
      }
    },
    {
      "type": "Microsoft.Web/sites",
      "apiVersion": "2020-06-01",
      "name": "[variables('webAppName')]",
      "location": "[variables('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
      ],
      "properties": {
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
      },
      "resources": [
        {
          "type": "slots",
          "apiVersion": "2020-06-01",
          "name": "staging",
          "dependsOn": [
            "[resourceId('Microsoft.Web/sites', variables('webAppName'))]"
          ],
          "properties": {}
        }
      ]
    },
    {
      "type": "Microsoft.Sql/servers",
      "apiVersion": "2020-02-02-preview",
      "name": "[variables('sqlServerName')]",
      "location": "[variables('location')]",
      "properties": {
        "administratorLogin": "sqladmin",
        "administratorLoginPassword": "ComplexPassword!123"
      },
      "resources": [
        {
          "type": "databases",
          "apiVersion": "2020-02-02-preview",
          "name": "[variables('sqlDatabaseName')]",
          "location": "[variables('location')]",
          "dependsOn": [
            "[resourceId('Microsoft.Sql/servers', variables('sqlServerName'))]"
          ],
          "properties": {
            "collation": "SQL_Latin1_General_CP1_CI_AS",
            "edition": "Standard",
            "maxSizeBytes": "1073741824",
            "requestedServiceObjectiveName": "S0"
          }
        }
      ]
    }
  ],
  "outputs": {
    "appServicePlanName": {
      "type": "string",
      "value": "[variables('appServicePlanName')]"
    },
    "webAppName": {
      "type": "string",
      "value": "[variables('webAppName')]"
    },
    "sqlServerName": {
      "type": "string",
      "value": "[variables('sqlServerName')]"
    },
    "sqlDatabaseName": {
      "type": "string",
      "value": "[variables('sqlDatabaseName')]"
    }
  }
}
```


# デプロイしてみる

上記のjsonからSQLのユーザ名とパスワードは適切に変えたうえで、azコマンドで何も考えずにまずは実行してみます。

```shell
>az group create --location japaneast --name test-aaa
>az deployment group create --resource-group test-aaa --template-file demo_gpt4v_iac_template.json
Please provide string value for 'randomString' (? for help):
```
ランダムな文字列を聞かれるので、適当にいれます。
が、残念ながら以下のエラーが発生します。
<font color="red">
"details":[{"code":"LocationRequired","message":"The location property is required for this definition."}]
</font>

JSONを確認してみると、App Serviceのスロットの定義にlocationがありませんでした。というわけで、以下を追加
```json
・・・
      "resources": [
        {
          "type": "slots",
          "apiVersion": "2020-06-01",
          "name": "staging",
          "location": "[variables('location')]", ※この１行を追加しました。
          "dependsOn": [
            "[resourceId('Microsoft.Web/sites', variables('webAppName'))]"
          ],
          "properties": {}
        }
      ]
・・・
```

再度実行します。
```shell
>az group create --location japaneast --name test-aaa
>az deployment group create --resource-group test-aaa --template-file demo_gpt4v_iac_template.json
Please provide string value for 'randomString' (? for help): 202401183

(省略)

"provisioningState": "Succeeded",
```

成功です。
きちんとリソースできてますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/d1528c8d-8411-51be-4c21-e4158ab78aac.png)

# おわりに
今回は雑な構成図からARMテンプレートを作成させてみました。わずか1個だけ抜けがありましたが、ほぼ正しく動くものができてひとまずは満足です。

もう少し良いものをつくるためには、[プログラムの方](https://qiita.com/shyamagu/items/6abf9d511a447a92258c)ではやっていた、参考となるIaCコードをつけてあげるなんかは内容によってはやったほうが良いかなとは思いました。

けど画像だけでも結構いい感じに動きましたので、terraformだとかbicepにしてみるとか、あるいはもっと複雑な構成がどうなるかを試してみたいですね。複雑な場合は個別のリソース単位でつくるといいかも、なんて思っていたりします。
ちなみに、入力とするのは手書き画像である必要性は別にないので、ちゃんとした絵をかいて入力とした方が精度的にはいいかもです。（ホワイトボードに書いたものを入れたいとかなら手書きですけど）

しかしこれがサクサク動くようになると、ホワイトボードで絵をかいたらリソースができる、なんて世界も見えてきましたね。with Vision、まだまだ使いどころがありそうです。