今回はAmazon LexというAWSが提供するフルマネージド型人工知能 (AI) サービスと組み合わせたチャットボットアプリを開発します。記事は全部で3回に分けており、2回目になるこの記事では、Lambdaの設定と前回設定したAmazon Lexとの統合までを紹介します。

1回目の記事では、Amazon Lexの設定を行います。
記事は下記リンクから確認ください。

■ Amazon Lexで対話型AIアプリを作ろう (Amazon Lex設定編)
https://press.monaca.io/hirose/8671

関数の作成

Lambdaサービスを利用して、Lexのチャットボットにユーザーが応答する度に呼ばれる関数を作成します。

AWSコンソールからLambdaのサービスを開きます。次のリンクからLambdaへアクセスできます。
https://ap-northeast-1.console.aws.amazon.com/lambda/home

「関数の作成」ボタンをクリックし、「一から作成」を選択します。

関数名に「MonacaPlanSuggestProcessor」などの任意の関数名を入力し、ランタイムは「Node.js 14.x」を選択。「アーキテクチャ」、「アクセス権限」、「詳細設定」は、デフォルトのままにします。

入力が完了したら、画面右下の「関数の作成」ボタンをクリックします。

コードソース

コードソースに次のコードを入力してください。
入力したら、「Deploy」ボタンをクリックします。


'use strict';

exports.handler = (event, context, callback) => {
  try {
    dispatch(event,
        (response) => {
          callback(null, response);
        });
  }
  catch (err) {
    callback(err);
  }
};

function dispatch(intentRequest, callback) {
  const slots = intentRequest.sessionState.intent.slots;
  const IsForEnterprise = slots.IsForEnterprise?.value?.interpretedValue;
  const UseEnterprisePlugins = slots.UseEnterprisePlugins?.value?.interpretedValue;

  if (typeof IsForEnterprise === 'undefined') {
    return callback(elicitSlot('IsForEnterprise', slots));
  }
  if (typeof UseEnterprisePlugins === 'undefined') {
    return callback(elicitSlot('UseEnterprisePlugins', slots));
  }

  if (UseEnterprisePlugins === 'yes') {
    return callback(close([{contentType: 'PlainText', content: 'あなたにおすすめのプランはEnterpriseプランです!'}], slots));
  }
  if (IsForEnterprise === 'no') {
    return callback(close([{contentType: 'PlainText', content: 'あなたにおすすめのプランはProプランです!'}], slots));
  }
  return callback(close([{contentType: 'PlainText', content: 'あなたにおすすめのプランはBusinessプランです!'}], slots));

}

function elicitSlot(slotToElicit, slots) {
  return {
    sessionState: {
      dialogAction: {
        type: 'ElicitSlot',
        slotToElicit,
      },
      intent: {
        name: 'SuggestMonacaPlan',
        slots,
        state: 'InProgress'
      },
    },
  };
}

function close(messages, slots) {
  return {
    sessionState: {
      dialogAction: {
        type: 'Close'
      },
      intent: {
        name: 'SuggestMonacaPlan',
        slots,
        state: 'Fulfilled',
      },
    },
    messages
  };
}

dispatch関数

コードの解説をしていきます。
exports.handler 内で呼んでいる、dispatch関数がメイン関数です。
この関数ではまず、Lex側でユーザー入力したインテント・スロットの値は intentRequest.sessionState.intent.slots から取得できるので、変数に設定しています。

  const slots = intentRequest.sessionState.intent.slots;
  const IsForEnterprise = slots.IsForEnterprise?.value?.interpretedValue;
  const UseEnterprisePlugins = slots.UseEnterprisePlugins?.value?.interpretedValue;

「IsForEnterprise」と「UseEnterprisePlugins」は必須項目 としています。
そのため、未設定であれば、elicitSlot関数を呼び、戻り値をコールバック関数「callback」に渡して終了します。
elicitSlot関数の戻り値で取得されるオブジェクトがLexへのレスポンスとなります。

  if (typeof IsForEnterprise === 'undefined') {
    return callback(elicitSlot('IsForEnterprise', slots));
  }
  if (typeof UseEnterprisePlugins === 'undefined') {
    return callback(elicitSlot('UseEnterprisePlugins', slots));
  }

elicitSlot関数

elicitSlot関数を見てみましょう。
この関数では、第一引数に次に発動するするスロット名、第二引数に現在のスロットを渡します。この関数の戻り値では、「sessionState」をキーとするオブジェクトを返します。

function elicitSlot(slotToElicit, slots) {
  return {
    sessionState: {
      dialogAction: {
        type: 'ElicitSlot',
        slotToElicit,
      },
      intent: {
        name: 'SuggestMonacaPlan',
        slots,
        state: 'InProgress'
      },
    },
  };
}

このオブジェクトの「dialogAction」には、Lexへの次の指示が含まれます。
「type」を'ElicitSlot'、slotToElicitに次に発動させるスロット名(「IsForEnterprise」または「UseEnterprisePlugins」)を指定すると、Lex側で次の質問を表示します。

会話のクローズ

ユーザーとLexの対話が進み、UseEnterprisePluginsとIsForEnterpriseに値が設定されたら、プランを決定できます。
今度はclose関数の戻り値をコールバック関数に渡します。

  if (UseEnterprisePlugins === 'yes') {
    return callback(close([{contentType: 'PlainText', content: 'あなたにおすすめのプランはEnterpriseプランです!'}], slots));
  }
  if (IsForEnterprise === 'no') {
    return callback(close([{contentType: 'PlainText', content: 'あなたにおすすめのプランはProプランです!'}], slots));
  }
  return callback(close([{contentType: 'PlainText', content: 'あなたにおすすめのプランはBusinessプランです!'}], slots));

close関数

close関数では、今度は「dialogAction」の「type」を'Close'にします。
そして、第一引数で渡したmessegesオブジェクトをsessionStateオブジェクトに渡します。

function close(messages, slots) {
  return {
    sessionState: {
      dialogAction: {
        type: 'Close'
      },
      intent: {
        name: 'SuggestMonacaPlan',
        slots,
        state: 'Fulfilled',
      },
    },
    messages
  };
}

messagesには条件に応じて3つのプランを提案するメッセージを設定します。

Lexに組み込む

それでは、作成したLambdaの関数をLexに組み込んでみます。
AWSコンソールからLexのサービスを開きます。次のリンクからアクセスできます。

https://ap-northeast-1.console.aws.amazon.com/lexv2/home

そして、前回作成した「MonacaSampleBot」をクリックします。
「ボットのバージョン」→「ドラフトバージョン」→「言語」セクションの「Japanese (Japan)」をクリックします。
次に「インテントの表示」をクリックし、「SuggestMonacaPlan」をクリックします。

一番下の「コードフック - オプション」の「初期化と検証に Lambda 関数を使用」にチェックを入れ、画面下部から「インテントの保存」と「構築」をクリックしてください。

構築が終わったら、「テスト」ボタンを押し、歯車マークをクリックします。

「Lambda 関数 - オプション」のソースは、「MonacaPlanSuggestProcessor」を選択します。他の項目はデフォルトのままにしておき、保存ボタンをクリックします。

テスト

それでは、前回同様に「こんにちは」と入力してみてください。
スロットに設定した質問の「法人でのご利用ですか?」が表示されます。この質問に回答すると、「アプリのソースコードやストレージの暗号化に興味はありますか?」という2つ目の質問が表示されます。
さらに回答すると、今度はプランの提案をするメッセージが表示されました。


以上で、LexとLambdaの連携の設定は完了です。
最終回となる次回ではMonacaでアプリに組み込む方法を紹介します。