するめごはんのIT日記

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

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はイイ感じです。国産ですし。

SDKをいじってまで、三番目に公開したスキル「IT業界の深い闇」の話

ごきげんよう

この記事は ADVENTARの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の3日目の記事であり、 同じくADVENTARの「Alexa Skills Kit SDK Advent Calendar 2018」の3日目の記事でもあるので、SDKについて自分なりに語ります。

今回もQiiitaの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の3日目の記事と若干異なりです。

3つほぼ同時はどうなのか。
よろしくない気がしますが、そういうやり方も可能なのか検証にも使います。

以前、このはてなブログに経緯などは記載済みの面もある

f:id:surumegohan:20181203080926p:plain
IT業界の深い闇

このスキルは公開後に、はてなブログに既にいろいろ書いています。

surumegohan.hatenablog.com

なお、この記事に記載している「IT業界の深い闇」というスキルは既にソースコードを以下に公開しています。

■VUIアプリケーションのフィードバック機能をAlexaスキルで公開した https://qiita.com/surumegohan/items/af700c96df5ccf1a1a74

「IT業界の深い闇」のdeveloper console画面

f:id:surumegohan:20181203082010p:plain
IT業界の深い闇のconsole画面

このスキルでは、IT業界の深い闇の話を流した後に、ユーザーに対して「いいね」「いまいち」「ストップ」の判断を依頼します。

そのためにgoodIntentとbadIntentがあり、goodIntentは以下の7つにしています。

  1. 深い
  2. 最高
  3. いい
  4. よいですね
  5. いいですね
  6. よかった
  7. いいね

これらの言葉は「いいね」として見なすようにしています。

なぜ今またこの記事を書くか

まず、Alexaで「状態」を管理する方法はいくつかあり、大雑把に言うと

  1. リクエスト単位(話しかけるごと)
  2. セッション単位(一度の起動から終了まで)
  3. 永続化(スキルを終了しても保存しておく)

のレベルがそれぞれあります。

Alexaスキルでユーザーの起動が2回目以降の場合などにおいて、3の永続化 として、DynamoDBにユーザーIDを記録しておくのはよくあるやり方だと認識しています。

AlexaのSDKの場合、

handlerInput.attributesManager.setPersistentAttributes(attribute)

とすると、DynamoDBにPutされます。

けれど、このスキルは DynamoDBにPutする部分のSDKを改編している ので、そこについて少し触れようと思います。

ここでのユーザーIDとは

本記事でのユーザーIDとは、Alexaでやり取りする際に発生するJSONに含まれるuserIdを示します。

この記事を書いている12月頭時点で、このスキルを起動すると以下のようなJSONが流れます。

AlexaのuserIdはスキル内では同じユーザーは、いつ同じスキルを起動しても同じユーザーIDです。 このユーザーIDの人は、他のスキルを起動すると、そこでは別のユーザーIDになります。

※一部マスキングしているのと、略しています。

{
    "version": "1.0",
    "session": {
        "new": true,
        "sessionId": "amzn1.echo-api.session.955d0538-a464-4beb-b2ac-AAAAAAAAAAA",
        "application": {
            "applicationId": "amzn1.ask.skill.76274913-c2f8-4159-8d0e-XXXXXXXX"
        },
        "user": {
            "userId": "amzn1.ask.account.AECCNHCZ53P2CLATS4ZQZKHWBT3ZCOOZKISGGPP4KTEBFEUANDGZ4OL4WPLBHYRMIHYVIWVYTBHJ6MNRMJJ3YN7NSGKMY3L4TL3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
        }
    },
    "context": {
        "System": {
            "application": {
                "applicationId": "amzn1.ask.skill.76274913-c2f8-4159-8d0e-XXXXXXXX"
            },
            "user": {
                "userId": "amzn1.ask.account.AECCNHCZ53P2CLATS4ZQZKHWBT3ZCOOZKISGGPP4KTEBFEUANDGZ4OL4WPLBHYRMIHYVIWVYTBHJ6MNRMJJ3YN7NSGKMY3L4TL3XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
            },
            "device": {
                "deviceId": "amzn1.ask.device.AEFHK3CB56PBTMR6NHFZ3MNIJ5RPZLKWIG3DKSOV4PDM3UWNKN553JXDJF2FOF7NPZMCWHUSWYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
                "supportedInterfaces": {}
            },

以降、略

今のところ、日本のAlexaSDKのV2だと、上記のようなJSON形式であり、userIdはsessionの中でも、contextの中でも同一となっています。 ※僕が認識している限り。

SDKの中身

再掲ですが、SDKで僕がいじった箇所は以下となります。 これを読むと上記のJSONのcontextの方のuserIdがPutされる対象になることがわかります。

本来はそうなるはずですが、「IT業界の深い闇」では何がなんでもuser1という文字列ををPKとしてDynamoDBにPutするコードにしました。 ちなみにdeviceIdも同じようにしています。

'use strict';

/*
 * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
var AskSdkUtils_1 = require("../../utils/AskSdkUtils");
/**
 * Object containing implementations of {@link PartitionKeyGenerator}.
 */
exports.PartitionKeyGenerators = {
    /**
     * Gets attributes id using user id.
     * @param {RequestEnvelope} requestEnvelope
     * @returns {string}
     */
    userId: function (requestEnvelope) {
        if (!(requestEnvelope
            && requestEnvelope.context
            && requestEnvelope.context.System
            && requestEnvelope.context.System.user
            && requestEnvelope.context.System.user.userId)) {
            throw AskSdkUtils_1.createAskSdkError('PartitionKeyGenerators', 'Cannot retrieve user id from request envelope!');
        }

        //何がなんでもキー値を user1 という文字列で返すように書き換え
        return 'user1';

        //本来書いてあった戻り値
        //return requestEnvelope.context.System.user.userId;
    },
    /**
     * Gets attributes id using device id.
     * @param {RequestEnvelope} requestEnvelope
     * @returns {string}
     */
    deviceId: function (requestEnvelope) {
        if (!(requestEnvelope
            && requestEnvelope.context
            && requestEnvelope.context.System
            && requestEnvelope.context.System.device
            && requestEnvelope.context.System.device.deviceId)) {
            throw AskSdkUtils_1.createAskSdkError('PartitionKeyGenerators', 'Cannot retrieve device id from request envelope!');
        }

        //何がなんでもキー値を device1 という文字列で返すように書き換え
        return 'device1';

        //本来書いてあった戻り値
        //return requestEnvelope.context.System.device.deviceId;
    },
};
//# sourceMappingURL=PartitionKeyGenerators.js.map

なぜこんなことをしたのか

SDKなどのライブラリを読む人と読まない人は別にどちらでも構わないのですが、それはおいておいて、これでDynamoDBにどのようにPutされるのか。

答えは以下となります。

f:id:surumegohan:20181203082131p:plain
DynamoDBのテーブル内は2行しかない

DynamoDBで、SDKの通りに何も考えないで永続化を試みると、userIdがidとしてPKになります。

上記に記載しましたが、もう一度、記載します。

AlexaのuserIdはスキル内では同じユーザーは、いつ同じスキルを起動しても同じユーザーIDです。 このユーザーIDの人は、他のスキルを起動すると、そこでは別のユーザーIDになります。

つまり、僕のLambdaのコードの書き方だと、ユーザーごとにidが異なるので、DynamoDBのデータ量がユーザー単位でどんどん増えていきます。

このスキルの目的は、IT業界の深い闇を伝えることよりも、「いいね」「いまいち」などを集計することが主目的です。 なので、ユーザーのIDはどうでもよくて、「いいね」「いまいち」の数が管理できればそれで良いのです。

DynamoDBはトランザクションに対応していないので、必ずしも正確な数字が記録されるとは限らないかもしれないですが、そこまでの厳密性は求めていませんでした。

※ 12月2日追記 トランザクション対応が発表されたみたいですね。。

なので、ユーザーのIDはそもそも管理したくない、かつ、数字の集計がめんどくさいという2つの理由から、本来は自分のスキルのLambdaを書き直せばよいものを、あえてSDKをいじっても審査が通るのかもチャレンジ してみました。

実はかなり審査員とバトルやりとりした

このスキル、Amazonの方々とものすごくやりとりしました。

当初、実はこのスキルは当初「スキルフィードバック」という名前のスキルで、ユーザーにいきなり「ストップと話しかけてください」と問うスキルで、その後に「いいね」「いまいち」を伝えてもらうモノだったためです。

もちろん、何度もリジェクトを頂きました。

・テスト目的のスキルにみえる ・ユーザーを困惑させる ・ストップの後で終了しないのはなぜなのか

これらのようなコメントをガンガンもらいました。 今までのスキルはせいぜい2~3営業日で初回の審査結果が来ていましたが、このスキルは当初で約5営業日かかりました。

Amazonの中でもいろいろあったと思います。

そして、僕としては ・開発者がユーザーのフィードバックを何も得られないのはおかしい ・テスト目的ではない、スキル作成者全体に関わることだ などなどと、激しく抵抗した結果・・・

何か他のスキルのようなやりとりをした後に、このスキルの機能を組み込むなら承認します

というコメントを頂きました。

なので、「IT業界の光闇」の際に、ボツにしていた、闇としては深いネタをランダムで選び、そして「ストップ」と話しかけたら、ユーザーに「いいね」「いまいち」を伝えてもらうようになっています。

結果的にSDKの改編については、一切の言及がなかったです。

このスキルはAxel Gadgets様に特集された

このスキルはAxel Gadgets様にリリース後すぐに特集記事を書いていただきました。 ※サイト画像の提示は以前許可をもらってます。

https://gadget.axelmedia.info/it%E6%A5%AD%E7%95%8C%E3%81%AE%E6%B7%B1%E3%81%84%E9%97%87/

f:id:surumegohan:20181203082101p:plain
Axel Gadgets様

気づいた方は気づいたかも

ここまできて、気づいた方は気づいたかもしれません。

僕がこのスキルの後にリリースした「ヒロインの告白」として美少女から告白されるスキルは、この「いいね」「いまいち」を★の数のレビューとフリーコメントの機能として継承されることになります。

そしてさらに、そこから特許出願に至る「「音声レビュー・ログデータ収集・分析システム」へと進化していきます。

ここからはQiitaにない話

もちろん?このスキルを公開した時、僕は無職でした。

今では個人事業主申請をして、フリーランスのVUIプロデューサー兼エンジニアとなっています。

そして、このスキルの「いいね」「いまいち」に目をつけた方々は実は当時からそれなりにおりまして、僕にお声がけがありました。

そのうちの1人というか1社がSmartHacks社です。

smarthacks.jp

SmartHacks社の山本さん(現:スマモト)から連絡がありました

日本中のAlexaスキルおよびGoogleのActionについてすべて録画公開している、SmartHacks社がこのスキルを見つけたとき、ものすごい熱量でいろいろありました。

要するにTシャツ目的のスキルが多いが、さらには本当にどうしようもないスキルが残念ながら存在する。

そういうのを、このスキルのような「声」でのフィードバックにてどうにかできないか。

という話になっていきます。

はい、3日目はここでおしまいです。 以上!

二番目に作成そしてVUILTで初登壇することになった「IT業界の光闇」

ごきげんよう

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

今回もQiiitaの 「するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の2日目の記事とほぼ同じです。

自作のスキルに関しては、Qiitaほぼ同じ内容で書いていこうかと。
さすがに50記事は無理ぽでした。。

IT業界の光闇

前日は

surumegohan.hatenablog.com

として、初めて公開したスキルについて触れました。

本日は2回目に公開したスキル「IT業界の光闇」の話です。

f:id:surumegohan:20181201192911p:plain
IT業界の光闇

このスキルは Qiitaだと @zono_0 と名乗っているNISHIZONOマスターのハンズオンイベントで思いつきました。

ハンズオンでは「とっさの豆知識」というランダムで豆知識を披露するスキルを作ることになっておりましたが、 factスキルが原形だったので、ハンズオンでは宇宙の話でした。 僕はそこでIT業界の豆知識を入れられないかなと考えて生まれたスキルです。

ただ、さすがにIT業界の豆知識というスキル名だとあんまりにもあんまりなので IT業界のよろしい面を光、よろしくない面を闇として、IT業界の光と闇をお届けすることとしました。

ネタはもうたくさんあるので・・・w

IT業界の光闇のdeveloper console画面

f:id:surumegohan:20181201193031p:plain
IT業界の光闇のコンソール画面

前日の大阪弁相槌で用いた any をここでも用いています。

また、光の話と、闇の話のためにインテントをわけて、当時のAlexaの1つの基準となる6つ以上のサンプル発話を含めました。

光の話のサンプル発話

光の話はhikariIntentなわけですが、以下のようになっています。

  1. 光の話で
  2. 光で
  3. 光を
  4. 光にして
  5. 光がいい
  6. 光の話が聞きたい
  7. 光をお願い
  8. 光を聞きたい
  9. 光の話をして
  10. 光の話をお願い
  11. 光の話

の12パターンを当時作成しました。 闇も同じように作成しています。

今思うと、「もっとスロット使えよ」って思いますが、成長の足跡みたいなもんだと認識しています。

ソースコード

このスキルのソースコードは以下のようになっています。

なお、現段階で公開されているスキルのうち光と闇の話はここに記載されている内容のみではありません。 実際はもっと収録されています。

たいした実装はしてなくて、ランダムで光か闇の話をします。

※ ブログとしての見やすさのため、ここでは1つのindex.jsにまとめたと認識していただけると助かります。

'use strict';
var Alexa = require('alexa-sdk');

//スキルID
var APP_ID = 'amzn1.ask.skill.XXXXXXXXXXXXXXXXXXXXXX';

//定型文
var SKILL_NAME = 'IT業界の光闇';
var WELCOME_MESSAGE = 'ようこそ。<break time="0.2s"/>IT業界の、光と闇の世界へ。<break time="0.2s"/>このスキルでは、IT業界の光と闇を垣間見ることができます。<break time="0.2s"/>「光」ならIT業界のいい話、<break time="0.2s"/>「闇」ならIT業界のよろしくない話を伝えます。「光」<break time="0.2s"/>もしくは「闇」<break time="0.2s"/>と話しかけてください。どうしますか?';
var HELP_MESSAGE = 'へるぷですね<break time="0.2s"/>このスキルでは、IT業界の光と闇を垣間見ることができます。<break time="0.2s"/>光<break time="0.2s"/>もしくは闇<break time="0.2s"/>と話しかけてください。光ならIT業界のいい話、<break time="0.2s"/>闇ならIT業界のよろしくない話を伝えます。<break time="0.2s"/>終わりたい時は「ストップ」<break time="0.2s"/>と話しかけてください。<break time="0.2s"/>それでは「光」と「闇」<break time="0.2s"/>どちらの話を聞きたいですか?';
var HELP_REPROMPT = 'このスキルでは、IT業界の光と闇を垣間見ることができます。<break time="0.2s"/>光<break time="0.2s"/>もしくは闇<break time="0.2s"/>と話しかけてください。光ならIT業界のいい話、<break time="0.2s"/>闇ならIT業界のよろしくない話を伝えます。<break time="0.2s"/>終わりたい時は「ストップ」<break time="0.2s"/>と話しかけてください。<break time="0.2s"/>それでは「光」と「闇」<break time="0.2s"/>どちらの話を聞きたいですか?';
var STOP_MESSAGE = 'IT業界に光があらんことを。<break time="0.2s"/>またね。';
var CONTINUE_MESSAGE = '<break time="1s"/>続けて<break time="0.1s"/>ほかの話を聞きたい場合は<break time="0.2s"/>「光」<break time="0.2s"/>もしくは「闇」<break time="0.2s"/>と話しかけてください。<break time="0.2s"/>終わりたい時は、「ストップ」<break time="0.2s"/>と話しかけてください。<break time="0.2s"/>どうしますか?';
var UNDEFINED_MESSAGE = 'すいません。聞き取れませんでした。<break time="0.2s"/>「光」<break time="0.2s"/>もしくは「闇」<break time="0.2s"/>と話しかけてください。光ならIT業界のいい話。<break time="0.2s"/>闇ならIT業界のよろしくない話を伝えます。<break time="0.2s"/>終わりたい時は「ストップ」<break time="0.2s"/>と話しかけてください。<break time="0.2s"/>それでは「光」と「闇」<break time="0.2s"/>どちらの話を聞きたいですか?';

//光と闇の話をし出す冒頭部分
var HIKARI_PREFIX = '光の話ですね。<break time="0.2s"/>';
var YAMI_PREFIX = '闇の話ですね。<break time="0.2s"/>';

//光のネタ
var hikariData = [
    '自分が関わった、システムやサービスが、世の中にでていくのは、いいものだよ。自分が作った、システムやサービスを、使ってくれている人を見かけると、嬉しくなることもあるよ。',
    'アイデアを、こうしてスマートスピーカーに、しゃべらせたりできるよ。まぁ、IT業界じゃなくてもできるけどね。',
    'カッコイイWebページを、作ることができるよ。パソコンはもちろん、携帯電話でもWebページは大切だからね。',
    'リモートワークができるところなら、家で仕事ができることもあるよ。家で集中できるかは別の話だけどね。',
    'フレックスタイム制だと、満員電車に乗らなくてもいいこともあるよ。',
    'スマートスピーカーに限らず、コミュニティーに入ると、いろんな人と出会えるし、それがきっかけで新しい発想が得られたりするよ。'
    
];

//闇のネタ
var yamiData = [
    'お客様の職場に、一人だけで派遣されることもあるよ。孤独感がすごいよ。人によるけどね。',
    '経験年数をごまかされることがあるよ。嘘は良くないよね。',
    'システム障害が発生すると、夜中でも休日でも連絡がきて、対応しないといけないこともあるよ。',
    '忙しいと、終電での帰宅や、会社に泊まることもあるよ。',
    'ローンを組んで自宅を購入しても、転勤しないといけないこともあるよ。',
    '最初のうちは終電にくわしくなれるよ。そのうちに深夜タクシー代にくわしくなって、最後は始発の時間を覚えるよ。',
    '腕が上がると、賞味期限の切れそうな、大盛りスパゲティが運ばれてくるよ。腕の見せ所だよ',    
    '裁量労働制とは言っても、早朝から呼ばれたりするよ。',
    '一次請け、二次請け、三次請け、よん、ご、ろく、なな。おっと。誰か来たようだ。',
    '技術がわからない人に人事評価されることもあるよ。それだと、お給料が、あがりにくいこともあるよ。',
    'ゴールデンウイークに、休みがまったくないこともあるよ。年末年始も同じだね。',
    'ノー残業デーがあるということは、いつもは忙しいということもあるよ。',
    '年単位や、年度の単位で、残業時間の制限があっても、としが明けたり、年度が替わると突然残業制限の時間がリセットされて、結局、制限がなくなることもあるよ。',
    '横須賀。リサーチ。プリズン。',
    '納期が近いのに、急な仕様変更を、無償でやれ、と言われることもあるよ。なんでIT業界は、それがまかり通るのかな。',
    '試験結果のエビデンスで嘘を言われることもあるから、本当に、要注意。',
    'クラウドを使えば、インフラが必要なくなると、勘違いしている人がいるよ。そんなわけないのにね。',
    'テストを自動化すれば、テスト工程が不要になると、勘違いしている人がいるよ。自動化のための、ソースコードの作成やメンテナンスコストを考えてくださいよ。',
    'RDBで、データを削除しないで、削除フラグをカラムに入れる設計、後でデータ量が増えたり、痛い目にあう、アンチパターン。'
    
];


//以下、実装部分
exports.handler = function(event, context, callback) {
    var alexa = Alexa.handler(event, context);
    alexa.APP_ID = APP_ID;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

var handlers = {
    'LaunchRequest': function () {
        
        var speechOutput = WELCOME_MESSAGE;
        var reprompt = HELP_REPROMPT;
        this.emit(':ask', speechOutput, reprompt);

    },
    'hikariIntent': function () {
        var factArr = hikariData;
        var factIndex = Math.floor(Math.random() * factArr.length);
        var randomFact = factArr[factIndex];
        var speechOutput = HIKARI_PREFIX + randomFact + CONTINUE_MESSAGE;
        var reprompt = HELP_REPROMPT;
        this.emit(':ask', speechOutput,reprompt)
    },
    'yamiIntent': function () {
        var factArr = yamiData;
        var factIndex = Math.floor(Math.random() * factArr.length);
        var randomFact = factArr[factIndex];
        var speechOutput = YAMI_PREFIX + randomFact + CONTINUE_MESSAGE;
        var reprompt = HELP_REPROMPT;
        
        this.emit(':ask', speechOutput, reprompt)
    },
    'AMAZON.HelpIntent': function () {
        var speechOutput = HELP_MESSAGE;
        var reprompt = HELP_REPROMPT;
        this.emit(':ask', speechOutput, reprompt);
    },
    'AMAZON.CancelIntent': function () {
        this.emit(':tell', STOP_MESSAGE);
    },
    'AMAZON.StopIntent': function () {
        this.emit(':tell', STOP_MESSAGE);
    },
    'SessionEndedRequest': function () {
        this.emit(':tell', STOP_MESSAGE);
    }, 
    'Unhandled': function () {

        var speechOutput = UNDEFINED_MESSAGE;

        var reprompt = HELP_REPROMPT;

        this.emit(':ask', speechOutput, reprompt);
    }
};

VUIのイベント初登壇のスキル

このスキルは VoiceUIライトニングトーク!/VUILT vol.2 at Google で登壇したスキルです。

https://iotlt.connpass.com/event/86989/

今では各種VUIのイベントで話させていただいておりますが、このスキルがVUI関連のイベントでの初登壇のスキルです。

めっちゃスベると思ったんですけど、非常に好評でして、ボイスアップラボのコバヤシトールさんをはじめ、現在VUI業界で活躍なさっている方々から懇親会でつながることになった思い出のあるスキルです。

実は5回バージョンアップしている

上記イベント時にも話したのですが、このスキルはTwitter、友人、知人、そしてまったくの他人から、ネタを都度提供いただきまして、ネタの追加を5回実施し、Ver1.5ということになっています。

みなさん、ありがとうございます。

ただ、、VUILTでも話した通り、闇ばかりが深くなっていくという状態に陥りました。 さすがにこれはコンプライアンスとかでヤバイだろという内部告発レベルの話題もきました。

Alexaのエンドポイントを更新したらAmazonさんに再申請するべきかどうかという話がありますが、このスキルというか僕の場合は修正したら再申請を一応実施しています。

ただ、Lambdaで何も考えずに直接書いているなら、審査も何も動いてしまっているので意味があるのかは微妙なところ。

ただし、 以前は指摘しませんでしたが●●を変更してください

という、既に公開しているのに、審査通過した箇所を修正しろと言われることがあります。 経験上、これはロジック面でも、スキルストアの説明文でもあり得ます。

え?リジェクト理由はそこなの??

このスキル、IT業界をディスっているので、そもそもAmazonさんが認めてくれるのかどうなのか という勝手な不安が当時あり、1回目はリジェクトを頂きました。

やっぱこういうのはダメかーー

と思ったら

スキル名(呼び出し名前)の「IT業界の光と闇」で当初申請していたのですが がNGという理由でした。

なので、無理やり感がありますが、「あいてぃぎょうかいのひかりやみ」になっています。

というわけで

2日目の記事として、「IT業界の光闇」について触れさせていただきました。

一番最初に作成したAlexaスキル「大阪弁相槌」

ごきげんよう

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

今回はQiiitaの するめごはんのVUI・スマートスピーカー Advent Calendar 2018」 の1日目の記事とほぼ同じです。

今年の4月末、僕はVUIに専念するために会社を辞めて無職になりました。 今ではフリーランスエンジニアとしてVUIプロデューサーを名乗り、VUI関連のお仕事を頂いて生活しております。

さて、最初の記事としては、僕自身が最初に世の中に公開したスキルについて記載します。

僕が初めて世の中にリリースしたVUIのスキル・アプリ(アクション)は2018年5月7日に公開された 大阪弁相槌(おおさかべんあいづち) というスキルです。

大阪弁相槌アイコン.png

Google Homeで個人で遊んでいたアクション(アプリ)は既にありましたが、一般公開された1作品目はこのAlexaスキルとなりました。

動きは単純で、 「アレクサ、大阪弁相槌を開いて」 と話しかけると、大阪弁で話してくるので、ユーザーが何かしら話しかけると、大阪弁で相槌を返します。

Alexa developer consoleでは以下のようになっていて、 インテントが1つ、そこにanyのスロットのみです。

あとは他のストップやキャンセルのインテントに、大阪弁としてのそれらの動きをするセリフを登録しています。

大阪弁相槌インテント.png

スロットの中身も以下のように「ほげほげ」しかありません。

大阪弁相槌スロット.png

つまり、Alexaのビルドインインテント以外の場合は、この「ほげほげ」として処理されるようになっています。

当時のオウム返し系のスキルは、このような作成方法が散見されました。

そして、そのソースコードが以下です。

'use strict';
const Alexa = require('alexa-sdk');

const APP_ID = 'amzn1.ask.skill.XXXXXXXXXXXXXXXXXXXX';

const SKILL_NAME = 'おおさかべんあいづち';
const HELP_MESSAGE = 'へるぷやな<break time="0.2s"/>何か話しかけてみい<break time="0.2s"/>大阪弁で相槌を返すでぇ<break time="0.2s"/>終了するには<break time="0.2s"/>もうええわ<break time="0.2s"/>とか<break time="0.2s"/>やっぱやめ<break time="0.2s"/>とかゆうてくれたら終わるでー<break time="0.2s"/>ほな<break time="0.2s"/>会話を始めよか<break time="0.2s"/>話しかけてみ?';
const HELP_REPROMPT = 'へるぷやな<break time="0.2s"/>何か話しかけてみい<break time="0.2s"/>大阪弁で相槌を返すでぇ<break time="0.2s"/>終了するには<break time="0.2s"/>もうええわ<break time="0.2s"/>とか<break time="0.2s"/>やっぱやめ<break time="0.2s"/>とかゆうてくれたら終わるでー<break time="0.2s"/>ほな<break time="0.2s"/>会話を始めよか<break time="0.2s"/>話しかけてみ?';
const STOP_MESSAGE = 'ほな<break time="0.2s"/>さいなら';
const WELCOME_MESSAGE = '何か話しかけてみい<break time="0.2s"/>大阪弁で相槌を返すでぇ<break time="0.2s"/>何かわからへんことあったり<break time="0.2s"/>ヘルプを呼び出したいときは<break time="0.2s"/>わからへん<break time="0.2s"/>どしたらええねん<break time="0.2s"/>ヘルプを開いて<break time="0.2s"/>とかいうてや';
const WELCOME_MESSAGE2 = '<break time="0.2s"/>終了するには<break time="0.2s"/>もうええわ<break time="0.2s"/>とか<break time="0.2s"/>やっぱやめ<break time="0.2s"/>とかゆうてくれたら終わるでー<break time="0.2s"/>ほな<break time="0.2s"/>会話を始めよか<break time="0.2s"/>話しかけてみ?';

const FEEDBACKS = [
    'なんでなんで',
    'しらんしらん',
    'そやねんそやねん',
    'まじで?',
    'うそやん',
    'そうなん?',
    'せやな',
    'まあ<break time="0.2s"/>それはアレやな',
    'そやな',
    'やばいな',
    'せやなぁぁ'
];

const PROMPTS = [
    'ほんでほんで?',
    'そんでそんで?',
    'それからどしたん?',
    'どないしたん?'
];

const WAIT = '<break time="0.2s"/>';

const handlers = {
    'LaunchRequest': function () {
        const speechOutput = WELCOME_MESSAGE + WELCOME_MESSAGE2;

        this.response.cardRenderer(SKILL_NAME, speechOutput);
        this.response.speak(speechOutput);
        this.emit(':ask', speechOutput);
    },
    'inputAnyIntent': function () {
        
        const speechOutput = FEEDBACKS[Math.floor(Math.random()*FEEDBACKS.length)] + WAIT + PROMPTS[Math.floor(Math.random()*PROMPTS.length)]; 

        this.response.cardRenderer(SKILL_NAME, speechOutput);
        this.response.speak(speechOutput);
        this.emit(':ask', speechOutput);
    },
    'AMAZON.HelpIntent': function () {
        const speechOutput = HELP_MESSAGE;
        const reprompt = HELP_REPROMPT;

        this.response.speak(speechOutput).listen(reprompt);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
        'SessionEndedRequest': function () {
        this.emit(':tell', STOP_MESSAGE);
    }, 
    'AMAZON.StopIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
        'SessionEndedRequest': function () {
        this.emit(':tell', STOP_MESSAGE);
    }, 
    'Unhandled': function () {

        var speechOutput = HELP_MESSAGE;

        var reprompt = HELP_REPROMPT;

        this.emit(':ask', speechOutput, reprompt);
    },
};

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.appId = APP_ID;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

今はもう懐かしい、SDKのV1ですが、100行にも満たない非常にシンプルな内容です。

ただ、セリフに関しては、 大阪生まれ、大阪育ち、大阪在住の大阪ネイティブの方 に監修して頂きました。 関西弁と大阪弁は違うと熱弁されて、もう半年以上経つことになります。

そして、このスキルを公開するために5回のリジェクトを頂いたのを今でも覚えています。

このスキルを皮切りに、この記事を書いている11月末現在では、多くの人に支えられ、VUIで招待講演や特許出願ができるようにまでなりました。

僕のアドベントカレンダーでは、そんな僕のVUI元年について記載していこうと考えております。