GoogleHomeの機能拡張~窓口業務アシスタント~(第5回)
こんにちは。ミーヤキャットです。
前回は独自サーバのアプリケーションを呼び出して、GoogleHomeに発声させることができました。
今回はアプリケーションの機能を充実させて、窓口業務を行うアシスタントを作っていきます。
- GoogleHomeの初期設定と使い方(第1回)
- Action on Google&DialogFlowで機能拡張(第2回)
- 機能拡張準備~クライアント編~(第3回)
- 機能拡張準備~サーバ編~(第4回)
- 窓口業務アシスタント作成(第5回)
- GoogleHome×ラズベリーパイ~カメラとの連動~(第6回)
窓口業務のフロー

今回はこの業務プロセスを基本として、メニューの提示や、おすすめの提示といったことができるカウンター業務アシスタントを作ることを目指します。
技術的なポイントは
- ユーザの会話内容に応じて処理の内容を切り替える
- ユーザからの注文(発言内容)を保存する
です。
ユーザの会話内容に応じて処理の内容を切り替える
まず「ユーザの会話内容に応じて処理の内容を切り替える」の部分について考えましょう。
ユーザの発言を処理する部分はDialogFlowが実施してくれます。
発言によって動く処理の単位をインテントと言います。
例えばアプリが起動されたときに動くインテントはウェルカムインテント(WellcomeIntents)といいます。
今回の場合は起動時に「挨拶」を実施したいので、ウェルカムインテントに「挨拶」を作りこみます。
第4回でやったようにインテントの加工をしてみましょう。
DefaultWelcomeIntentsをWellcomeIntentsに変更して来店時の「挨拶」を作りこみます。
応答は
「いらっしゃいませ。こんにちわ。こちらでお召し上がりですか?」
「いらっしゃいませ。ご主人様。ご飯にしますか?」
の2つを用意します。

次に店内で食べるかテイクアウトかを確認するインテントを作成します。
「店内」「お持ち帰り」といったユーザの発言をパラメータとして受け取るためにエンティティを登録しておきましょう。

ここではPLACEというエンティティに店内、お持ち帰りの2つを登録しました。
シノニム(右側)に様々な言いまわしを登録しておくことで、発言をグループ化できます。
例えば「テイクアウト」と発言した場合でもパラメータとしては「お持ち帰り」としてわたってくるようになります。
次にインテントの作成です。名前はIsEatInIntentにしました。
Training phrasesにユーザの発言を入力します。
ここでは「店内」「お持ち帰り」かどうかを判断したいので、
@PLACEというパラメータを要求し、先ほど定義したエンティティを指定します。

Webhookを有効にして保存します。
同じように注文を受けるインテントを作っていきます。
名前はOrderIntentです。

受け付けるメニューはFoodとDrinkです。これもエンティティに設定しておきます。
ハンバーガーを2個個くださいと言われた場合に数量を受け取るため、numberというパラメータも作成しています。
数値のパラメータは独自でエンティティを作成しなくても@sys.numberを使って受け取ることができます。
インテントごとに処理を分岐する方法
インテントが作成出来たら、インテントごとに処理を分岐する方法を考えてみましょう。
全体の業務プロセスに対するインテントのフローは以下の通りです。
インテントのフロー

インテントそれぞれの役割

この中でオレンジの範囲はwebhook経由で独自アプリを使って処理します。
Webhookでリクエストを受け取るプログラムでは、GoogleアシスタントからのJSONリクエスト(API V2)のqueryResult.intent.displayNameに起動されたインテントが保持されますので、それごとに処理を記載していきます。
インテント名を取得し、処理を分岐するサンプルソース
reqJson = json.load(sys.stdin)
~~中略~~
try:
intentName = reqJson["queryResult"]["intent"]["displayName"]
except KeyError:
intentName = ""
if intentName == "IsEatInIntent":
inParm = reqJson["queryResult"]["parameters"]
place = inParm["PLACE"]
resJson ={
"fulfillmentText": place+u"でお召し上がりですね。ご注文をお伺いします。",
"fulfillmentMessages": [
{
"text": {
"text": [
place,
u"でお召し上がりですね。ご注文をお伺いします。"
]
}
}
]
}
elif intentName == "MenuIntent":
resJson ={
"fulfillmentText": u"フードメニューは、ハンバーガー、ナゲット、ポテトがあります。ドリンクメニューはコーラ、オレンジジュース、コーヒーがあります。何になさいますか?",
"fulfillmentMessages": [
{
"text": {
"text": [
u"フードメニューは、ハンバーガー、ナゲット、ポテトがあります。",
u"ドリンクメニューはコーラ、オレンジジュース、コーヒーがあります。",
u"何になさいますか?"
]
}
}
]
}
~~後略~~
print('Content-type: application/json\n\n')
json.dump(resJson,sys.stdout)
ユーザからの注文(発言内容)を保存する方法
ではもう一つのポイントとなる「ユーザからの注文を保存する」部分について見ていきましょう。
ユーザとの会話内容を保存する方法
Responseとして返すJSONのコンテクストに情報を書き込んでおけば、一定期間情報を保持することができます。
今回のアプリで保持しておきたい情報は
注文の情報です。
それぞれ以下のような構造で保持します。
| 名前 | 説明 |
| class | 「Drink」もしくは「Food」 |
| name | 注文したものの名前 |
| number | 注文した数量 |
Pythonの辞書では
{
“class”:”Drink”
,”name”:u”コーラ”
,”number”:”1″
}
のように定義できます。
また、注文は複数持ちたいので配列で保持することにします。
そしてWebhookResponseにコンテキストを保存します。
マニュアルを見ると「outputContexts」を使えばできそうです。
API-V2のWebhookResponse(context)フォーマットページへ
Fields「Name」の説明を見るとフォーマットが書かれています。
Required. The unique identifier of the context. Format: projects/<Project ID>/agent/sessions/<Session ID>/contexts/<Context ID>.
どうやらコンテキストの利用には、セッションIDが必要そうなので取得処理を追加し、下のようにレスポンスを作ったら無事保存できました。
アプリ用コンテキスト生成サンプルソース
reqJson = json.load(sys.stdin)
~~中略~~
try:
intentName = reqJson["queryResult"]["intent"]["displayName"]
sessionID = reqJson["session"]
except KeyError:
intentName = ""
contextsPrm={}
orders =[]
try:
for con in reqJson["queryResult"]["outputContexts"]:
if con["name"] == sessionID+"/contexts/counter_business":
contextsPrm = con["parameters"]
orders = contextsPrm["ORDER"]
except KeyError:
pass
if intentName == "OrderIntent":
inParm = reqJson["queryResult"]["parameters"]
food = inParm["Food"]
number = inParm["number"]
drink = inParm["Drink"]
if food != "":
order = food
orderClass = "Food"
else:
order = drink
orderClass = "Drink"
if number != "":
order_number = str(int(number))
else:
order_number = "1"
orders.append({"class":orderClass,"name":order,"number":order_number})
contextsPrm.setdefault("ORDER", orders)
resJson ={
"fulfillmentText": order+u"を" + order_number + u"ですね。他にご注文はありますか?",
"fulfillmentMessages": [
{
"text": {
"text": [
order+u"を" + order_number + u"ですね。",
u"他にご注文はありますか?"
]
}
}
],
"outputContexts": [
{
"name": sessionID+"/contexts/counter_business",
"lifespanCount": 10,
"parameters": contextsPrm
}
]
}
コンテキストのもう一つの使い方
コンテキストのもう一つの使い方も紹介します。
一度おすすめを紹介したあとに、さらにおすすめ紹介を行う機能を作りながら考えてみましょう。
インテントのインプットコンテストに任意のコンテキストを指定するとそのコンテキストが無い場合は起動されなくなります。
また、アウトプットコンテストを指定すると、そのインテントが起動されたときに任意のコンテストを追加することができます。
これらを組み合わせると、一度商品をおすすめしたあとに、さらにおすすめを行う機能を作ることができます。
具体的にやってみましょう。
まず、通常のおすすめ提示インテントのアウトプットにRecommendedというコンテキストを作ります。
こうすることで、おすすめを提示するインテントを起動した後、コンテキストにRecommendedが保持されるようになります。

次にMoreRecommendIntentを作成し、インプットコンテキストにRecommendedを指定します。こうすることで、一度おすすめした後にしかMoreRecommendIntentは起動しなくなります。

つまり一回目の「おすすめは?」に対するインテントの起動と二回目以降の「おすすめは?」に対するインテントの起動を分けることができます。
シミュレータで動作を確認しましょう。
一回目の「おすすめは?」でrecommendedコンテキストが追加されます。
二回目の「おすすめは?」でMoreRecommendIntentが起動されます。

MoreRecommendIntentに対するサーバ処理では現在の注文状況に応じて、ドリンク注文があればフードメニュー、なければドリンクメニューをお勧めするようにしています。
また、似たような機能としてFollowUpIntentも紹介しておきましょう。
Intents一覧からインテントに対する応答を受け付けるFollowUpIntentを作成できます。
Add follow-up intentを選択し、「yes」を選択すると「はい」といった肯定的な発言があった場合に起動されるインテントが作成できます。

作成されたインテントはこんな感じです。

MoreRecommendIntent-followupがインプットインテントに自動で追加されます。
実はMoreRecommendIntentのアウトプットインテントにも自動でMoreRecommendIntent-followupが追加されています。よく使うような「はい/いいえ」に対するトレーニングフレーズも自動で入っているのでWebhookを有効にするだけでOKです。
対話の前後関係をつけるのはfollowupインテントを使うのが便利そうです。
最終的なフロー
最終的にはこんなフローになりました。

いざ実行!!
では出来上がったアプリを実際に動かしてみましょう。
いかがでしたか?
今回はGoogleHomeにカウンター業務のアシスタントをやってもらいました。
作成したサーバソースはこちらで公開しています。
次回はカメラ機器とGoogleHomeの連携をしてみる予定です。
それではまたよろしくお願いします。
=======================
記事についてはリンクフリーですが、著作権は放棄していません。
また、この記事は個人の見解を示したものであり、書かれている情報を元に生じた損害についての保証は行いません。
書かれている内容が事実と異なる場合や、人権、知的財産権を侵害している場合は、問合せフォームよりご連絡ください。訂正、削除を行います。
=======================
