するめごはんのIT日記

主にITネタを書いていくのさ

Node-REDを使ってAlexaスキルを創る

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の10日目の記事です。

以前、Alexaスキルを作るツールとしてNOIDについてこのアドベントカレンダーで触れました

surumegohan.hatenablog.com

今回は同じくツールとしてNode-REDを使って挨拶スキルを作ります。

作り方

作り方と言っても、Qiitaや個人ブログ等に情報はあふれていると思います。

今回は以下の本に従ってみます。

f:id:surumegohan:20181210144941p:plain
スマートスピーカーアプリ開発入門

スマートスピーカーアプリ開発入門 3大スマートスピーカー Amazon Echo Google Home LINE Clova対応

1.まずはNode-REDのアカウントを作成

https://nodered.org/

f:id:surumegohan:20181210145035p:plain
Node-REDの画面

2.次にIBM Cloudのアカウント作成

https://www.ibm.com/cloud-computing/jp/ja/lite-account/

ライトで大丈夫です。

f:id:surumegohan:20181210145059p:plain
IBM Cloudの画面

3.IBM Cloudにログイン

ここで、アカウントIDはメールアドレスでログインできます。

f:id:surumegohan:20181210145122p:plain
ログイン画面

4.何も考えないと一部英語になる

本の通りに進めていけば問題ないですが、何も考えないでポチポチやっていくと、画面の一部が英語になります。

が、特に躓くような英単語はないと思います。

f:id:surumegohan:20181210145146p:plain
一部英語になる

5.作成できる画面

商業誌の内容をそのままは書けないのですが、以下のような画面になります。

話しかけられたら、インテントの種類を判別して起動時はLaunchRequestなのでそのまま挨拶をする。 それ以外はインテントの場合分けをswitchをかませて対応を分岐させ、応答を返したらHttpのResponseにつないであげます。

f:id:surumegohan:20181210145205p:plain
Node-REDで作成した最終的な画面

6.Alexa Consoleもほぼ通常通り

Node-REDで作成したインテント名で、挨拶のためのインテントを作成してあげます。

f:id:surumegohan:20181210145316p:plain
Alexa Console画面

ただし、エンドポイントはLambdaではないので、Node-REDで作成したURLをWebhookとして指定します。

f:id:surumegohan:20181210145340p:plain
エンドポイントを設定すること

7.後は話しかけるだけ

きちんと動きます。

f:id:surumegohan:20181210145407p:plain
テスト画面

もし、「応答に時間がかかっている」というエラーになった場合は、Node-REDの画面で、線がすべて結びきれているか確認してみましょう。 JSONの作成はできていても、レスポンスを返すようになっていない場合があります。

まとめ

Node-REDでもスキル作成はできますし、本を参考にすれば、Node-REDもAlexaも初体験だったとしても、ぜいぜい2~3時間で作成できると思います。

Amazonアカウント作成で躓く場合は別

Alexaのスキルをつくることに特化するならNOIDの方が断然楽だと思います。 ただ、Webhookとして、もっと汎用的にいろいろなことをやりたいとなると、Node-REDは選択肢の1つにはなるのではないでしょうか。

今回は以上です。

AMAZON.SearchQueryでビルドが通らない場合のTips

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の9日目の記事です。

みなさん、SearchQuery使ってますか?

これを使うとユーザーから何かしら話しかけられ、他のIntentに分類できなかった場合、 このSearchQueryが実装されているIntentに割り振られます。

つまり、自由な単語・発話をSearchQueryに対応させたいという発想になりますが、ビルドが通らない場合あります。 今回はそれを 無理やり通す 方法について記載します。

もちろん、各自の自己責任にて。

一般的?の実装方法

SearchQueryはこの「なんでも該当」という強さから、一般的に他の単語と組み合わせて実装します。

例えば以下のように。

●●●と、サーチクエリ と話しかけれた際の●●の部分に用います。

f:id:surumegohan:20181209084327p:plain
通常の利用方法

ビルドに通らない場合

ただ、上記のような、「●●とサーチクエリ」のようにやりたくはなく、SearchQueryのみで実装したい場合、ビルドが通らない場合があります。

f:id:surumegohan:20181209084349p:plain
SearchQureyのみだとビルドエラーが発生する

エラーメッセージは

Sample utterance "{searchQuerySlot} " in intent "SearchQueryIntent" must include a carrier phrase. Sample intent utterances with phrase types cannot consist of only slots.エラーコード: MissingCarrierPhraseWithPhraseSlot

自由度が高すぎて制御されているのではないかと考えています。 なので通常は他の具体的な話しかけ方と組みあわせて使うべきなのでしょう。

それでもビルドしたい場合

他のフレーズは一切いれず、何がなんでもSearchQueryのみ実装して、想定していないユーザーの発話はすべてここに集約したいという場合も存在します。

その場合、以下のようにするとビルドに通ります。

f:id:surumegohan:20181209084431p:plain
ビルドを通す技

さて、どうやったでしょうか。

答えは 半角スペース + SearchQuery です。

こうすると他のIntentやSlotに割り振られない場合、このように作成したSearchQueryに無理やり分類させることができます。

ご利用は自己責任で

おそらくこれは、不具合に近いバグのような気がします。 仕様といえば仕様なのでしょうが。。。

ともあれ、ユーザーかどのように話しかけられたのかを丸々取得したい等の場合は、このような技で切り抜けることが一応できます。

実際には他のフレーズを組み合わせるべきだとは思いますが、このようなやり方も可能ではあるということを記載してみました。

本日は以上です。

技術書典5にて「スマートスピーカーを遊びたおす本」を執筆し、反響があった話

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の8日目の記事です。

今回は技術書典5にむけて 「スマートスピーカーを遊びたおす本」 を執筆・編集し、頒布後に反響があった話について記載します。

f:id:surumegohan:20181208102943j:plain
スマートスピーカーを遊びたおす本

■当日、僕が頒布した本「スマートスピーカーを遊びたおす本」

この本の概要は以下に記載のクラスメソッド社のDevelopersIOに掲載されているので、そちらをご確認ください。

というわけで、僕が記載した章の技術的な話。

本の内容の技術的な話

僕の担当箇所はたまたま1章になりましたが、Echo Spot対応のスキル「ヒロインの告白」の制作プロセスの話です。

本の内容をすべてブログに載せてしまうと、お金を払ってくれた方に申し訳ないので一部のみ記載します。

DynamoDBにputする関数は事前に作成してしまう

僕はDynamoDBにデータを格納する際の形式は自分で決めたい派です。

このアドベントカレンダーの3日目

surumegohan.hatenablog.com

でも取り上げましたが、SDKをいじるくらい自分で操りたい人です。

なので、今ではPut用のfunctionは共通化して作成してしまっています。

//DynamoDBにputする関数
function putDynamo(params) {
    
    console.log("=== putDynamo function ===" + params);
    
    docClient.put(params, function (err, data) {
    
        console.log("=== put ===");
    
        if (err) {
            console.log(err);
        } else {
            console.log(data);
        }
    });
}

2回目以降の起動ユーザーは説明文を省くように実装する

上記のDynamoDBへのPutのfunctionをもちいて、LaunchRequestでの2回目以降の起動ユーザーは、スキルの説明をカットして、告白しても良いかどうかのみを結城琴葉ちゃんが話しかけるようにしています。

これはVUIのデザイン・設計として非常に重要だと僕は考えていて、2回目以降のユーザーは説明文をいちいち聞くのは極めて不快です。

もちろん、スキルの特性にもよりますが、このスキルは告白に対する受け答えをするスキルなので、複雑な操作は不要だと感じています。

また、2回目以降のユーザーでも初回起動時の説明文が流れるように「ヘルプ」では初回起動時のメッセージを流すようにしています。

ちなみに、ここでは余談ですが、僕がスキルを創るときは、ユーザーとVUIアプリケーションの対話の状況によって、「ヘルプ」のメッセージを変更するように実装しています。

// スタート音声
const kotoha_start_01 = '<audio src=\"https://hogehoge/kotoha_start_01.mp3\" />';
const kotoha_start_existuser = '<audio src=\"https://hogehoge/kotoha_start_existuser.mp3\" />';

//中略

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  async handle(handlerInput) {


    // Amazonから提供されているTemplate 6を使用
    if (supportsDisplay(handlerInput)){
      const myImage1 = new Alexa.ImageHelper()
        .addImageInstance(DisplayImg1.url) // 結城琴葉の画像
        .getImage();

      const myImage2 = new Alexa.ImageHelper()
        .addImageInstance(DisplayImg2.url) // 今回はDisplayImg1.urlと同じ
        .getImage();

      const primaryText = new Alexa.RichTextContentHelper()
        .withPrimaryText('')  // 文字を出力させたくなかったのでこの記載
        .getTextContent();

        handlerInput.responseBuilder.addRenderTemplateDirective({
        type: 'BodyTemplate6',
        token: 'string',
        backButton: 'HIDDEN',
        backgroundImage: myImage2,
        image: myImage1,
        title: "",
        textContent: primaryText
      });
    }
               
    // 起動時メッセージを一旦、2回目以降の起動ユーザー用に代入
    // 初回起動ユーザーで初期化するか迷いましたが、2回目以降のユーザー用に代入
    let kotoha_start = kotoha_start_existuser;

    try{
        
        // DynamoDBにあるテーブルに対して、既に存在するuserIdかどうか検索を実行
        const queryItems = await docClient.query({
          TableName: "kotohaTable", 
          KeyConditionExpression: "#userId = :userId",
          ExpressionAttributeNames: {"#userId": "userId"},
          ExpressionAttributeValues: {":userId": json_userId} // JSONからuserIdを事前取得しておいた値
        }).promise();
                
        try{
            
            // 検索した結果、Items[0]にuserIdが入っているかログ出力を兼ねて確認
            console.log("queryItems.Items[0].userId: " + queryItems.Items[0].userId);
        
        } catch (err){
            
            // 検索した結果がゼロ件ならばログを出力できず、ここのcatchに分岐
            // ここに流れてきた場合は、再生するmp3へのリンクを初回起動のユーザーとして再度代入
            kotoha_start = kotoha_start_01;
            
            console.log("user Nothing");

            // DynamoDBに存在しないuserIdだったため、格納する処理をここから記載
            var item = {
                // JSONから取得しているuserIdをDynamoDBのuserIdとして格納する            
                userId: json_userId
            };

            // DynamoDBに格納する情報をparamsとしてまとめる
            var params = {
                TableName: kotohaTable,
                Item: item
            };

            // DynamoDBに格納する
            // 本来はここでもエラー処理を入れているが割愛
            await putDynamo(params);

        }
        
    } catch(err){
        
        // エラー時の処理を入れているが割愛

    }

    // sessionAttributesに起動後状態に移ったことを示す

    var sessionAttribute = '';
    
    sessionAttribute = {
    "STATE": "after_start"
    };
    
    handlerInput.attributesManager.setSessionAttributes(sessionAttribute); 

    // 起動時の「スマートマキアート」の後に0.7秒の間を開けてから、起動メッセージを流す
    speechText = kotoha_smartmacchiato + '<break time="0.7s"/>' + kotoha_start;

    // 再生する内容はmp3のみであるため、speakもrepromtもspeakタグでくくっておく
    return handlerInput.responseBuilder
      .speak('<speak>' + speechText + '</speak>')
      .reprompt('<speak>' + speechText + '</speak>')
      .withShouldEndSession(false) //セッションを切らないことを明示する
      .getResponse();
  }
};

■各方面での反響

1.委託先のCOMIC ZIN 秋葉原店でド正面に平積み

BOOTHさん、とらのあなさん、ZINさんに売れ残った分を委託しておりますが、特にZINさんの扱いがすごすぎまして、秋葉原店の技術書典コーナーにないので店員さんに確認したら、まさかの入り口から入ってド正面のド真ん中に平積みしてもらっていました。

逆に気づかなかったという。。

f:id:surumegohan:20181208103050p:plain
COMIC ZINさん

2.クラスメソッド社のせーのさんが記事にしてくれた

日ごろ、非常に、大変、ものすごく、お世話になっているクラスメソッド社の清野さん(せーのさん)にDevelopersIOの記事として掲載いただけました。

非常にありがたいことです。

■VUIをトータルに勉強できる本「スマートスピーカーを遊びたおす本」を読んでみた。 #Alexa

dev.classmethod.jp

f:id:surumegohan:20181208103118p:plain
みんな大好きDevelopersIO

3.この本をきっかけに商業誌の執筆依頼が出版社から頂く

この本をきっかけに、某スマートスピーカーおよび関連技術に関する商業誌の執筆依頼がきて、契約しました。

4.イベントでめっちゃ感謝された

先日の某イベントに参加していたら、この本と千代田まどか様(ちょまど)のファンの方から熱烈にお礼を伝えていただきました。

その人はGoogle Homeユーザーのようですが、著者としてもそういった声をいただけるのはうれしい限りです。

■アウトプットはいいぞ

というわけで、アウトプットをし続けていると、いろんな人が自分を観てくれます。 もちろんたまにはディスられます。

それでも、アウトプットをし続けることで、多くの人が応援してくれます。

みなさんもブログやイベントの登壇などでアウトプットをしてみてはいかがでしょうか。

以上です。

Alexaが240秒再生可能になったので約4分間告白され続けるスキルを創った

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の7日目の記事です。

Alexaのオーディオファイルの再生時間が90秒から240秒に延びた

この記事を書いている今日、つまり12月6日の Alexa Dev Summit TokyoのAlexaスキルのクイズでオーディオ再生が4分まで対応できるようになった旨が出題されました。

■Alexa Dev Summit TokyoのAlexaスキル https://www.amazon.co.jp/Amazon-Alexa-Dev-Summit-Tokyo/dp/B07KGH1VJ1/

また、smartioさんのツイートでも同様の報告がありました。

早速、約240秒告白されてみる

とのことなので、早速240秒、つまり4分間何かしらを流そうかと思い、考えた結果、やはり?もってる音声ファイルとしてAlexaスキル ヒロインの告白のうちの告白セリフを連結して3分58秒にしました。

f:id:surumegohan:20181207102935p:plain
結城琴葉ちゃんアイコン

実機テスト通りましたので録画・公開してみました。 なお、何も考えないで動くことのみを目的としたため、結城琴葉ちゃんの表示が遅れたりしています。

youtu.be

ソースコード

たいして何もしてないので、今まで通りです。

とりあえず、LaunchRequestだけ作成して、実機で動くかのみ確かめました。

'use strict';

const Alexa = require('ask-sdk');


//20種類の告白セリフを結合したファイル
const kotoha_20 = '<audio src=\"https://s3-XXXXXXXXXXXXXXXXXX.mp3\" />';


//画像
const DisplayImg1 = {
      title: '結城琴葉',
      url: 'https://s3-ap-XXXXXXXXXXXXXXXXXXXXX.png'
    };
    
const DisplayImg2 = {
      title: '結城琴葉',
      url: 'https://s3-ap-XXXXXXXXXXXXXXXXXXXXX.png'
};

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  async handle(handlerInput) {

    // Template 6
    if (supportsDisplay(handlerInput)){
      const myImage1 = new Alexa.ImageHelper()
        .addImageInstance(DisplayImg1.url)
        .getImage();

      const myImage2 = new Alexa.ImageHelper()
        .addImageInstance(DisplayImg2.url)
        .getImage();

      const primaryText = new Alexa.RichTextContentHelper()
        .withPrimaryText('')
        .getTextContent();

        handlerInput.responseBuilder.addRenderTemplateDirective({
        type: 'BodyTemplate6',
        token: 'string',
        backButton: 'HIDDEN',
        backgroundImage: myImage2,
        image: myImage1,
        title: "",
        textContent: primaryText
      });
    }

    ///////////////////////////////////////////
    var hoge = 'さぁ、付き合いますか!?';
    ///////////////////////////////////////////


    let speechText =  '<break time="0.7s"/>' + kotoha_20;

    return handlerInput.responseBuilder
      .speak('<speak>' + kotoha_20 + '</speak>')
      .reprompt('<speak>' + kotoha_20 + '</speak>')
      .withShouldEndSession(false) //本来きちんと対応する
      .getResponse();
  }
};


// returns true if the skill is running on a device with a display (show|spot)
function supportsDisplay(handlerInput) {
  var hasDisplay =
    handlerInput.requestEnvelope.context &&
    handlerInput.requestEnvelope.context.System &&
    handlerInput.requestEnvelope.context.System.device &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces.Display;

  console.log("Supported Interfaces are" + JSON.stringify(handlerInput.requestEnvelope.context.System.device.supportedInterfaces));
  return hasDisplay;
}


// LaunchRequestHnadlerより後に書く必要あり
exports.handler = Alexa.SkillBuilders.standard()
  .addRequestHandlers(LaunchRequestHandler)
  .lambda();

これによりどうなるか

僕個人ですと、もっとキャラクターの表現がしやすくなりますし、ポッドキャストに近いこともできることになります。

90秒は結構厳しかったので、今回の対応はうれしい限りです。

が、4分間告白され続け、終わった後8秒間返答しないと repromptで再度4分間告白される動きを上記のコードだとしてしまいます。

いずれにしろ、この長くなったからといって、やみくもに長いのはユーザーからすると鬱陶しいので、それこそVUIデザインの話になるかと思います。 スキルの特性によって適宜利用していきましょう。

以上

IT業界の光闇のGoogle版

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の6日目の記事です。

先日、このアドベントカレンダーの2日目の記事として、Alexaスキル「IT業界の光闇」について触れました。

■二番目に作成そしてVUILTで初登壇することになった「IT業界の光闇」 https://qiita.com/surumegohan/items/854a8f9cc7f79adf266c

surumegohan.hatenablog.com

今回は、このスキルのActions on Google版を公開し、今のUIだとどうなっているか記載します。

f:id:surumegohan:20181206102725p:plain
Google版のIT業界の光闇アイコン

ログインから

まずは https://dialogflow.com/ を開いて、SIGN UP FOR FREEをポチ。

Googleアカウントでのサインインを求められます。

f:id:surumegohan:20181206102214p:plain
サインイン

アカウントアクセスが求められるので、利用するGoogleアカウントで、各種リクエストを許可するか問われます。

f:id:surumegohan:20181206102153p:plain
許可するか問われる

Dialogflowに入ると・・

2018年5月に「IT業界の光闇」は作成しましたが、2018年12月5日現在だとこのようなUIになっているようです。

Intentは基本的にAlexaと同じにしています。

f:id:surumegohan:20181206102232p:plain
Dialogflowの画面

Delault-Welcome Intent

とりあえず「ほげほげ」と入力していた痕跡があります。

f:id:surumegohan:20181206102313p:plain

そして、Responsesも「こんにちは!」だけだったみたいです。

f:id:surumegohan:20181206102337p:plain

EndIntent

Alexaのストップのように作成していました。

・おしまい ・ストップ ・やめて ・とめて

などが定義されています。

f:id:surumegohan:20181206102354p:plain
EndIntentはAlexaのSTOPに近い

HikariIntent

IT業界の光の話をするためのHikariIntentは以下のようになっています。 これはAlexa版とほとんど同じです。

1.光の話をお願い 2.光にして 3.光 4.光の話で 5.光がいい

などとユーザーが「光の話」を聴きたいときに話しかけるであろうフレーズを書いていきます。

f:id:surumegohan:20181206102431p:plain
光のIntent

闇の話も同じようなものなので省略します。

hikariIntentの画面の下の方にスクロールしていき、FulfillmentがEnableであることを確認します。 作成時からかなり時間が経っているのでEnableになっていない場合はここで、トグルをオンにします。

f:id:surumegohan:20181206102450p:plain
FulfillmentをトグルでON

fullfillment

fullfillmentをNode.jsのインラインエディタとして扱っています。

lastupdate.png

f:id:surumegohan:20181206102539p:plain
インラインエディタとして使用

ちなみに、このfullfillmentの最終更新日は以下のようです。

f:id:surumegohan:20181206102617p:plain
最終更新日

書いてあるソースコード

ここで記載しているソースコードは以下のようになっています。

なかなか香ばしい光と闇の話が盛り込まれていますね。

// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';

const functions = require('firebase-functions');
const { dialogflow } = require('actions-on-google');

const app = dialogflow();
 
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
 
 
var SKILL_NAME = 'IT業界の光闇';
var WELCOME_MESSAGE = 'ようこそ。<break time="800ms"/>IT業界の光と闇の世界へ。このアプリでは、IT業界の光と闇を垣間見ることができます。光と闇。どちらの話を聞きたいですか?<break time="200ms"/>「光」<break time="200ms"/>もしくは「闇」<break time="200ms"/> と話しかけてください。「光」ならIT業界のいい話、<break time="200ms"/> 「闇」ならIT業界のよろしくない話を伝えます。<break time="200ms"/>ヘルプを呼び出したい時は「ヘルプ」<break time="100ms"/> と話しかけてください。終わりたい時は「ストップ」<break time="200ms"/>と話しかけてください。それでは、光と闇。どちらの話を聞きたいですか?';
var HELP_MESSAGE = 'ヘルプですね。<break time="200ms"/>このアプリでは、IT業界の光と闇を垣間見ることができます。<break time="200ms"/> 光<break time="200ms"/>もしくは闇<break time="200ms"/>と話しかけてください。光ならIT業界のいい話、<break time="200ms"/> 闇ならIT業界のよろしくない話を伝えます。 <break time="200ms"/>終わりたい時は「ストップ」<break time="200ms"/>と話しかけてください。 それでは「光」<break time="100ms"/>と「闇」<break time="100ms"/> どちらの話を聞きたいですか?';
var HELP_REPROMPT = 'このアプリでは、IT業界の光と闇を垣間見ることができます。 光、もしくは闇、と話しかけてください。光ならIT業界のいい話、 闇ならIT業界のよろしくない話を伝えます。 終わりたい時は「ストップ」、 と話しかけてください。 それでは「光」、と「闇」、 どちらの話を聞きたいですか?';
var STOP_MESSAGE = 'IT業界に光があらんことを。 <break time="700ms"/>またね。';
var CONTINUE_MESSAGE = '<break time="1000ms"/>続けて、<break time="200ms"/>ほかの話を聞きたい場合は 「光」<break time="200ms"/> もしくは「闇」<break time="200ms"/> と話しかけてください。 <break time="200ms"/>終わりたい時は「ストップ」<break time="200ms"/>、と話しかけてください。 どうしますか?';

var HIKARI_PREFIX = '光の話ですね。<break time="800ms"/>';
var YAMI_PREFIX = '闇の話ですね。<break time="800ms"/>'; 

var SPEAK_PREFIX = '<speak>';
var SPEAK_SUFFIX = '</speak>';
 
var hikariData = [
    '自分が関わった、システムやサービスが、世の中にでていくのは、いいものだよ。自分が作った、システムやサービスを、使ってくれている人を見かけると、嬉しくなることもあるよ。',
    'アイデアを、こうしてスマートスピーカーに、しゃべらせたりできるよ。まぁ、IT業界じゃなくてもできるけどね。',
    'カッコイイWebページを、作ることができるよ。パソコンはもちろん、携帯電話でもWebページは大切だからね。',
    'リモートワークができるところなら、家で仕事ができることもあるよ。家で集中できるかは別の話だけどね。',
    'フレックスタイム制だと、満員電車に乗らなくてもいいこともあるよ。',
    '飲み物とか、お菓子とか、無料で食べ放題の職場もあるよ。食べ過ぎると、後悔しちゃうけどね。',
    '音楽を聴きながら、お仕事をしていい職場もあるよ。',
    '常に新技術に出会えるし、ものづくりは楽しいよ。これが醍醐味さ。',
    'いろんな業界の人と、お仕事できることもあるよ。',
    '実力主義の現場だと、できる人ほどお給料がもらえることもあるよ。',
    '技術に関する本とか、資格試験の受験にかかるお金が、会社からもらえることもあるよ。資格をとると、お給料があがる会社もあったりするよ。資格だけあっても、技術がないと、意味ないけどね。',
    '社会に新しいモノ、価値を提供できることもあるよ。このシステム、自分が作ったんだって、ドヤッ、とできるよ。'
    
];


var yamiData = [
    'お客様の職場に、一人だけで派遣されることもあるよ。孤独感がすごいよ。人によるけどね。',
    '経験年数をごまかされることがあるよ。嘘は良くないよね。',
    'システム障害が発生すると、夜中でも休日でも連絡がきて、対応しないといけないこともあるよ。',
    '忙しいと、終電での帰宅や、会社に泊まることもあるよ。',
    'ローンを組んで自宅を購入しても、転勤しないといけないこともあるよ。',
    '裁量労働制とは言っても、早朝から呼ばれたりするよ。',
    '一次請け、二次請け、三次請け、よん、ご、ろく、なな。おっと。誰か来たようだ。',
    '技術がわからない人に人事評価されることもあるよ。それだと、お給料が、あがりにくいこともあるよ。',
    'ゴールデンウイークに、休みがまったくないこともあるよ。年末年始も同じだね。',
    'ノー残業デーがあるということは、いつもは忙しいということもあるよ。',
    'ストレスが溜まって、病気になってしまう人もいるよ。',
    'そうだな。あの話をしよう。いや。やはりこの話は、やめておこう、あまりに闇が深すぎるからね。',
    'じゃば、と、じゃばすくりぷと、は、別のプログラム言語だって、わかってくれない人もいるよ。',
    'しようしょ。しようしょ、かっこ、いち。しようしょ、かっこ、に。しようしょ、最新版。しようしょ、最終版。しようしょ、ふぃっくす。',
    'なるはやで、いいかんじに、やっておいて。って言われることもあるよ。困っちゃうよね。',
    'きみ、いちにち何時間働ける?。と聞かれることもあるよ。さぶろく協定とかご存知でしょうか',
    '年単位や、年度の単位で、残業時間の制限があっても、としが明けたり、年度が替わると突然残業制限の時間がリセットされて、結局、制限がなくなることもあるよ。'
    
    
];
 
 

app.intent('Default Welcome Intent', conv => {

    var speechOutput = WELCOME_MESSAGE;
    
    conv.ask(SPEAK_PREFIX + speechOutput + SPEAK_SUFFIX);
});

app.intent('hikariIntent', conv => {
    
    var factArr = hikariData;
    var factIndex = Math.floor(Math.random() * factArr.length);
    var randomFact = factArr[factIndex];
    var speechOutput = HIKARI_PREFIX + randomFact + CONTINUE_MESSAGE;    
    
    conv.ask(SPEAK_PREFIX + speechOutput + SPEAK_SUFFIX);
});

app.intent('yamiIntent', conv => {
    
    var factArr = yamiData;
    var factIndex = Math.floor(Math.random() * factArr.length);
    var randomFact = factArr[factIndex];
    var speechOutput = YAMI_PREFIX + randomFact + CONTINUE_MESSAGE;
    
    conv.ask(SPEAK_PREFIX + speechOutput + SPEAK_SUFFIX);
});

app.intent('helpIntent', conv => {
    
    var speechOutput = HELP_MESSAGE;
    conv.ask(SPEAK_PREFIX + speechOutput + SPEAK_SUFFIX);
    
});

app.intent('stopIntent', conv => {
    
    var speechOutput = STOP_MESSAGE;
    conv.close(SPEAK_PREFIX + speechOutput + SPEAK_SUFFIX);
    
});


exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

Alexaとの違いとして気をつけるのば、文法面もありますが、<speak>タグの扱いです。

var SPEAK_PREFIX = '<speak>';
var SPEAK_SUFFIX = '</speak>';

conv.ask(SPEAK_PREFIX + speechOutput + SPEAK_SUFFIX);

のように当時の僕は作成しました。

Alexaは<speak>タグを隠蔽してくれますが、Googleさんは明示的に記載する方法があります。 なので、タグを変数化して、光と闇の話題の前後にプレフィックスサフィックスとして挟むようにしています。

まとめ

テストシミュレーターや、申請の仕方などは他の方ががたくさん書いているので、あえて記載しません。

5月くらいにAlexaで作成した「IT業界の光闇」スキルをGoogle対応し、現在のUIだとこのようになっているようです、というお話でした。

<speak>がAlexaから入ると隠蔽されているので、そこで引っ掛かった当時の記憶が蘇ります。

以上、 ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の6日目の記事でした。

はてなブログを読み上げるフラッシュブリーフィングスキルを作る

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の5日目の記事です。

今回は少し指向を変えて、カスタムスキルではなく、フラッシュブリーフィングスキルを作成した話です。

僕のはてなブログを読み上げるという、なかなか香ばしいスキルです。

はてなブログを読み上げるスキルの記事をQiitaに載せるというプレイ

f:id:surumegohan:20181205090719p:plain
スキルアイコン

フラッシュブリーフィングスキルとは

要するに、 「アレクサ、今日のニュースは?」 と話しかけると流れるニュースのスキルです。

カスタムスキルは 「アレクサ、〇〇を開いて」 などで起動しますが、フラッシュブリーフィングスキルは、一連のニュースとして読み上げるスキルのうちの1つを作成することになります。

フラッシュブリーフィングスキルの作り方

さすがの @zono_0 さんが非常にわかりやすい資料を既に公開していらっしゃるので、これを閲覧すればAPI GateWayで呼び出させるLambdaで作成することができます。

https://qiita.com/zono_0/items/10719a9df59d3af3edd2

とはいえ、毎日流れるニュースがLambdaで決め打ちのコードというのもなかなかしんどいので、今回は僕自身のはてなブログを流すスキルを創った話です。

はてなブログを読み上げるフラッシュブリーフィングスキルの作り方

基本的には、上記の @zono_0 さんの記事の通りです。

違うところを記載していきます。

1.まずはAlexa developer console

まずは、みんな大好きAlexa developer consoleにて「フラッシュブリーフィングスキル」を選択して「スキルを作成」です。

最新のUIに対応するため、適当なスキル名でとりあえず進めます。

f:id:surumegohan:20181205090743p:plain
コンソール画面でフラッシュブリーフィングを選択

2.カスタムエラーメッセージ

エラーメッセージをいれます。 「ほげほげ」の箇所に「現在、このニュースは利用できません」等を書いておけば良いかと思われます。

f:id:surumegohan:20181205090825p:plain
エラーメッセージ入力

そして「新しいフィードを追加」をポチります。

3.新しいフィードを追加を押したら各種情報を入力

以下のような画面になるので、必要事項を入れていきます。

f:id:surumegohan:20181205090853p:plain
必要事項を入力する画面

するめごはんはてなブログ では、以下のように作成しています。

f:id:surumegohan:20181205090922p:plain
するめごはんはてなブログの場合

フィードの箇所に、はてなブログRSSのURLを記入すれば解決です。

事実上、今回のスキル作成は。ほぼこれだけです。

4.警告がでまくる

警告がものすごくでてきます。

f:id:surumegohan:20181205090957p:plain
4500文字を超えていると警告が発生

実は読み上げられる文字数の最大値は決められており、4500文字までとなっています。

この文字数に半角英数字なり、記号なり、改行コードなり、等がどのように計算されているのかは僕は確かめてないです。

が、気にせずに「保存」してしまいます。

ちなみに、スキルの説明は最低限だけ記載すればOKです。 何を読み上げる(流す)のかと、ストップで止めることができる旨を書いておけば良いみたいです。

5.Amazon.co.jp alexaにサインイン

ここからは @zono_0 さんの https://qiita.com/zono_0/items/10719a9df59d3af3edd2 とほぼ同じです。

https://alexa.amazon.co.jp/spa/index.html

を開いて、作成したフラッシュブリーフィングスキルの「設定」を押します。

f:id:surumegohan:20181205091045p:plain
スキルを有効化して「設定」を押す

画面が遷移したら「フラッシュニースの管理」を押します。

f:id:surumegohan:20181205091144p:plain
フラッシュニュースの管理

後は、自分のスキルを「オン」にするようにトグルを押します。

f:id:surumegohan:20181205091220p:plain
ニュースとして読み上げたいスキルをトグルでONにする

ニュースを流す順番は個々人で好きに調整できます。

公開されると

審査が通過すると、自分のはてなブログを読み上げるスキルが少なくとも日本国内にお届けできます。

実際に自分で試してみると、RSSを取得して読み上げているだけなので、URLなどの記号を丁寧に読み上げてくれる素敵なスキルが公開されます。

はい、5日目の記事は以上です。

NOIDのイベントすごい良いし、NOID自体もすごい良いという話

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の4日目の記事 です。

Qiitaにも投稿しましたが、こちらの方が詳しいです。
技術面以外も触れられるので。

先ほど、NOIDのもくもくイベントに行ってきたのでアドベントカレンダーを急遽NOIDの記事に差し替えました。

noid.connpass.com

f:id:surumegohan:20181204013744j:plain
NOIDのアイリッジ社でもくもく

さて、先日、Alexaスキルをノンプログラミングで作成、申請できてしまう素敵ツールNOIDのハンズオンに参加してきました。

■NOID スマートスピーカー ハンズオンNIGHT

noid.connpass.com

ここで生まれたスキルが以下です。

f:id:surumegohan:20181204013522p:plain
するめじゃんけん

なお、スキルの説明文にも記載しましたが、するめ」はじゃんけんとはまっったく関係ないです。

そして12月3日にNOIDのもくもく会に参加してきたので、アドベントカレンダーの予定を変更して12月4日になってしまったところで、この記事を書いています。

この記事を書いている現在の画面なので、今後変更される可能性が大いにあります。

NOIDとは

NOID(ノイド)は、話題のスマートスピーカー/AIスピーカーの スキル制作ツールです。プログラミング知識がなくても、開発から申請公開まで完了。 無料プランあり。

って、Google検索で表示されます。

Webページが以下のようになっています。

f:id:surumegohan:20181204013925p:plain
NOIDのWebページ

ログインすると以下のような画面になります。 いろんな情報を取得してグラフにしてるっぽいです。

f:id:surumegohan:20181204014000p:plain
ログイン後の画面

ハンズオンにて じゃんけん のスキルを作成

ハンズオンすごい

ハンズオンがすごくて、何がすごいって、Amazonのアカウント作成から実施しました。

知らないとハマる、AmazonのjpアカウントとAlexaのアカウントを作成して~~~~な、ところからハンズオンして、その場全員がスキルを作成できたという凄さ。

もちろん、NOIDのアカウントも作成して、製作開始。

テキストがじゃんけんだったので、ユニーク性のため、スキルの名称がするめじゃんけん」という意味不明なワードになってしまったけれども作成しました。

ハンズオンは、その性質上どうしても静かなイベントになりがちな面があります。作業に集中してしまいますから。

けれども、NOIDのイベントはアイリッジ社の岩屋さんのファシリテーターがすごくうまくて、場のつなぎ方、盛り上げ方がとにかく良くてイベント自体が楽しくなります。

そして、やっぱりスマ―スピーカーは知っているけれど自分でスキル・アクションを作成したことがない人が、自分で入力した文字列をスマ―スピーカーが読み上げてくれる最初の感動を届けるイベントとして物凄く良かったです。

Alexaが「こんにちは」と発話する、まさにハローワールドなことをするわけですが、エンジニアではない方もそれがその場で実現できて、会場が
「おおおおお!!!」
とか、拍手がたくさんでたり、みんな笑顔で楽しくワイワイやってました。

あの会場にいるとき、もちろん自分が初めてスキル・アクションを作ったときの喜びを思い出せましたし、何よりエンジニアとしての原点も思い出せました

僕が人生で初めてプログラムを書いたのは中学時代のHTMLで自作サイトを作成した時。
それから少したって、C言語でprint文でまさしくハローワールドをした時。

あの体験が、NOIDのイベントでは参加者全員が体感してました。

すごく良かった。
アイリッジさん、本当にありがとうございます。

カスタムスキルを作成してみる

NOIDでは以下のように画面をポチポチしてスキルが作成できちゃいます。

フラッシュブリーフィングスキルも作れるらしいですが僕はまだ試してないです。

NOIDはGUIのツールなので、最初は操作がわからないですが、すぐに慣れます。

f:id:surumegohan:20181204014052p:plain
じゃんけんのスキル作成中の画面

僕みたいに既にスキル公開している人ではなく、本当にアカウント作成からの人でも作成できました。

以下のように、各ブロックを作成していきます。

f:id:surumegohan:20181204014123p:plain
ブロック作成

直感的に、Alexaが話すセリフ、ユーザーが話しかけてくる(Intentやスロット)の操作が視覚的に非常にわかりやすく構成されています。

一部機能制限がありますが、Alexaが発話する内容をランダムにできます。

ここでは、じゃんけんで、ユーザーが「グー」なら、ランダムで、グー、チョキ、パーが選ばれるように作成できます。

f:id:surumegohan:20181204014150p:plain
ランダムでの発話に対応

そして、しれっとすごいことしているのですが

mp3ファイルをドラッグ&ドロップするとAlexaに対応したフォーマットに自動変換してアップロードしてくれる!

※90秒や1回の返答に5ファイルまで等の制限には流石にこの時点では未対応

ちなみに、数時間前のNOIDもくもくイベントで気づいたのですが以下の赤線部のようにSSMLにも対応しています。

ちゃんと指定した時間だけ待機してくれました。

f:id:surumegohan:20181204014249p:plain
SSMLにも対応

申請までできる

スキルを作成したら、申請までNOIDの画面でできちゃいます。 審査を通過するための文言は既に記述されているので、スキル作成が初体験の方でも十分に申請可能です。

f:id:surumegohan:20181204014312p:plain
申請用の画面

テスト手順などの必要事項を入力して保存し、青枠の申請ボタンを押すとAmazon社に審査をだしてくれます。

f:id:surumegohan:20181204014333p:plain
保存ボタンを押して申請ボタンを押すと審査に申請される

テストはAlexaのコンソールもしくは実機

テストは既存のテスト画面でそのまま実行します。 けれども、NOIDの性質上、実機で行った方が良いと思われます。

自動生成されているアレコレ

NOIDの画面のみでポチポチできてしまうので、裏で自動的に生成されているアレコレがあります。

例えば赤枠のランダムと思われる文字列のIntentと、CatchAllIntentが生成されています。 ちなみにエンドポイントとしてLambdaが生成されています。

f:id:surumegohan:20181204014412p:plain
AlexaのConsole画面

CatchAllIntentの中身

この名称からして、なんでもこのIntent該当させようとしていることが、サンプル発話からよくわかります。

f:id:surumegohan:20181204014442p:plain
CatchAllIntentの中身

NOIDで審査に申請する場合の注意点

NOIDでスキルを作成し審査に申請する場合、以下の点に注意が必要です。

注意点1 アイコンは自分で用意する

スキルのためのアイコン2種類は自分で用意する必要があります。

注意点2 「NOID」という単語を使用できない

そりゃないよAmazonさんとも思えるレベルだと個人的には思っているのですが、発話メッセージ内部およびスキル説明にNOIDを含めるとリジェクトになります。

例えば、 「このスキルはNOIDで作成しました」 をAlexaから話しかけてくるようにするとリジェクト対象です。

また、スキルストアに表示されるスキルの説明文にNOIDで作成した旨を記載してもリジェクト対象です。

もちろん 「NOIDのハンズオンで作成しました」 でもリジェクト対象です。 音声でも文字でも「NOID」は扱えないです。

なぜこれらを知っているかというと、これすべて僕がリジェクトを頂いた内容だからです。

非エンジニアやVUIの知見が深くない人にはとても良い

スキルを自身で作成できる人なら、NOIDを選ばなくてもASK CLIなりでスキルは作れるでしょう。

けれども、VUIのデザイナさんや、これからスキルを作りたい個人や企業がモックを作成する場合にイメージとして動くモノが作れるので共通認識がしやすくなると思います。

もちろんエンジニアにも良い

VUIの対話モデルが図示されるので、自分で音声でのやりとりをどのように設計しているのか理解がしやすく、対話モデルの可視化としても非常に良いです。


はい、そんなこんなでNOIDはイイ感じです。国産ですし。