はじめに
DialogFlowと出会い、チャットボット開発が面白くなって、ここ数日、暇があれば開発方法について調べていました。
その時、Qiitaで掲載されている方の記事のおかげでチャットボットが作成できたので、恥ずかしながらアップさせていただくことにしました。
何かしらどなたかの助けになれば幸いです。
ボットの紹介
キャラクター
うさ仙人
クラフトホリックのウサギ型宇宙人ですがうちでは仙人風のしゃべり方をさせます。
(/ω\)
内容
英単語ゲームができるようにしました。
会話の流れ
FB Messenger | LINE | SLACK |
---|---|---|
![]() |
![]() |
![]() |
|
準備
使用するサービス
・DialogFlow
LINEやSLACKなどのチャットツールと連携してメッセージを送受信してくれます。
・Actions on google
google assistantと連携するのに必要。
・LINE Developer
DialogFlowと連携するのに必要
・Slack
DialogFlowと連携するのに必要
大まかな流れ
肝となるチャットツールとの連携はDialogFlowがやってくれます。
こんな感じ↓
LINEなどのチャットツール→DialogFlow→Firebaseプログラム
開発手順
設計
アプリケーション開発と同じで設計が重要です。何度も何度もインテントを書き直しました。
ほんとしておかないと後悔します。
Dialogflowで少しずつ作りこみには限界がある
この記事を参考にさせていただきました。
DialogFlow
先にこちらの記事を読むことをおすすめします。
サルにもわかる Dialogflow FAQ
インテント
Default Welcome Intent
MainMenuコンテキストをセットしてメインメニューを表示している状態を他のインテントに認識させます。
Welcomeイベントはデフォルトのまま
Training phrasesは最初にボットを呼びかけるような言葉を設定します。
呼びかけられたら返す言葉を設定します。
Default、Googleアシスタント、SLACK、LINEタブがあり、まずはDefaultタブで設定した言葉がチャットツール側に返答され、その後で各チャットツールのところで設定されたものが返答されます。
LINEタブではQuick repliesで設定しているのでLINE側にボタン選択型メッセージが表示されます。
IntQuestionEJ
Default Welcome Intentで1が選択されると呼び出されるインテントです。
InputContextでMainMenuをセットしているのはDefault Welcome Intentから呼び出された場合にのみ有効ということを意味します。複数セットするとand条件になります。
OutputContextのIntQuestionEJ-followupを設定し、このインテントが呼び出されていることを他のインテントに認識させます。MainMenuのライフスパンを0にしているのは、このコンテキストが残ることがあったので明示的に削除することを意味します。
このイベントはプログラムからこのインテントへ遷移させるときに使用します。インテントのIDだと思えばわかりやすいかも。。。?
トレーニングフレーズはこんな感じにしてあります。
レスポンスでは単語をランダムに返すように設定しています。
FulfillmentをONにしています。
ここで気づいた方もいるかもしれませんが問題はプログラムで出力するはずなのにここでレスポンスを設定しています。
その理由は2つあります。1つはコーディングを書く前段階でイメージをつかむためにです。もう1つはプログラムでレスポンスを返さずに終了した場合、このレスポンスが使用されます。まあ、できた後では削除してしまってもいいのですが、削除するのも面倒ですし、また使用する機会が生まれるかもしれないので残しています。
IntAnswerEJ
タイトルのところがON/OFF設定できます。これをONにしている=fallback intentということになります。
そして、InputContextにIntQuestionEJ-followupがセットされているので、IntQuestionEJが呼ばれた後に何かしら文字を入力された場合に呼び出されるということになります。
FullfillmentがONになっているので、このインテントが呼び出されると直ちにWebhookプログラムが呼び出されることになります。
IntAnswerEJ_correct
このインテントは正解時にWebhookプログラムから呼び出されます。
パラメータにはプログラムから送られた正解値を受け取れるようにする為に用意しています。
レスポンスには正解のメッセージをランダムで返答するようにしています。
LINEタブの設定で選択型メッセージを返すようにしています。
IntAnswerEJ_miss
このインテントは不正解時にWebhookプログラムから呼び出されます。
正解時のインテントとは逆のメッセージが表示されるようにしています。ここでセットしている。「正解:$correct」の部分がプログラムから受け取ったパラメータ値を出力しています。
Default Fallback Intent
IntQuestionJE
IntAnswerEJ - claim
IntAnswerEJ - end
IntAnswerEJ - next
IntBadWord
IntEnd
Webhook(Firebase, Node.js)
Dialogflowだけでオリジナルの3択クイズを作ろう
を参考に作らせていただきました。
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion, Suggestions, LinkOutSuggestions} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
const quesion_ej = [
{ question : "action\n[ˈækʃən]", one : "行動", two : "事柄", three : "機関", correct : "行動"},
{ question : "weather\n[wéðɚ]", one : "風上", two : "天気", three : "勇気", correct : "天気"},
{ question : "athlete\n[ˈæθliːt]", one : "動き", two : "動作", three : "運動選手", correct : "運動選手"},
{ question : "barbeque\n[bɑ́rbɪkjù]", one : "焼き鳥", two : "バーベキュー", three : "焼肉", correct : "バーベキュー"},
{ question : "birthday\n[bˈɚːθdèɪ]", one : "誕生", two : "誕生日", three : "生まれる", correct : "誕生日"},
{ question : "business\n[bíznəs]", one : "トレンド", two : "職業", three : "仕える", correct : "職業"},
{ question : "clothes\n[klóʊ(ð)z]", one : "水着", two : "服", three : "靴", correct : "服"},
{ question : "February\n[fébruèri]", one : "3月", two : "10月", three : "2月", correct : "2月"},
{ question : "hierarchy\n[hάɪ(ə)rὰɚki]", one : "階層", two : "規律", three : "法律", correct : "階層"}
];
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function answer_ej_next(agent) {
agent.setFollowupEvent('StartQuestionEJ');
}
function answer_ej_end(agent) {
agent.setFollowupEvent('Welcome');
}
function question_ej(agent) {
console.log("question_ej");
var random = Math.floor( Math.random() * 9 );
let myQuestionEJ = quesion_ej[random];
// 質問を出力
agent.add(myQuestionEJ.question);
// agent.add(new Suggestion({title : 'どれにするかの?'}));
agent.add(new Suggestion(myQuestionEJ.one));
agent.add(new Suggestion(myQuestionEJ.two));
agent.add(new Suggestion(myQuestionEJ.three));
//答えを格納
agent.setContext({ name: 'question_ej_answer', lifespan: 1, parameters: { correct: myQuestionEJ.correct}});
}
function answer_ej(agent){
console.log("answer_ej");
// 回答を取得
var answer1 = request.body.queryResult.queryText;
console.log("answer1 : " + answer1);
// 正解を取得
var context = agent.getContext('question_ej_answer');
console.log("context : " +JSON.stringify(context));
var correct = context.parameters.correct;
console.log("correct : " + correct);
var eventName = '';
if(answer1 == correct){
eventName = 'answer_ej_correct';
} else {
eventName = 'answer_ej_miss';
}
// 答えをeventのパラメータで返す
let callEvent = {
name: eventName,
parameters: {correct: correct},
languageCode: 'ja',
};
agent.setFollowupEvent(callEvent);
}
function googleAssistantHandler(agent) {
let conv = agent.conv(); // Get Actions on Google library conv instance
//agent.requestSource = agent.ACTIONS_ON_GOOGLE;
//conv.close('Hello from the Actions on Google client library!'); // Use Actions on Google library
conv.ask('Hello from the Actions on Google client library!'); // Use Actions on Google library
agent.add(conv); // Add Actions on Google library responses to your agent's response
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('IntQuestionEJ', question_ej);
intentMap.set('IntAnswerEJ', answer_ej);
intentMap.set('IntAnswerEJ - next', answer_ej_next);
intentMap.set('IntAnswerEJ - end', answer_ej_end);
agent.handleRequest(intentMap);
});
Actions on googleとの連携
LINEとの連携
こちらを参考にさせていただきました。
Dialogflowと連携してLINE Botを作る
DialogflowでLINE Botの応答をWebhookで成形してみた
Slackとの連携
こちらを参考にさせていただきました。
DialogflowからSlackのBotとして動かす
Facebook messengerとの連携
こちらの記事を参考にさせていただきました。
DialogflowからFacebook MessengeのBotとして動かす
Facebookページを作成
FacebookのTOPページ→Facebookページ→Facebookページを作成ボタンをクリック
次に作成したFacebookページと連携させるアプリを登録します
Facebook Developer Consoleへ移動
https://developers.facebook.com/
Messengerの設定ボタンをクリック
Facebook messengerに「うさ仙人」が表示されるのでメッセージを送信して返答があれば完了です。
ハマったところ
インテントとwebhook(google firebase)との連携
・インテントのトレーニングフレーズは誤検知されやすい
たとえば、メニューで「1、2、3から選んでね」として、123だけを検知するインテントを作成。しかし、テストで「5月」と入力したのにこのインテントにヒットすることが判明。
・アウトプットはインテントを使った方が便利
プログラムからメッセージを返すようにしようかと考えたが、どのように返すのかを調べるのに時間がかかった。知識がつくまではインテントを使った方がよさそう。
・最初、Contextの使い方がよくわからなかったがフラグ変数と考えればスッキリした。
・FirebaseでDEPLOYした直後にテストをすると前のバージョンが動く
ここでDEPLOYして完了していても、ちゃんと反映されるまでに数分かかるもよう。。。
分からなかったこと
知っている方、コメントいただければ幸いです。
・メッセージを返した後にインテントを移動する方法
インテントAとBがあり、
A→webhook→B→チェットメッセージ表示はできるが、
A→webhoo→メッセージ出力→Bとするとメッセージは出力されずにそのままBへ移るだけ。
addしたあとにeventをするとだめ
・webhookプログラムを使わずにDialogFlowのインテントだけでインテント間を移動ができないか
・DialogFlowのインテントで条件設定できないか?
・FollowUpインテントを作成して、そのinput contextを削除して一旦セーブするとcontextを戻してもインテント一覧ではfollowupとならない。バグ?
・Googleアシスタントでsuggetion chipsを返すとcancelボタンが標準でついてくるがこれを消す方法
・webhookプログラムからsuggetion chipsを出力するとタイトルがつかない
「choose an item」となる。
その他参考文献
この本はオールカラーで見やすく、また内容も濃く、何度も読み返しました。おすすめです!