LoginSignup
8
2

More than 3 years have passed since last update.

【古い記事です】小ネタ - Watson API / Node.jsでコールバック(やPromise)を意識せずAPIを逐次実行するコードを書く方法

Last updated at Posted at 2018-11-08

:warning: (2019/06/12) 当記事にあるpromisifyを使う方法はWatson SDK V3までの古い情報です。2019/3月に出たSDK V4ではもっと簡単になったので、最新のV4を使う予定の方は記事 :newspaper:小ネタ: Watson SDK V4(Node.js)では、もっと楽にコールバック(やPromise)を意識せずAPIを呼び出せるようになりました」のほうをご参照くださいませ。


たとえば「Assistantの応答の確信度が基準値以下の場合は、Discoveryでロングテールの検索を行って回答を提示する」など複数のWatson APIを同期的に/シーケンシャルに呼び出したい場合があります。

image

釈迦説ですがNode.js環境は非同期実行の環境なので、こういうことをしたい場合のコードではコールバックとかPromiseを使う必要があります。でもPromiseって直感的でなくて、なんか面倒だなあと思ってました1。で、StackOverflowで以下のエントリーを発見しました。

:newspaper:Using Async/Await in WATSON Nodejs SDK
:newspaper:How can I promisify Watson Assistant functions to allow async/await in node?

要はNode.jsのV8からはpromisifyが標準で搭載されて簡単に非同期を意識しないコードが書ける2そうですが、この機構がWatson APIでも利用できます。例えば以下の感じでWatsonAssistantのインスタンスの”message”APIをpromisifyでくるんでawait付きで呼び出すと、Watson側で処理が完了するまで制御は待ち状態になり、完了してからプログラムに制御が戻る=同期的なコーディングができます。(非同期のややこしいコードが不要になります)

const messagePromise = util.promisify(assistant.message);
var response = await messagePromise.call(assistant, params);

以下は「Assistantに問いかけて確信度が基準値を下回ったらDiscoveryに改めて問いかける」シナリオの簡単なサンプルですが、同期的な=わかりやすいコードが実現できているのが見て取れると思います。3

app.js
'use strict';
/*
    Watson Assistant & Watson Discoveryをpromisifyを使って逐次実行
*/
/* ibmcloud上のnode.jsで実行するならここをコメントアウト
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(port, () => console.log('Example app listening on port %d', port));
*/
const util = require('util');

console.log('==== start=======');
const assistant_iam_apikey ='xxxxxxxxxxxxxx';
const assistant_url ='https://gateway-syd.watsonplatform.net/assistant/api';
const assistant_workspace_id = 'xxxxxxxxxxxx';

const discovery_iam_apikey ='xxxxxxxxxxxxxx';
const dicovery_iam_url ='https://gateway-syd.watsonplatform.net/discovery/api';
const discovery_collection_id ='xxxxxxxxxxxxxx';
const discovery_configuration_id ='xxxxxxxxxxxxxx';
const discovery_environment_id = 'xxxxxxxxxxxxxx';

var AssistantV1 = require('watson-developer-cloud/assistant/v1');
var assistant = new AssistantV1({
    version: '2018-09-20',
    iam_apikey: assistant_iam_apikey,
    url: assistant_url
});

var DiscoveryV1 = require('watson-developer-cloud/discovery/v1');
const discovery = new DiscoveryV1({
  url: dicovery_iam_url,
  version: '2018-10-15',
  iam_apikey: discovery_iam_apikey
});

// Assistant
async function message(text) {
    console.log("message Start:[" + text + "]")
    var params = {
        input: { text: text },
        workspace_id: assistant_workspace_id
    };
    const messagePromise = util.promisify(assistant.message);
    var response = await messagePromise.call(assistant, params);
    return response;
}

// Discovery
async function query(text) {
    console.log("query Start:[" + text + "]")
    var params = {
        environment_id: discovery_environment_id,
        collection_id: discovery_collection_id,
        passages: true,
        passages_count: 1,
        natural_language_query: text
    };
    const queryPromise = util.promisify(discovery.query);
    var response = await queryPromise.call(discovery, params);
    return response;
}

//***********************************************
//  Promise/非同期を意識したコードを書かずに済んでます
//***********************************************
async function main() {
    var min_confidence = 0.6; // 求める確信度。これを下回ったらDiscoveryに聞きなおす
    var text = 'Excelで表の必要な部分だけを印刷するにはどうすればいいでしょうか';

    var response = await message(text);
    console.log('confidence of Assistant:',
            JSON.stringify(response.intents[0].confidence, null, 2));

    if (response.intents[0].confidence < min_confidence ){
        // Assistantの回答の確信度が所定の基準を下回ったらDiscoveryにて再度検索
        var response = await query(text);
        console.log('message response from Discovery:',
            JSON.stringify(response.passages, null, 2));        
    } else {
        // Assistantの回答の確信度が所定の基準以上ならAssistantの回答を提示
        console.log('message response from Assistant:',
            JSON.stringify(response.output.text[0], null, 2));
    }
}

main()

以下は実行結果例です

==== start=======
message Start:[Excelで表の必要な部分だけを印刷するにはどうすればいいでしょうか]
confidence of Assistant: 0.504277801513672
query Start:[Excelで表の必要な部分だけを印刷するにはどうすればいいでしょうか]
message response from Discovery: [
  {
    "document_id": "673b0e32529bf4c614c15acaebb47bab",
    "passage_score": 40.21290429830693,
    "passage_text": "Excelでは、印刷対象を選択して、必要な部分だけを印刷することができます。ここでは
、ご使用のExcelのバージョンに応じた参照先を案内します。\n\n対処方法\n\nExcelで表の必要な部分だけを印
刷する方法については、以下の情報を参照してください。\n※ ご使用のExcelのバージョンに応じた項目をクリ
ックしてください。\n\nExcel 2013の場合\nExcel 2013で表の必要な部分だけを印刷する方法\n\nExcel 2010の
場合\nExcel 2010で表の必要な部分",
    "start_offset": 762,
    "end_offset": 998,
    "field": "text"
  }
]

以上です。


  1. じゃPythonとか別の言語使えばいいじゃないか、というツッコミもありますが。 

  2. コーディングの観点で同期的なイメージで素直に書ける、というだけであって内部的には非同期/Promiseの機構を使っています。 

  3. 話を簡単にするため、コードはnodejs環境下でjsをバッチ的に実行する形にしています。実際にはWatson_APIの呼び出しはAPサーバー環境下でapp.getやapp.postなどのリクエストを受けた際に使うことになるでしょう。 

8
2
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2