ChatGPT API使用しすぎ防止のため、API利用料金をCloudWatchで監視してみた。

# はじめに
こんちには、山田です。
今回はChatGPT APIの料金をCloudWatchで監視する方法について記載していきます。
よろしくお願いします。

# 構成図
今回は、前回下記の記事で記載したコードも使用するのでリンクを貼っておきます。

https://qiita.com/kuromame1020611/items/4fca48683908956320c3

構成図は以下の通りです。
![空白の図 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/ed8fb1c0-1a6f-4735-60d2-8faf47c392d2.png)
ChatGPT APIのレスポンスより得られた、トークン数をCloudWatch連携用のLambdaに連携し、CloudWatch連携用のLambdaよりCloudWatchにデータを引き渡すようにしています。また、1日の終わりにChatGPT APIの利用料金通知用のLambdaも作成していきます。

# Lambda
以下の記事に記載しているコードに、以下を追加します。

https://qiita.com/kuromame1020611/items/4fca48683908956320c3

```python
    #トークン数取得
    total_tokens = response_data['usage']['total_tokens']
    
    #辞書型に変換
    data_to_pass = {
    'total_tokens': total_tokens
    }
        
    #CloudWatch連携用のLambdaにトークン数を渡す
    response = lambda_client.invoke(
    FunctionName='CloudWatch連携用のLambda名記載',
    InvocationType='RequestResponse',
    Payload=json.dumps(data_to_pass)
    )
```
処理内容は以下の通りです。

**①：トークン数の取得**
total_tokens = response_data['usage']['total_tokens'] で、response_data という辞書から、'usage' キーの辞書にある 'total_tokens' の値を取得しています。

**②：辞書型に変換**
data_to_pass = {'total_tokens': total_tokens} で、新しい辞書 data_to_pass を作成し、取得した total_tokens の値を 'total_tokens' キーに関連付けています。これにより、次の Lambda 関数にデータを渡す準備が整います。

**③：CloudWatch 連携用の Lambda にトークン数を渡す**
lambda_client.invoke() 関数を使用して、'test-yamada4' という名前の別の Lambda 関数を呼び出し、Payload パラメータとして JSON 形式の文字列に変換された data_to_pass を渡しています。この例では、呼び出しタイプとして RequestResponse を使用しているため、呼び出し元は同期的に関数の結果を受け取ります。これは、関数が完了するまで呼び出し元が待機し、関数の実行結果やステータスコードを受け取ることを意味します。

CloudWatch連携用のLAmbdaのコードは以下の通りです。
```python 
import os
import boto3
from datetime import datetime

cloudwatch = boto3.client('cloudwatch')

def lambda_handler(event, context):
    total_tokens = event['total_tokens']  # 受信したトークン数
    namespace = "test"

    cloudwatch.put_metric_data(
        Namespace=Test,
        MetricData=[
            {
                'MetricName': 'Tokens',
                'Value': total_tokens,
                'Unit': 'Count',
                'Timestamp': datetime.utcnow()
            },
        ]
    )
```
処理内容は以下の通りです。

**①：CloudWatch クライアントの作成**
boto3.client('cloudwatch') を使用して、CloudWatch クライアントを作成しています。これにより、CloudWatch API にアクセスできます。

**②：lambda_handler 関数の定義**
AWS Lambda 関数のエントリポイントとなる lambda_handler 関数を定義しています。この関数は、event と context という 2 つの引数を受け取ります。event は、呼び出し元からの入力データを含む辞書です。

**③：トークン数の取得**
total_tokens = event['total_tokens'] で、event 辞書から受信したトークン数を取得しています。

**④：CloudWatch のネームスペースを定義**
namespace = "test" で、CloudWatch のネームスペースを定義しています。ネームスペースは、CloudWatch のメトリクスをグループ化する方法で、関連するメトリクスを簡単に見つけられるようにするために使用されます。

**⑤：CloudWatch にメトリクスデータを送信**
cloudwatch.put_metric_data() 関数を使用して、トークン数を CloudWatch に送信しています。この関数には、ネームスペース、メトリクス名（'Tokens'）、値（total_tokens）、単位（'Count'）、タイムスタンプ（datetime.utcnow()）を指定しています。

1日のChatGPT API 利用料金連携用 Lambdaのコードは以下の通りです。
```python
import os
import boto3
from datetime import datetime, timedelta

cloudwatch = boto3.client('cloudwatch')
sns = boto3.client('sns')

def lambda_handler(event, context):
    end_time = datetime.utcnow()
    start_time = end_time - timedelta(days=1)
    namespace = "test"
    metric_name = "Tokens"
    sns_topic_arn = 'arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:test-yamada2'

    response = cloudwatch.get_metric_data(
        MetricDataQueries=[
            {
                'Id': 'metric1',
                'MetricStat': {
                    'Metric': {
                        'Namespace': namespace,
                        'MetricName': metric_name,
                    },
                    'Period': 86400,
                    'Stat': 'Sum',
                },
                'ReturnData': True,
            },
        ],
        StartTime=start_time,
        EndTime=end_time
    )

    metric_value = response['MetricDataResults'][0]['Values'][0]
    price = (metric_value/1000)*0.002
    message = f"Daily metric data for Price : {price}$"

    sns.publish(
        TopicArn=sns_topic_arn,
        Subject='Daily CloudWatch Metrics Report',
        Message=message
    )

```
処理内容は以下の通りです。
①：boto3ライブラリを使って、CloudWatchとSNSクライアントを作成します。
②：lambda_handler関数が、このLambda関数のエントリーポイントです。
③：関数内では、まず、UTCの現在時刻を取得し、1日前の時刻を計算します。
④：次に、CloudWatchメトリックの名前空間(namespace)とメトリック名(metric_name)を定義します。この例では、名前空間は "test"、メトリック名は "Tokens" です。
⑤：SNSトピックのARNを定義します。これにより、後でSNS通知を送信できます。
⑥：cloudwatch.get_metric_data()を使って、メトリックデータを取得します。この例では、直近1日間のメトリックデータを取得し、合計値を計算しています。
⑦：レスポンスからメトリックの値を取得し、トークンの数に基づいて価格を計算します。価格は、メトリックの値を1000で割り、0.002を掛けたものです。
⑧：価格に関するメッセージを作成し、sns.publish()を使ってSNSトピックに通知を送信します。通知には、件名とメッセージが含まれます。

# CloudWAtchアラーム作成
メトリクスが連携できていることが確認できたら、アラームを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/fe7237e9-8842-1bbe-4975-7bffca65db2c.png)
ChatGPTの料金は以下になります。

**コスト = (トークン数 / 1,000) * 0.002**

そのためアラーム作成時に数式を追加し、料金を監視できるようにします。
``カスタムメトリクスの場合、期間としては最大で1日``になるので、今回は期間1日でChatGPT APIの利用料金が100$以上になったら、通知が来るように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/163febe7-c338-e893-6e5d-eb43708f7308.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/3c19841d-ae40-dc67-364d-fd6599648485.png)
# EventBridge
毎日23時59分に1日のChatGPT API 利用料金連携用 Lambdaが実行されるように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/f0cb10ce-f921-3aff-4fb3-516d580b7bea.png)
# 通知メール確認
以下のように、１日の終わりにChatGPT API 利用料金通知メールが来ていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/06e34fc4-9b82-fb6a-d427-4e59f4dc3078.png)
# 終わりに
今回は、ChatGPT API利用料金を監視できるように設定しました。
まだ、1日ごとの料金のみを監視しているので次は1か月の料金を監視できるように設定を変更していこうと思います。
拝見いただきありがとうございます。