Amazon SESとAmazon Bedrockで問い合わせメールの一次対応を自動化する

「メールでのやり取りなんてレガシーだよね」? いえいえ、生成AIを使えばまだまだ可能性が見えてきます。
2023.11.01

みなさん、こんにちは!
福岡オフィスの青柳です。

みなさん、生成AIを使って業務改善してますか? (挨拶)

今回は、「改善したい業務」の上位にランクインしているであろう (俺調べ) 「問い合わせメール対応」を、生成AIを使って自動化・効率化してみたいと思います。

やりたいこと

「問い合わせメール」の対応窓口が抱える課題

利用者から問い合わせを受けた際、なるべく早く「あなたの問い合わせを認識していますよ」という点を利用者に知らせてあげると、利用者は安心しますし、問い合わせ窓口に対する信頼感も向上すると思います。

そのため、問い合わせ窓口では「できるだけ早く一次応答を返す」ことを目標にしている場合も多いかと思います。 しかし、多忙なサポート担当者にとっては様々な対応に追われて、どうしても一次応答を返すのが遅れがちになるのではないでしょうか。

定型文の「自動応答メール」を機械的に返すという方法もありますが、単に「お問い合わせを受け付けました」だけでは利用者も「機械的だな」と感じますし、早く回答が欲しいのにとヤキモキしてしまうでしょう。

生成AIを使った「問い合わせメール」の一次対応自動化

そこで、問い合わせ内容を「生成AI」が判断して、画一的ではなく問い合わせ内容に応じて適切な「一次応答メール」を返すようにします。

もちろん、問い合わせメール対応の全てを完全に自動化することは不可能です。 それでも、全ての問い合わせメールに対して適切かつスピーディーに一次応答をしておけば、その後はサポート担当者の手が空き次第、順次、問い合わせ内容に対して返信することができます。 これにより、利用者の「待たされた」感が軽減し、満足度が向上するのではないかと思います。

システム構成

今回の仕組みは、メールの受信および送信に「Amazon SES」、問い合わせ内容の分類や応答文の生成に「Amazon Bedrock」を利用します。

なお、各AWSリソースは原則として「東京リージョン」に作成します。 ただし、Amazon Bedrockについては、当記事執筆時点 (2023.11.01) では利用できるLLMモデルが限定されていることから、より多くのLLMモデルが利用できる「北部バージニアリージョン」を使用しています。

環境の構築

「Amazon SES」の設定

SESを使ってメールの受信および送信が行えるようにするには、いくつかのステップを実施する必要があります。

まずは、以下の作業を行います。

  • SESで使用するドメインを用意する (Route 53または他のレジストラを利用)
  • Route 53ホストゾーンを作成する
  • SESでドメインを検証する (IDタイプ「ドメイン」を選択して「検証済みID」を作成する)

具体的な手順は、下記のブログ記事を参考にしてください。 (この記事では無料のドメインレジストラを利用していますが、もちろんAWSのRoute 53を使っても構いません)

続いて、メール受信に必要な設定を行います。

  • ドメインに「MXレコード」を作成する
  • 受信メールの保存先となる「S3バケット」を作成して、必要なバケットポリシーを設定する
  • SESの「受信ルールセット」と「受信ルール」を作成する

具体的な手順は、下記のブログ記事を参考にしてください。

最後に、メール送信に必要な設定を行います。
メールが送信可能な条件として、以下のいずれかの状態にします。

  • SESのサンドボックスを解除する
  • SESがサンドボックス内にある場合は、メール送信先となるメールアドレスを「検証済みID」として登録しておく

今回、私は、サンドボックス内で送信先メールアドレスを「検証済みID」に登録することで、作業を行いました。

全ての設定が終わりましたら、念のために「SESでメールが受信できること」「SESからメールが送信できること」のテストを行なっておくと良いでしょう。

「Amazon Bedrock」の設定

Bedrockは、利用する前にマネジメントコンソールから「モデルの有効化」の操作を行なっておく必要があります。

「システム構成」のところで説明しました通り、今回は「北部バージニアリージョン」でBedrockを利用します。 利用するモデルは「Anthropic」の「Claude」ですので、こちらのモデルを有効化しておいてください。 (Anthropic Claudeは有効化の際にAWSへの「リクエスト」が必要となっています)

モデル有効化の具体的な手順については、下記ブログ記事を参考にしてください。

問い合わせ履歴を格納する「DynamoDBテーブル」を作成

マネジメントコンソールから、DynamoDBのテーブルを新規作成します。 作成する際に指定するのは、以下の項目のみです。

  • テーブル名: (任意の名前で構いません)
  • パーティションキー:
    • キー名: 「InquiryID」
    • キーのタイプ: 文字列 (String)
  • ソートキー:
    • (設定しません)

上記の他は全てデフォルト設定で構いません。

「Lambda関数」の作成

Lambda関数を作成します。

ランタイムは「Python 3.11」を選択します。 その他の設定は任意で構いません。

Lambdaレイヤーの設定

当記事の執筆時点 (2023/11/01) では、Lambda関数の標準の状態ではBedrockを利用することができません。 何故なら、Lambda関数に標準で組み込まれているBoto3のバージョンが、Bedrockに対応していないためです。

  • Lambda関数に標準で組み込まれているBoto3のバージョン: 1.27.01
  • Bedrockを利用可能なBoto3のバージョン: 1.28.57以降

Lambda関数でBedrockを使えるようにするために「Lambdaレイヤー」の設定を行います。

具体的な手順については、下記ブログ記事の「(参考情報) Boto3のバージョンが古い場合の対応方法」の章を参考にしてください。

IAMロールの設定

作成したLambda関数の「実行ロール」(IAMロール) に対して、Lambdaから各リソースに対するアクセス権限を設定します。

具体的には、以下のリソースに対して、それぞれ記載した権限が必要です。

  • S3: バケットに格納されたオブジェクトの読み取り
  • DynamoDB: テーブルへの書き込み (新規アイテムの作成)
  • SES: SESを使ったメール送信
  • Bedrock: LLMモデルを利用するためのAPI呼び出し

IAMポリシーまたはインラインポリシーを使って、下記の許可ポリシーを設定してください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "dynamodb:PutItem",
                "ses:SendEmail",
                "bedrock:InvokeModel"
            ],
            "Resource": "*"
        }
    ]
}

その他の設定

Lambda関数の「設定」-「一般設定」にある「タイムアウト」の時間設定を、デフォルトの「3秒」から「1分」程度に増やしておきます。 (BedrockのLLMが処理を行うのに数十秒オーダーの時間を要するため)

また、「環境変数」を以下のように設定しておきます。

  • SENDER_EMAIL_ADDRESS: 送信元のメールアドレス
  • DYNAMODB_TABLE_NAME: DynamoDBテーブル名

Lambda関数のコードを記述

今回、記述したLambda関数のコードは以下のようになりました。

Lambda関数のコード全体 (クリックすると展開します)

lambda_function.py

import boto3
import json
import os
import urllib.parse
import email
import email.policy
import datetime
import uuid

sender_email_address = os.environ['SENDER_EMAIL_ADDRESS']
dynamodb_table_name  = os.environ['DYNAMODB_TABLE_NAME']

s3              = boto3.client('s3')
dynamodb        = boto3.client('dynamodb')
ses             = boto3.client('sesv2')
bedrock_runtime = boto3.client('bedrock-runtime', region_name='us-east-1')


######################################################################
# Lambdaハンドラーメソッド
######################################################################
def lambda_handler(event, context):
    # for debug
    print(f'boto3 version: {boto3.__version__}')
    print(json.dumps(event))

    # イベントデータからS3バケット名とオブジェクトキーを取得する
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    object_key_name = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    print(f'bucket_name: {bucket_name}')
    print(f'object_key_name: {object_key_name}')

    # S3に保存されたメールを読み込む
    response = get_email(bucket_name, object_key_name)
    email_date = response['Date']
    email_from = response['From']
    email_body = response['Body']
    print(f'email_date: {email_date}')
    print(f'email_from: {email_from}')
    print(f'email_body: {email_body}')

    # メール文面から本文のみを抽出する
    main_text = extract_main_text(email_body)
    print(f'main_text: {main_text}')

    # 問い合わせ内容から問い合わせカテゴリを分類する
    inquiry_categiry = categorize_inquiry(main_text)
    print(f'inquiry_categiry: {inquiry_categiry}')

    # カテゴリに応じて処理を分岐
    if '質問' in inquiry_categiry:
        # 「質問」の場合のみ、質問に対する回答を生成して、回答から返信文を作成する
        answer_text = answer_inquiry(main_text)
        response_text = \
            'このたびはお問い合わせ頂き、ありがとうございます。\r\nお問い合わせ頂きました件について以下の通り回答いたします。' + \
            '\r\n\r\n' + answer_text + '\r\n\r\n回答は以上でございます。'
    elif '要望' in inquiry_categiry:
        # 「質問」以外の場合、定形の返信文を返す (以下同様)
        answer_text = ''
        response_text = 'このたびは貴重なご意見を頂き、誠にありがとうございます。\r\nお客様から頂いたご意見は、弊社サービスの改善に役立たせて頂きます。'
    elif '苦情' in inquiry_categiry:
        answer_text = ''
        response_text = 'このたびはご不快な思いをさせてしまい大変申し訳ございませんでした。\r\nお客様のご指摘を真摯に受け止め、今後の再発防止に努めて参ります。'
    else:
        answer_text = ''
        response_text = 'このたびはお問い合わせ頂き、ありがとうございました。'

    print(f'answer_text: {answer_text}')
    print(f'response_text: {response_text}')

    # 返信メールを送信する
    send_email(email_from, response_text)

    # 問い合わせ内容をデータベースへ登録する
    inquiry_id = str(uuid.uuid4())  # 主キーとして一意なIDを生成
    write_database(inquiry_id, email_date, email_from, inquiry_categiry, main_text, answer_text)


######################################################################
# メールを読み込む
######################################################################
def get_email(bucket_name: str, object_key_name: str) -> dict:
    # S3オブジェクトを読み込む
    response = s3.get_object(
        Bucket=bucket_name,
        Key=object_key_name,
    )
    print(response)

    # 読み込んだデータからメールメッセージ全体を取り出す
    object_body = response['Body'].read()
    email_message = email.message_from_bytes(object_body, policy=email.policy.default)

    # メールのヘッダを取り出す
    email_header_date = str(email_message.get('date', ''))
    email_header_from = str(email_message.get('from', ''))
    email_header_subject = str(email_message.get('subject', ''))

    # メールの本文を取り出す
    email_body = email_message.get_body(preferencelist=('plain', 'html'))
    email_body_content = email_body.get_content()

    return {
        'Date'   : email_header_date,
        'From'   : email_header_from,
        'Subject': email_header_subject,
        'Body'   : email_body_content,
    }


######################################################################
# メール文面から本文のみを抽出する
######################################################################
def extract_main_text(input_text: str) -> str:
    # プロンプト
    prompt_text = \
        '以下のメール文面から冒頭の挨拶や署名などを除去して、本文のみを抽出してください。' + \
        '結果のみを発言してください。'

    # Bedrockを使用して回答を生成する
    answer_text = generate_answer(prompt_text, input_text)

    return answer_text


######################################################################
# 問い合わせ内容をカテゴリに分類する
######################################################################
def categorize_inquiry(input_text: str) -> str:
    # プロンプト
    prompt_text = \
        '以下の文章を「質問」「要望」「苦情」のカテゴリのいずれかに分類してください。' + \
        '結果のみを発言してください。' + \
        'いずれのカテゴリにも属さない場合は「その他」と答えてください。'

    # Bedrockを使用して回答を生成する
    answer_text = generate_answer(prompt_text, input_text)

    return answer_text


######################################################################
# 問い合わせの質問に回答する
######################################################################
def answer_inquiry(input_text: str) -> str:
    # プロンプト
    prompt_text = \
        '以下の質問に回答してください。' + \
        '回答内容のみを発言してください。'

    # Bedrockを使用して回答を生成する
    answer_text = generate_answer(prompt_text, input_text)

    return answer_text


######################################################################
# Bedrockを使用して回答を生成する (Anthropic Claude 2)
######################################################################
def generate_answer(prompt_text :str, inquiry_text: str) -> str:
    # プロンプトの指定
    prompt = f'\n\nHuman: {prompt_text}\n---\n{inquiry_text}\n\nAssistant:'

    # 各種パラメーターの指定
    model_id = 'anthropic.claude-v2'
    accept = 'application/json'
    contentType = 'application/json'

    # リクエストBODYを組み立てる
    body = json.dumps({
        'prompt': prompt,
        'max_tokens_to_sample': 400,
    })

    # Bedrock APIの呼び出し
    response = bedrock_runtime.invoke_model(
    	modelId=model_id,
    	accept=accept,
    	contentType=contentType,
        body=body,
    )
    print(response)

    # APIレスポンスから応答テキストを取り出す
    answer_text = json.loads(response.get('body').read()).get('completion')

    return answer_text


######################################################################
# メールを送信する
######################################################################
def send_email(dst_addr:str, body:str) -> None:
    src_addr = sender_email_address
    subject  = 'お問い合わせ頂きました件につきまして'
    body     = '当メールはサポート窓口から自動送信しております。\r\n\r\n' + body

    # SESを使用してメール送信を行う
    response = ses.send_email(
        FromEmailAddress=src_addr,
        Destination={
            'ToAddresses': [
                dst_addr,
            ],
        },
        Content={
            'Simple': {
                'Subject': {
                    'Data': subject,
                    'Charset': 'utf-8',
                },
                'Body': {
                    'Text': {
                        'Data': body,
                        'Charset': 'utf-8',
                    },
                },
            },
        },
    )
    print(response)


######################################################################
# 問い合わせ内容と回答をDynamoDBへ書き込む
######################################################################
def write_database(inquiry_id: str, date: str, email_address: str, categiry: str, inquiry: str, answer: str) -> None:
    # DynamoDBへアイテムを新規作成する
    response = dynamodb.put_item(
        TableName=dynamodb_table_name,
        Item={
            'InquiryID': {
                'S': inquiry_id,
            },
            'Date': {
                'S': date,
            },
            'EmailAddress': {
                'S': email_address,
            },
            'Category': {
                'S': categiry,
            },
            'Inquiry': {
                'S': inquiry,
            },
            'Answer': {
                'S': answer,
            },
        },
    )
    print(response)

各処理について、一つずつ解説していきます。

コード解説: メイン処理

以下のような処理の流れを行います。

  • S3バケットからのイベント通知 (トリガー) を受けて、イベントデータから必要な情報を取得する
  • S3に保存されたメールを読み込む
  • メール文面に書かれている内容から「問い合わせカテゴリ」を分類する
  • カテゴリに応じて以下の処理を行う
    • カテゴリが「質問」だった場合: Bedrockを使って質問内容に対する回答文を生成して、回答文を基に返信メール文面を作成する
    • カテゴリが「質問」以外の場合: カテゴリに応じた定型の返信メール文面を作成する
  • SESを使ってメールを返信する
  • 問い合わせ内容をDynamoDBテーブルへ登録する

それぞれの処理はサブルーチン (関数) 化していますので、そちらで解説します。

「メイン処理」Lambda関数コード (クリックすると展開します)

lambda_function.py

######################################################################
# Lambdaハンドラーメソッド
######################################################################
def lambda_handler(event, context):
    # for debug
    print(f'boto3 version: {boto3.__version__}')
    print(json.dumps(event))

    # イベントデータからS3バケット名とオブジェクトキーを取得する
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    object_key_name = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    print(f'bucket_name: {bucket_name}')
    print(f'object_key_name: {object_key_name}')

    # S3に保存されたメールを読み込む
    response = get_email(bucket_name, object_key_name)
    email_date = response['Date']
    email_from = response['From']
    email_body = response['Body']
    print(f'email_date: {email_date}')
    print(f'email_from: {email_from}')
    print(f'email_body: {email_body}')

    # メール文面から本文のみを抽出する
    main_text = extract_main_text(email_body)
    print(f'main_text: {main_text}')

    # 問い合わせ内容から問い合わせカテゴリを分類する
    inquiry_categiry = categorize_inquiry(main_text)
    print(f'inquiry_categiry: {inquiry_categiry}')

    # カテゴリに応じて処理を分岐
    if '質問' in inquiry_categiry:
        # 「質問」の場合のみ、質問に対する回答を生成して、回答から返信文を作成する
        answer_text = answer_inquiry(main_text)
        response_text = \
            'このたびはお問い合わせ頂き、ありがとうございます。\r\nお問い合わせ頂きました件について以下の通り回答いたします。' + \
            '\r\n\r\n' + answer_text + '\r\n\r\n回答は以上でございます。'
    elif '要望' in inquiry_categiry:
        # 「質問」以外の場合、定形の返信文を返す (以下同様)
        answer_text = ''
        response_text = 'このたびは貴重なご意見を頂き、誠にありがとうございます。\r\nお客様から頂いたご意見は、弊社サービスの改善に役立たせて頂きます。'
    elif '苦情' in inquiry_categiry:
        answer_text = ''
        response_text = 'このたびはご不快な思いをさせてしまい大変申し訳ございませんでした。\r\nお客様のご指摘を真摯に受け止め、今後の再発防止に努めて参ります。'
    else:
        answer_text = ''
        response_text = 'このたびはお問い合わせ頂き、ありがとうございました。'

    print(f'answer_text: {answer_text}')
    print(f'response_text: {response_text}')

    # 返信メールを送信する
    send_email(email_from, response_text)

    # 問い合わせ内容をデータベースへ登録する
    inquiry_id = str(uuid.uuid4())  # 主キーとして一意なIDを生成
    write_database(inquiry_id, email_date, email_from, inquiry_categiry, main_text, answer_text)

コード解説: メール読み込み処理

SESがS3バケットに保存した受信メール (S3オブジェクト) を読み込んで、「Date:」「From:」などのヘッダー情報や、メール本文を取り出します。

メールデータの処理のために、Pythonの「email」ライブラリを使用しています。

古いバージョンのPythonでは「マルチパートメールの解析」や「BASE64のデコード」などを処理するために個別のメソッドを呼び出す必要がありました。 しかし、現在のバージョンのPythonでは、マルチパートメールから「メール本文」を自動的に識別したり、日本語で書かれたメールを自動的にデコードしてくれます。(楽です)

「email」ライブラリの使い方については、Python公式ドキュメントを参照してください:
email --- 電子メールと MIME 処理のためのパッケージ — Python 3.11.6 ドキュメント

「メール読み込み処理」Lambda関数コード (クリックすると展開します)

lambda_function.py

######################################################################
# メールを読み込む
######################################################################
def get_email(bucket_name: str, object_key_name: str) -> dict:
    # S3オブジェクトを読み込む
    response = s3.get_object(
        Bucket=bucket_name,
        Key=object_key_name,
    )
    print(response)

    # 読み込んだデータからメールメッセージ全体を取り出す
    object_body = response['Body'].read()
    email_message = email.message_from_bytes(object_body, policy=email.policy.default)

    # メールのヘッダを取り出す
    email_header_date = str(email_message.get('date', ''))
    email_header_from = str(email_message.get('from', ''))
    email_header_subject = str(email_message.get('subject', ''))

    # メールの本文を取り出す
    email_body = email_message.get_body(preferencelist=('plain', 'html'))
    email_body_content = email_body.get_content()

    return {
        'Date'   : email_header_date,
        'From'   : email_header_from,
        'Subject': email_header_subject,
        'Body'   : email_body_content,
    }

コード解説: Bedrockを利用した各処理 (本文抽出/カテゴリ分類/回答生成)

Bedrockを使用して以下の処理を行わせます。

  • メール文面から本文のみを抽出する
  • 問い合わせ内容をカテゴリに分類する
  • 問い合わせの質問に回答する

BedrockのAPI呼び出しの部分はコードを共通化してgenerate_answer()という関数にしています。 (この関数の解説は後述します)

3つの処理の内容に応じて、LLMに与える「プロンプト」をそれぞれ定義しています。

例えば、「メール文面から本文を抽出」を行うプロンプトは、以下のようになっています。

Human:
以下のメール文面から冒頭の挨拶や署名などを除去して、本文のみを抽出してください。
結果のみを発言してください。
---
<ここにメール文面が入る>

Assistant:
「Bedrockを利用した各処理 (本文抽出/カテゴリ分類/回答生成)」Lambda関数コード (クリックすると展開します)

lambda_function.py

######################################################################
# メール文面から本文のみを抽出する
######################################################################
def extract_main_text(input_text: str) -> str:
    # プロンプト
    prompt_text = \
        '以下のメール文面から冒頭の挨拶や署名などを除去して、本文のみを抽出してください。' + \
        '結果のみを発言してください。'

    # Bedrockを使用して回答を生成する
    answer_text = generate_answer(prompt_text, input_text)

    return answer_text


######################################################################
# 問い合わせ内容をカテゴリに分類する
######################################################################
def categorize_inquiry(input_text: str) -> str:
    # プロンプト
    prompt_text = \
        '以下の文章を「質問」「要望」「苦情」のカテゴリのいずれかに分類してください。' + \
        '結果のみを発言してください。' + \
        'いずれのカテゴリにも属さない場合は「その他」と答えてください。'

    # Bedrockを使用して回答を生成する
    answer_text = generate_answer(prompt_text, input_text)

    return answer_text


######################################################################
# 問い合わせの質問に回答する
######################################################################
def answer_inquiry(input_text: str) -> str:
    # プロンプト
    prompt_text = \
        '以下の質問に回答してください。' + \
        '回答内容のみを発言してください。'

    # Bedrockを使用して回答を生成する
    answer_text = generate_answer(prompt_text, input_text)

    return answer_text

コード解説: Bedrock呼び出しサブ処理

前の項で説明した「本文抽出」「カテゴリ分類」「回答生成」の各処理から呼び出される共通のサブ処理です。

今回は、どの処理においても「使用するLLMの種類」「与えるパラメーター」を共通にしています。(プログラムコードの簡略化のため)

精度を高めるためには、処理内容に応じて使用するLLMやパラメーターを変えた方が良い場合があるかもしれません。

「Bedrock呼び出しサブ処理」Lambda関数コード (クリックすると展開します)

lambda_function.py

######################################################################
# Bedrockを使用して回答を生成する (Anthropic Claude 2)
######################################################################
def generate_answer(prompt_text :str, inquiry_text: str) -> str:
    # プロンプトの指定
    prompt = f'\n\nHuman: {prompt_text}\n---\n{inquiry_text}\n\nAssistant:'

    # 各種パラメーターの指定
    model_id = 'anthropic.claude-v2'
    accept = 'application/json'
    contentType = 'application/json'

    # リクエストBODYを組み立てる
    body = json.dumps({
        'prompt': prompt,
        'max_tokens_to_sample': 400,
    })

    # Bedrock APIの呼び出し
    response = bedrock_runtime.invoke_model(
    	modelId=model_id,
    	accept=accept,
    	contentType=contentType,
        body=body,
    )
    print(response)

    # APIレスポンスから応答テキストを取り出す
    answer_text = json.loads(response.get('body').read()).get('completion')

    return answer_text

コード解説: メール送信処理

メール受信はS3バケットを介して行いましたが、メール送信はAmazon SESのSDKを直接呼び出して行います。

今回は、「ファイルの添付」や「HTML形式でのメール送信」など凝ったことは行わず、シンプルな処理内容となっています。

「メール送信処理」Lambda関数コード (クリックすると展開します)

lambda_function.py

######################################################################
# メールを送信する
######################################################################
def send_email(dst_addr:str, body:str) -> None:
    src_addr = sender_email_address
    subject  = 'お問い合わせ頂きました件につきまして'
    body     = '当メールはサポート窓口から自動送信しております。\r\n\r\n' + body

    # SESを使用してメール送信を行う
    response = ses.send_email(
        FromEmailAddress=src_addr,
        Destination={
            'ToAddresses': [
                dst_addr,
            ],
        },
        Content={
            'Simple': {
                'Subject': {
                    'Data': subject,
                    'Charset': 'utf-8',
                },
                'Body': {
                    'Text': {
                        'Data': body,
                        'Charset': 'utf-8',
                    },
                },
            },
        },
    )
    print(response)

コード解説: DynamoDBテーブル書き込み処理

最後の処理は、受信したメールの内容や、Bedrockによって処理した結果などを、DynamoDBテーブルに書き込む処理です。

パーティションキーである「InquiryID」は、ランダムかつ一意な文字列として「UUID (バージョン4)」を採用しています。 (UUIDの生成はメイン処理の方で行っています)

今回の仕組みでは、DynamoDBテーブルに対する「新規アイテム登録」(put_item) のみを使用しており、アイテムの更新や削除は行っていません。

「DynamoDBテーブル書き込み処理」Lambda関数コード (クリックすると展開します)

lambda_function.py

######################################################################
# 問い合わせ内容と回答をDynamoDBへ書き込む
######################################################################
def write_database(inquiry_id: str, date: str, email_address: str, categiry: str, inquiry: str, answer: str) -> None:
    # DynamoDBへアイテムを新規作成する
    response = dynamodb.put_item(
        TableName=dynamodb_table_name,
        Item={
            'InquiryID': {
                'S': inquiry_id,
            },
            'Date': {
                'S': date,
            },
            'EmailAddress': {
                'S': email_address,
            },
            'Category': {
                'S': categiry,
            },
            'Inquiry': {
                'S': inquiry,
            },
            'Answer': {
                'S': answer,
            },
        },
    )
    print(response)

「S3バケット」の「イベント通知」設定

設定の最後として、受信メールがS3バケットに格納されたことをトリガーにLambda関数が実行されるように、S3バケットの「イベント通知」を設定します。

S3バケットの「プロパティ」タブから「イベント通知を作成」を選択して、以下のように設定します。

「一般的な設定」は、以下のように設定してください。

  • イベント名: (任意の名前で構いません)
  • プレフィックス: (設定しません)
  • サフィックス: (設定しません)

「イベントタイプ」は、「オブジェクトの作成」の「PUT」のみにチェックを入れます。

「送信先」は、以下のように設定してください。

  • 送信先: 「Lambda関数」を選択
  • Lambda関数を特定: 「Lambda関数から選択する」を選択して、前項で作成したLambda関数を指定

全て入力しましたら、設定を保存します。

これで、全ての設定が完了しました。

動作確認

それでは、実際に「自動化された問い合わせ窓口」宛にメールを送ってみて、動作確認をしましょう。

いくつかのパターンで検証してみました。

「質問」に分類される問い合わせメール

問い合わせメールの内容:

サポート担当者さま

株式会社くらにゃん工業で社内ITインフラを担当している山田と申します。

このたび弊社で本格的にAWSを利用しようと検討しております。
その際、社内のセキュリティポリシーにより、安全にクラウドへの接続を行う必要があります。
社内ネットワークからAWS環境へ安全に接続する方法として、どのような方法がありますでしょうか?
ご教示のほど、よろしくお願いいたします。

---
くらにゃん工業 情報システム部
山田 太郎

システムから自動返信されたメールの内容:

当メールはサポート窓口から自動送信しております。

このたびはお問い合わせ頂き、ありがとうございます。
お問い合わせ頂きました件について以下の通り回答いたします。

AWS Direct ConnectやVPN接続を利用することで、社内ネットワークからAWS環境へ安全に接続することができます。
Direct Connectは専用の物理回線を使ってAWSに直接接続するので、インターネット経由ではない安全な接続が実現できます。
VPN接続はIPsec VPNやAWS Managed VPNなどを使い、暗号化されたVPNトンネルをインターネット経由で構築することで、安全な通信が可能です。
いずれもセキュリティ面で優れているため、社内セキュリティポリシーを満たす接続方法として適していると言えます。

回答は以上でございます。

それっぽい回答が返ってきました。

実際の運用では、問い合わせ内容をさらに深く分析をして、「単純な質疑応答で満足するのか」「追って担当者から詳しい説明の連絡をすべきか」といった点が判別できると、より良いユーザー体験になりそうです。

「要望」に分類される問い合わせメール

問い合わせメールの内容:

弊社では出先で作業を行うことも多く、ヘルプページをスマホからでも見られると助かります。
ご検討頂けないでしょうか。

システムから自動返信されたメールの内容:

当メールはサポート窓口から自動送信しております。

このたびは貴重なご意見を頂き、誠にありがとうございます。
お客様から頂いたご意見は、弊社サービスの改善に役立たせて頂きます。

ありがちな返信内容ですね (笑)

それでも、自分が送ったメールを「意見や要望」と判断してくれて、それに応じたお礼メールが届くのは、悪く無い気持ちになるのではないでしょうか。

「苦情」に分類される問い合わせメール

問い合わせメールの内容:

サポートに先週問い合わせを行ったが、いまだに回答が無い
一体どういうことか。
大変困っているので至急責任者から連絡を要求する。

システムから自動返信されたメールの内容:

当メールはサポート窓口から自動送信しております。

このたびはご不快な思いをさせてしまい大変申し訳ございませんでした。
お客様のご指摘を真摯に受け止め、今後の再発防止に努めて参ります。

メール内容から「苦情」であると判定して、お詫びメールが返信されました。

実際の運用では、「追ってサポート担当者から連絡させて頂く」旨などを記載しておいた方が良いかもしれません。

更なる発展系

今回は「試してみた」レベルの内容ですので、Bedrockの「プロンプト」はごくシンプルなものとなっています。 プロンプトをもっと精査していくことで、回答精度をより高めたり、多様な問い合わせ内容に対応できるようになるのではないかと思います。

また、「RAG」(Retrieval Augmented Generation) の手法を使って、特定のデータ (社内文書や公式マニュアルなど) や、指定したWebサイトの最新情報 (Webクローリング) を用いた回答生成を行うのも非常に効果的だと思います。

おわりに

「電子メールによるコミュニケーション」と言うと、ともすれば「レガシーな手段であって改善の余地もあまりない」と思われがちなのではないかと思います。

しかし、生成AIを活用することで、今までに無いユーザー体験を提供することができると考えています。

「メール」や「電話」といった一見レガシーなコミュニケーション手段による運用でお困りの方、生成AIを使うことで業務改善が行える余地は十分にあります。 クラスメソッドでは、これからも生成AIを使った業務改善・新しい体験の可能性について、情報発信して行きたいと思います。