LoginSignup
12
9

More than 5 years have passed since last update.

DialogFlowで作るFacebookメッセンジャー、LINE、Slack、GoogleAssistant対応のチャットボットを作成する

Last updated at Posted at 2019-03-10

はじめに

 DialogFlowと出会い、チャットボット開発が面白くなって、ここ数日、暇があれば開発方法について調べていました。
 その時、Qiitaで掲載されている方の記事のおかげでチャットボットが作成できたので、恥ずかしながらアップさせていただくことにしました。
 何かしらどなたかの助けになれば幸いです。

ボットの紹介

キャラクター

うさ仙人image.png
クラフトホリックのウサギ型宇宙人ですがうちでは仙人風のしゃべり方をさせます。
(/ω\)
 

内容

英単語ゲームができるようにしました。

会話の流れ

FB Messenger LINE SLACK
jvnyg-2ej4d.gif dpoz1-54b01.gif 73c2m-pdyr2.gif

準備

使用するサービス

・DialogFlow
 LINEやSLACKなどのチャットツールと連携してメッセージを送受信してくれます。

・Actions on google
 google assistantと連携するのに必要。

・LINE Developer
 DialogFlowと連携するのに必要

・Slack
 DialogFlowと連携するのに必要

大まかな流れ

肝となるチャットツールとの連携はDialogFlowがやってくれます。

こんな感じ↓
 LINEなどのチャットツール→DialogFlow→Firebaseプログラム

開発手順

設計

 アプリケーション開発と同じで設計が重要です。何度も何度もインテントを書き直しました。
 ほんとしておかないと後悔します。

Dialogflowで少しずつ作りこみには限界がある
この記事を参考にさせていただきました。

DialogFlow

 先にこちらの記事を読むことをおすすめします。
 サルにもわかる Dialogflow FAQ

インテント

image.png

Default Welcome Intent

image.png
 MainMenuコンテキストをセットしてメインメニューを表示している状態を他のインテントに認識させます。
image.png
 Welcomeイベントはデフォルトのまま
image.png
 Training phrasesは最初にボットを呼びかけるような言葉を設定します。
image.png
 呼びかけられたら返す言葉を設定します。
 Default、Googleアシスタント、SLACK、LINEタブがあり、まずはDefaultタブで設定した言葉がチャットツール側に返答され、その後で各チャットツールのところで設定されたものが返答されます。
image.png
 LINEタブではQuick repliesで設定しているのでLINE側にボタン選択型メッセージが表示されます。

IntQuestionEJ

 Default Welcome Intentで1が選択されると呼び出されるインテントです。
image.png
 InputContextでMainMenuをセットしているのはDefault Welcome Intentから呼び出された場合にのみ有効ということを意味します。複数セットするとand条件になります。
 OutputContextのIntQuestionEJ-followupを設定し、このインテントが呼び出されていることを他のインテントに認識させます。MainMenuのライフスパンを0にしているのは、このコンテキストが残ることがあったので明示的に削除することを意味します。
image.png
 このイベントはプログラムからこのインテントへ遷移させるときに使用します。インテントのIDだと思えばわかりやすいかも。。。?
image.png
 トレーニングフレーズはこんな感じにしてあります。
image.png
 レスポンスでは単語をランダムに返すように設定しています。
image.png
 FulfillmentをONにしています。
 ここで気づいた方もいるかもしれませんが問題はプログラムで出力するはずなのにここでレスポンスを設定しています。
 その理由は2つあります。1つはコーディングを書く前段階でイメージをつかむためにです。もう1つはプログラムでレスポンスを返さずに終了した場合、このレスポンスが使用されます。まあ、できた後では削除してしまってもいいのですが、削除するのも面倒ですし、また使用する機会が生まれるかもしれないので残しています。

IntAnswerEJ

image.png
 タイトルのところがON/OFF設定できます。これをONにしている=fallback intentということになります。
image.png
 そして、InputContextにIntQuestionEJ-followupがセットされているので、IntQuestionEJが呼ばれた後に何かしら文字を入力された場合に呼び出されるということになります。
image.png
 FullfillmentがONになっているので、このインテントが呼び出されると直ちにWebhookプログラムが呼び出されることになります。

IntAnswerEJ_correct

 このインテントは正解時にWebhookプログラムから呼び出されます。
image.png
image.png
 パラメータにはプログラムから送られた正解値を受け取れるようにする為に用意しています。
image.png
 レスポンスには正解のメッセージをランダムで返答するようにしています。
image.png
 LINEタブの設定で選択型メッセージを返すようにしています。

IntAnswerEJ_miss

 このインテントは不正解時にWebhookプログラムから呼び出されます。
image.png
image.png
image.png
 正解時のインテントとは逆のメッセージが表示されるようにしています。ここでセットしている。「正解:$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ページを作成ボタンをクリック

image.png

image.png
あとはとりあえずスキップ

次に作成したFacebookページと連携させるアプリを登録します

DialogFlowでCallbackURLを生成
image.png

image.png

Facebook Developer Consoleへ移動
https://developers.facebook.com/

マイアプリから新しいアプリを追加する
image.png

image.png

image.png
image.png

Messengerの設定ボタンをクリック

image.png
ウィザードに従って進みます

image.png
その後でアクセストークンが生成されます

image.png

image.png

image.png

Facebook messengerに「うさ仙人」が表示されるのでメッセージを送信して返答があれば完了です。

ハマったところ

インテントとwebhook(google firebase)との連携

・インテントのトレーニングフレーズは誤検知されやすい
  たとえば、メニューで「1、2、3から選んでね」として、123だけを検知するインテントを作成。しかし、テストで「5月」と入力したのにこのインテントにヒットすることが判明。
・アウトプットはインテントを使った方が便利
 プログラムからメッセージを返すようにしようかと考えたが、どのように返すのかを調べるのに時間がかかった。知識がつくまではインテントを使った方がよさそう。

・最初、Contextの使い方がよくわからなかったがフラグ変数と考えればスッキリした。

・FirebaseでDEPLOYした直後にテストをすると前のバージョンが動く
image.png
 ここで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を出力するとタイトルがつかない
image.png
「choose an item」となる。

その他参考文献

この本はオールカラーで見やすく、また内容も濃く、何度も読み返しました。おすすめです!

【Google Home対応】ステップバイステップで力がつく Googleアシスタントアプリ開発入門
image.png

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9