Azure OpenAI で万単位の課金が発生しないように

# 背景

会社において、月の請求で三桁万円の高額請求が発生した。

理由はと言えば、provisioned model を意識せずにデプロイしていた、という話。

本来であれば、コスト管理で予算設定しておいてアラーム通知で即検知、の流れなんですが・・
コスト管理権限が制約されていた為に、気付けなかった、という :sweat: 

ということで、サブスクリプションのデプロイ状況を調べる script を用意してみた記録

## サブスクリプションで制限してしまう場合は、policy で

```json:  Provisioned のデプロイを制限する方法
{
    "policyRule": {
        "if": {
            "allOf": [
                {
                    "field": "type",
                    "equals": "Microsoft.CognitiveServices/accounts/deployments"
                },
                {
                    "field": "Microsoft.CognitiveServices/accounts/deployments/sku.name",
                    "equals": "ProvisionedManaged"
                }
            ]
        },
        "then": {
            "effect": "Deny"
        }
    }
}
```

https://jpaiblog.github.io/blog/2024/08/29/AzureOpenAIService-Provisioned-Deployment/

# 出力イメージ

この sku 部分を確認すればよい筈

| サブスクリプション名 | サブスクリプション ID | リソースグループ名 | リソース名 | モデル名 | SKU | バージョン | デプロイメント名 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Visual Studio Professional サブスクリプション | {subscription ID} | {resource group} | {resource name} | gpt-4o | GlobalStandard | 2024-05-13 | gpt-4o-deployment |

# script 

- $output_file に指定したファイルに、markdown の表として出力
- 自分のアクセスできるサブスクリプション内の、OpenAI リソースにおける、デプロイモデルを一覧化

```powershell: OpenAI model check
# 初期設定
$output_file = "result_DeploymentModels.md"
$defaultSubscription = az account show --query "id" -o tsv

# 出力ファイルの存在をチェックし、存在しない場合は新規作成
if (-Not (Test-Path -Path $output_file)) {
    New-Item -Path $output_file -ItemType File
} else {
    Set-Content -Path $output_file -Value $null
}

# ヘッダー行を追加
Add-Content -Path $output_file -Value "| サブスクリプション名 | サブスクリプション ID | リソースグループ名 | リソース名 | モデル名 | SKU | バージョン | デプロイメント名 |"
Add-Content -Path $output_file -Value "| --- | --- | --- | --- | --- | --- | --- | --- |"

# 現在のAzureサブスクリプションリストを取得
$subscriptions = az account list --query "[].{Name:name, Id:id}" -o tsv

foreach ($subscription in $subscriptions) {
    $subscriptionDetails = $subscription -split "`t"
    $subscriptionName = $subscriptionDetails[0]
    $subscriptionId = $subscriptionDetails[1]

    try {
        # サブスクリプションを設定
        az account set --subscription $subscriptionId

        Write-Host "サブスクリプションを確認中: $subscriptionName ($subscriptionId)"

        # OpenAIサービスリソースのリストを取得
        $resources = az cognitiveservices account list --query "[?kind=='OpenAI'].{Name:name, ResourceGroup:resourceGroup}" -o tsv

        if (-not $resources) {
            Write-Host "対象のサブスクリプションにOpenAIリソースがありません: $subscriptionId"
            Add-Content -Path $output_file -Value "| $subscriptionName | $subscriptionId | - | OpenAI リソースがありません | - | - | - | - |"
        } else {
            foreach ($resource in $resources) {
                $resourceDetails = $resource -split "`t"
                $resourceName = $resourceDetails[0]
                $resourceGroup = $resourceDetails[1]

                Write-Host "リソースを確認中: $resourceName (リソースグループ: $resourceGroup)"

                try {
                    # リソースのデプロイメント情報を取得
                    $deployments = az cognitiveservices account deployment list --name $resourceName --resource-group $resourceGroup --output json

                    $deployments = $deployments | ConvertFrom-Json

                    if (-not $deployments) {
                        Add-Content -Path $output_file -Value "| $subscriptionName | $subscriptionId | $resourceGroup | $resourceName | デプロイ無し | - | - | - |"
                    } else {
                        foreach ($deployment in $deployments) {
                            $deploymentName = $deployment.name
                            $modelName = $deployment.properties.model.name
                            $modelVersion = $deployment.properties.model.version
                            $skuName = $deployment.sku.name

                            Add-Content -Path $output_file -Value "| $subscriptionName | $subscriptionId | $resourceGroup | $resourceName | $modelName | $skuName | $modelVersion | $deploymentName |"
                        }
                    }
                } catch {
                    $errorMsg = "デプロイメント情報の取得中にエラーが発生しました。"
                    Write-Host "$errorMsg リソース: $resourceName (リソースグループ: $resourceGroup)"
                    Add-Content -Path $output_file -Value "| $subscriptionName | $subscriptionId | $resourceGroup | $resourceName | デプロイメント情報取得エラー | - | - | - |"
                }
            }
        }
    } catch {
        $errorMsg = "サブスクリプションの設定中にエラーが発生しました。"
        Write-Host "$errorMsg サブスクリプション: $subscriptionId"
        Add-Content -Path $output_file -Value "| $subscriptionName | $subscriptionId | - | - | エラー | - | - | - |"
    }
}

az account set --subscription $defaultSubscription
Write-Host "デプロイ済みモデル情報が $output_file に保存されました。"

```

# あとがき

まぁ、コストはオープンにしないと、コスト意識なんて育たないよねっていう良い例かなと思う。

そういう意味で、給料も経費も全部見える化してしまえば、
給料泥棒なんて減るんじゃないの？って思ってる。

とはいえ、年を重ねて楽をして稼ぎたいっていう上位層が多いんだろうなぁ・・とも