Node.js
Clova
CEK

Clova公式SDK(Node.js)の使い方まとめ

はじめに

Clova Extensions Kit(以下CEK)が公開され、公式SDKもリリースされたものの、チュートリアルで使われてなかったり、ドキュメントも見当たらなかったり、サーバはご自由にという状況で戸惑った方も少なからずいらっしゃるのではないでしょうか(私がそうです)。
というわけでひとまず公式SDKのNode.js版ソースを流し読みしてみたので、使い方について簡単にまとめて説明してみようと思います。

※AWS(Lambda)、Firebase(Functions)での動かし方について↓の記事にまとめました。
Clova公式SDK(Node.js)のAWS、Firebaseでの動かし方

公式SDK

2018/7/20時点ではNode.js版Swift版が公開されています。
今後も随時追加、更新されていくそうです。

今回はAlexaスキル、Google Assistantアプリの開発でもお馴染みのNode.js版を説明していきます。

インストール

npm installでさくっといけます。

$ npm i @line/clova-cek-sdk-nodejs

使い方

require

書くまでもないですが一応。

const clova = require('@line/clova-cek-sdk-nodejs');

handler

ClientクラスのconfigureSkillメソッドでSkillConfiguratorクラスのインスタンスを生成してます。
そのあとに各リクエストイベントでの処理をonメソッドの引数に渡していきます。

const handler = clova.Client
  .configureSkill()
  .onLaunchRequest(launchHandler)
  .onIntentRequest(intentHandler)
  .onSessionEndedRequest(sessionEndedHandler)
  .handle();

Clovaではリクエストが以下の4タイプあります。

リクエスト 内容
LaunchRequest スキル起動時のリクエスト。
IntentRequest スキル起動~終了までの間呼ばれ続けるリクエスト。主にここをあれこれしてく。
SessionEndedRequest スキル終了時のリクエスト。応答を返したりはできない。
EventRequest ユーザの発話の有無に関わらず、クライアントの状態の変化や、それに伴うリクエストをExtensionに渡すために使用されるリクエスト。ハードウェアボタンとか?

このうちEventRequestを除く3つのリクエストのイベントが用意されています。
引数のハンドラー関数にリクエストが呼ばれた際の処理を書いていきます。

Contextクラス

各リクエストイベントから呼ばれるハンドラー関数の引数としてresponseHelperが渡されます。
このresponseHelperContextクラスのインスタンスで、インテント名やスロットを取得したりする色んな便利メソッドが詰まってるので簡単に説明していきます。

getSessionId()

セッションIDを取得します。

getIntentName()

インテント名を取得します。
onIntentRequestで拾ったリクエストは基本的にインテント名でswitchして処理を分岐させていきます。

getSlots()

スロットオブジェクトをごっそり取得します。

getSlot(slotName)

指定のスロット情報のみを取得します。

endSession()

このリクエストでスキルを終了させます。
Clovaから返事を返してそのままスキル終了させたいときはこのメソッドを入れときます。
スキルを継続したい場合はなにもしなくておっけー。

返答メソッド

Clovaからの返答には主に以下の3タイプあり、それぞれのsetメソッドが用意されています。
responseHelperから同様に呼び出せます。

返答タイプ 内容
SimpleSpeech 単文タイプの音声情報です。最も基本となるタイプです。
SpeechList 複文タイプの音声情報です。複数の文章を出力する際に使用されます。
SpeechSet 複合タイプの音声情報です。画面を持たないクライアントデバイスに、要約音声情報と詳細音声情報を渡す際に使用されます。

また、それぞれのメソッドの最後の引数にrepromptをboolean型で指定でき(省略した場合はfalse)、trueを指定した場合はユーザーが無反応の状態で一定時間経過後にClovaが聞き返しを行ってくれて、入力待ち時間を最大1回延長できます。
通常の返答とリプロンプトはそれぞれ個別にメソッドを実行しないとダメです。

// まずこっちで返答して、
responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechText('通常の返答だよ!')
);

// ユーザーが無反応の状態で一定時間経過後にこっちを喋る
responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechText('リプロンプトだよ!'), true
);

// さらにユーザーが無反応の状態で一定時間経過するとスキルが終了される

setSimpleSpeech(speechInfo[, reprompt])

文章または音声データ(mp3とか)を返答させます。
引数のspeechInfoSpeechInfoObjectという形式のオブジェクトになります。
なお後述するSpeechBuilderクラスにvalueの指定のみでSpeechInfoObjectオブジェクトを生成するメソッドがあります。

// 文章
{
   "type": "PlainText",
   "lang": "ja",
   "value": "返答させる文章"
}

// 音声データ
{
  "type": "URL",
  "lang": "" ,
  "value": "https://www.example.com/sound.mp3"
}

setSpeechList(speechInfo[, reprompt])

複数の文章や音声データのURLを返答させます。
引数のspeechInfoは配列になります。

setSpeechSet(speechInfoBrief, speechInfoVerbose[, reprompt])

画面があるデバイスとないデバイスで出力を出し分けるっぽいです。
多分動きとしては画面がある場合はspeechInfoBriefで、ない場合はspeechInfoVerboseで応答するみたいです。
現状画面なしデバイスしかなく、Clova Friendsで試した限りはspeechInfoVerboseの応答のみ返ってきました。
対話モデル画面のテストからだと「応答がありません。(undefined)」となってしまいます。

speechInfoBriefはSpeechInfoObject形式のオブジェクトを渡します。
speechInfoVerboseはSpeechInfoObjectとは少し異なる形式のオブジェクトとなります。
SimpleSpeechとSpeechListのどちらかを以下の形式で渡します。

// SimpleSpeech
{
  type: 'SimpleSpeech', 
  values: SpeechInfoObject
}

// SpeechList
{
  type: 'SpeechList', 
  values: Array<SpeechInfoObject>
}

Clova.SpeechBuilderクラス

文章と音声データそれぞれのSpeechInfoObjectオブジェクトを生成するスタティックメソッドがあります。

createSpeechText(value[, lang])

文章のSpeechInfoObjectオブジェクトを生成してくれます。
また省略可能の第二引数で言語を指定できます。

responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechText('文章だよ!')
)

createSpeechUrl(value[, lang])

音声データのSpeechInfoObjectオブジェクトを生成してくれます。

responseHelper.setSimpleSpeech(
  clova.SpeechBuilder.createSpeechUrl('https://www.example.com/sound.mp3')
)

Clova.Middleware(MiddlewareOptions)

Extension IDが正しいか検証するっぽいメソッド。

const clovaMiddleware = clova.Middleware({ applicationId: "YOUR_EXTENSION_ID" });
app.post('/clova', clovaMiddleware, clovaSkillHandler);

なおID検証しなくてもおっけー。

app.post('/clova', bodyParser.json(), clovaSkillHandler);

サンプルコード

以上を把握するとGitHubのREADMEのサンプルはさくっと読めるんじゃないでしょうか。

const clova = require('@line/clova-cek-sdk-nodejs');
const express = require('express');
const bodyParser = require('body-parser');

const clovaSkillHandler = clova.Client
  .configureSkill()
  .onLaunchRequest(responseHelper => {
    responseHelper.setSimpleSpeech({
      lang: 'ja',
      type: 'PlainText',
      value: 'おはよう',
    });
  })
  .onIntentRequest(async responseHelper => {
    const intent = responseHelper.getIntentName();
    const sessionId = responseHelper.getSessionId();

    switch (intent) {
      case 'Clova.YesIntent':
        // Build speechObject directly for response
        responseHelper.setSimpleSpeech({
          lang: 'ja',
          type: 'PlainText',
          value: 'はいはい',
        });
        break;
      case 'Clova.NoIntent':
        // Or build speechObject with SpeechBuilder for response
        responseHelper.setSimpleSpeech(
          clova.SpeechBuilder.createSpeechText('いえいえ')
        );
        break;
    }
  })
  .onSessionEndedRequest(responseHelper => {
    const sessionId = responseHelper.getSessionId();

    // Do something on session end
  })
  .handle();

const app = new express();
const clovaMiddleware = clova.Middleware({ applicationId: "YOUR_APPLICATION_ID" });
// Use `clovaMiddleware` if you want to verify signature and applicationId.
// Please note `applicationId` is required when using this middleware.
app.post('/clova', clovaMiddleware, clovaSkillHandler);

// Or you can simply use `bodyParser.json()` to accept any request without verifying, e.g.,
app.post('/clova', bodyParser.json(), clovaSkillHandler);

おわりに

今回まとめた情報はここのソースを読み解いたものです。
コード量も少なく、TypeScriptで書かれていますがTypeScript全く触ったことない私でもなんとなく読めたので、一度さくっと目を通してみるのも楽しいかもしれません。