slotで小数を扱う


はじめに

先日Alexaスキル、ワクワク燃費計算をリリースし、その中で小数点を含む数値をスキルの中で扱いました。
アメリカではビルドインスロットのAMAZON.NUMBERで取得できるので、日本でもとれるだろうと思ったら・・・まだ出来ませんでした。
では日本の場合どうしたらいいか?
開発にあたり色々と調べたのですが、意外と小数を扱うことがやられてなかったので、まとめてみようと思います。

既に決まった数字を取得する

発話される数値が限定されており、複数の選択肢の中から小数を選ぶ場合です。
この場合はとてもシンプルで、カスタムスロットを作成することで解決します。
ポイントは色々な言い方があるので、それに対応できるように同義語を多く追加しておくことです。

これをインテントの中でslotに設定すれば取得できるようになります。
この時バックエンドのコードは、以下のようにすればOKです。

sample.js
//この例はsampleIntentにsampleSlotというものが入っている場合のコード

const sampleHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'sampleIntent'
               && handlerInput.requestEnvelope.request.intent.slots.sampleSlot.value !== undefined //値が取れている。
               && this.event.request.intent.slots.sampleSlot.resolutions.resolutionsPerAuthority[0].status.code == 'ER_SUCCESS_MATCH' //選択肢の数値がvalue候補に含まれる
    },
    handle(handlerInput) {
        const slots = handlerInput.requestEnvelope.request.intent.slots;
        //今回はIDを取得しているため、IDで取得
        let num = slots.sampleSlot.resolutions.resolutionsPerAuthority[0].values[0].value.id
        const speechText = '取得出来た値は' + num + 'です'

        return handlerInput.responseBuilder
            .speak(speechText)
            .getResponse();
    }
};

ポイントはresolutions.resolutionsPerAuthority[0]を使っているところです。
slots.sampleslots.valueを使うと、認識した文字列をそのまま取ってしまうため、この数値を使って計算する場合はエラーになる恐れがあります。

どんな小数にも対応する(ただし条件あり)

こちらが本命。次にどんな数値がきても取れるようにする設計方法です。

○対話モデルの設計
まずは実際にスキルで使用したサンプル発話を紹介します。

見ていただいてわかるように[整数値] 点 [小数値]という形でスロットを整数、小数と分けます。
少し面倒ですが、このようにしないと値がとれません。
そして小数点は ”点” と表記します。記号はNGです。

そして各値のスロットタイプですが、以下の通りです



整数値はAMAZON.NUMBERで取得、小数値はAMAZON.FOUR_DEGIT_NUMBERで取得します。

条件があるというのは、FOUR_DEGIT_NUMBERで取得するので、小数点第4位までしか取れないということです。

○小数の処理
lambda側ではどのように処理をするのか、実際のワクワク燃費計算の処理の一部を例にします
値として、走行距離の整数値と小数値が取得できたとします。この時数値として取得しようとすると、コードは以下の通りです

sample2.js
//calcurateProcess
const startCalcurateOrder = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'calcurateIntent'

    },
    handle(handlerInput) {
        console.log("in startCalcurateOrder")
        const slots = handlerInput.requestEnvelope.request.intent.slots;
        if (slots.mileage.value !== undefined && slots.mileage.value > 0 && slots.mileage.value !== '?' ) {
            //まず小数点の計算をする
            var mileageDemi = parseFloat(0);
            if (slots.demical.value !== undefined) {
                mileageDemi = parseFloat(slots.demical.value) / (10 ** (String(slots.demical.value).length))
                console.log('距離: %f', mileageDemi)
            }
            //小数点以下を含む、発話された数値に変換する
            var mileageValue = parseFloat(slots.mileage.value) + mileageDemi
            var result = (mileageValue).toFixed(2) //小数第3位で四捨五入している

            console.log(mileageValue)

            var speechText = "走行距離は" + result + "キロメートルです。"

            return handlerInput.responseBuilder
                .speak(speechText)
                .getResponse();
        }
    }
};

小数値は実際に入らない可能性もあります。整数の場合でも処理することができるよう、整数値が入っているか判断し、その上で小数値の確認、処理を行うようにしました。

さいごに

スキル内で小数を扱うことはあまりないかもしれません。それでも使いたいときはこの方法がベストではないかと思います。
ただ、このやり方にも一部問題があり、1つのインテントに2つの小数値を含む値を有していると、小数値のスロットが誤ってもう一方の小数値のスロットにはいってしまうというバグも見つけました。
ワクワク燃費計算では数値の後に単位を言わないで発話した時にこの症状がありました。
この辺りはAmazonさんに改善してもらう必要がありそうです。