【Azure】Azure OpenAI Chat+社内文書検索デモアプリ（Trial & Error編）

### はじめに

ChatGPTライクなインターフェースを使用して企業の社内文書を検索するアプリケーションが構築できると聞いて実際に試してみました。

デモアプリのソースはGitHubに公開されています。セットアップガイド付き。

https://github.com/Azure-Samples/jp-azureopenai-samples/tree/main/5.internal-document-search

こちらを参考にすれば すぐに Chat+社内文書検索デモアプリが構築できる！
**...と思いきや、いろいろ試行錯誤したので、その内容の備忘録です。**

余談ですが、トライアンドエラーは「Try & Error」ではなく「Trial & Error」みたいですね。

### Azure CloudShellで構築するよ

セットアップガイドにいろいろと事前準備の記載がありますが、すぐに試せる環境が良いのでAzure CloudShellを使います。Azure CloudShellでのセットアップガイド:point_down:もあります。

https://github.com/Azure-Samples/jp-azureopenai-samples/blob/main/5.internal-document-search/deploy_from_azurecloudshell_without_azd.md

さっそく、やってみる！

### 概要

構成はこちら。（[こちら](https://github.com/Azure-Samples/jp-azureopenai-samples/tree/main/5.internal-document-search)より参照。）
* ユーザインタフェースのアプリデプロイにAzure App Services
* ChatのやりとりにAzure OpenAI
* Chatの会話を保存するためにAzure Cosmos DB
* 社内文書を保存するためにAzure Blob Storage
* 社内文書を検索するためにAzure AI Search

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/49dec8b0-b2d4-33c5-98fb-34b1280fb2c6.png)

手順はこちら。（[こちら](https://github.com/Azure-Samples/jp-azureopenai-samples/blob/main/5.internal-document-search/deploy_from_azurecloudshell_without_azd.md)より参照。）

1. 事前準備 - 環境変数設定
1. 事前準備 - 権限確認
1. 事前準備 - コード取得
1. Azureのリソース作成
1. アプリケーションコードのビルドとデプロイ - フロントエンドのビルド
1. アプリケーションコードのビルドとデプロイ - Pythonコードのデプロイ
1. アプリケーションコードのビルドとデプロイ - Create and assign Cosmos DB data actions roles
1. データの投入（data配下のPDFを利用して Search Index を作成） - 環境変数の設定
1. データの投入（data配下のPDFを利用して Search Index を作成） - スクリプト実行用にCosmosDB権限付与
1. データの投入（data配下のPDFを利用して Search Index を作成） - Run prepdocs.py

### Trial & Error

ここからは私がつまずいた点を書いていきます。

#### :x: "The subscription does not have QuotaId/Feature required by SKU 'S0' from kind 'OpenAI' or contains blocked QuotaId/Feature."

```text:エラーメッセージ
{"code": "SpecialFeatureOrQuotaIdRequired", "message": "The subscription does not have QuotaId/Feature required by SKU 'S0' from kind 'OpenAI' or contains blocked QuotaId/Feature."}
```

手順4において、`az deployment sub create --name $ENV:DEP_NAME --location $ENV:LOC --template-file main.bicep --parameters principalId=$ENV:AZURE_PRINCIPAL_ID environmentName=$ENV:AZURE_ENV_NAME location=$ENV:LOC`を実行すると上記のエラーメッセージが出ました。
使用しているサブスクリプションでAzure OpenAIが利用できないようです。

Azure OpenAIの利用申請をします。
Azure OpenAIの作成画面で、赤枠の中のリンクをクリックすると、申請Formに遷移します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/a93e35f2-a35b-a9e9-8a0a-98736e059b2d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/6feec075-ce4d-e4c2-d39a-b3f6d4e7e6b6.png)

必要事項を入力し申請すれば :ok: です。（半日程度で申請が通ります。（2024/6月時点））

#### :x: "Subscription XXX has a quota of 20 for resources of type PublicIpAddress with sku SkuNotSpecified. Subscription currently has 20 resources and the template contains 1 new resources of the this type which exceeds the quota. Please contact support to increase the quota for resource type PublicIpAddress"

```text:エラーメッセージ
{"code": "ResourceCountExceedsLimitDueToTemplate", "message": "Subscription XXX has a quota of 20 for resources of type PublicIpAddress with sku SkuNotSpecified. Subscription currently has 20 resources and the template contains 1 new resources of the this type which exceeds the quota. Please contact support to increase the quota for resource type PublicIpAddress"}
```

手順4において、`az deployment sub create --name $ENV:DEP_NAME --location $ENV:LOC --template-file main.bicep --parameters principalId=$ENV:AZURE_PRINCIPAL_ID environmentName=$ENV:AZURE_ENV_NAME location=$ENV:LOC`を実行すると上記のエラーメッセージが出ました。
使用しているサブスクリプションでパブリックIPアドレスの使用上限に引っかかっているようです。

パブリックIPアドレスの使用上限を緩和します。
サブスクリプションの使用量＋クォータ画面で「プロバイダ：Networking」を選択し、下図の赤枠の :pencil2: アイコンから上限緩和申請します。（すぐに設定されます。）
※下図はすでに上限緩和した後の図です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/08ae1f59-b393-e9bb-db89-f8e57b2ccb28.png)

#### :x: "This operation require 120 new capacity in quota Tokens Per Minute (thousands) - GPT-35-Turbo, which is bigger than the current available capacity 60. The current quota usage is 240 and the quota limit is 300 for quota Tokens Per Minute (thousands) - GPT-35-Turbo."

```text:エラーメッセージ
{"code": "InsufficientQuota", "message": "This operation require 120 new capacity in quota Tokens Per Minute (thousands) - GPT-35-Turbo, which is bigger than the current available capacity 60. The current quota usage is 240 and the quota limit is 300 for quota Tokens Per Minute (thousands) - GPT-35-Turbo."}
```

手順4において、`az deployment sub create --name $ENV:DEP_NAME --location $ENV:LOC --template-file main.bicep --parameters principalId=$ENV:AZURE_PRINCIPAL_ID environmentName=$ENV:AZURE_ENV_NAME location=$ENV:LOC`を実行すると上記のエラーメッセージが出ました。
使用しているサブスクリプションでAzure OpenAIの利用上限に引っかかっているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/18a6a28d-9eff-80c3-32bb-598b319d8d15.png)

このときは、一度最初から作り直すため、リソースグループから削除してもう一度やり直していました。
リソースグループからすべてのリソースを削除したはずなのに、、、。

原因は、Azure OpenAIのリソースがまだ完全に削除されていなかったためでした。
Azure OpenAIは一時的に「削除されたリソースの管理」に移動します。
すぐに削除する場合は、「削除されたリソースの管理」で削除します。
Document intelligenceも同様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/4d6f532d-6136-d504-d317-ef9c809ccc57.png)

#### :x: "SKU 'standard' cannot be provisioned in 'XXX' at this time RequestId: XXX"

```text:エラーメッセージ
{"code": "DeploymentFailed", "target": "/subscriptions/XXX/resourceGroups/XXX/providers/Microsoft.Resources/deployments/search-searvice"}
{"code": "StampNotFound", "message": "SKU 'standard' cannot be provisioned in 'XXX' at this time RequestId: XXX"}
```

手順4において、`az deployment sub create --name $ENV:DEP_NAME --location $ENV:LOC --template-file main.bicep --parameters principalId=$ENV:AZURE_PRINCIPAL_ID environmentName=$ENV:AZURE_ENV_NAME location=$ENV:LOC`を実行すると上記のエラーメッセージが出ました。
Search Services（AI Search）でSKU 'standard' のデプロイに失敗しているようです。

AzureのコンソールからリソースAI SearchでSKU 'standard' を作成しようとしたところ同じエラーが出ました。
今回はデモアプリのためSKUは 'standard' ではなく 'basic' で対応することにします。
（なぜSKU 'standard' で作成できないのかはわかっていません。）

以下のソースを修正します。
5.internal-document-search/infra/main.bicep

```diff_python
+ param searchServiceSkuName string = 'basic'
- param searchServiceSkuName string = 'standard'
```

#### :x: The subscription is not registered to use namespace 'Microsoft.AlertsManagement'.

```text:エラーメッセージ
The subscription is not registered to use namespace 'Microsoft.AlertsManagement'. See XXX for how to register subscriptions. (コード: MissingSubscriptionRegisteration、ターゲット: Microsoft.AlertsManagement)
```

手順4において、`az deployment sub create --name $ENV:DEP_NAME --location $ENV:LOC --template-file main.bicep --parameters principalId=$ENV:AZURE_PRINCIPAL_ID environmentName=$ENV:AZURE_ENV_NAME location=$ENV:LOC`を実行後、デプロイログを確認すると上記のエラーメッセージが出ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/43efa4da-c6c5-f1f3-fa0f-f51af5631805.png)

使用しているサブスクリプションでAlerts Managementが登録されていないようです。

Alerts Managementを登録します。
リソースプロバイダーの画面でAlerts Managementを選択し、登録します。
※下図はすでに登録した後の図です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/ef3c5845-5fe6-3a36-b7e5-3b4b9d8e23d8.png)

その後、失敗したデプロイだけ再実行すれば :ok: です。

#### :x: Container XXX didn't respod to HTTP pings on port: 8000, failing site start.

```text:エラーメッセージ
Deployment failed because the site failed to start within 10mins.
Error: Deployment for site 'XXX' with DeploymentId 'XXX' failed because the worker process failed to start within the allotted time.
Please check the runtime logs for more info : https://[App Servicesの名前].scm.azurewebsites.net/api/logs/docker
```

手順6において、`az webapp deployment source config-zip --resource-group $ENV:AZURE_WEBAPP_RESOURCE_GROUP --name $ENV:WEBAPP_NAME --src ./app.zip`を実行すると上記のエラーメッセージが出ました。
10分以内にデプロイができずに失敗しているようです。
なんでや、、、。

ログのURLにアクセスすると詳細のログが確認できます。

```text:エラーメッセージ
ERROR - Container XXX didn't respond to HTTP pings on port: 8000, failing site start.
```

どうやらポート8000をリッスンしていないようです。
Webアプリの環境変数画面で環境変数を追加します。
「WEBSITE_PORT」「8000」を追加して、「適用」をクリック。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/6f2f0854-ffe1-d854-19ce-bb93b1fbbec0.png)

#### :x: (Forbidden) Request blocked by Auth XXX : Request is blocked because principal [XXX] does not have required RBAC permissions to perform action [Microsoft.DocumentDB/databaseAccounts.readMetadata] on resource [/].

```text:エラーメッセージ
Deployment failed because the site failed to start within 10mins.
Error: Deployment for site 'XXX' with DeploymentId 'XXX' failed because the worker process failed to start within the allotted time.
Please check the runtime logs for more info : https://[App Servicesの名前].scm.azurewebsites.net/api/logs/docker
```

手順6において、`az webapp deployment source config-zip --resource-group $ENV:AZURE_WEBAPP_RESOURCE_GROUP --name $ENV:WEBAPP_NAME --src ./app.zip`を実行すると上記のエラーメッセージが出ました。
10分以内にデプロイができずに失敗しているようです。
なんでや、、、。（2回目）

ログのURLにアクセスすると詳細のログが確認できます。

```text:エラーメッセージ
azure.cosmos.exceptions.CosmosHttpResponseError: (Forbidden) Request blocked by Auth XXX : Request is blocked because principal [XXX] does not have required RBAC permissions to perform action [Microsoft.DocumentDB/databaseAccounts.readMetadata] on resource [/].
```

どうやらAzure Cosmos DBへのアクセスに失敗しているようです。
Azure Cosmos DBへのアクセス設定は、次の手順（手順7）のようですが、先に実施した方が良さそうです。
手順7の後、手順6を実施します。

### Chatできるよ

手順7まで実施してApp ServiceのURL（https://[App Servicesの名前].azurewebsites.net）にアクセスすると、無事Chatができるデモアプリが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/d6b8fb50-9109-4ecd-7276-3eccd522c3ce.png)

まずはここまで。手順8以降でつまずきがあったら、後日追記します。

**[2024/06/14 追記]**

#### :x: ManagedIdentityCredential.get_token failed: (invalid_request) Timeout waiting for token from portal.

```text:エラーメッセージ
Code: invalid_request
Message: Timeout waiting for token from portal. Audience: https://storage.azure.com/
ManagedIdentityCredential.get_token failed: (invalid_request) Timeout waiting for token from portal. Audience: https://storage.azure.com/
```

手順10において、`Start-Process -FilePath $venvPythonPath -ArgumentList "./scripts/prepdocs.py $cwd/data/* --storageaccount $env:AZURE_STORAGE_ACCOUNT --container $env:AZURE_STORAGE_CONTAINER --searchservice $env:AZURE_SEARCH_SERVICE --searchkey $env:AZURE_SEARCH_KEY --index $env:AZURE_SEARCH_INDEX --formrecognizerservice $env:AZURE_FORMRECOGNIZER_SERVICE --formrecognizerkey $env:AZURE_FORMRECOGNIZER_KEY -v --managedidentitycredential" -Wait -NoNewWindow`を実行すると上記のエラーメッセージが出ました。
ストレージへの認証に失敗しているようです。

実行するprepdocs.pyを確認すると、ストレージアカウントのキーを取得していない場合、うまく処理できないようです。
ストレージアカウントのキーを指定します。
変数`$env:AZURE_STORAGE_KEY`にストレージアカウントのキーを指定し、手順10のコマンドを以下に変更。（`--storagekey $env:AZURE_STORAGE_KEY`を追加しています。）
`Start-Process -FilePath $venvPythonPath -ArgumentList "./scripts/prepdocs.py $cwd/data/* --storageaccount $env:AZURE_STORAGE_ACCOUNT --container $env:AZURE_STORAGE_CONTAINER --searchservice $env:AZURE_SEARCH_SERVICE --searchkey $env:AZURE_SEARCH_KEY --index $env:AZURE_SEARCH_INDEX --formrecognizerservice $env:AZURE_FORMRECOGNIZER_SERVICE --formrecognizerkey $env:AZURE_FORMRECOGNIZER_KEY --storagekey $env:AZURE_STORAGE_KEY -v --managedidentitycredential" -Wait -NoNewWindow`

### 社内文書検索できるよ

全ての手順を実施してApp ServiceのURL（https://[App Servicesの名前].azurewebsites.net）にアクセスすると、無事社内文書検索ができるデモアプリが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288944/3494a965-59b6-9bef-a650-5de0b8f89d05.png)

なお、社内文書検索の対象を追加する場合は、
5.internal-document-search/infra/main.bicep/data/直下にファイルを追加して
再度手順10を実行します。

### さいごに
* 手順4において、プライベートな環境に構築するかどうかが選択できます。
プライベートな環境を選択すると、オープンな環境に比べて、プライベートエンドポイントやVMのリソースが追加でデプロイされます。
* つまずくことがなければ1時間くらいで構築できるかも。
* 少しでもみなさんの参考になりますように！