Dialogflow v2を自サーバーからREST方式で利用する


Dialogflowを使った文脈解析を自分のWEBサービスに利用できないか試行錯誤しています。
Dialogflow自体の話ではないのですが、Dialogflowと自前のWEBサービスとの連携部分について調べた事を残しておきます。

プリセットされたWEBサービス以外からREST形式でDialogflowを利用する

DialogflowにはIntegrationsとして、Google Assistantを始めとした各種WEBサービスとの連携手段が用意されています。
しかしながら、それに含まれていないWEBサービスからDialogflowを呼び出す方法についてDocumentではあまり解説されていません。

また、外部サービスとのやりとりはWebhookを利用する形式がメインの様です。
チャットボット用途では問題がないのですが、自然言語解析のバックエンドとして利用できるか試したいのでREST形式で呼び出してみたいと思います。

Ruby Client for Dialogflow APIを入手

自分のWEBサービスはRailsで実装する手前、rubyを使ってDialogflowを呼び出します。
まずは御決まりのgem installですね。

gem install google-cloud-dialogflow

V2対応クライアント[https://github.com/googleapis/google-cloud-ruby/tree/master/google-cloud-dialogflow]

DialogflowのAPIにはV1とV2があります。V1に対応したgemもあるのですが、V1は廃止予定ですので今からではV2に対応した方の利用をお勧めします。
V1対応クライアント[https://github.com/dialogflow/dialogflow-ruby-client]

Google Service Accountの取得

Dialogflow V2ではGoogle Service AccountのCredentialを利用してAPIにアクセスします。

それについてはDocumentが詳しいです。
APIアクセスにはService AccountとそのJSON fileが必要です。

テキストをPOSTして自然言語解析をしてもらう

Documentを読むにWebhookを使って解析結果を受け取る様ですが、そもそも、Google Assistant等の用意されたWEBサービス以外でテキストを渡して結果を受け取る方法がはっきりしません。

APIリファレンスを読む限りdetectIntentを呼び出せばなんとかなりそうです。
早速呼んで見ましょう!! 
(本当はその前に取得したService Accountの情報を使って認証を突破する必要があるのですが、それは後でちょっと補足します)

 dialogflow.rb

require "google/cloud/dialogflow"
require "google/cloud/dialogflow/v2/sessions_client"

formatted_session = Google::Cloud::Dialogflow::V2::SessionsClient.session_path(
    "(google cloud platformのプロジェクトID)",
    "(ランダムな32文字以下の文字列)"
)

response = Google::Cloud::Dialogflow::Sessions.new().detect_intent(
    formatted_session,
    {
        "event": {
            "name": "welcome",
            "language_code": "ja"
        },
    },
)

p response.query_result.fulfillment_text
p response.query_result.intent.display_name
"こんにちは!"
"Default Welcome Intent"

やった!
Dialogflowにあらかじめ登録されているWelcomeイベントを呼び出したのですが結果が返ってきました。
あとはdetect_intectに渡しているオプション次第です。

response = Google::Cloud::Dialogflow::Sessions.new().detect_intent(
    formatted_session,
    {
        "text": {
            "text": "こんにちは。明日の天気は晴れですか?",
            "language_code": "ja"
        },
    },
)

p response.query_result.parameters

テキストを渡してあげればDialogdflowが解析した結果、適当だと思われるIntentの結果が返ってきます。
渡したテキストに何が含まれていたかもquery_result.parametersで取得可能です。
ただ、Intentを指定することはできなそう。
session_pathについてAPI referenceに載っているので割愛します。
すみません

Service Accountを使った認証方法

さて、飛ばしてしまった認証部分です。
ここは自分も混乱してしまったので備忘録としてもメモします。

GOOGLE_APPLICATION_CREDENTIALSから認証情報を渡す

環境変数[GOOGLE_APPLICATION_CREDENTIALS]に取得したjsonファイルのパスを指定しておくとGoogle::Cloud::Dialogflow::Sessions.new()のタイミングでjsonから認証情報を作成してくれます。

EXPORT GOOGLE_APPLICATION_CREDENTIALS=hogehoge.json
 dialogflow.rb

require "google/cloud/dialogflow"
require "google/cloud/dialogflow/v2/sessions_client"

formatted_session = Google::Cloud::Dialogflow::V2::SessionsClient.session_path(
    "(google cloud platformのプロジェクトID)",
    "(ランダムな36文字以下の文字列)"
)

response = Google::Cloud::Dialogflow::Sessions.new().detect_intent(
    formatted_session,
    {
        "event": {
            "name": "welcome",
            "language_code": "ja"
        },
    },
)

p response.query_result.fulfillment_text
p response.query_result.intent.display_name

一部の環境変数に値をセットして認証を認証情報を渡す

環境変数[GOOGLE_APPLICATION_CREDENTIALS]以外にも環境変数を使って認証情報を渡す方法があります。GOOGLE_PRIVATE_KEY,GOOGLE_CLIENT_EMAIL,GOOGLE_ACCOUNT_TYPEの3つです。
この場合はjsonファイルのパスではなく、jsonファイル中に書かれている内容をセットします。

EXPORT GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n ..... -----END PRIVATE KEY-----\n"
EXPORT [email protected]
EXPORT GOOGLE_ACCOUNT_TYPE=service_account
 dialogflow.rb

require "google/cloud/dialogflow"
require "google/cloud/dialogflow/v2/sessions_client"

formatted_session = Google::Cloud::Dialogflow::V2::SessionsClient.session_path(
    "(google cloud platformのプロジェクトID)",
    "(ランダムな36文字以下の文字列)"
)

response = Google::Cloud::Dialogflow::Sessions.new().detect_intent(
    formatted_session,
    {
        "event": {
            "name": "welcome",
            "language_code": "ja"
        },
    },
)

p response.query_result.fulfillment_text
p response.query_result.intent.display_name

jsonファイルを作成して認証情報を渡す

環境変数に値をセットする方法はキーが固定で、複数のGoogleサービスと連携している場合にはやりにくいですね。
認証情報のファイルを動的に読み込ませる方法もできるので、こちらの方がより実用的かもしれません。

EXPORT DIALOGFLOW_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n ..... -----END PRIVATE KEY-----\n"
EXPORT DIALOGFLOW_CLIENT_EMAIL=hoge@hoge-project.iam.gserviceaccount.com
 dialogflow.rb

require "google/cloud/dialogflow"
require "google/cloud/dialogflow/v2/sessions_client"

def credential_json_path
  file_path = './dialogflow_service_account.json' # 動的に作成する認証情報ファイルのパスとファイル名
  return file_path if File.exist?(file_path)

  File.open(file_path, 'w') do |f|
    JSON.dump({
                  client_email: ENV['DIALOGFLOW_CLIENT_EMAIL'],
                  private_key: ENV['DIALOGFLOW_PRIVATE_KEY'].gsub("\\n", "\n")
              }, f)
  end

  file_path
end

def dialogflow_session_client
  credentials = Google::Auth::ServiceAccountCredentials.make_creds(
      json_key_io: File.open(credential_json_path),
      scope: ['https://www.googleapis.com/auth/cloud-platform'])

  Google::Cloud::Dialogflow::Sessions.new(
      credentials: credentials.updater_proc
  )
end

formatted_session = Google::Cloud::Dialogflow::V2::SessionsClient.session_path(
    "(google cloud platformのプロジェクトID)",
    "(ランダムな36文字以下の文字列)"
)

response = dialogflow_session_client.detect_intent(
    formatted_session,
    {
        "text": {
            "text": "こんにちは。明日の天気は晴れですか?",
            "language_code": "ja"
        },
    },
)

p response.query_result

見ての通り、利用している環境変数のキーは任意なのでご自由に。
セットする値はダウンロードしたjsonファイルの物を利用してください。

終わりに

Dialogflowをバックエンドから呼び出したくてREST形式での方法を調査してみました。
自然言語解析であればGoogle Natural Language APIを利用するのがベターだと思うのですが、自前のサービスに組み込むとしたら学習と呼び出しにかかるコストがまだちょっとお高いです...
Dialogflowであれば今の所、無料枠が大きく学習も無制限ですので、この方法でデータを食べさせてどのくらい育つか色々試してみたいですね。
DialogflowのわかりやすいUIで、文脈理解できる自然言語解析を、自サービスに組み込めるようになるとこれは大きな力になってくれるはずです。
ユーザーが何を欲しているか推測してカスタマイズクーポンを発行するとか、結構ライトにできるようになるんじゃないかな?

おまけ

React Native / Firebaseを使ってギフトボックスというモバイルアプリを作成しています。
ios / Android
もし、よろしければダウンロード&フィードバックいただければと思います。
よろしくお願いします!