1
3

More than 5 years have passed since last update.

Hangouts Chat のチャットボットを、Python on Google App Engineを使って実装してみた(2)

Posted at

はじめに

前回のやまびこBOT を DialogFlow と連携させて、より「BOTらしい」アプリケーションを実装してみたいと思います。

DialogFlow の準備

今回はこちらの投稿にある例(前半「Intentを作る」まで)に倣って、DialogFlow 側のテスト環境を準備しました。
尚、DialogFlow の詳しい設定方法等は、紹介した投稿内で丁寧に説明されていますので、ここでは割愛します。

DialogFlow Python SDK のインストール

DialogFlow Python SDK をインストールします。

$ pip install apiai -t lib

尚、Python on GAE では、C言語で開発されたモジュールをアップロードしても利用することが出来ない(仮にアップロードしても動かない)為、GAE側で用意された組み込みサードパーティ ライブラリを使う必要があります。
そこで、DialogFlow Python SDK をインストールした際、依存関係でインストールされた numpy がC言語で開発されている為、これをディレクトリごと削除し、Python仮想環境へ numpy をインストールし直します。
また、インストールする numpy のバージョンは GAE 組み込みモジュールのバージョンに合わせます。

$ rm -rf lib/numpy*
$ pip install numpy==1.6.1

app.yaml に GAE の組み込みモジュール numpy を利用する設定を追加します。

$ cat <<EOF >>app.yaml
libraries:
- name: numpy
  version: "1.6.1"
EOF

DialogFlow のアクセストークンを取得

DialogFlow を API 経由で利用する場合、Client access token が必要なため、次の手順でアクセストークンを取得します。

DialogFlow コンソールを開き、エージェント名の右側に表示されている歯車マークをクリックします。
screencapture-console-dialogflow-api-client-2018-04-11-16_04_57 - コピー.png

表示されたページに Client access token が表示されます。
screencapture-console-dialogflow-api-client-2018-04-11-16_04_57.png

アクセストークンを環境変数として設定

app.yaml 内にアクセストークンを環境変数として設定することもできますが、トークンはその性質上秘匿したい情報にあたり、GitHub に誤ってコミットされないよう別ファイルに定義します。

秘匿したい環境変数を定義する secret.yaml を作成し、アクセストークンを記述します。

$ cat <<EOF >secure.yaml
env_variables:
  CLIENT_ACCESS_TOKEN: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
EOF

app.yaml に、secret.yaml をインクルードする設定を追加します。

$ cat <<EOF >>app.yaml
includes:
- secret.yaml
EOF

.gitignoresecret.yaml を追加します。

$ cat <<EOF >>.gitignore
secret.yaml
EOF

BOTアプリケーションの変更

Hangouts Chat からのリクエストメッセージを DialogFlow に渡し、DialogFlow からのレスポンスメッセージを Hangouts Chat へ返すよう、前回作成した bot.py を変更します。

$ vi bot.py
bot.py
#!/usr/bin/env python
# coding: utf-8
"""Example bot that returns a synchronous response."""

from flask import Flask, request, json

app = Flask(__name__)

import os
import re
from apiai import ApiAI
from uuid import uuid4

@app.route('/', methods=['POST'])
def on_event():
  """Handles an event from Hangouts Chat."""
  event = request.get_json()
  if event['type'] == 'ADDED_TO_SPACE' and event['space']['type'] == 'ROOM':
    text = 'Thanks for adding me to "%s"!' % event['space']['displayName']
  elif event['type'] == 'MESSAGE':
    ''' 元のコードをコメントアウト
    text = 'You said: `%s`' % event['message']['text']
    '''

    ''' 追加(ここから) '''
    ai = ApiAI(os.environ['CLIENT_ACCESS_TOKEN'])
    req = ai.text_request()                         #リクエストはテキストデータ
    req.lang = 'ja'                                 #リクエストメッセージは日本語(ja)
    req.session_id = str(uuid4())                   #DialogFlow間のセッションIDをランダムなUUID型でセット
    req.query = re.sub(r'^@.* ', '', event['message']['text'])  #メッセージからメンション("@ボット名 ")を削除
    res = req.getresponse()                         #DialogFlowへリクエスト、レスポンスを取得
    dic = json.loads(res.read())                    #レスポンスボディを辞書型に変換
    text = dic['result']['fulfillment']['speech']   #レスポンスボディからテキストメッセージを取得
    ''' 追加(ここまで) '''

  else:
    return
  return json.jsonify({'text': text})

if __name__ == '__main__':
  app.run(port=8080, debug=True)

ローカルテスト

テストデータ(event.json)のメッセージ部分(['message']['text'])を、DialogFlow にリクエストする日本語メッセージ(@Demo 1日の東京を予約 等)へ変更します。

event.json
{
  "type": "MESSAGE",
  "eventTime": "2017-03-02T19:02:59.910959Z",
  "space": {
    "name": "spaces/AAAAAAAAAAA",
    "displayName": "Chuck Norris Discussion Room",
    "type": "ROOM"
  },
  "message": {
    "name": "spaces/AAAAAAAAAAA/messages/CCCCCCCCCCC",
    "sender": {
      "name": "users/12345678901234567890",
      "displayName": "Chuck Norris",
      "avatarUrl": "https://lh3.googleusercontent.com/.../photo.jpg",
      "email": "chuck@example.com"
    },
    "createTime": "2017-03-02T19:02:59.910959Z",
    "text": "@Demo 1日の東京を予約",
    "thread": {
      "name": "spaces/AAAAAAAAAAA/threads/BBBBBBBBBBB"
    },
    "annotations": [
      {
        "length": 8,
        "startIndex": 0,
        "userMention": {
          "type": "MENTION",
          "user": {
            "avatarUrl": "https://.../avatar.png",
            "displayName": "TestBot",
            "name": "users/1234567890987654321",
            "type": "BOT"
          }
        },
        "type": "USER_MENTION"
      }
    ]
  },
  "user": {
    "name": "users/12345678901234567890",
    "displayName": "Chuck Norris",
    "avatarUrl": "https://lh3.googleusercontent.com/.../photo.jpg",
    "email": "chuck@example.com"
  }
}

ローカル開発サーバを起動します。

$ dev_appserver.py .

別ターミナルから、BOTへテストデータをリクエストしてみます。

$ curl http://localhost:8080 -X POST H "Content-Type: application/json" -d @event.json
{
  "text": "2018-05-01 \u306e \u6771\u4eac\u30bf\u30a6\u30f3\u30db\u30c6\u30eb \u3092\u4e88\u7d04\u3057\u307e\u3057\u305f\u3002"
}

上記のようにレスポンスメッセージにマルチバイト文字が含まれていると、マルチバイト文字が Unicode にエンコードされて表示されます。
そういう場合は、レスポンスを jq にパイプすることで Unicode がデコードされて正しく表示されます。
もし jq がインストールされていない場合は、EPEL からインストールしましょう。(インストール方法は割愛します)

$ curl http://localhost:8080 -X POST H "Content-Type: application/json" -d @event.json | jq
{
  "text": "2018-05-01 の 東京タウンホテル を予約しました。"
}

BOTアプリケーションのデプロイ

ローカルテストで問題がなければ、BOTアプリケーションを GAE へデプロイします。

$ gcloud app deploy

Hangouts Chat からテスト

Hangouts Chat から BOT にメッセージを送り、動作を確認します。

BOTへダイレクトメッセージを送ると、次のようなレスポンスが返ってくると思います。
screencapture-chat-google-dm-jNN4pAAAAAE-2018-04-12-10_46_31.png

次回

Hangouts Chat ではテキストメッセージの他に、カードメッセージと呼ばれる、より複雑なフォマットのメッセージをサポートしています。
次回は、そのカードメッセージを BOT に実装してみたいと思います。

1
3
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
1
3