Kotlin + Googleアシスタントでアクションのバックエンドを作ってみる


はじめに

2019/01/16にActions on Google(AoG)Client LibraryのJava/Kotlin版が発表されました

公式では、Kotlin + Google App Engine上のJavaサーブレットで構築する方法が紹介されています。

KotlinにはKtorというWebフレームワークがあります。
今回はフルKotlinでGoogleアシスタントのアクションを構築する方法を紹介します。

使う技術

次の表に、各言語で使う技術構成をまとめました。
GoogleアシスタントのバックエンドはNode.js + Cloud Functions For Firebaseの構成で作られることが多いです。
今回は、Kotlin + Ktor + Google App Engine(GAE)で作ります!
Ktor, GAEは環境構築の一番最初の部分から解説していきます。

言語 フレームワーク 実行環境
Kotlin Ktor PaaS: Google App Engine
Node.js (Express) FaaS: Cloud Functions For Firebase

Ktorについて

Ktor とは、Kotlinの軽量で簡単に使えるWebフレームワークです。
おなじみJetBrains社が開発をしており、安心感があります。

去年(2018)の11月にv1.0がリリースされており、Kotlinサーバサイドでの利用が期待が高まっています。
- Ktor 1.0 Released: A Connected Applications Framework by JetBrains | Kotlin Blog

構築手順

Actions on GoogleプロジェクトとDialogflowエージェントの作成

こちらは従来どおりの作り方と変わりありません。

開発環境の準備(テンプレートを利用)

↑のGihtubリポジトリにテンプレートを作成しておきました。環境構築の際にはぜひお使いください!
ダウンロード後、Intellij IDEで開いてください。

AoG Java/Kotlinクライアントライブラリの使い方

次に本題のAoG Java/Kotlinクライアントライブラリの使い方を解説していきます。

AoG Java/Kotlinクライアントライブラリは、
build.gradle に↓を指定することでダウンロードしています。

compile "com.google.actions:actions-on-google:1.0.0"

IntentHandler作成

Dialogflowからのリクエストを受け取り処理するためのIntentHandlerを作成します。

新規にConversationComponentsApp.ktを作成してください。
IntentHandlerの作成ポイントは

  1. DialogflowAppを継承させる。
  2. ForIntentアノテーションを使ってDialogflowで定義したIntent名と処理を紐付ける。
  3. ResponseBuilderを使ってレスポンスを組み立てる。

コードはこんな感じです。


class ConversationComponentsApp : DialogflowApp() {

    @ForIntent("Default Welcome Intent")
    fun welcome(request: ActionRequest): ActionResponse {
        val responseBuilder = getResponseBuilder(request)
        responseBuilder
            .add(SimpleResponse()
                .setDisplayText("純情Action!")
                .setTextToSpeech("純情Action!Action!"))
        return responseBuilder.build()
    }

    @ForIntent("Good Bye")
    fun goodBye(request: ActionRequest): ActionResponse {
        val responseBuilder = getResponseBuilder(request)
        responseBuilder
            .add("会話を終了します。さようなら")
            .endConversation()    // 会話を終了させる。 Node.js版でのconv.close
        return responseBuilder.build()
    }
}

例ではSimpleResponseを使っていますが、他にBasicCardCarouselTableなどのサンプルを公式がGithubあげてくれています。
気になる方は、こちら↓も見てみるといいでしょう!

POSTリクエストのエンドポイント用意

続いて、作ったIntentHandlerにDialogflowからのデータを渡せるようにしましょう。
DialogflowからはPOSTリクエストでJSONが送られてくるので、次のコードの通りに / をルートパスとしてJSONを受け取ります。

call.receiveText()でJSONデータを受け取り、
先程作ったした ConversationComponentsApp#handleRequestにヘッダー情報と一緒に渡します。
これによりIntent名に対応してレスポンスが返されます。
最後に、call.respond を使ってDialogflow側に返却してあげます。

fun Application.module(testing: Boolean = false) {
    val actionsApp= ConversationComponentsApp()
    routing {
        post("/"){
            // body取得
            val jsonBody: String = call.receiveText()
            // header取得
            val headerMap  = getHeaderMap(call.request)
            // IntentHandlerにbody,header情報を渡す
            val jsonResponse = actionsApp.handleRequest(jsonBody, headerMap).get()
            call.respond(jsonResponse)
        }
    }
}

// ヘッダー情報をHashMapにKey-Valueでつっこんであげる。
fun getHeaderMap(request: ApplicationRequest): Map<String, String>{
    val headers= request.headers
    val headerMap = HashMap<String, String>()
    headers.forEach{ k, v -> headerMap[k] = v.toString() }
    return headerMap
}

GAEへのデプロイ準備

ターミナル編

Google Cloud SDKをMacにインストールし、初期化まで完了させます。

初期化中にGoogle Cloud Platformプロジェクトを選択する必要があります。Actions on GoogleプロジェクトIDと同じものを選びましょう。

設定できたら、現在の設定に間違いないか確認しておくといいでしょう。

$ gcloud config list
[app]
promote_by_default = true
[core]
account = <AoGプロジェクト作成時と同じGoogleアカウントのメールアドレス>
disable_usage_reporting = False
project = <Actions on GoogleのプロジェクトID(GCP Project id)>

GAEのJavaプラグインをインストールします。

$ gcloud components install app-engine-java
$ gcloud components update

Intellij IDE編

GAE関連の設定を解説します。

build.gradleに↓を記入しておくことで、GAE用のGradleタスクが活用できるようになります。

buildscript {
    ... 
    dependencies {
        ...
        classpath "com.google.cloud.tools:appengine-gradle-plugin:1.3.4"
    }
}

...
apply plugin: 'war'
... 

dependencies {
    ...
    providedCompile "com.google.appengine:appengine:1.9.60"
}

appengine {  
    // ご自身の環境に合わせて変更してください。
    tools.cloudSdkHome="<google-cloud-sdkディレクトリへのパス>"
}

テンプレートコードのディレクトリ構成を整理しておきます。
ディレクトリツリー構造はこんな感じになっています。

...
.
└── main
    ├── kotlin
    │   ├── Application.kt
    │   └── ConversationComponentsApp.kt
    ├── resources
    │   ├── application.conf
    │   └── logback.xml
    └── webapp
        └── WEB-INF
            ├── appengine-web.xml
            ├── logging.properties
            └── web.xml

kotlinファイルは、src/main/kotlinに、プロジェクトルートに存在するktorの設定ファイルがsrc/main/resourcesにあります。
src/main/webappというディレクトリがあるのですが、詳細については↓を参考にしてみてください

GAEへデプロイ! と動作確認!

ここまでの設定が完了したら、
./gradlew appengineDeployかIDEのGradleタスクからappengineDeployを実行してください。
成功すると、https://<gcp-project-id>.appspot.comのURLがコンソールに表示されるので、
こちらをメモっておき、DialogflowのFullfillmentに設定すれば完了です!

...
Deployed service [default] to [https://<gcp-project-id>.appspot.com]
...