チャットボットAdventCalendarの8日目を担当します株式会社コンシェルジュの白倉です。
半年前から、チャットボットの応答を返すエンジン「コンシェルジュ」を作っています。
いくつかのサービスで使われていますが、その中でも1ヶ月前から始めたニッポン放送様のオールナイトニッポンモバイルとのコラボでカノエラナさんのウェブラジオをLINE上で本人と対話しながら聞かせる試みを始めました。
我々にとっては、初めてのそこそこアクセスが来るサービスでしたが、幸いなことに今のところ順調にユーザー数を伸ばせています。
今回は、実際にこの「カノエラナのオールナイトニッポンモバイル」の裏で動いているボットアーキテクチャを説明しようと思います。
めちゃくちゃ大規模なサービスじゃないけど、そこそこのユーザーが来るボットサービスの裏側として誰かの参考になれば幸いです。
Botサービスの概要
今回は、裏のアーキテクチャの話をしようと思っていますが、先にそもそもどんなアプリケーションなのかを簡単に説明します。主要な機能は、
- チャットアプリ上でBotがユーザーのメッセージに対してどう返答すべきかのシナリオを、フローチャートで作成できる
- Facebook, Line, Slackのユーザーに対して、このシナリオに沿ったBotメッセージを送信できる
- コンシェルジュ独自のチャットシステムで、Botと対話ができ、かつ、人間のオペレーターがBotに代わってユーザーと話すことができる。
- Botの応答を返すAPIを提供し、顧客が自社のシステム上に上記シナリオに沿って動くChatbotを作ることができる。
となります。
なので、基本的には、なんらかのチャットアプリからHTTPでユーザーメッセージを受け取り、すでに作成されてるシナリオに沿ったBotメッセージを返すウェブサービスです。
基本的な技術要素
- Ruby / Ruby on Rails
- React.js, JQuery
- AWS
はじめに、我々のシステムですが、RailsをベースにフロントエンドにReact.jsやらJQueryを使ったウェブアプリケーションとなります。
サーバーは全てAWSで運用してます。
基本方針
このサービスのアーキテクチャにする上で僕が個人的に考えた方針を説明します。
アプリケーションは機能単位で分ける
1つのサーバーに全ての機能を詰めるのでなく、機能単位でサーバーを分割し、互いのサーバーはAPIを介してHTTP通信する形をとります。
古くは(今もかな)SOAとか今はマイクロサービスとか言われる話なんですが、もちろんメリットデメリットがあり、特に1つのアプリケーションとして作る方針に比べ余計なAPI通信が増えるのでその部分の開発工数が余分にかかるのは初期のスタートアップにとっては痛いところです。
ただ、やはりサービスが大きくなった時に、後からの分割は過去の経験やいろいろな人の話を聞くと非常に辛いということと、システム構成的に意外と疎結合なものが多いというのがこの方針にした理由となります。
AWSのElastic Beanstalkに頼り切る
現状、我々はかなり小さいチームで、インフラ専任のエンジニアはいません。
その状況の中で、インフラ運用をしようとするならばできる限り、色んなお手製のインフラツールや設定をせずにデフォルトでサクッと環境構築してくれて、あんまり手をかけなくても運用できちゃうものがいいと思います。
Herokuも少し運用しましたが、さすがにやりたいことができないことも多かったのでもうちょい自由にいじれるけど、基本は、デフォルトで使えるElastic Beanstalkを採用しました。
インフラ構成
サービス単位のインフラ構成
さて、まずは今のインフラ構成を説明します。
先ほど説明した通り、機能単位でサービスを分けて作っており、1つのサービスはElastic Beanstalkで管理されています。まずは、このサービス1つのインフラ構成を見ます。
Elastic Beanstalkの良さは、とりあえずボタンぼちぼちするだけで、LoadBalancerから自動でオートスケールするウェブサーバー群、DBサーバー、監視系・セキュリティ系の設定をやってしまうお手軽さにあります。
このぼちぼちとするデフォルト構成で1つのウェブサービスは、以下のような構成になります。
ボットシステム全体のインフラ構成
各サブサービスをまとめた全体のインフラ構成は、以下のようになります。
今回のカノエラナのBotの場合は、Lineを使用するため、流れとしては以下のようになります。
- ユーザーがLineにメッセージを打つ
- LineからフロントWebサーバーに対してHTTPでメッセージが送られる
- フロントWebサーバーは自身のサーバー上にworkerを作成しLineにリクエストを返す
- フロントWebサーバー上に作られたworkerは、対話エンジンサーバーに対してHTTPでリクエストを投げる
- 対話エンジンサーバーは、認証サーバーに正しいところから来たリクエストかどうかを問い合わせたのち、Botが返すべきメッセージをシナリオを参照しながら作成し、workerに返す。
- workerは、Lineに対して、Botメッセージを送信する。
フロントWebサーバー
フロントWebサーバーは、LineやFacebookなどのチャットアプリからのリクエストを受ける窓口的なサービスになります。
各チャットアプリごとに違う形式でリクエストが送られて来るので、このサーバーで共通のフォーマットにします。
そして、対話エンジンにリクエストを投げるためのworker(バックエンドのジョブ)を作ります。
workerは、ActiveJob(Resque)を使いました。
なお、workerを作らずに直接対話エンジンにリクエストを投げる方式も作っており、Lineのライセンスに応じて使い分けれるようにしています。
workerを作ってすぐにHTTPレスポンスをLineへ返すという方式は大きなリクエストが同時に来た場合にもそれなりに耐えれる利点がありますが、一方でLineの場合、push通知を使う必要があり、ライセンスによって使えません。ちなみにFacebook messengerは誰でもpush通知が使えるので全てworkerを作る形式で実行することができます。
対話エンジンサーバー
対話エンジンサーバーは本botサービスの中核となるサービスで、メッセージを受け取り適切なbotメッセージを作るサーバーとなります。
また、botメッセージをどのように返すべきかを定義するシナリオを作る機能、人間のオペレーターがbotに代わって対話するための機能も含まれています。
オペレーターがユーザーと対話するチャット機能は、Rails5から標準となったAction Cableを使って実装しています。このActionCableが使うWebSoecketはAWSの従来のクラシックロードバランサーと大変相性が悪く、かなり四苦八苦しました。
が、今年の8月にApplication Load Balancerというレイヤー7で動くロードバランサーをAWSがリリースし、おかげですごく簡単になりました。
また顧客のウェブサイトやウェブアプリケーションから直接対話エンジンを使ってもらえるよう、外部に向けてAPIを公開しています。
そのAPIは、Grapeをベースに作りました。
うちでは、すべてのAPIをGrapeベースで作っています。
Railsのコントローラーで作るのと比べると、どういうAPI仕様かがわかりやすい点が気に入っています。
認証サーバー
認証サーバーは、ユーザーの認証・認可及び、APIの認証を行うサーバーになります。
他のサブサービス群にログインするときは、必ずここで認証したのちに、飛ぶ形になります。
doorkeeperを使ったoauth認証を実装しましたが、ちょっとtoo muchだったかもしれません。認証を通したいサービスが全て社内の信用できるサーバーである場合かつ、同じドメイン・サブドメインである場合、単純にセッションを共有してしまえばいいだけだったかなと思いました。
なお、APIは実行前に必ずこちらの認証サーバーでトークン認証を行います。
具体的には、すべてのAPIに向けたリクエストが事前に発行したトークンをパラメーターに乗せて実行され、APIはそのパラメーターを認証サーバーに問い合わせることで認証・認可します。
APIは、問い合わせがかなり多くなるため、トークンのデータは、Redis(ElastiCache)にキャッシュされています。
各サービス専用の情報管理サーバー
実際のチャットボットは、それぞれのボットごとに固有の情報データを必要とします。
例えば、ワインを販売するボットだったら、ワインのデータベースがいりますね。
こういうサービス固有のデータはそれ自体を1つのサービスとして立ち上げ、API経由で対話エンジンサーバーから呼び出します。
最後に
弊社のBotの裏側のアーキテクチャをざっくり説明しました。
チャットボットは今熱い技術要素の1つだと思います。
我々も手探りで色々作っており、こういう風に作ったほうがいいなど色々情報交換したくて仕方ないです。
また、我々は、一緒にチャットボットを作ってくれるエンジニア・デザイナーを募集中です。
まだ10名以下の小さな会社ではありますが、わきあいあい楽しくやっています。
興味あれば、気軽にこちらまで!
さらにカノエラナのオールナイトニッポンモバイルは、誰でも参加できます!
是非是非参加して、カノエラナさんの音楽を聴いてもらえればと!
こちらからどうぞ!