Google Homeと会話してみる

この記事は[Retty Inc. Advent Calendar 2017](https://qiita.com/advent-calendar/2017/retty) 3日目です。
昨日は @tkngue さんの[BigQuery の ベストプラクティス 〜 2017年度版 〜](https://qiita.com/tkngue/items/a6f6f21dabe392041af8)でした。

こんにちは！Rettyでエンジニアをしている諏訪です。

今スマートスピーカーとっても熱いですね！
2017年に入り、Google Homeをはじめとした日本語に対応したスマートスピーカーが数種類発売されました。
勢いに乗って私もGoogle Home Miniを買ってしまったので、今回はGoogle Homeと会話するアプリケーションを作ってみようと思います。

アプリを作る手段はいくつかあるみたいですが、私はActions SDKと自宅サーバーを使用しました。

## プロジェクトを作る

[Actions on Google](https://console.actions.google.com)のコンソールへアクセスし、適当な名前でプロジェクトを作成します。

<img width="739" alt="スクリーンショット 2017-11-17 19.59.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/147445/28761281-740b-c174-6f18-fdd0f8b0d9cb.png">

今回はActions SDKを使用してアプリケーションを作っていくのでActions SDKのBUILDをクリックします。

<img width="754" alt="スクリーンショット 2017-11-17 20.01.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/147445/3962b633-db22-d0c9-08aa-6dae98ca6e67.png">

するとプロジェクトにアクションを追加するためのコマンドが出てくるので、メモっておきましょう。

<img width="690" alt="スクリーンショット 2017-11-17 20.04.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/147445/80b95cd7-a3a1-06f2-45c8-ee27fbd07cd4.png">

## アプリを作る

Actions SDKを使用するためNodeとExpressを使ってエンドポイントを作っていきます。
会話部分は自作の会話AIを使用したかったのですが、５秒以内にレスポンスしないといけないという制限があるため、今回はリクルートのTalk APIを使用します。

私の環境は以下の通りです。

- Node v8.7.0

`npm init`をして作っていきます。


```index.js
var express = require("express"),
  log4js = require("log4js"),
  morgan = require('morgan'),
  bodyParser = require('body-parser'),
  ActionsSdkApp = require('actions-on-google').ActionsSdkApp;
  app = express(),
  talk = require('./talk.js');

app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

log4js.configure({
  "appenders": {"out": {"type": "stdout"}},
  "categories": {"default": {"appenders": [ "out" ], "level": "debug"}}
});
var logger = log4js.getLogger('out');
app.use(log4js.connectLogger(logger));

var server = app.listen(3000, function(){
  logger.info("Node.js is listening to PORT:" + server.address().port);
});

app.post('/', function(request, response, next){
  let sdk = new ActionsSdkApp({request, response});
  logger.info(JSON.stringify(request.body));

  function mainIntent (sdk) {
    logger.info('New Conversation: ', JSON.stringify({
      conversationId: sdk.getConversationId(),
      userId: sdk.body_.user.userId
    }));
    sdk.ask({speech: "こんにちは", displayText: "こんにちは"});
  }

  function rawInput (sdk) {
    let input = sdk.getRawInput();
    logger.info('Input: ', JSON.stringify({
      rawInput: input,
      userId: sdk.body_.user.userId
    }));
    if (input === 'バイバイ') {
      sdk.tell('ばいばい');
    } else {
      talk(input, function(text){
        sdk.ask({speech: text, displayText: text})
      });
    }
  }

  let actionMap = new Map();
  actionMap.set(sdk.StandardIntents.MAIN, mainIntent);
  actionMap.set(sdk.StandardIntents.TEXT, rawInput);

  sdk.handleRequest(actionMap);
});
```
mainIntentはアプリに接続した時に呼び出され、rawInputはユーザーから発話があった時に呼び出されます。
sdk.tell()ではレスポンス後アプリが終了しまうので、
sdk.ask()でユーザーの発話を待ち、会話のループに入ります。

この例では`バイバイ`を終了命令にしてますが、`停止`でも終了できます。

```talk.js
var request = require('request');

module.exports = function(talk, callback){
  request({
    url: 'https://api.a3rt.recruit-tech.co.jp/talk/v1/smalltalk',
    method: 'POST',
    form: { apikey: process.env.TALK_APIKEY, query: talk },
    json:  true
  }, (err, response, body) => {
    let text = err ? "エラーだよ" : body.results[0].reply;
    callback(text);
  });
}
```

アプリの作成が終わったら好きなところにデプロイしましょう。
私はDockerでコンテナ化して自宅サーバーにデプロイしました。

## アクションの設定

アクションの設定には[gactions CLI](https://developers.google.com/actions/tools/gactions-cli)が必要なので以下のコマンドでダウンロードしてきましょう。実行権限も必要なので付与します。

    $ curl -O https://dl.google.com/gactions/updates/bin/darwin/amd64/gactions/gactions
    $ chmod +x gactions

action.ja.jsonを作成してアクションの定義をします。複数の言語に対応するにはそれぞれのロケールに合わせたファイルを作成する必要があります。

```action.ja.json
{
  "locale": "ja",
  "actions": [
    {
      "description": "Default Welcome Intent",
      "name": "MAIN",
      "fulfillment": {
        "conversationName": "conversation_1"
      },
      "intent": {
        "name": "actions.intent.MAIN"
      }
    }
  ],
  "conversations": {
    "conversation_1": {
      "name": "conversation_1",
      "url": "YOUR APP URL",
      "fulfillmentApiVersion": 2
    }
  }
}
```

作成し終わったらプロジェクトを作成した時にメモったコマンドを使用してアクションを設定できます。`PACKAGE_NAME`は先ほど作成した`action.ja.json`に変更します。初回は認証情報を取得する必要があるためコマンドラインの出力にしたがって取得します。

    $ ./gactions update --action_package action.ja.json --project talkapp-hoge

`Your app for the Assistant for project talkapp-hoge was successfully updated with your actions. Visit the Actions on Google console to finish registering your app and submit it for review at https://console.actions.google.com/project/talkapp-hoge/overview`みたいな出力が出れば成功です。出力されたURLにアクセスし、アプリの概要の設定をしていきます。

<img width="806" alt="スクリーンショット 2017-11-17 20.41.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/147445/762fda5d-9320-d681-c601-5c5c54acfe9e.png">

2のApp informationから始めていきます。

<img width="794" alt="スクリーンショット 2017-11-17 20.59.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/147445/4051696d-b68c-82af-c733-ad3aa7c3384f.png">

まず左上の言語をJapaneseに設定します。
`Assistant app name`はgoogle homeからこのアプリを呼ぶために使用する名前なのでよく考えて設定しましょう。呼んだときにgoogle homeがどう認識するのか(ひらがな、カタカナ、漢字等)をしっかり確認しておかないと実機テストの時に呼んでも反応せずハマります（1時間無駄にしました）
[マイアクティビティ](https://myactivity.google.com/myactivity?product=31)で自分の発話がどう認識されてるのか確認できるので設定前に確認しておいたほうがいいです。

Detailsセクションの`Assistant app voice`で声を変更できるので使用したい声をあらかじめ[ここ](https://developers.google.com/actions/localization/languages-locales)で確認しておきます。

あとはよしなに設定します。

`Account linking`の設定はなくてもいいようなので今回は設定しません。

<img width="808" alt="スクリーンショット 2017-11-17 21.11.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/147445/29399b70-49a4-9387-ada9-c7a0e8a5a625.png">

ここまでくればほとんど完成です。
TEST DRAFTからWEBシミュレーターに入れます。ブラウザで会話のテストができます。また、アプリを作成しているアカウントと同じアカウントに接続したGoogle Homeであれば実機でもテストできます。

<img width="1628" alt="スクリーンショット 2017-11-17 21.22.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/147445/da8bf1d5-d1e2-ff08-acb2-904478678488.png">

このままでも自分だけなら使用できますが、他の人にも公開したい場合SUBMIT DRAFT FOR REVIEWからGoogleのレビューをもらうことで他人にも公開できるようです。

## まとめ

いかがでしたでしょうか？
今回はGoogle Homeで簡単に会話アプリをつくってみました。
Google Homeでは高性能な音声認識ができるため、ぜひ色々と試して見てください！