post Image
Watson Assistant(旧 Conversation)で行こう ③コンテキスト(Context)を活用した、ホテル予約チャットボット

目標

知識ゼロの状態でもWatsonを活用したSlack BotやLine Botが作成できるところまでもっていきます。

【2018年10月5日追記】本投稿はWatson Assistantの前進であるWatson Conversation時代の説明になっており画面キャプチャなどは古いままですがWatson Assitant基本的な考え方や動作は変わらないので最新の環境のものと読み替えてくださいませ。

■以下に触れたいとおもいます。

 ①考え方入門、アカウント作成
 ②”ホテル予約”を例にした対話フローの設計方法
 ③コンテキストの活用、Tutorialで触れられていない各種機能の実用的な使い方 ←本稿
 ④Javaロジックとの連携方法
 ⑤Watson + Java + Slackでチャットボットをつくる

今回は第3回として、③コンテキストの活用、Tutorialで触れられていない各種機能の実用的な使い方を説明していきます。

前回はこちら→②”ホテル予約”を例にした対話フローの設計方法です。


今回もホテル予約チャットボットを題材に、Watson Conversationの機能をさらに深堀りした強化版をつくります。

本稿ではWatson ConversationのContext機能を活用して、以下のような対話を実現します。

im01.png

ホテルの部屋を予約する時に必要になる、宿泊する日人数何泊するかを対話の中でユーザーから聞きだし、部屋の予約を実行するという流れになります。

手っ取り早くWorkspaceを取り込んで実行してみたい場合はWorkspaceファイルを以下からダウンロード可能です。

https://riversun.github.io/wcs/org.riversun.HotelReservationV2.zip
(ZIPを開くと、JSONファイルがありますので、それをWatson ConversationのWorkspacesにインポートすると実行できます)

早速 ”Context”についてみていきます。

Contextとは何か

対話(チャット)の中で宿泊日人数泊数といった複数のパラメータを順次確定していく必要がような場合どのように設計していけば良いでしょうか。

また、対話が進む中で、アプリの状態が変化(状態遷移)するような場合にはどうすれば良いでしょうか。

ここでは、Context(コンテキスト)という仕組みを活用します。

Contextとは、対話全体(ノードをまたがって)を通じて共通して値を保持しておける領域のようなものです。

こののことを、Context変数(Context Variables)と呼び、必要なタイミングで変数(変数名と値のペア)をContextに記憶できます。

Context変数をセットする

Contextの使い方は簡単です。

ノード編集画面でvdots.jpgをおすとOpen Json editorというメニューが出てきますのでこれをクリックします。

im03.png

すると、このような画面がでてきます。

im04.png

これはJSONエディタ(JSON Editor)と言い、Watsonの応答(Responses) を詳細に編集することができます。

Watsonからの応答として、テキストしか返さない場合にはJSONエディタの出番はありませんでしたが、テキスト以外の応答や、Context変数をセットしたいときにはJSONエディタを使ってJSONを直接編集します

Watson Conversationの内部的なデータな持ち方としてJSONが使われており、
Watsonと外部プログラムの通信をする際のプロトコルも、ほぼ同様のフォーマットのJSONが使われていますのでJSONとは仲良くしていきます。

余談ですが、前回 Watsonの後ろはSpring on Javaという話をかきましたが、JSONの処理はgsonが担っている模様です。

さて、JSONエディタを開いた初期状態だとこうなっています

{
"output": {}
}

ためしにContext変数を作ってみます。Context変数は名前と値のペアでJSONとして記述します。

例えば、変数名がfooで、値がsomethingのContext変数をセットしたい場合は以下のように記述します

{
  "context": {
    "foo": "something"
  },
  "output": {}
}

JSONのトップ階層に

"context":{
}

のように、contextという名前のオブジェクト({ })を定義し、その中にfooという子要素を定義しています。

im05.png

Context変数を入れ子(階層化)にすることも可能です。

入れ子にすると下のようになります。

Context変数としてbar追加しました。
barオブジェクトは2つの子要素(param1param2)を持っています。

{
  "context": {
    "foo": "something",
    "bar": {
      "param1": 0,
      "param2": 15
    }
  },
  "output": {}
}

Context変数を参照する

次にContext変数を参照するシーンと参照方法をみていきます。

参照シーンその1】・・・Context変数を条件分岐に使う

im06.png

一番多いのがこのシーンかと思います。Context変数の値をノードの条件(Trigger)で使います。

”Context変数foo‘something’である”、という条件は以下のように記述します

context[‘foo’] == ‘something’

context[‘foo’]における”context”は組み込みグローバル変数(Built-in global variables)といい、あらかじめWatsonに定義されている変数です。

他にも

  • intents[ ](Watsonが解釈したIntent変数)
  • entities[ ](Watsonが解釈したEntity変数)
  • input(ユーザーの入力テキスト等が入った変数)

などがあります。

<省略形>
また、Contextの省略形$からはじまる表現を用いて
$foo:something
と記述しても
context[‘foo’] == ‘something’
と同じ意味になります。

また、
context.foo==’something’
でもOKです。

入れ子になっているContext変数の場合は、
$bar.param1==0
のようにドット(.)でつないで子要素を指定できます。


参照シーンその2】・・・Context変数を応答に使う

Context変数として保持された値を応答(Responses)の中で使うこともできます。

実験用に下のようなノードを作ってみました。

im07.png

ノードエディタでWatson Responses(Then response with)に、Context変数を出力する応答を記述してみます

Contextが以下のようになっているとして、

{
  "context": {
    "foo": "something",
    "bar": {
      "param1": 0,
      "param2": 15
    }
  },
}

bar.param1を含むテキストを返すには、以下のようにします。

コンテキスト変数の値は<? context[‘bar’]?.param1 ?>です

im08.png

これを実行すると、
コンテキスト変数の値は0です
となります。

<? context[‘bar’]?.param1 ?>
とは何を意味しているでしょうか。

まず<??>で囲む表記ですが、これは、<? ?>で囲まれた中の変数や式を計算した結果、という意味になります。

context[‘bar’]Contextの中にあるbarという変数を参照せよという意味で、

context[‘bar’]?.param1Contextの中にあるbarという変数の子要素param1を参照せよ という意味になります。

ここでしれっと出てくる”?”は何でしょうか。

これはヌルポ(null pointer)防止のための表記方法です。

この例では仮に、?を消してcontext[‘bar’].param1としても動作しますが、
context[‘bar1’].param1のように存在しないContext変数bar1を参照しようとするとヌルポが発生してエラーになりノードの実行が止まってしまいます。

そこで、context[‘bar1’]?.param1のように?をつけてあげることでヌルポを防止(ヌルポにならず空が返ります)することができます。

context.bar1?.param1という表記もOKです。

<省略形>

省略形を使う場合はコンテキスト変数の値は<? $bar?.param1 ?>ですのように記述します。

また<? ?>を付けずに、コンテキスト変数の値は $bar.param1 ですのようにそのまま記述しても動作します。

ちなみに<? ?>なしで、以下のような表記はNGです。

【×】コンテキスト変数の値は $bar?.param1 ですのようにヌルポ防止表記を使う
【×】コンテキスト変数の値は$bar.param1ですのように、日本語文章の前後にスペースをあけずに$bar.param1を記述する


参照シーンその3】・・・Context変数を更新する

Context変数が数値型で、それをインクリメントしたい場合はJSON内で自分自身を参照します

bar.param2に1足したいときは、以下のようになります。

im09.png

{
  "context": {
    "bar": {
      "param2": "<? $bar.param2+1 ?>"
    }
  },
  "output": {}
}

ホテル予約チャットボット(強化版)を作る

Contextについて理解を深めたところで、ホテル予約チャットボットの強化版をつくっていきます。

完成イメージを先に示します。以下のような一連の会話ができるものを作ります

対話パターン1・・・対話しながら、1つずつ条件を埋めていく

利用者「部屋を予約したいです。」

ボット「いつご宿泊ですか?」

利用者「来週の水曜日です」

ボット「日付は2017年6月21日(水)ですね。
    何名様でご宿泊しますか?」

利用者「2人です。」

ボット「人数は 2 名様ですね。何泊しますか?」

利用者「1泊です。」

ボット「宿泊日数は、1 泊ですね。
    それでは、ご予約内容を確認させてください。
    2017年6月21日(水) から1泊、2名様で、よろしいですか?」

利用者「はい」

ボット「2017年6月21日(水) から1泊、2 名様でご予約を受け付けいたしました。
    ご来館をお待ちしております。ありがとうございました。」

対話パターン2・・・一度に複数の条件を言う

利用者「来週の水曜日から2泊で部屋を予約したいです。」

ボット「日付は2017年6月21日(水)ですね。
    宿泊日数は、2泊ですね。
    何名様でご宿泊しますか?」

利用者「4名です」

ボット「申し訳ありません。
    4名様は受け付けられません。3名以下で再度ご入力ください。」

利用者「間違えました、3名です。」

ボット「人数は 3 名様ですね。
    それでは、ご予約内容を確認させてください。
    2017年6月21日(水) から2泊、3 名様で、よろしいですか?」

利用者「はい」

スロットフィリング(Slot Filling)

上の2パターンはどちらも、宿泊する日泊数人数を何らかの対話によってユーザーから聞きだしています。

来週の水曜日から1泊の予定で部屋を予約したいです。
という文章は、以下のように部屋の予約に必要になる条件を含んでいます。

ある目的達成(ここではホテルの部屋を予約することが目的ですね)のために必要になる条件・情報スロット(Slot)と呼びます。

下のslotbox.jpgで囲まれた部分がスロットにあたります。

im10.png

この例では、部屋を予約するときには、 宿泊する日泊数人数が必要になるので、これらがスロットとなります。

たとえば、利用者が
「部屋を予約したいです。」
と言っただけでは宿泊する日泊数人数がわかりません。

そこで、ボットは利用者に対して
「いつご宿泊ですか?」「何泊しますか?」
など問い合わせて宿泊する日泊数を把握していく必要があります。

このように不足しているスロットを利用者との対話(質問など)によって埋めていくことをスロット・フィリング(Slot Filling)と呼びます。

このように対話は、目的を達成のためのスロット・フィリングのための一連のフローと考えることもできます。

対話フローを作る

ここまで前提知識をみてきたので、これらを生かした対話フローをみていきます。

既に作成済のホテル予約チャットボット(強化版)Workspaceファイル(JSON形式)をここにおきましたので、これをつかって対話フローをみていきます。

ダウンロード
https://riversun.github.io/wcs/org.riversun.HotelReservationV2.zip
(ZIPを開くと、JSONファイルがありますので、それをWatson ConversationのWorkspacesにインポートすると実行できます)

これをWorkspaceにインポートして使います。

【インポート手順】

Watson ConversationのWorkspaces画面にあるupload.jpgアイコン をクリックして、Workspaceファイルを選択します。

im11b.png

Importするとこのようになります。

HotelReservationV2というworkspaceができたとおもいます。

img11c.jpg

早速HotelReservationV2を開いて見ていきます。

im12.png

Welcomeノードを開くと、このようになります。

**im13.png

WelcomeノードをJSONエディタで開いてみます。

im14.png

このようなJSONになっています。

{
  "context": {
    "mode": ""
  },
  "output": {
    "text": {
      "values": [
        "こんにちは、リバーサンホテルです。ご用件は何でしょうか?"
      ],
      "selection_policy": "sequential"
    }
  }
}

ポイントはここです。

{
  "context": {
    "mode": ""
  },

ここでmodeというContext変数を””に初期化しています。

このmodeは、現在の対話処理モードを表す変数で、

  • 現在の対話状態が部屋の予約モード(reservation)
  • 予約した部屋の取り消しモード(cancellation)
  • それ以外

の値をとります。初期値は””とします。

Contextの項で説明したとおり、複雑な対話では、状態(State)変数をContextに格納して対話をつくっていきます。

このようなアプリのステート管理、状態遷移などはソフトウェア設計の一般的な考え方ですので、 Watson Conversationに限った話ではありませんがWatson Conversationの場合は、対話の遷移自体はWatsonのDialog処理エンジンが担当してくれる点がメリットと言えます。

次にProcess_inputノードを開きます。


Process_inputノード

Process_inputノードは、ホテル予約の対話処理全体の親のノードとなり、
Welcomeノード以外の処理は、すべてこのノード以下で行います。

im15.png

実行順序ですが

(1) まずWelcomeノードが実行される
(2) ユーザーがテキストを入力する
(3) Process_inputノードのTriggerはtrueなので、このノードが処理されるがProcess_inputノード自体にResponseは無く、Set_reservation_modeノードのTrigger(Condition)に自動的にジャンプする
(4) ユーザーの入力テキストを元に順次(Set_reservation_mode→Set_cancellation_mode→Abort→Start_making_a_reservation)評価されていく。

まず、 Process_inputノードの子ノードの
Set_reservation_modeノードと、Set_cancellation_modeノードからみていきます。


Set_reservation_modeノード

Set_reservation_modeノードはIntent #reserve_roomがTriggerです。

im16.png

つまりユーザーが「部屋を予約したい」旨、入力したときにこのノードが実行されます。

このノードでは、JSONエディタで編集されており、以下のJSONを返します。

{
  "context": {
    "mode": "reservation",
    "date": "",
"night": 0,
    "people": 0
  },
  "output": {}
}

ここでは、4つのContext変数をセットしています。
modereservationにセットします。 対話処理モード部屋の予約モード(reservation) という意味になります。

datenightpeopleは、それぞれ宿泊する日泊数人数のスロットに対応するContext変数です。

mode date night people
対話処理モード 宿泊する日 泊数 人数
reservation ”” 0 0

ユーザーと対話しながら、これらの変数に値を入れて行くようにノードを設計します。


Set_cancellation_modeノード

Set_cancellation_modeノードはIntent #cancel_roomがTriggerです。

im17.png

つまりユーザーが「予約を取り消したい」旨、入力したときにこのノードが実行されます。

{  "context": {
    "mode": "cancellation“
}
}

ここでは、Context変数mode対話処理モードcancellationに指定しています。
本稿では部屋の予約に重きをおくので、予約取り消しはここにあるとおり、深い対話は設計しません。

これから予約に必要な情報を対話によってユーザーから聞きだしていきますが
対話途中でやっぱり予約をやめることができるように #input_abortというIntentを追加しました。

im18.png

次は、Set_cancellation_modeノードに次にある、Abortノードを開いてみます。

im19.png


Abortノード
AbortノードのTriggerは、さきほどみた #input_abort です。

im20.png

さらに、もう1つの条件、intents[0].confidence >=0.90 があります。
これは何でしょうか。

誤判定への対処 ~confidenceの活用~

intents[0].confidenceとは、ユーザーが入力したテキストに対してWatsonが判定したIntentの確からしさです。

これまで、ユーザーが入力したテキストに対してWatsonが自動的にIntentが判定し、それに応じてノードが実行されていましたが、実はIntentには確からしさ(=信頼度 confidence)のスコアが存在します。

どんな入力テキストもWatsonは機械的に判定しますので、場合によっては本来割り当てられるべきでないIntentに割り当てられる場合もあります。
こうした誤判定への対処としてIntentの確からしさがある一定値以上のスコア場合のみノードを実行するという条件づけをします。

これが intents[0].confidence >=0.90 の部分です。

ちなみにIntentsが配列表現[0]になっているのは、Intentは内部では以下のような配列形式になっているため、その先頭を取り出すという意味になります。

"intents": [
    {
      "confidence": 0.67357128262519836,
      "intent": " input_abort "
    }

このようにIntentの確からしさを対話フロー設計側でハンドリングしたいとき(Watsonまかせにせず)には #input_abort and intents[0].confidence >=0.90のようにIntentとconfidenceをセットで用いることになります。

(ここでは深くふれませんが、confidenceのテクニックはIntentの切り方と、トレーニング用の発話例文の質・量のノウハウの話につながっていきます。)


Start_making_a_reservationノード

次は、Start_making_a_reservationノードを見ていきます

im21.png

このノードが今回のメインディッシュです。

Start_making_a_reservationノードを開くと、多くの子ノードがあります。1つずつみていきます。

im22.png

まず、Start_making_a_reservationノードはTriggerが$mode:reservationとなっています。
Contextの項でみたとおり、これはcontext[‘mode’]==‘reservation’と同じ意味で、Context変数 mode’reservation’であった場合つまり対話処理モード部屋の予約モード(reservation)だった場合に、Start_making_a_reservationノード以下に処理が移行します。

さて、このStart_making_a_reservationノードと、上にあるSet_reservation_modeノードは意図的に分けています。

Start_making_a_reservationノードの右側に予約に関する対話(「何泊しますか?」「何名様ですか?」をつなげていく作り方をすることもできますが、予約に関する対話をしている最中に気が変わって予約に関する対話を中止したくなったりしたときに「やっぱり止めます」という入力をハンドリングするため、modeという状態変数を導入しました。

さらに、後ほどくわしくみますが、右下の赤枠内は予約に関するユーザーからの入力を受けつけ、処理をするノードですが、ユーザーの入力をノードが処理した後に次の入力を受け付けるために、図の矢印線のように、左上のユーザー入力まで戻します。

im23.png

このあたりははじめのうちはピンと来ませんが、対話設計していくうちに、いろんな対話要件をさばいていると自然とこういう設計になってきます。なので、はじめピンと来なくても大丈夫です。

この件は、本稿でのちほどまたふれます。

また、今回はこの設計にしていますが、要件次第では別パターンの構造になるとおもいます。

さて、Start_making_a_reservationノード以下にある、子ノードをみていきます。

ユーザーが「部屋を予約したいです」とホテルの部屋を予約したい意思を示すと、modereservationになり、 Start_making_a_reservationノード以下が実行されることを念頭においておきます。


Set_dateノード*】

一番上にあるのは、Set_dateノードです。

im24.png

Triggerは@sys-date and context[‘date’] == ‘‘です。

これは、“ユーザーの発話にEntity @sys-date(日付)が含まれていて、かつ、Context変数dateが初期状態だった場合”という条件です。

たとえば、ユーザーが最初に
「来週の水曜日に部屋を予約したい」

im25.png

と言った場合、以下のようにノードが遷移します

(1)Intentが#reserve_roomなので、Set_reservation_modeノードが実行され、Context変数modereservationにセットされる

(2)Context変数modereservationなのでStart_making_a_reservationノードが実行される

(3)Entityに@sys-date(“来週の水曜日“)が含まれており、かつ、Context変数dateが初期値””なので、Set_dateノードが実行される

Set_dateノードのResponses(応答)は

im26.png

{
  "context": {
    "date": "<?entities['sys-date']?.value?>"
  },
  "output": {
    "text": {
      "values": [
        "日付は<?entities['sys-date']?.value.reformatDateTime('yyyy年M月d日(E)')?>ですね。"
      ],
      "selection_policy": "sequential"
    }
  }
}

となります。

 "context": {
    "date": "<?entities['sys-date']?.value?>"
  },

ここでdate“<?entities[‘sys-date’]?.value?>“を代入しています。

つまり、ユーザー入力テキストに日付(@sys-date)Entityが含まれていたら、 @sys-dateをContext変数dateに記憶させます。

これで宿泊する日を覚えました。

ついでに、こちらをみてみます。

 "output": {
    "text": {
      "values": [
        "日付は<?entities['sys-date']?.value.reformatDateTime('yyyy年M月d日(E)')?>ですね。"
      ],
      "selection_policy": "sequential"
    }

outputは、その名の通り出力に関するオブジェクトです。Response(応答)に関するフォーマットはJSONではoutput以下に入ります。
output.text.valuesの中に、ノードエディタで入力したテキストが入っていることがわかります。

ここでは“日付は<?entities[‘sys-date’]?.value.reformatDateTime(‘yyyy年M月d日(E)’)?>ですね。“を返すので、実際には、
日付は2017年6月28日(水)ですね。
のように応答が返ります。

ユーザーの入力した宿泊する日を復唱する応答となります。

さて、このoutputオブジェクトについて少しふれておこうとおもいます。

output以下の要素は、実は自由に編集することができます。

チャットはテキストだけのやりとりですが、もし画面描画も行うようなチャットボットを作りたい場合、output.textではなく別の要素をJSONの中に入れてもOKです。

たとえば、以下のように拡張することも可能です。

{
 "output": {
    "myVisual": {
      "imageUrl":"http://foo/bar.png"
    }
}

みてわかるとおり、JSONに新要素を追加し、Watson Conversationを外部プログラムと連携させるときに、output以下をハンドリングして適切な描画処理をすればテキスト以外の要素を対話に含めることもできます。

Set_dateノードの実行が終わった後は、次のCheck_num_of_peopleにジャンプします。

im27.png

もしジャンプしないと、以下のように、ここまでで対話処理が止まってしまいます。

利用者「来週の水曜日に部屋を予約したい」
ボット「日付は2017年6月28日(水)ですね。 」
(対話ストップ・・・)

あるノードの実行が終わったあとも、対話処理(条件確認→条件に合致するノード実行)を続けていく必要がある場合は、次のノードにジャンプする処理を明示的に入れていく必要があります。


Check_num_of_peopleノード*】

次はCheck_num_of_peopleノードとSet_num_of_peopleノードに注目します。

im28.png

ここは、ユーザーが宿泊人数を入力してきたときに実行されるノードです。
さて、「宿泊人数は?」と聞かれたら「3人です」とか「3名です」と答えるとおもいます。

また、テキスト入力の際に「三人です」と言われる場合もあります。
そういった数値入力を取り扱うには、特別なEntityを使います

@sys-number(数値Entity)

画面上部のEntitiesタブをクリックし、その下にあるSystem entitiesを選択します。
すると、以下のようにWatsonが提供するSystem entities一覧が表示されます。

im29.png

@sys-numberonにすると、数値を扱う特別なEntityを利用可能になります。

@sys-numberに対する単位

つぎにMy entitiesを選択します。

3人三人3の部分は@sys-numberが抽出してくれますが、という単位をハンドリングする仕組みは今のところありません(※)

※単位つきでハンドリングしてくれるのは通貨(@sys-currency)とパーセンテージ(@sys-percentage)のみです

そこで、人数の単位である(にん)を抽出できるよう、Entityを@unit_of_peopleとして登録しました。

マッチさせるキーワードとして(にん)と(めい)を登録しました。はSynonym(同義語)です。

im30.png

ちなみにMy entitiesに登録したEntityは、処理としては単なるテキストマッチングです。

つくった@sys-number@unit_of_peopleの2つのEntityは以下のように条件(Trigger)で組み合わせて使います。

If @sys-number and @unit_of_people

こうすれば、3人三名といった入力テキストをとらえることができます。

ちなみに、これで完璧ではなく、たとえば、あまのじゃくな人がいて「名3」などと言ってもマッチしますので、どうしても完璧にしたければ正規表現(Watson Conversationでもサポートしてます)や、連携させる外部プログラム側のロジックでチェックするということも可能です。本稿ではそこまでは追いかけません。


Check_num_of_peopleノード

さて、ではCheck_num_of_peopleノードとSet_num_of_peopleノードの中身をみていきます。
この2つのノードはユーザーが人数について言ってきた場合に実行されます。

たとえば部屋を予約する対話の中で、
「3人でお願いします」
と言ってきたような場合です。

Check_num_of_peopleノードは、宿泊人数制限を超えていないかどうかをチェックするノードです。

im31.png

その次のSet_num_of_peopleノードは宿泊人数をいったん確定しContext変数に記憶するノードです。
当然ながら、宿泊人数の確定よりも、人数が多過ぎないかのチェックを先に行う必要があるので、
Check_num_of_peopleノード→Set_num_of_peopleノード の順番で並んでいます。

こうした順番はプログラミングでif文を書く順序と同じですので、条件式の評価順をよく意識してノードを配置していく必要があります。

Check_num_of_peopleノードのTriggerは、

@sys-number and @unit_of_people and $people:0 and @sys-number.numeric_value>3 and @sys-number.size()==1

です。

  • @sys-number and @unit_of_peopleはEntityのところでみたように、3人のようなEntityがユーザーの入力テキストに含まれているかの判定です

  • $people:0は Context変数people0(=初期値)かどうかの判定です

  • @sys-number.numeric_value>3は、はじめてでてくる表現です

@sys-numberは、前でみた数値Entityです。
numeric_valueは数値Entityを数値化したときの値です。

たとえば、という数値Entityの@sys-number.numeric_value3となります。

ちなみに、””のまま取得したい場合は@sys-number.literalを使います。

@sys-number.numeric_value>3は、数値が3より大きい場合という条件になります。という条件になります。

4人」と入力すると、
「申し訳ありません。@sys-number 名様は受け付けられません。3名以下で再度ご入力ください。」
と応答されるようにしています。

このノードについてまとめると、
@sys-number and @unit_of_people and $people:0 and @sys-number.numeric_value>3という条件式は

ユーザー入力テキストに数値があり、”人”という表現があり、数値が3より大きくて、Context変数peopleが初期値(まだ値がセットされていない)場合
という条件になります。

@sys-number.size()==1については、次のノードで説明します。

Set_dateノードと同様、このノードも実行したら次のノードにジャンプします。


Set_num_of_peopleノード

次のSet_num_of_peopleノードのTriggerは、
@sys-number and @unit_of_people and $people:0 and @sys-number.size()==1で、前のCheck_num_of_peopleノードから人数チェックをのぞいた条件で、「人数は2人です」のような入力があると実行されます。

im32.png

このノードが実行されると、以下のようにContext変数peopleに、宿泊人数に相当する@sys-numberが格納されます。

 "context": {
    "people": "@sys-number"
  },

さて、@sys-number.size()==1とは何でしょうか。

@sys-number.size()は、ユーザーの入力テキストに含まれる@sys-number Entityの数です。
条件では、この@sys-number Entityの数を1つに限定しています。

理由は以下のユーザー入力の例で説明します。
「今週の水曜日から、3人で2泊で人数は部屋をとりたいのですが」
という入力文の場合、3人3という@sys-numberEntityと、2泊2という@sys-numberEntityが抽出されます。
この2つの@sys-numberEntityがどちらが人数でどちらが泊数かをこのWatson Conversationのノードエディタ上で判定するのは実は難しいです。

説明したとおり、たとえば、3人というのは、@sys-number@unit_of_peopleという2つの条件があったとき、というゆるい判定で検出しているためです。

そのため現状では、残念ながら、
「今週の水曜日から、3人で2泊で人数は部屋をとりたいのですが」という入力は受け付けられない設計となります。

たとえば、
「今週の水曜日から、3人で部屋をとりたいのですが」
なら、登場してくる@sys-numberが1つなのでハンドリング可能です。

他には、
「6月30日から、3人で部屋をとりたいのですが」
はNGです。6月30日というのは@sys-dateと検出されますが、実は同時に@sys-numberとしても抽出されてしまいます。

というわけで、エディタ上で作れる対話の実力値もみえてきたわけですが、
こうした場合はどのように対処すればよいか。

現状では、こうした場合の対処は、Watson Conversationと連携させる外部プログラム側で対処します。

次回以降の稿で、外部プログラムと連携させるときに別途説明予定です。

どこまでをノードエディタ上で編集できるWorkspaceでがんばって、どこからを外部プログラムでハンドリングするかのバランスはノード設計の留意点となります。


Set_nightsノード】
次のSet_nightsノードはユーザーの泊数(night)を取扱い、Set_num_of_peopleと同様に処理されます。

続いて、その下にある、4つのノードをみてみます。

im34.png

Confirm_dateノード→Confirm_num_of_peopleConfirm_nightsConfirm_reservationと続きます。


Confirm_dateノード

Confirm_dateノードは、宿泊日を問い合わせるノードです。
Triggerはcontext[‘date’] == ”です。

im35.png

Context変数date初期状態だった場合=まだ宿泊する日が入力されていなかった場合に、
「いつご宿泊ですか?」
とユーザーに問い合わせます。

もし、ユーザーが最初に
「来週の水曜日に、部屋を予約したいです」と言っていたら、Set_dateノードが実行されContext変数date来週の水曜日がセットされるので、
このConfirm_dateノードは実行されません。

ユーザーが単に「部屋を予約したいです」と言っていたら、Set_dateノードが実行されずContext変数は初期値のままですので、
このConfirm_dateノードが実行されます。

このように、必要なスロット(宿泊する日、人数、泊数)をセットするノードを前側に配置、スロットを埋めさせる質問を後側に配置するパターンで、
ホテル予約のスロットフィリングを実現できます。

さて、ユーザーがまだ宿泊日を入力していない場合に、Confirm_dateノードが実行されると、
Watsonは「いつご宿泊ですか?」と返しユーザーの質問への回答の入力促しますが、この入力はどこでハンドリングすると良いでしょうか。

こたえは、こちらの赤枠実線部分のユーザー入力待ちです。これは、Process_inputノードの直下です。

im36.png

ここにジャンプすることで、何が嬉しいかというと、
「いつご宿泊ですか?」という質問に対して、
ユーザーは「予約を中断したい」と言うことができます。

もしジャンプ先が赤枠点線部分のユーザー入力待ちへのジャンプだとすると、受け付けられる入力が予約関連のみに限定されてしまい、部屋の予約以外の意思表示ができなくなってしまいます。
(そこで、「予約を中断したい」といっても、しつこく「いつご宿泊ですか?」などと言われてしまいます。)

このようにユーザー入力待ちのジャンプ先を工夫することで、ユーザーによる入力を柔軟にさばける対話を設計することができます。


後続の2つのConfirm_dateノードとConfirm_nightsノードも同様にユーザーへのスロット入力を促します。

im37.png

Confirm_dateノード
宿泊する日を入力を促します

Confirm_nightsノード
宿泊日数(泊数)の入力を促します


Confirm_reservationノード
Confirm_reservationノードは、部屋の予約の最終確認です。

Triggerはcontext[‘date’] !=” and context[‘night’]>0 and context[‘people’]>0です。

im38.png

Context変数date(宿泊する日)、night(泊数)、people(人数)の3つのスロットがすべて埋まっているかどうか判定し、埋まっていれば、

「それでは、ご予約内容を確認させてください。<?$date.reformatDateTime(‘yyyy年M月d日(E)’)?> から<? $night ?>泊、<?$people?> 名様で、よろしいですか?」

と最終確認メッセージをだします。

勘の良い方ならすぐにわかるとおもいますが、このTriggerに記述した条件は実は冗長です。

これまでのノードの判定で実はスロットが埋まっているいるかどうか判断されているので、ここの条件はtrueでも良かったのですが、そうせず1つずつスロットを確認しています。
このあたりは設計ポリシー次第ですが、各ノードが何を判定するかを明確にするため、このようにしています。

さて、このノードの実際の応答として各スロットが埋まった状態で、
「それでは、ご予約内容を確認させてください。2017年6月21日(水) から1泊、2名様で、よろしいですか?」
のように返ります。


Express_yesノード

Express_yesノードは、左側にあるConfirm_reservationノードのユーザー入力を受け付けます。

im39.png

Confirm_reservationノードは
「それでは、ご予約内容を確認させてください。2017年6月21日(水) から1泊、2名様で、よろしいですか?」
のような「はい」「いいえ」の入力をうながす質問形式です。
Express_yesノードでは、「はい」を受け取れるようにします。

そこで、YESをハンドリングするための#input_positiveというIntentを追加しました。
「はい」、「うん」、「いいよ」、「OK」、「オッケー」などを学習用データとして登録しています。

im40.png

このように短いワードをIntentに登録すると誤検出も多く、Intentnoconfidence(確からしさ)スコアと同時に使うか、または、キーワードマッチングとほぼ同じであるEntityを使うか、用途に応じて検討します。

Express_yesノードでは、Intentを使います。

Triggerは、
#input_positive and intents[0].confidence >= 0.90
にしました。confidenceを一緒につかって誤検出を防ぐ努力をします。

応答は
「<? $date.reformatDateTime(‘yyyy年M月d日(E)’)?> から<? $night ?>泊、<?$people?> 名様でご予約を受け付けいたしました。ご来館をお待ちしております。ありがとうございました。」
のようにしていますので、

実際には
2017年6月21日(水) から1泊、2名様でご予約を受け付けいたしました。ご来館をお待ちしております。ありがとうございました。
のようになります。


Express_otherノード
さいごは、Express_otherノードです。

こちらもExpress_yesノードと同様にConfirm_reservationノードのユーザー入力を受け付けます。

im41.png

Confirm_reservationノードの「それでは、ご予約内容を確認させてください。2017年6月21日(水) から1泊、2名様で、よろしいですか?」
という質問に対して「いいえ」で答えるときに実行されるノードです。

Triggerはtrueです。
つまりExpress_yes以外のときには、こちらのノードになります。

ちなみに、trueについてはあまり説明してきませんでしたが、
trueは特別なCondition(Trigger)としてWatson Conversationに予約されており、conditionが常にtrueとなります。

この特性をつかって、縦にならぶ複数のノードの評価の最後にELSEの役割としてtrue Triggerとしてコンディションを置きます。

また、falseというCondition(Trigger)は逆の働きをします。

さて、話を戻して、ユーザーが「いいえ」といって、このExpress_otherノードが実行されると

「かしこまりました。再度、宿泊条件を教えてください。」

が返され、Set_reservation_modeのResponseにジャンプします。

ジャンプ先では、部屋の予約のスロットにあたるContext変数の初期化されますので、再び部屋の予約に関する質問がゼロからはじまります。

im42.png

実際に、作った対話を実行してみる

右上のAsk Watsonaskwatson.jpgをクリックして、これまでみてきた対話を試します。

以下のように、うまく動作しているようです。

im43.png

ホテル予約チャットボット(強化版)のまとめ

さて、ここまでホテル予約チャットボットをみてきました。

もうお気づきのとおり、ホテル予約のようにゴールが明確な対話のフロー設計とはすなわち、スロットフィリングのさせかたに帰着します。

そのため、ユーザーからみたときに、どうすれば自然な対話になるか柔軟にユーザーの入力をさばけるかを意識してノードの設計、配置、ジャンプの工夫しました。

複数の単位つきの@sys-number@のハンドリングのところでみたように、ノードエディタだけで作っていける対話の現時点での実力値もみえ、
Watson Conversationと連携させる外部プログラムとの役割分担の必要性も感じました。

そこで次回は、外部プログラム(Javaロジック)との連携について触れたいと思います。

Workspaceのダウンロード

以下から本稿で使用したworkspaceをダウンロードすることができます

https://riversun.github.io/wcs/org.riversun.HotelReservationV2.zip
(ZIPを開くと、JSONファイルがありますので、それをWatson ConversationのWorkspacesにインポートすると実行できます)


『 機械学習 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。