Watson AssistantからIBM Cloud functionsを使う時の考慮点


初めに

本記事は、開発相談会#4 : IBM CloudFunction/Laravelでのディスカッション内容です。

サンプルアプリケーションについて

こちらで作成した、LaravelとWatson Assistantを連携したアプリケーションを使って検証をしています。
ソースコードはGithubで公開しています。

Watson AssistantからIBM Cloud Functionsを使うには…

使うだけであれば簡単です。
[公式のドキュメント](https://cloud.ibm.com/docs/assistant?topic=assistant-dialog-webhooksの力を借りることで、かなり簡単にWatson AssistantからIBM Cloud Functionsを使うことが出来るようになりました。

実際に使ってみると…

ただ、実際にユーザーアプリケーションからWatson Assistantを呼び出すにあたって、疑問ポイントがありました。

IBM Cloud Functionsのキーを持たせる場所

IBM Cloud Functionsを呼び出す際には、credentialsというパラメータで、userpasswordを渡す必要があります。

下記の様に実際に呼び出すNodeでAPIキーを設定して呼び出すこともできますが、公式ドキュメントにも記載がある通り、コンテキストの一部として資格情報を渡すのが正解のようです。

資格情報を保護するために、資格情報は Watson Assistant ワークスペースに保管しないでください。
代わりに、コンテキストの一部としてクライアント・アプリケーションから渡してください。
メッセージ・コンテキストの $private セクション内にコンテキスト変数をネストすると、
この情報が Watson ログに保管されることを防止できます (例: `$private.my_credentials`)。

(contextをNodeで直接設定する方法)←本番でこれはNG

{
  "context": {
    "private": {
      "my_credentials": {
        "user": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    }
  },
  "output": {
    "generic": [
      {
        "values": [
          {
            "text": "どこの天気を知りたいですか?"
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

IBM Cloud Functionsを呼び出すNodeの書き方

{
  "output": {
    "generic": [
      {
        "values": [
          {
            "text": ""
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  },
  "actions": [
    {
      "name": "/enomotuProject_testSpace/getWeather",
      "type": "server",
      "parameters": {
        "location": "$repeat"
      },
      "credentials": "$private.my_credentials",
      "result_variable": "context.weather"
    }
  ]
}

PHPから呼び出すときのSample

<?php
namespace App\Services;

use GuzzleHttp\Client;

class CallWatsonAssistant{
    /**
     * Watson Assistantを呼び出すモジュール
     *
     * @param string $spokenWord ユーザーが入力した文字列
     * @param array $context watson assistantのcontextデータ
     * @return json Watson AssistantをCallした結果
     */
    public function call(string $spokenWord,array $context)
    {
        $context["private"] = ["my_credentials" => 
            [
                //ここは環境変数から取得
                "user"     => config('watson.icf_user'),
                "password" => config('watson.icf_password')
            ]
        ];

        $requestData  = json_encode(['input'=>['text'=>$spokenWord],'context'=>$context]);
        $headers = ['Content-Type' => 'application/json','Content-Length' => strlen($requestData)];
        $curlOpts = [
            CURLOPT_USERPWD        => config('watson.user_name').':'.config('watson.password'),
            CURLOPT_POSTFIELDS     => $requestData
        ];
        $path         = config('watson.workspace_id') . '/message?version=2018-07-10';
        $guzzleClient = new Client(['base_uri'=>'https://gateway-fra.watsonplatform.net/assistant/api/v1/workspaces/']);
        return $guzzleClient->request('POST',$path,['headers'=> $headers,'curl'=>$curlOpts])->getBody()->getContents();
    }
}

ただ、このままですとREST APIのレスポンスにIBM Cloud FunctionsのcontextにAPIキーが埋め込まれてしまう(下図参照)のでWebアプリから使う時には何らかの工夫が必要だと思います。

私は下記の様にprivatecontextをクリアするNodeを持たせて、全てのNodeの最後はここにJump toするようにしています。
ただ、必ずJumptoを仕込む手間が面倒なのと、このJump toを忘れた時にAPIキーが外に出る恐れがあるので、より良い方法を模索中です

エラーハンドリングはどうするか?

何も判定していない状態では、ありえない住所を入力した場合に下記のようになってしまします。

ただ、実際のWebアプリケーションに返る値は正常値で、上記の例ですと「ほげほげの天気は」で文章が止まったままとなってしましまいます。
理想は「入力された地域が見つかりません」となることだと思いますが、このエラーハンドリングも掘り下げて考える必要があります。

CASE1 資格情報などが誤っており呼び出しに失敗する。

この場合は、公式ドキュメントにある通り、contextcloud_functions_call_errorが設定されます。
この時にはエラー内容をNodeに返してしまっても良いかもしれません。

CASE2 呼び出し自体には成功したがICF側でエラーとなった場合

様々な原因が考えられますが、可能な限りでIBM Cloud Function側でエラーの原因やメッセージなどをセットして返せると良いのでは、と思います。
というのも、ICF側で502等のステータスとなった場合には、returnしたcontextにはerrorという値のみが入ってくることになる為です。
ここも気を付ける点になりそうです。

おまけと宣伝

上記とは無関係ですが、勉強会の当日にはIBM様や@tokidaさんからIBM Cloud Functionに関する興味深い話を聞くことが出来ました。
箇条書きではありますが、IBM Cloud Functionsについて下記のメリットについて細かく聞くことが出来ました。

  1. 応答時間はCloud Foundryとほぼ同じ応答時間です!
  2. IBM Cloud FunctionsのSLAは99.95%です!
  3. dockerを使えばどのような言語でもサービスでも実装OK
  4. 必ずしもマイクロサービス的な使い方をする必要はない…が、1つのFunctionは60sまでしか動かないので注意。
  5. 最初からLog Analysysサービスと統合されているのですぐにログ管理ができる!
  6. 今のシステムの隙間を埋めるような使い方が可能。
    Apache Open Whiskはログ管理機能がない(IBM Cloud Functionsの優位性はここにある)

IBM Cloudを使ってみたい(もしくは実際に使った)けど、どう使ったらよいか分からない!という方は、是非、相談会に遊びに来てください。
次回の開催情報はこちらです。