はじめに
恥ずかしながらスマートスピーカーのスキルを初めて公開しました。その時に躓いた点を語っていきます。
VUILT vol.6を聞いていたらふと__LINE BOOT AWARDS 2018に応募__したくなり、締め切りの約一週間でスキルを作成&リジェクトを食らいながらもなんとか公開にこぎつけた話
作成したスキル
ヤンデレちゃんトーク
LINE BOOT AWARDS 2018のエントリー作品より
作成に当たる背景
- Clovaでなければならないスキルとは、Botを活用するスキルが良いと思っていた
- 誰からBotが来ると嬉しいか、可愛いヤンデレ娘から大量のLINE通知が来る経験は一度はしてみたいと思うのではないか、私は経験してみたい
- 会話の途中で一定時間放置するとLINEを飛ばしてくる可愛いヤンデレ娘、ヤンデレちゃんを作ってみよう!
躓いた点
スキルのストーリ編
ストーリー構築と締め切り
元々考えていたストーリーは__ヤンデレちゃんとの会話中に一定時間放置するとLINEを大量に飛ばしてくる__といったものです。
会話中に一定時間放置することをトリガーにLINEを通知するということには「会話中にClovaを聞き返し状態を一定時間維持できるのか」という壁がありました。
公式SDKを使っていたのですが、せいぜい一度聞き返すくらいで終わってしまいます。
参考になる文献が見当たらなかったこと、締め切りまでの時間が少ないこともあり泣く泣くトリガーを__ヤンデレちゃん以外の誰かを褒める__と病みモードになりLINE通知が来ることに変更しました。
(GoogleHomeならば自発プッシュが可能であるため当初のストーリーが簡単に実現可能かもしれません)
ヤンデレちゃんのキャラがブレる
ヤンデレちゃんは清楚な見た目で敬語使うけど病むと面倒くさいLINEを沢山送っちゃう困ったお嬢さんくらいにしか考えていなかったため、台詞を増やすうちに徐々にキャラがブレました。
時間がなかったとはいえ、あらかじめキャラ設定などきちんと練ってから台詞を考えるべきでした。
あと、声もOpennJTalkを使ったりして可愛いものに変えればよかったですね。
Clovaスキル作成編
自由発話が取れない
ヤンデレちゃん以外の誰かを褒めることでLINE通知が来ることになったため、名前でスロットタイプを作成しました。このスロットによりヤンデレちゃんとヤンデレちゃん以外の名前が入ってきた時を区別することができます。
しかし、ヤンデレちゃん以外を褒めた場合とスロットに値が入らなかった時に見分ける術がありません。
つまり「ツンデレちゃん可愛い」と「可愛い」の見分けがつきません。
「ヤンデレちゃん可愛い」と発話した時、slotsにyandereNameのvalueが入っています
"slots": {
"yandereName": {
"name": "yandereName",
"value": "ヤンデレちゃん"
},
"praise": {
"name": "praise",
"value": "可愛い"
}
}
「可愛い」または「ツンデレちゃん可愛い」と発話した時、そもそもslotsにyandereNameがないため2つの発話は区別がつきません
"slots": {
"praise": {
"name": "praise",
"value": "可愛い"
}
}
もし上記で病みモードになる仕様にしてしまうと、ユーザーが「(ヤンデレちゃん)可愛い」という意図で発話しても、ヤンデレちゃんは病みモードになりLINEを通知してくるといった残念な仕様になってしまいます。
~~それはそれでマイナス思考の早とちりヤンデレちゃんで可愛いですが、~~ユーザーからするとヤンデレちゃんと話しているわけで、「(ツンデレちゃん)可愛い」という意図で発話することはまずありません。
そこで、「可愛い」だけ発話した時にはヤンデレちゃんから名前を付けてとほしいと聞き返すことで誰に対しての可愛いなのか特定することにしました。
スキル再起動後、セッション情報が残ってしまう
ヤンデレちゃんは聞き返すためセッションにカウンターを持たせていましたが、公式SDKのLaunchRequestが呼ばれた時にカウンターを初期化していました。
これは、Clova審査一発通過のための注意点やClova公式SDK(Node.js)の使い方まとめにも書かれているようにスキルを起動時にセッション情報が残っていることがあるためです。
ただ、__すぐにスキルを再度起動させたときにLaunchRequestが呼ばれないときもある__ため、IntentRequestでも特定の処理が終わった段階でセッションの値を初期化しておくといったことをしていました。
...
.onLaunchRequest(responseHelper => {
// セッション情報の初期化
const sessionAttributes = {
loveCount: 0,
yandereNameAskCount: 0
};
responseHelper.setSessionAttributes(sessionAttributes);
})
.onIntentRequest(async responseHelper => {
// 例えばloveCountに使用していた処理が終わった場合
const sessionAttributes = {
loveCount: 0,
yandereNameAskCount: sessionAttribute.yandereNameAskCount
};
responseHelper.setSessionAttributes(sessionAttributes);
})
...
リジェクト
リジェクト内容はあとでまとめます。
リジェクト内容をClovaスキル初公開の私が受けたリジェクト内容一覧にまとめました。
基本的にClovaの公式ドキュメントをきちんと読むと確実です。
公式ドキュメントをきちんと読めば食らわなかったリジェクト内容もありました。申し訳ありませんでした。
課題
大量のLINE送信
LINEを大量に飛ばす仕様にするためsetTimeout
を使ってLINE送信の繰り返す関数を作成してみるとLINE送信の繰り返しが途中で終了していることが分かりました。
下記だとloopPush
が途中で終わってしまいます。
別関数にしているとメインのスレッドが終わってしまうとLINE通知処理が途中でも処理が終了してしまうようです。
...
.onIntentRequest(async responseHelper => {
const intent = responseHelper.getIntentName();
switch (intent) {
case "YandereModeIntent":
// 病みモードに突入したときのLINE送信処理
if(process.env["channelAccessToken"]){
const userId = responseHelper.getUser().userId;
await loopPush(userId);
}
break;
}
}
...
async function loopPush(userId,limit=3, counter=0)
{
// 繰り返されず途中で終了してしまう
var lineMsgWord = 'ヤンデレちゃんの台詞';
if(counter > limit) return;
var client;
client = new line.Client({
channelAccessToken: process.env["channelAccessToken"]
});
var message = {
type: "text",
text: lineMsgWord
}
await client.pushMessage(userId, message)
.then(() => {
console.log("--- bot success ---");
})
.catch( err => {
console.log("--- bot err ---");
console.log(util.inspect(err), false, null);
});
setTimeout(function(){loopPush(userId,limit, ++counter)}, 1);
}
よって、SQSをトリガーにして別に作成したLINE通知のlambdaを実行する方法も考えましたが時間がなかったため別関数にせずハンドラー関数に処理を書くことにしました……。
良い方法があれば教えてください。
まとめ
- 軌道修正は大変なので、ストーリー構成・キャラ設定は事前に熟考するとよいです
- ストーリー構成にもかかわってくるためJSONの取れ方やSDKの特徴など知っておくとよいです