はじめに
先日書いた下記記事の、詳細解説その1です。
まずは開発したきっかけとメインの機能の解説です(公開の話はまた次回)。
himanago.hatenablog.com
この記事では、
LINE Developer Community : 第 2 回 ボット自慢 LT 大会でLT登壇した資料
および
[Cogbot 勉強会 Special ★ LINE から話せる楽しいチャットボットを作ろう!] (https://cogbot.connpass.com/event/124711/)でLT登壇した資料
www.slideshare.net
をベースに書いていきます。上記2つの資料はほとんどいっしょです。
開発のきっかけ
Azureを使うとすごいBotやVUIスキルが作れるよ!ということを示したかったのがきっかけです。
結果的にVUIの常識を覆すような面白いものができたのでよかったです。
ちなみにBOOT AWARDSでファイナル進出した「絵本読み聞かせ」でも使ったテクニックの流用で、これのリニューアル中に思いついたネタでもあります。
(でも、この「絵本読み聞かせ」はあてにしていたText-to-speechのAPIが廃止されちゃったため公開を断念…ゴメンナサイ。。)
Demo
デモ動画です。LT登壇したときはClova Friendsでやりましたが、自宅で撮ったこの動画はClova Deskです。
※ちなみにこれは開発時のものなので、現在のバージョンと少し動作が異なります
こんなふうに、スキル起動中にClovaが待機状態になり、その間にLINEでメッセージを送るとその内容をしゃべってくれる、というスキルです。
通常、決まったことしか話してくれないClovaに好きなセリフを言わせ、はげましてもらったり、ごっこ遊びなんかに使ったりできます。
Botには、事前に使いたいセリフをテンプレートとして登録しておく機能も備えています。
すごいところ
対話が原則のClovaスキルの常識を覆す!
まずポイントは、ユーザーが話しかけなくてもClovaだけがしゃべりだす点です。
VUIは基本的にユーザー側から問いかけて使うものなので、スマートスピーカー側からしゃべってくる、というのは新しい可能性につながりそうな予感がします。
その場で何でもしゃべってくれる!
LINEで入力した内容をそのまましゃべらせる、文字通りの「腹話術」ができます。
Clovaに言ってほしいと思う自由な言葉を言わせることができることができます。
いつまでも続くスキルのセッション!
一番の目玉が、セッションが勝手に切れず長時間遊べる点です。
スマートスピーカースキルを使ったことがある方なら、スキル起動後はユーザーと対話を続けないとセッションが切れてスキルが終了してしまいます。
また、スキルを開発したことがある方なら、サーバー側でもタイムアウトが厳しく、スキル起動中に長時間待たせることができない点に苦労することが多いと思います。
しかし、このスキルではユーザーからの操作を、スキルを起動したまま「無限に」待ち受けることができます。
これを実現しているのが「無音無限ループ」と「Durable Functions」のコンボです。
アーキテクチャ
アーキテクチャはこれだけです。
基本的にバックエンドはAzure Functionsのみで実装しています。
は?
長時間起動したり、テンプレート作成のように会話の流れを覚えてステートフルに処理する機能を持っていますが、本来単純な関数を作る機能であるFaaS(Function as a Service)のAzure Functionsだけで、このような処理を実現できるとは思えません。
でも、できちゃうのがAzure Functionsなんです。Durable Functionsという拡張機能を使うことにより、関数がステートフルに動くようになるのです。
Durable Functions
Durable Functionsでできること
図は公式のドキュメントからとってきたものです。
Durable Functionsは、状態を維持して行うこれらのような一連のワークフローをシンプルな関数・コードのみで実現できる拡張機能です。
Durable Functionsのしくみ
Durable Functionsでは3種類の関数を組み合わせてステートフルな処理を実現します(v2.0からはさらにEntity Functionというものが追加され、さらに幅が広がります)。
関数の実行状況など、ステートフルに情報を保持しながらいいかんじに動いてくれるものですが、履歴などの実行情報をストレージに書き込んで勝手に管理してくれます。
OrchestrationClient(Starter関数)
外部から呼び出し/実行される関数本体で、CEKやMessaging APIからのHTTPリクエストをで呼び出されるものです。
今回使ったものではHTTPトリガーの関数です。Orchestratorを起動する役割を持ちます。
Orchestrator関数
次に挙げるActivity(実処理を担当する関数)を呼び出して状態を管理し、関数のオーケストレーションを担当します。
制約として、オーケストレーターではランダム値やI/O処理、非同期APIの呼び出しを直接行うことは禁止です(Activityにやらせる必要がある)。
Activity関数
Orchestratorからの実行指示で起動する関数で、アプリケーションの機能を担当します。
Durable Functionsではこれらの組み合わせで複雑なフローをシンプルに実現していきます。
Durable Functionsの関数で使用する代表的なメソッド
関数同士を連携させて"Durable"(持続的)な処理を作る部品が揃っています。ここに挙げたのは一部ですが、全体でもそこまで数が多いわけではないので、それらを一度押さえてしまえば、組み合わせでいろんなことが実現できます。
公式ドキュメントに載っている実装例(監視とか人による操作とか)がピンと来なくても、それ以外にも意外と使える場面は多いのではないか?と思うので、いろいろ試してみたいと思っています。
腹話術スキルでの利用例
Durable Functionsは、まずBotのテンプレート作成で使っています。
リッチメニューから「テンプレート作成」を押してオーケストレーターを起動し、外部イベントを待機する状態にします。
その間、LINEからメッセージを送られた際にそれを外部イベントとして RaiseEventAsync
することで、待機していたオーケストレーターが動きセリフリストに追加する、という具合です。
コード
こんなふうに、本当にシンプルなコードだけで複数リクエストの連携が実装できるというのは手軽で素晴らしいです。
ストレージや複数の連携などを意識せず実装できるという点で、より「サーバーレス」になるものだということもできるのですが、そもそもステートレスでない点で「サーバーレスを逸脱する」と言う人もいそうで、なかなか説明が難しいと最近は感じています。
無音無限ループ
さらにこのスキルでもう一つ肝になっているのが、「無音無限ループ」というCEK(Clova Extensions Kit)の裏技です。
これ自体は「絵本読み聞かせ」の機能を実装しているときに見つけたものです。
無音無限ループとは
CEKには、 AudioPlayer
という機能があり、mp3再生をClovaにさせることができます。
この機能でmp3を再生すると、再生終了時、AudioPlayer.PlayFinished
というイベントが発行され、サーバーに通知されます。
ごく短い長さの「無音のmp3ファイル」を用意し、これをAudioPlayerで再生させ、PlayFinished
イベントが来たら再び同じ無音mp3を再生させるということをすれば、スキルを維持したままClovaスキルを待機状態にし、理論上無限のセッションを得ることができるのです。
もちろん、そのまま無限に繰り返すだけだと何もできないので、何らかのフラグを持たせ、特定の条件でループをbreakするようにしておけば、さまざまなことが実現できます。
そのbreakする部分にDurable Functionsのイベント待機が相性がよいです。
腹話術スキルでの利用例
今回は、こんなかんじで「LINEからの入力」で無限ループを脱出するように実装しています。
LINEからの入力を待機するオーケストレーターを作っておき、その実行状態を監視、無限ループの継続条件にします。
外部イベントが発生すると無限ループが終了、Clovaに入力内容をしゃべらせる処理を実行するという流れです。
コード
Durable Functionsではオーケストレーターの実行状態を確認するメソッド(外部イベントの待機が継続しているか確認することができる)があるので、無限ループの継続条件にそのまま使えばOK。
超お手軽です!
苦労した点
複数のイベントをお互いに監視しあうようなロジックになっているので、タイミングを合わせるのが結構大変です。
微妙に隙が生まれて無反応になってしまうことがあったので、そのあたりをできる限り調整した部分がここのコードです。
Durable Functionsは便利ですが、完全リアルタイムで同期されるような処理を作るのには、実はあんまり向いていないなと実感した部分です。
ここまでやっても、タイムラグはどうしても生まれてしまいます(腹話術スキルでは無音mp3の秒数を短くして反応をよくすることもできます)。
イベント監視の反応をよくする方法
Functionsの host.json
の設定ファイルで、 maxPollingInterval
を設定することで反応をよくすることができるようです。
{ "version": "2.0", "extensions": { "queues": { "maxPollingInterval": "00:00:05" } } }
ポーリングの間隔はデフォルト30秒なようなので、リアルタイム性を求めるのであれば、ここを設定することが必要です。
おまけ:翻訳機能
Cogbot勉強会でのLT登壇をきっかけに、Cognitive Servicesを使った翻訳機能を追加しました。
勉強会の時間内では間に合わなかったものの、あとから完成させました。
Clovaがしゃべれる英語と韓国語に対応しています。
おわりに
今回はDurable Functionsの機能を最大限に活用するサンプルとして実装し、LTで紹介しましたが、このあとこのスキルはストア公開につながりました。
その話はまた別の機会に書いていこうと思います。
公開に伴い何か所か修正したので、その前のプロトタイプ版(翻訳追加時点)のコードへのリンクを置いておきます。