カテゴリー: BackEnd

[Dialogflow + CF] アクア様が罵倒してくれたり天気を教えてくれるSlackボットを作る

はじめに

このすば、面白いですよね。
あまりアニメは見ない私ですが、このすばは全部見ました。
OVAか何かで続編があるみたいなので、期待したいです。
アニメ『この素晴らしい世界に祝福を!2』公式サイト

さて、Dialogflowをちょっとずつ触っていますが、ボット的なものを作ってみようと思いました。
基本的にはDialogflowのチュートリアルをもとに進めていましたが、少し味気なくなります。
そこで敬虔なアクシズ教徒である私は、アクア様のSlackボットを作ることに決めました。

過去の記事はこちら。
Rubyを使ってDialogflowのお勉強

Dialogflowの準備

プロジェクトを作る

とにもかくにもまずはプロジェクトを作ります。
今回は日本語設定でいきます。
(英語の方がデフォルトで辞書が登録されているようですが。。。)

Intentを作る

まずはとりあえず適当にでも呼びかければ返してくれるようにします。
Intentの項目の中にDefault Fallback Intentがあるかと思います。

Intentで基本的には投げかけられる単語を定義するのですが、どれにもマッチしなかった場合は、このDefault Fallback Intentが返されます。
下記のようにアクア様らしい言葉を埋めてみます。

試してみる

画面右側で試すこともできます。
試しに「こんにちは」と言ってみましょう。
設定したレスポンス文からランダムに1つ返されます。
IntentもDefault Fallback Intentになっています。

WELCOME Intentを作る

もう1つ、Intentには初めから用意されているもの(Action)があります。

Default Welcome Intentは、例えばボットが新しくグループに参加する際などのメッセージを定義できます。
WELCOMEメッセージは他にも定義できます。
詳細はこちらをご覧ください。

さて、アクア様らしい文言を考えて、Text Responseに埋めてあげます。
その他の部分はそのままで良いかと思います。

ここまでで先にSlackボットの方を作っていきます。

Slackボットを作る

まずはSlack用APPを作成します。
Dialogflowの公式サイトで連携方法が記載されているので、そちらを参考に作成します。
https://dialogflow.com/docs/integrations/slack

どこかに日本語で書いてあるドキュメントがあった気がするのですが、見つかりませんでした。。。

あとはアクア様に相応しい画像を選択し、Slackにインストールします。

Dialogflowとの連携が完了していれば、もう動くようになっていると思います。

しかしアクア様は賢い方なので、もう少しDialogflowを操作します。

名前を答える

せめて名前を聞かれたら答えられるようにします。
新しいIntentを作成します。

下図のような感じです。
User saysが、こちらから聞く内容です。
複数登録できます。

保存して学習が完了すれば、答えてくれるようになります。

Cloud Functionsを使用してAPIを叩く

さてただ受け答えするだけでは面白くないので、チュートリアルを参考に、APIを叩いて、そのレスポンスを返したいと思います。
今回叩くのは、WorldWeatherOnlineという天気のAPIです。

WorldWeatherOnlineのAPIキーを取得する

WorldWeatherOnlineで会員登録をし、APIキーを取得します。
基本無料のようです。

Cloud Functionsのプロジェクトを作る

バックエンドとしては、Dialogflowでも紹介されているCloud Functionsを使用します。
(DialogflowがGoogle傘下なので当たり前かもしれませんが。。。)
Cloud Functionsはサーバーレスアーキテクチャで、このようなボット用のイベントを作るにはちょうど良いと思います。

私も初めて使用したので、クイックスタートなんかを読んでプロジェクトを作成しました。
はじめにクレジットカードを登録しなければなりませんが、無料使用枠があるので、個人使用としては課金されることはないかと思います。

プロジェクトを作成したら、新しく関数を作ります。

  • 関数の名前は分かりやすくつけておきます。(関数名と同じにすれば良いのではないでしょうか。)
  • トリガーはHTTPトリガーにします。これを選択するとURLが表示されるのでメモしておきます。
  • 実行されるソースコードはインラインエディタで作成します。(詳しいコードは後述)
  • ステージパケットを選択します。(とりあえずどこでも良いのかな?)
  • 実行する関数名を記述します。(もし今回のコードを使用するのであれば、`weatherWebhook`になります。)

さて、ソースコードですが、チュートリアルのものを使用させていただきました。
ただ、そのままだとうまく動かないので、下記のように修正しました。

  • 先ほどメモしたAPIキーを`[YOUR_API_KEY]`の部分に記載。
  • チュートリアルでは`use strict`を使用していますが、ここでコケるようなので削除します。

ソースコードはこちらにも貼り付けておきます。

const http = require('http');
const host = 'api.worldweatheronline.com';
const wwoApiKey = '[YOUR_API_KEY]';

exports.weatherWebhook = (req, res) => {
  // Get the city and date from the request
  let city = req.body.result.parameters['geo-city']; // city is a required param
  // Get the date for the weather forecast (if present)
  let date = req.body.result.parameters['date'];
  response = "Date: " + date + ", City: " + city;
  
  // Call the weather API
  callWeatherApi(city, date).then((output) => {
    // Return the results of the weather API to Dialogflow
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ 'speech': output, 'displayText': output }));
  }).catch((error) => {
    // If there is an error let the user know
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ 'speech': error, 'displayText': error }));
  });
};
function callWeatherApi (city, date) {
  return new Promise((resolve, reject) => {
    // Create the path for the HTTP request to get the weather
    let path = '/premium/v1/weather.ashx?format=json&num_of_days=1' +
      '&q=' + encodeURIComponent(city) + '&key=' + wwoApiKey + '&date=' + date;
    console.log('API Request: ' + host + path);
    // Make the HTTP request to get the weather
    http.get({host: host, path: path}, (res) => {
      let body = ''; // var to store the response chunks
      res.on('data', (d) => { body += d; }); // store each response chunk
      res.on('end', () => {
        // After all the data has been received parse the JSON for desired data
        let response = JSON.parse(body);
        let forecast = response['data']['weather'][0];
        let location = response['data']['request'][0];
        let conditions = response['data']['current_condition'][0];
        let currentConditions = conditions['weatherDesc'][0]['value'];
        // Create response
        let output = `Current conditions in the ${location['type']} 
        ${location['query']} are ${currentConditions} with a projected high of
        ${forecast['maxtempC']}°C or ${forecast['maxtempF']}°F and a low of 
        ${forecast['mintempC']}°C or ${forecast['mintempF']}°F on 
        ${forecast['date']}.`;
        // Resolve the promise with the output text
        console.log(output);
        resolve(output);
      });
      res.on('error', (error) => {
        reject(error);
      });
    });
  });
}

さて、保存した後に生成されるURLをメモしておきましょう。

DialogflowのFulfillmentの設定

Dialogflowの左メニュー、Fulfillmentの項目でWebhookの設定ができます。
先ほどのURLの出番です!

  • ENABLEDにチェックを入れます。
  • URLに先ほどのURLを入れます。

これで保存します。

天気用のIntentを作る

新しく天気を尋ねる用のIntentを作ります。
ここはチュートリアルと一緒に見ていただくと分かりやすいと思います。(Gif画像で動き付きなので)

User saysの前に、先に下記をやっておきます。

PARAMETERS

変数のようなものを用意します。
今回の場合は日付のdateと、場所のgeo-cityです。
上図のように設定すればよしなに判断してくれます。

また、REQUIREDにチェックをつけておくと、仮にそのパラメーターがなかった場合に聞き返してくれます。

Text Response

APIのレスポンスが得られなかった場合の言葉です。

Fulfillment

Webhookにチェックを入れておきます。

さて、ここまできたらUser Saysを埋めていきましょう。
WorldWeatherOnlineが、あまり日本の地理が得意ではなさそうなので、今回はアメリカの地名でいきます。

先にパラメータを設定しておくと、勝手に色がつきます。
もし色がつかなければ、文字を選択すると設定できます。

さて、試してみます。

パラメータもちゃんと判断されていることが分かります。
「SHOW JSON」を見ると、API(Cloud Functions)のレスポンスを確認できます。

Contextを使って会話っぽく

Contextを使うと会話っぽくすることもできます。
先ほどの天気のIntentに設定します。

  • Add input contextは、そのIntentを実行するために必要なContextです。
  • Add output contextは、そのIntentが終了した時に渡されるContextです。

私も初めて読んだ時はよく分からなかったのですが、触っているうちになんとなく分かってきました。

Contextを理解するために、新しく「weather.context」というIntentを作成します。
今回やりたいことは、

  1. 「weather」Intentで「本日の天気」を聞いて
  2. 「weather.context」で「同じ土地の次の日の天気」を答えてもらいます。

まずはContextを設定します。
このIntentを実行するためには「location」というContextが必要です。
また、Intentが終了する時に再度「location」というContextが渡されます。

次にパラメータやText Responseです。

特筆すべきは、geo-cityのVALUEが`#location.geo-city`になっていることです。
Contextには、値が格納されます。
今回の場合は、「weather」で聞いた時の土地の名前ですね。

またText Responseでは、パラメーター名に$をつけてアクセスもできます。

User saysにはシンプルに「明日は?」を入れておきます。

最後に試してみます。
ちゃんと同じワシントンで、違う日付になっていることが分かります。

さいごに

実際にアクア様ボットを動かした図です。

ちょっとAPIからのレスポンスは考えないといけないですね。。
今回はわざわざDialogflowやCloud Functionsなんかを使う必要がない程度だったかもしれませんが、色々と応用できそうです。

最後に、このすばの続編期待してます!

naoki85

シェア
執筆者:
naoki85
タグ: Dialogflow

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前