Amazon Connectのチャットで送った画像をAmazon Bedrockで解析する

こんにちは、yokomachiといいます。

本記事は[Amazon Bedrock Advent Calendar 2024](https://qiita.com/advent-calendar/2024/amazon-bedrock-generative-ai)の16日目のエントリです。

Amazon Bedrockについてはまだまだ勉強中の身ではありますが、今回は何とか独自性を出せないかということで、自分の得意領域であるAmazon Connectとの連携について書きます。

Amazon Bedrock関連では他にもこんなブログ👇を書いていたりするので良ければご覧ください。
[LangGraph入門！LLMアプリ開発ライブラリのQuick StartをAmazon Bedrockでやってみる](https://www.geekfeed.co.jp/geekblog/langgraph_tutorial_with_bedrock/)
[LexとBedrockをノーコードで連携できるQnAIntentを試してみた](https://www.geekfeed.co.jp/geekblog/lex_qnaintent/)

# Amazon Connectとは？

Amazon ConnectとはAWSが提供するクラウドコンタクトセンターサービスです。
https://aws.amazon.com/jp/connect/

電話はもちろん、チャットやSMS、Web通話などもサポートするオムニチャネルで、
数分でコンタクトセンターを構築でき、利用した分だけの従量課金制のため超小規模やPoCにも使いやすいクラウドサービスです。

Amazon Connectはコンタクトセンターの稼働効率やエージェント（いわゆるオペレータ）の業務サポートを行うため、生成AIとの相性もかなり良いサービスです。

実際Amazon Connectでは、Amazon Bedrockが一般リリースされる以前の2021年時点で、音声や会話分析を行うContact Lensという機能をサポートしており、現在では以下のような機能をAmazon Connect単体で利用可能です。

- 通話内容の文字起こしや要約
- 会話の感情分析
- リアルタイムな会話分析と、キーワードや感情検出によるアラート
- エージェント（オペレータ）の応対評価

また、Amazon Q in Connectという名称でAmazon Connectのエージェント（オペレータ）向けに特化したAmazon Qの機能も提供されています。（Amazon Qの登場以前からWisdomという名称で一部機能が提供されていました）


一方でコンタクトセンター業界全体でも生成AIの潮流は以前から盛んであり、今年25回目を迎えた「コールセンター/CRM デモ＆コンファレンス」でも生成AIに関連した講演や展示ブースが数多く見られました。

[【25回記念開催】国内最大のコンタクトセンター業界専門展示会がサンシャインシティ・文化会館ビル[池袋]にて11/21(木)-22(金)開催 | インフォーマ マーケッツ ジャパン株式会社のプレスリリース](https://prtimes.jp/main/html/rd/p/000000150.000011263.html)

というわけで、今後もまだまだAmazon Connectの生成AI系の機能はアップデートが続いていくことと思いますが、Amazon Connectの導入支援を行っている身としては、主体的なカスタマイズで機能拡張をしよう、ということでAmazon ConnectとAmazon Bedrockとの連携デモを構築していきます。

# 今回の目的（ConnectとBedrockの連携内容）

と言っても、上述の通り会話分析系の機能はすでに充実しているので、今回はAmazon BedrockのマルチモーダルLLMを活用して、Amazon Connectにチャットで送られてきた画像をAmazon Bedrockで分析する、という連携をしてみます。

もう少し具体的に想定するシナリオを書き出してみると👇こういうことになります。
1. コンタクトセンターは食品を提供している会社のお客様窓口と仮定する
2. 顧客は購入した食品に問題があった場合、食品の画像を撮影してチャットで窓口に問い合わせる
3. 問い合わせに際して、顧客には食品の消費期限を撮影してもらう
4. エージェント（オペレータ）は顧客から連携された消費期限を記録する

機能要件に落とすとこんな感じでしょうか。
- Amazon Connectでチャットと画像添付を利用可能にする（単なる設定作業）
- チャットで送られた画像はAmazon Connectの仕様により事前に指定したS3バケットに保存される（単なる設定作業）
- 顧客から送信された画像がS3バケットに置かれたとき、それをトリガーにしてLambdaを実行する（今回のメイン作業）
- LambdaからBedrockに対してプロンプトと画像を送り、画像認識結果を取得する（今回のメイン作業）
- 画像認識結果をAmazon Connectの問い合わせ属性に更新する（今回のメイン作業）

![Connect_Chat_Bedrock.drawio (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/c4a42135-dede-8140-883f-a847e3f9df4c.png)

では次節以降から構築に入っていきます

# Amazon Connectの構築～チャット有効化

まずはAmazon Connect側でチャットを利用可能にします。

詳細な手順については[公式のドキュメント](https://docs.aws.amazon.com/ja_jp/connect/latest/adminguide/enable-chat-in-app.html)を参照ください。

以下のように顧客とエージェントのチャットができるところまで設定します。
（今回はチャット機能の確認は全てAmazon Connect上のプレビューを使用します）

![Screenshot at Dec 08 10-26-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/9aee9766-51c7-ba01-d9e1-4ed72aeb2db6.png)

また、今回はチャットに画像ファイルを添付するため、Amazon Connectのインスタンスの設定からファイル添付の許可を行います。
![Screenshot at Dec 08 10-30-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/d037b991-f07e-5615-f2fe-5c8d611297bc.png)

※この時、添付ファイルを保存する先のS3でCORSの設定をしておくことを忘れないようにしましょう（最初忘れてた）

[Enable attachments in your CCP so customers and agents can share and upload files - Amazon Connect](https://docs.aws.amazon.com/connect/latest/adminguide/enable-attachments.html#step2-update-cors-policy)

以上の設定で、顧客からチャットで送られてきた画像ファイルをS3に保存することができるようになりました。

# Bedrockの設定

続いてはAmazon Bedrockでモデルの有効化を設定します。

今回は画像認識を行うため、マルチモーダルモデルを使用します。
2024年12月16日現在、Amazon Bedrockで使用可能なマルチモーダルモデルのうち、画像をインプットとしてテキストを生成できるものとしては以下が挙げられます。
- Amazon 
    - Nova Lite
    - Nova Pro
- Anthoropic
    - Claude 3 Haiku, Claude 3.5 Haiku
    - Claude 3 Sonnet, Claude 3.5 Sonnet, Claude 3.5 Sonnet v2
    - Claude 3 Opus
- Meta
    - Llama 3.2 11B Instruct
    - Llama 3.2 90B Instruct

今回は先日のre:Invent 2024でリリースが公開されたAmazonの新しいモデル「Nova」シリーズから「Nova Lite」を使用します。

Novaシリーズはまだ東京リージョン（ap-northeast-1）では利用できないので、バージニア北部リージョン（us-east-1）でモデルアクセスを有効化します。
![Screenshot at Dec 08 11-13-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/02a76dc8-f00e-e4e0-a484-e68657be5d5e.png)

# Lambda関数の作成

続いてはS3へのファイル保存をトリガーに、Bedrockに対して画像解析を依頼するLambda関数を作成します。

## トリガーの設定

S3バケットへのオブジェクト作成をLambdaのトリガーに設定します。

このとき、プレフィックスの指定として「connect/sandbox-yokomachi/Attachments/chat/」を含めます。

Amazon Connectのチャットは添付ファイルを保存するときなどに「connect/sandbox-yokomachi/**Staging**/chat/**」にファイルサイズなどの検証のためにオブジェクトの保存をします。このステージングのオブジェクト作成によって不要なトリガーが発生しないようにプレフィックスの指定は忘れないようにしましょう
![Screenshot at Dec 08 11-21-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/17c108d2-a247-719d-b0ae-f1ececcbfc6c.png)

## ソースコード

ではLambda関数のソースコードを書いていきます。

全体の流れは以下の通りです。

1. eventからS3に保存された画像の情報を取得
2. Amazon BedrockにConverse APIで画像とプロンプトを送信
3. 画像解析結果をAmazon Connectの該当のコンタクトの属性に更新

```python
import os
import json
import boto3
import base64
import urllib.parse

def lambda_handler(event, context):
    # S3, Bedrock, Amazon Connectクライアントの初期化
    s3_client = boto3.client('s3')
    bedrock_runtime = boto3.client('bedrock-runtime', region_name='us-east-1')
    connect_client = boto3.client('connect', region_name='ap-northeast-1')
    
    try:
        # S3バケットとキーの取得（S3トリガーから）
        bucket = event['Records'][0]['s3']['bucket']['name']
        key = event['Records'][0]['s3']['object']['key']
        decoded_key = urllib.parse.unquote_plus(key)

        # Contact IDを取得
        contact_id = decoded_key.split('/')[-1][:36]

        # 画像の拡張子に基づいて画像フォーマットを設定
        file_extension = decoded_key.lower().split('.')[-1]
        if file_extension in ['png', 'jpeg', 'jpg']:
            image_format = file_extension if file_extension != 'jpg' else 'jpeg'
        else:
            raise ValueError(f'サポートされていない画像形式: {file_extension}')
        
        # 画像をS3から取得
        response = s3_client.get_object(Bucket=bucket, Key=decoded_key)
        image_bytes = response['Body'].read()
        

        # プロンプト
        prompt = '''
            この画像は食品や飲料品の賞味期限を写したものです。読み取れる賞味期限、または消費期限を次の<answer>タグ内の内容で回答してください。
            ただし回答に<answer>タグは含めないでください。
            <answer>
                YYYY年MM月DD日
                （日付が読み取れない場合はYYYY年MM月）
                （全体が読み取れない場合は「読み取り不可」）
            </answer>
        '''
        # Claude 3 Haikuへのconverse APIリクエスト
        bedrock_response = bedrock_runtime.converse(
            modelId='amazon.nova-lite-v1:0',
            messages=[
                {
                    'role': 'user',
                    'content': [
                        {
                            'image': {
                                'format': image_format,
                                'source': {'bytes': image_bytes}
                            }
                        },
                        {
                            'text': prompt
                        }
                    ]
                }
            ]
        )
        
        # レスポンスの取得
        image_description = bedrock_response['output']['message']['content'][0]['text']
        
        print(f'Image Recognition Results for {decoded_key}:')
        print(image_description)


        # Amazon Connectへ問い合わせ属性更新
        connect_client.update_contact_attributes(
            InstanceId = os.environ['INSTANCE_ID'],
            InitialContactId = contact_id,
            Attributes = {
                'expiration_date': image_description,
            }
        )

        print('画像認識が正常に完了しました')
    
    except Exception as e:
        print(f'画像 {decoded_key} の処理中にエラーが発生しました: {str(e)}')
```

# デモ

以上で構築は完了です。

最後に全体の動きを確認してみます。

今回デモに使う食品・飲料の画像は、とりあえず家にあったトマトジュースとカップ麺、コーヒーにします。

ちなみにカップ麺のほうは賞味期限部分がフィルムで若干見づらく、コーヒーのほうはピンボケしています。
![20241213_134701.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/1fb0a136-7c30-d2a7-285a-5332c72601dc.png)


まずはAmazon Connectでチャットの問い合わせを開始します。
![image (35).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/397383de-ece2-d556-b12d-3fea42fc7f1d.png)

顧客側の画面から画像を添付します。まずはトマトジュースの画像を送ってみます。
![image (65).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/3903f228-963a-1c69-c4f4-033cdee4c9ca.png)


すると裏でLambdaが動き、このチャットの問い合わせ属性に賞味期限が更新されました。
![image (37).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/3677accc-7872-b68b-d9ba-c623f7ce6df3.png)


今度はカップ麺の画像を送ってみます。
![image (38).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/9843a5e0-0f99-0507-da07-5cfc50db1b02.png)
2025年4月21日のところが2023年になってしまいました。

最後にコーヒーの画像を送ってみます。
![image (39).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/0e3fe5f4-73d1-26dd-2ec0-f597448e88dd.png)
こちらは画像がピンボケしているもののちゃんと認識できていますね。

# おわりに

今回構築したデモにより、エージェントは特にメモを取らずとも、リアルタイムでも後からでも商品の賞味期限を確認できるようになりました。

今回は賞味期限をターゲットに画像解析をしてみましたが、工夫次第でいろいろと使いようがあるかと思います。

Amazon Bedrockをはじめとした生成AI系サービスとの連携により、コンタクトセンターの業務の効率化や利便性の可能性はまだまだ考えられそうです。