LambdaからLex インテントのサンプル発話を更新しビルドする

# はじめに
本記事では、AWS Lambda のスクリプトからLex Bot のサンプル発話を更新し、DRAFTバージョンをビルドする処理を実装していきます。

# 環境
- Python：3.12
- Lex：v2


# ゴール
Lambda関数から下記の2点を実行する
- Lex Botのサンプル発話の内容を更新する
- Lex BotのDRAFTバージョンをビルドする

LambdaからLexのビルドについては下記の記事に実装しています。

https://qiita.com/enumura1/items/2e1564871f861f2578ac


# Lex
本記事では下記のように6つのインテントのあるLex Botを想定しています。
```:インテント（初期）
・intent0001（サンプル発話：あああ）
・intent0002（サンプル発話：いいい）
・intent0003（サンプル発話：ううう）
・intent0004（サンプル発話：えええ）
・intent0005（サンプル発話：おおお）
・FallbackIntent
```
これらLex インテント 6つのうち、4つ（intent0001～intent0004）をLambdaで更新しビルドします。
```:インテント（Lambdaで更新後）
・intent0001（サンプル発話：リンゴ）
・intent0002（サンプル発話：バナナ）
・intent0003（サンプル発話：ぶどう）
・intent0004（サンプル発話：えええ）
・intent0005（サンプル発話：もも）
・FallbackIntent
```

# Lambdaの作成

本記事では、`lex_operations.py` と `lambda_function.py` の2つのスクリプトを作成します。


- **lambda_function.py**
環境変数からインテント名を取得し、各インテントに登録されてるサンプル発話の文言を `fruit_utterances` の内容に更新します。
その後Botのビルドを開始し、使用可能な状態（"Built"）になるまで待機します。

https://docs.aws.amazon.com/lexv2/latest/APIReference/API_BuildBotLocale.html#API_BuildBotLocale_ResponseSyntax


```python:lambda_function.py
import boto3
import json
import os
from lex_operations import update_intents, build_bot, get_draft_status, wait_for_build_completion

def lambda_handler(event, context):
    lex_client = boto3.client('lexv2-models')
    bot_id = os.environ['BOT_ID']
    bot_version = 'DRAFT'
    locale_id = os.environ['LOCALE_ID']
    intent_names = [
        os.environ['INTENT_NAME_1'],
        os.environ['INTENT_NAME_2'],
        os.environ['INTENT_NAME_3'],
        os.environ['INTENT_NAME_4']
    ]
    
    # サンプル発話の内容
    fruit_utterances = ["リンゴ", "バナナ", "ぶどう", "もも"]
    
    # ビルド前の DRAFT ステータスを取得
    pre_build_status = get_draft_status(lex_client, bot_id, locale_id)
    print(f"ビルド前の DRAFT ステータス: {json.dumps(pre_build_status)}")
    
    # インテントの更新
    update_intents(lex_client, bot_id, bot_version, locale_id, intent_names, fruit_utterances)
    
    # DRAFT バージョンをビルド
    build_bot(lex_client, bot_id, bot_version, locale_id)
    
    # ビルドの完了を待機
    wait_for_build_completion(lex_client, bot_id, bot_version, locale_id)
    
    # ビルド後の DRAFT ステータスを取得
    post_build_status = get_draft_status(lex_client, bot_id, locale_id)
    print(f"ビルド後の DRAFT ステータス: {json.dumps(post_build_status)}")
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': 'インテントが更新され、ボットのビルドが完了しました',
            'preBuildStatus': pre_build_status,
            'postBuildStatus': post_build_status,
            'buildSuccess': post_build_status['buildStatus']
        }, ensure_ascii=False)
    }
```

- **lex_operations.py**
`lambda_function.py` から呼び出す関数をそれぞれ作成しています。
関数の詳細はコメントアウトの通りです。

```python:lex_operationspy
import time

# サンプル発話を更新
def update_intent_utterances(client, bot_id, bot_version, locale_id, intent_id, intent_name, new_utterance):
    client.update_intent(
        botId=bot_id,
        botVersion=bot_version,
        localeId=locale_id,
        intentId=intent_id,
        intentName=intent_name,
        sampleUtterances=[{'utterance': new_utterance}]
    )

# インテントを更新
def update_intents(client, bot_id, bot_version, locale_id, intent_names, utterances):
    intents = client.list_intents(
        botId=bot_id,
        botVersion=bot_version,
        localeId=locale_id
    )
    intent_summaries = intents['intentSummaries']
    
    for intent_name, utterance in zip(intent_names, utterances):
        intent_id = None
        
        for intent in intent_summaries:
            if intent['intentName'] == intent_name:
                intent_id = intent['intentId']
                break
        
        update_intent_utterances(client, bot_id, bot_version, locale_id, intent_id, intent_name, utterance)

# DRAFT ステータスを取得
def get_draft_status(client, bot_id, locale_id):
    response = client.describe_bot_locale(
        botId=bot_id,
        botVersion='DRAFT',
        localeId=locale_id
    )
    return {
        'buildStatus': response['botLocaleStatus'],
        'lastBuildDateTime': str(response.get('lastBuildSubmittedDateTime'))
    }

# ビルドが完了するまで待機
def wait_for_build_completion(client, bot_id, bot_version, locale_id):
    while True:
        response = client.describe_bot_locale(
            botId=bot_id,
            botVersion=bot_version,
            localeId=locale_id
        )
        print(f"現在のビルドステータス: {response['botLocaleStatus']}")
        
        if response['botLocaleStatus'] in ['Built', 'Failed']:
            break
        
        time.sleep(5)

# Botをビルド
def build_bot(client, bot_id, bot_version, locale_id):
    client.build_bot_locale(
        botId=bot_id,
        botVersion=bot_version,
        localeId=locale_id
    )
```

# 動作確認
Lambdaをテスト実行すると、200のステータスコードでレスポンスが返ってきました。
Lex Botを確認するとインテント（intent0001～intent0004）の中のサンプル発話が 4つともフルーツ名に更新されている事が確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/f960f464-2046-b09e-1986-ba3b43bdec53.png)


- CloudWatch
Lambdaの実行ログをCloudWatchで確認してみると、BotのビルドステータスがBuilding⇒ReadyExpressTesting⇒Builtとなっている事を確認できます。
ビルドが完了し使用可能な状態（Built）になっている事を確認できました。

```:CWログ
・・・
ビルド前の DRAFT ステータス: {"buildStatus": "Built", "lastBuildDateTime": "2024-09-28 11:27:20.595000+00:00"}
現在のビルドステータス: Building
現在のビルドステータス: Building
現在のビルドステータス: Building
現在のビルドステータス: ReadyExpressTesting
現在のビルドステータス: ReadyExpressTesting
現在のビルドステータス: ReadyExpressTesting
現在のビルドステータス: ReadyExpressTesting
現在のビルドステータス: Built
ビルド後の DRAFT ステータス: {"buildStatus": "Built", "lastBuildDateTime": "2024-09-28 11:30:59.952000+00:00"}
・・・
```


# 参考

- https://qiita.com/enumura1/items/2e1564871f861f2578ac

- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lexv2-models/client/describe_intent.html
- https://docs.aws.amazon.com/lexv2/latest/APIReference/API_BuildBotLocale.html#API_BuildBotLocale_ResponseSyntax
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lexv2-models/client/describe_bot_locale.html