Dialogflow v2 で SSML を使おうとしてハマった話
背景
ちょうど1週間前の5月9日に社内で Google Assistant アプリ開発の HelloWorld を披露する機会がありました。
そして、偶然にも5月9日は言わずと知れた「ピッコロ記念日」でした。
Google Assistant に「今日はピッコロ記念日です。」と言わせるのは簡単ですが、
どうしてもピッコロ大魔王様の声で音声を流したかったのです。
そこで、SSML で <audio>
を使おうとしたのが始まりでした。
この記事では試行錯誤順に記載していきます。
上手くいったパターンは一番最後に載ってます。
【NGパターン】Intent の TextResponse に設定してみる
確か、TextResponse に直接 SSML を書いても再生してくれるはずなので書いてみました。
そして、Integration をしてシミュレーターで試してみたところ、
以下のようなエラーになったのです。
MalformedResponse
expected_inputs[0].input_prompt.rich_initial_prompt.items[0].simple_response: 'display_text' must be set or 'ssml' must have a valid display rendering.
シミュレーターの Surface はデフォルトでは一番左の Phone が選択されていますが、
それだと、どうもエラーになってしまうようです。
試しに、以下のようにシミュレーターを Google Home にしてみると、
期待通りに再生されるようです。
display_text
と言っているし、
どうやら、画面を持つデバイスの場合に怒られるようです。
【NGパターン】Fulfillment の Inline Editor で設定してみる
InlineEditor
agend.add('<speak><audio src="*****.mp3"/></speak>');
agend.add('<speak><audio src="*****.mp3"/></speak>');
上記の部分です。
文字列を SSML にしてみました。
結果は変わらず。
シミュレーターが Phone だとエラーに。
Google Home だと、ちゃんと再生される。
【NGパターン】agend.add()
にオブジェクトを渡してみる
agent.add() の先、つまり dialogflow-fulfillment を追ってみることにしました。
grep してみると text-response.js
に以下のようなコードが。
getV2ResponseObject_(platform) {
~略~
if (this.ssml) {
response.simpleResponses.simpleResponses[0].ssml = this.ssml;
} else {
response.simpleResponses.simpleResponses[0].textToSpeech = this.text;
}
response.simpleResponses.simpleResponses[0].displayText = this.text;
~略~
}
this.ssml
や this.text
という情報だけをもとに、
何も考えずに agent.add()
に渡すものを文字列からオブジェクトに変えてみました。
- agend.add('<speak><audio src="*****.mp3"/></speak>');
+ agend.add({
+ text: '今日はピッコロ記念日です。',
+ ssml: '<speak><audio src="*****.mp3"/></speak>'
+ });
うーん。ダメだ。
そもそも動かない。。。
というか、ふと Cloud Function のログを見てみると、以下の通り Error を出力していました。
Error: unknown response type
at WebhookClient.add (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:225:13)
at test (/user_code/index.js:27:11)
at WebhookClient.handleRequest (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:251:44)
at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/user_code/index.js:68:9)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:47)
at /var/tmp/worker/worker.js:684:7
at /var/tmp/worker/worker.js:668:9
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
agent(WebhookClient)の add を見てみるとそりゃーダメです。
this.ssml
や this.text
という情報だけで何も考えなかったのが悪い。
add(response) {
if (typeof response === 'string') {
response = new Text(response);
}
~略~
} else {
throw new Error('unknown response type');
}
}
そして見事に throw
!!
【NGパターン】直接 Text オブジェクトを agent.add()
してみる
- const {Card, Suggestion} = require('dialogflow-fulfillment');
+ const {Card, Suggestion, Text} = require('dialogflow-fulfillment');
- agend.add({
+ agend.add(new Text({
text: '今日はピッコロ記念日です。',
ssml: '<speak><audio src="*****.mp3"/></speak>'
- });
+ }));
- const {Card, Suggestion} = require('dialogflow-fulfillment');
+ const {Card, Suggestion, Text} = require('dialogflow-fulfillment');
- agend.add({
+ agend.add(new Text({
text: '今日はピッコロ記念日です。',
ssml: '<speak><audio src="*****.mp3"/></speak>'
- });
+ }));
結果は変わらず。
シミュレーターが Phone だとエラーに。
Google Home だと、ちゃんと再生される。
【調査】真面目に追ってみる
入口は WebhookClient.add()
付近から、
そして、displayText を設定しているのが Text.getV2ResponseObject_()
シーケンス図
シーケンス図中段 alt
の上側、
どうやら agent.add()
に1つだけ文字列を渡す時と、
1つだけText オブジェクトを直接渡す時は、この alt の上側
に入るパターンの模様。
Text.getV2ResponseObject_()
が呼ばれるためには alt の下側
に入らなければならない。
そう思い sendMessagesResponse_()
付近を見ていると以下のようなコードを発見
// If AoG response and the first response isn't a text response,
// add a empty text response as the first item
if (
requestSource === PLATFORMS.ACTIONS_ON_GOOGLE && messages[0] &&
!(messages[0] instanceof Text) &&
!this.existingPayload_(PLATFORMS.ACTIONS_ON_GOOGLE)
) {
this.responseMessages_ = [new Text(' ')].concat(messages);
}
※ ざっくり first response isn't a text response, add a empty text
とのこと。
※ messages
は agent.add()
でどんどん追加されていくものです。
[new Text(' ')].concat(messages);
という、なんとも気になるコード。。。
messages[0] が Text ではないときに何かやっている。
Card オブジェクトの時とかに動くのだと思いますが。。。
確かにこのコードがあれば、messages が1個ではなくなります。
【OKパターン?】試しにやってみよう
+ agend.add(' ');
agend.add(new Text({
text: '今日はピッコロ記念日です。',
ssml: '<speak><audio src="*****.mp3"/></speak>'
}));
できたー。
できたー。。。?
1週間前と動きが変わったような?
1週間前 ' '
の空白のチャット(吹き出し)が Assistant から返ってきたような。。。
実は全角スペース入れちゃったのかもしれませんね。
【OKパターン】SSML に画面に表示できる文字列も入れてみる
そもそもエラーで 'ssml' must have a valid display rendering.
と言われているので、
なんとなく SSML の中に <audio>
だけでなく、何か文字列も入れれば良さそう。
ただし、単純に文字列を入れてしまうと音声再生後に読み上げられてしまうので、
そこも SSML でなんとかしないといけないです。
agend.add(new Text({
text: '今日、5月9日は「ピッコロ記念日」です',
ssml: `<speak><audio src="*****.mp3" /><sub alias="">今日、5月9日は「ピッコロ記念日」です。</sub></speak>`
}));
alias で読み上げられないように。
このパターンは、シーケンス図でいうところの alt の上側
のロジックに入り、
display_text
等は設定されないのですが、
Phone のシミュレーターで期待通りの動作をするパターンです。
まとめ
超神水は飲まなくて済んだ。
Author And Source
この問題について(Dialogflow v2 で SSML を使おうとしてハマった話), 我々は、より多くの情報をここで見つけました https://qiita.com/chibi929/items/4f3bcef9370186222684著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .