himanago

C#とかAzureとかMS系の技術メモ中心に書きたいです。最近はLINE関連技術やVUIも多め。

Durable FunctionsとCEK裏技「無音無限ループ」で「テキスト腹話術」を開発しました

はじめに

先日書いた下記記事の、詳細解説その1です。
まずは開発したきっかけとメインの機能の解説です(公開の話はまた次回)。 himanago.hatenablog.com

この記事では、

LINE Developer Community : 第 2 回 ボット自慢 LT 大会でLT登壇した資料

www.slideshare.net

および

[Cogbot 勉強会 Special ★ LINE から話せる楽しいチャットボットを作ろう!] (https://cogbot.connpass.com/event/124711/)でLT登壇した資料

www.slideshare.net

をベースに書いていきます。上記2つの資料はほとんどいっしょです。

開発のきっかけ

Azureを使うとすごいBotやVUIスキルが作れるよ!ということを示したかったのがきっかけです。
結果的にVUIの常識を覆すような面白いものができたのでよかったです。

ちなみにBOOT AWARDSでファイナル進出した「絵本読み聞かせ」でも使ったテクニックの流用で、これのリニューアル中に思いついたネタでもあります。

f:id:himanago:20190714095212p:plain

(でも、この「絵本読み聞かせ」はあてにしていたText-to-speechのAPIが廃止されちゃったため公開を断念…ゴメンナサイ。。)

f:id:himanago:20190714095216p:plain

Demo

デモ動画です。LT登壇したときはClova Friendsでやりましたが、自宅で撮ったこの動画はClova Deskです。
※ちなみにこれは開発時のものなので、現在のバージョンと少し動作が異なります

f:id:himanago:20190714100143p:plain

こんなふうに、スキル起動中にClovaが待機状態になり、その間にLINEでメッセージを送るとその内容をしゃべってくれる、というスキルです。

通常、決まったことしか話してくれないClovaに好きなセリフを言わせ、はげましてもらったり、ごっこ遊びなんかに使ったりできます。
f:id:himanago:20190714100253p:plain

Botには、事前に使いたいセリフをテンプレートとして登録しておく機能も備えています。

f:id:himanago:20190714100437p:plain

すごいところ

f:id:himanago:20190714100519p:plain

対話が原則のClovaスキルの常識を覆す!

まずポイントは、ユーザーが話しかけなくてもClovaだけがしゃべりだす点です。

VUIは基本的にユーザー側から問いかけて使うものなので、スマートスピーカーからしゃべってくる、というのは新しい可能性につながりそうな予感がします。

その場で何でもしゃべってくれる!

LINEで入力した内容をそのまましゃべらせる、文字通りの「腹話術」ができます。

Clovaに言ってほしいと思う自由な言葉を言わせることができることができます。

いつまでも続くスキルのセッション!

一番の目玉が、セッションが勝手に切れず長時間遊べる点です。
スマートスピーカースキルを使ったことがある方なら、スキル起動後はユーザーと対話を続けないとセッションが切れてスキルが終了してしまいます。

また、スキルを開発したことがある方なら、サーバー側でもタイムアウトが厳しく、スキル起動中に長時間待たせることができない点に苦労することが多いと思います。

しかし、このスキルではユーザーからの操作を、スキルを起動したまま「無限に」待ち受けることができます。

これを実現しているのが「無音無限ループ」と「Durable Functions」のコンボです。

アーキテクチャ

f:id:himanago:20190714101237p:plain

アーキテクチャはこれだけです。

基本的にバックエンドはAzure Functionsのみで実装しています。

f:id:himanago:20190714101308p:plain

は?

f:id:himanago:20190714101345p:plain

長時間起動したり、テンプレート作成のように会話の流れを覚えてステートフルに処理する機能を持っていますが、本来単純な関数を作る機能であるFaaS(Function as a Service)のAzure Functionsだけで、このような処理を実現できるとは思えません。

f:id:himanago:20190714101535p:plain
でも、できちゃうのがAzure Functionsなんです。Durable Functionsという拡張機能を使うことにより、関数がステートフルに動くようになるのです。

Durable Functions

Durable Functionsでできること

f:id:himanago:20190714101637p:plain
図は公式のドキュメントからとってきたものです。
Durable Functionsは、状態を維持して行うこれらのような一連のワークフローをシンプルな関数・コードのみで実現できる拡張機能です。

Durable Functionsのしくみ

f:id:himanago:20190714101856p:plain

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の関数で使用する代表的なメソッド

f:id:himanago:20190714102510p:plain
関数同士を連携させて"Durable"(持続的)な処理を作る部品が揃っています。ここに挙げたのは一部ですが、全体でもそこまで数が多いわけではないので、それらを一度押さえてしまえば、組み合わせでいろんなことが実現できます。

公式ドキュメントに載っている実装例(監視とか人による操作とか)がピンと来なくても、それ以外にも意外と使える場面は多いのではないか?と思うので、いろいろ試してみたいと思っています。

腹話術スキルでの利用例

Durable Functionsは、まずBotのテンプレート作成で使っています。

f:id:himanago:20190714102947p:plain

リッチメニューから「テンプレート作成」を押してオーケストレーターを起動し、外部イベントを待機する状態にします。

その間、LINEからメッセージを送られた際にそれを外部イベントとして RaiseEventAsync することで、待機していたオーケストレーターが動きセリフリストに追加する、という具合です。

コード

f:id:himanago:20190714103231p:plain

こんなふうに、本当にシンプルなコードだけで複数リクエストの連携が実装できるというのは手軽で素晴らしいです。

ストレージや複数の連携などを意識せず実装できるという点で、より「サーバーレス」になるものだということもできるのですが、そもそもステートレスでない点で「サーバーレスを逸脱する」と言う人もいそうで、なかなか説明が難しいと最近は感じています。

無音無限ループ

さらにこのスキルでもう一つ肝になっているのが、「無音無限ループ」というCEK(Clova Extensions Kit)の裏技です。
これ自体は「絵本読み聞かせ」の機能を実装しているときに見つけたものです。

無音無限ループとは

f:id:himanago:20190714103555p:plain

CEKには、 AudioPlayer という機能があり、mp3再生をClovaにさせることができます。
この機能でmp3を再生すると、再生終了時、AudioPlayer.PlayFinished というイベントが発行され、サーバーに通知されます。

ごく短い長さの「無音のmp3ファイル」を用意し、これをAudioPlayerで再生させ、PlayFinished イベントが来たら再び同じ無音mp3を再生させるということをすれば、スキルを維持したままClovaスキルを待機状態にし、理論上無限のセッションを得ることができるのです。

もちろん、そのまま無限に繰り返すだけだと何もできないので、何らかのフラグを持たせ、特定の条件でループをbreakするようにしておけば、さまざまなことが実現できます。

そのbreakする部分にDurable Functionsのイベント待機が相性がよいです。

腹話術スキルでの利用例

今回は、こんなかんじで「LINEからの入力」で無限ループを脱出するように実装しています。
f:id:himanago:20190714103734p:plain

LINEからの入力を待機するオーケストレーターを作っておき、その実行状態を監視、無限ループの継続条件にします。

外部イベントが発生すると無限ループが終了、Clovaに入力内容をしゃべらせる処理を実行するという流れです。

コード

f:id:himanago:20190714104615p:plain
Durable Functionsではオーケストレーターの実行状態を確認するメソッド(外部イベントの待機が継続しているか確認することができる)があるので、無限ループの継続条件にそのまま使えばOK。

超お手軽です!

苦労した点

f:id:himanago:20190714104855p:plain
複数のイベントをお互いに監視しあうようなロジックになっているので、タイミングを合わせるのが結構大変です。

微妙に隙が生まれて無反応になってしまうことがあったので、そのあたりをできる限り調整した部分がここのコードです。

Durable Functionsは便利ですが、完全リアルタイムで同期されるような処理を作るのには、実はあんまり向いていないなと実感した部分です。

ここまでやっても、タイムラグはどうしても生まれてしまいます(腹話術スキルでは無音mp3の秒数を短くして反応をよくすることもできます)。

イベント監視の反応をよくする方法

Functionsの host.json の設定ファイルで、 maxPollingInterval を設定することで反応をよくすることができるようです。

{
  "version": "2.0",
  "extensions": {
    "queues": {
      "maxPollingInterval": "00:00:05"
    }
  }
}

ポーリングの間隔はデフォルト30秒なようなので、リアルタイム性を求めるのであれば、ここを設定することが必要です。

おまけ:翻訳機能

Cogbot勉強会でのLT登壇をきっかけに、Cognitive Servicesを使った翻訳機能を追加しました。
勉強会の時間内では間に合わなかったものの、あとから完成させました。

Clovaがしゃべれる英語と韓国語に対応しています。
f:id:himanago:20190714105605p:plain

おわりに

今回はDurable Functionsの機能を最大限に活用するサンプルとして実装し、LTで紹介しましたが、このあとこのスキルはストア公開につながりました。

その話はまた別の機会に書いていこうと思います。

公開に伴い何か所か修正したので、その前のプロトタイプ版(翻訳追加時点)のコードへのリンクを置いておきます。

github.com

Clovaスキル「テキスト腹話術」の開発とそれにまつわる登壇、ストア公開までの道のり

はじめに

平成最後の月である2019年4月に、スマートスピーカースキルの開発と、それにあわせた登壇をいくつか行いました。

本当は、登壇後すぐにブログを書いておきたかったのですが、4月の終わりごろに自宅に住めなくなり、引っ越し先として契約した家もまたすぐに住めなくなるというけっこう深刻かつレアな状況に陥ったため、なかなかブログ執筆などのためのまとまった時間がとれませんでした。

今月に入り、無事に新しい家へ引っ越してこれたので、心機一転、書いていきたいと思います。

きっかけ

LINE Developer Communityの第1回ボット自慢大会(自分は参加していなかった)で、こんなツイートをみたのがきっかけ。

Azureが好きで、LINE関連の開発もしている身としては、AzureがLINE開発との相性がいいことも知っていて、なんだか悔しく思ったので、こうリプライ。

こんな流れで、第2回でAzureを使ったLINE Bot(+Clovaスキル)を披露することになりました。

第2回自慢大会

そして迎えた自慢大会。 linedevelopercommunity.connpass.com

Azureの中でも特徴的なDurable Functionsを使ったClovaスキルで「Azureはいいぞ」をしようと思い、サンプルスキルを開発してLT登壇しました。
LINE Botとも連携し、Bot・スキル双方でDurable Functionsを使用しました。

また、「無音無限ループ」というClovaスキル開発での自分のとっておきの技を使ったので、けっこう大きなインパクトを与えることができました。

Cogbot勉強会

つづいて、こちらのイベントでもほぼ同じ内容でLT登壇しました。
cogbot.connpass.com

Bot Framework (Azure Bot Service) と Cognitive ServicesでLINE Botを作るハンズオンが中心の勉強会だったので同じ内容はどうかな…と思いながらの参加でしたが、冒頭のセッションではClovaへの言及もあり、ミニハッカソンの成果LTとしてわりと自然な流れでお話をさせていただくことができました。

Global Azure Boot Camp 2019

そして最後に、大変ありがたいことに、Azureの大きなイベントで登壇機会を得られました。 jazug.connpass.com

こちらは、公募セッションとして45分お話しできる機会をいただきました。

自慢大会、Cogbot勉強会で紹介したスキルのほか、Qiitaに書いたLogic Appsでのクロスプラットフォームスキル開発の話や、Functionsのコールドスタート対策の話も紹介し、それまでにやってきたAzureでのスマートスピーカースキル開発の総決算としてお話をしました。

新しく生まれたスキル

自慢大会をきっかけに生まれたのは、LINE Botを使ってClovaで腹話術ができるスキル、「テキスト腹話術」です。

Durable Functionsで楽に複雑なことができるよ!というサンプルスキルのつもりで作ったものでしたが、ストアで公開いただくことができました。

clova.line.me

そしてなんと、LINE Clova Skill Awards の「LINE(Messaging API、LIFF、LINE Pay等のAPI)との連携がうまく活用されているスキル」部門で入賞することもできました(下記リンクの下のほうに載ってます)。

engineering.linecorp.com

5月中旬が締め切りだったこのミニアワードですが、実は、これに向けて別のスキルを作っていました。

が、開発に苦戦&アイディアが他の方のスキルと被ってしまったため、締め切り間際に急遽サンプルスキルとして作っていた腹話術を提出…という経緯でした。

「無音無限ループ」がかなり裏技的なハックだったため、ストア公開は厳しいと思っていたので一種の賭けでしたが、なんとか審査を通過。
サンプルスキルを公開用にアレンジするのも結構大変でしたが、そのぶんかなり勉強にもなりました。

登壇とストア公開の話を書いていきます

さて、ざっと4月以降の話を書きましたが、それぞれ細かい部分で詳細にアウトプットしておきたいことがあります。

  • 登壇したそれぞれの発表内容に関する細かい解説的な話
  • 「テキスト腹話術」スキルのしくみ
  • スキルのストア公開時の注意することと「無音無限ループ」安全版
  • Clova Skill Awards 受賞と今後の展望

こんな内容を、これからいくつかの記事に分けて書いていこうかなと思っています。

今回はあまりまとまりのない雑多な投稿になってしまいましたが、上記について、なるべく間をあけずに書いていきたいなと思います。

LINE CONFERENCE 2019に参加してきました~LINEやClovaの未来について感じたことと「Clover」

LINE CONFERENCE 2019に行ってきた

6月27日に、LINE社の事業戦略発表会の「LINE CONFERENCE 2019」に行ってきました。
会場は舞浜アンフィシアターTDRな立地で、イクスピアリとアンバサダーホテルを抜けていきます。すごいとこでやりますね…。

conference.linecorp.com

内容は、LINEのこれからがいろんな事業戦略の話楽しかったです。

LINEへのOpenChatの導入や、クリエイターズスタンプの定額制プラン、LINEスコア、広告配信まわりの話など。
開発者としてはLINEで育ったAIの機能を「販売」するというLINE BRAINなんかが気になりました(これは業務での使用を検討したいので説明会応募した)。

特に印象に残ったのは

自分はClovaのスキル開発などをよくやっているClovaユーザー/ファンなので、Clovaの話が終盤までほとんど出てこなくてやきもき…。
それどころか「DUET」という音声で対話できるAIが出てきたりして、「Clovaは…?」とちょっと不安になりました。

しかし、終盤はClovaの今後の展開についてたっぷり話が聞けたので、一安心&大満足です。

特にいいなと思ったのがこれです。LINEカーナビ!
コンセプトムービーが超テンションあがりました。

スマホでClova

f:id:himanago:20190703232035p:plain

まずね。Clovaがスマホから呼べてるんですよ!

OK GoogleとかHey SiriなノリでClova呼べるとか最高すぎません?Alexaもアプリから呼べるし、Clovaもついにスマホへ。

コンセプトムービー的には、カーナビと連携するスマホアプリって感じですね。 そして運転中は声でウェイクできる。
ストアのスキルは呼べるのかな?LINEカーナビ向けスキルとかも作られていくのかも。

VUIが活きるカーナビ

f:id:himanago:20190703232236p:plain カーナビ操作を声でできるのは本当にいい。
運転中はVUIの最も自然で必要な場面のひとつですからね。

f:id:himanago:20190703232319p:plain 行先を声だけで操作できるって本当に必要な機能。
運転中にカーナビをGUIで操作するのは危ないですからね。

LINE連携という最大の強み

f:id:himanago:20190703232433p:plain 声でLINEが送受信できるというのもClovaならでは。本当、LINE/Clovaの強みが活かされてて素晴らしい。

LINE MUSICも

そして、動画の最後、LINE MUSICが使えるよという場面。 f:id:himanago:20190703232615p:plain

ここで流れるのがタイアップ曲である北原ゆかさんの「Clover」という曲。

コンセプトムービーで流れたサビ部分で、"クローバ クローバ クローバ~♪ "って歌ってるんです。
完全にClovaのテーマ曲じゃないですか。

Clovaテーマ曲としての「Clover」

YouTubeで前半部分が聴けます。

Clovaの気持ちを歌った曲?

この曲の歌詞が、もちろん普通に良いんですが、「Clovaの曲」として聴くと、Clovaユーザー/スキル開発者としてはかなり刺さるものになっているんです(ということを言いたくてこの記事を書いた)。

たとえばこの冒頭の一節。

笑わない君の笑顔を
この手でつかむ時まで
きっと 頑張るよ任せてよ
もっと頼ってよ

これがClovaの言葉だと思うと、アシスタントとして頑張るClovaの気持ちみたいですよね。健気。
「もっと頼ってよ」=「もっと話しかけてよ/使ってよ」ってことなので、なんだか泣けてきます。ねぇClovaって言いたくなります。

2番冒頭もまたそんな感じで、

弾まない会話さえも
癖になって
もっと欲しくなって 無理するんじゃなくて
本当の笑顔が見たいんだ

Clovaとの会話が弾まないこと(誤認識とか「すみませんわかりません」とか)って結構多いから、「これはまさにClova」って感じがします。

「本当の笑顔が見たい」というのは、ちゃんと真の意味で役に立ちたい、喜んでほしいというClovaの想いだと捉えることができるのでやっぱり泣ける。

そしてサビ。

clover clover clover
君の幸せは僕が見つけるよ
必ず叶えてみせるよ

いいですねここ。

君(=ユーザー)、僕(=Clova)で考えると、AIアシスタントとしてユーザーの願いを叶えられる存在になりたいという想いだと解釈できます。

ボロボロになっても
消えそうになっても
届けたいんだ
世界の高鳴りを

ここが最高。
「世界の高鳴りを届ける」ってフレーズ、すごくないですか?

他社のAIアシスタントもそうですけど、AIアシスタントの使命のひとつとして、世界中の情報の中から、ユーザーが求めるものを時に期待以上のかたちで提供することがあると思うんです。
本当にAIアシスタントにぴったりなフレーズだと思い、とても感動しました。

LINE CONFERENCEで出た話だと、Clovaも「LINE Search」に絡んだりとか、スカパーと共同開発の次世代テレビあたりがまさにそんな感じになるのかなぁと期待を膨らませています…。

(ちなみに次世代テレビ。これも実現したらかなりすごい)

開発者目線?

サビ部分、「clover clover clover」から始まるので、「Clova Clova Clova」と呼びかけている歌としても聴けます。

その場合、君(=Clova)、僕(=スキル開発者)としてとらえると、「Clovaの幸せは僕が叶える」ということで、開発者としてClovaをより進化させられるスキルを作る!という決意の歌としても考えることができそうです。

歌詞解釈としてはなんだか変ですが、そういうふうにも聴くことができるな…と思い、開発意欲が刺激させられたりもしました。

おわりに

後半はほとんどCloverという曲の話でした。

この曲自体、Clovaの発売前に発表されているものなので、Clovaのことを歌ったものでは当然ないのですが、よくこんなドンピシャなタイアップ曲を見つけてきたなぁと。

LINE CONFERENCEでも話があったように、さすがLINE MUSICでより多くの音楽との出会いを謳うだけあります。

LINE MUSICは今後全曲が1日1回無料再生できるフリーミアムプランも来るし、ClovaでCloverを聴いて気持ちを高めていこうと思いました。

Azure Durable Functionsでハマった話

前置き

Clovaスキルの開発でDurable Functionsを使った機能をいろいろ試しています。
そのうちのひとつで、「時間のかかる処理」をやらせるということを試していた中でちょっとハマってしまったので、記録として残しておきます。

作っていたもの

MSのかずきさんがやっていた、
blog.okazuki.jp
を発展させたものを作っていました。

作っていたのは2つ。

まずは、Clovaのオーディオ再生機能を利用した無限ループで終了を待機させるというもの(以下、待機版とよぶ)。
Durable Functionsの呼び出した処理のステータス確認をする機能を使ってループの終了判定をしています。
github.com

もうひとつは、完了したらLINE通知するというもの(以下、通知版とよぶ)。
github.com
LINE通知はClovaならではなのでおもしろいかなと思ったのですが、よく考えたら別に処理の中で待機とか状態確認とかする必要もなく、単純に終わったらLINEにPushすればいいだけ。
作っているときからDurableを使うほどのものではないな…とは思っていたものの、上記の派生でそのまま比較用に作りました。

何が起きたか

待機版を作って試して、理想通りにうまく動きました。

その後通知版を作ってデプロイして動作確認していたのですが、どうも待機版のほうが動かなくなってしまっている。
呼び出したオーケストレーター関数のステータスがPendingになることがかなり多いし、指定した待機時間より明らかに長く時間がかかっている。
しかも最終的な結果がFailed

なぜ?と思いながらもあまり深いところまで調べることができないまま、偶然?かずきさんとお会いする機会がやってきました。

スマートスピーカーを遊びたおす会にて

参加したスマートスピーカーの勉強会でかずきさんがサプライズ登壇。
まさか参考にさせていただいている方ご本人にこんなにすぐお会いできるとは思いませんでした。
blog.okazuki.jp

内容もタイムリーにDurable Functionsの話だったので、懇親会で質問させていただき、アドバイスをいただきました。

そのひとつが、↑の記事で言及いただいている、履歴削除です。
こちらを導入し、履歴を消したところ、Pendingが起きないようになったような気がします。
(細かく原因を調べてから対処したわけではないので履歴が原因だったのかははっきりしません)

ハマっているときはとにかくPendingが出まくって処理が滞っていたのでそれがなくなったのはよかったのですが、それでもまだ結果がFailedになってしまう現象は変わらずでした。

上記のほかにも、Storageに書き出されている履歴を見たり、Application Insightsで調べてみたりするとよい、というアドバイスをかずきさんよりいただいたので、そのあたりを調べてみることにしました。

エラー内容と発生場所

Storageをみてみると、こんなエラーが出ていました。

Orchestrator function 'LongTimeOrchestrationFunction' failed: Value cannot be null.Parameter name: basePath

basePath…????

見覚えがありました。これは通知版のほうの

// 結果をLINEで通知
var config = new ConfigurationBuilder()
                .SetBasePath(context.GetInput<string>())    // FunctionAppDirectory
                .AddJsonFile("local.settings.json", true)
                .AddEnvironmentVariables()
                .Build();

の処理で出そうなエラーです。
basePathをオーケストレーターのinputで渡していますが、それが渡ってきていないときのエラーと同じです。

ところがいま動かないのは、待機版。
なぜ通知版のようなエラーが…?

そう、待機版から、通知版のオーケストレーター関数が呼ばれてしまっていたのです。

再現手順と原因

結論からいうと、原因は同一タスクハブ内に同一名称のオーケストレーター関数を置いたことが原因でした。
docs.microsoft.com

今回の再現手順

同一のストレージアカウントを使い、Durable Functionsを使用した以下の2つのFunction Appを作成。

  • Function App①(待機版)
    実行しているオーケストレーターの名称は「LongTimeOrchestrationFunction」

  • Function App②(通知版)
    実行しているオーケストレーターの名称は「LongTimeOrchestrationFunction」
    ※①と同じ名前だが、実行する内容は異なる。

まず①待機版をデプロイし、②通知版をデプロイしてそれぞれを実行すると、
結果は①成功、②失敗

次に①待機版をデプロイしなおすと、
結果は①失敗、②成功

そして②通知版を再度デプロイしなおすと、
なんと①成功、②失敗

エラー内容などから見て、どうも①②のどちらを実行しても、前にデプロイしたほうのオーケストレーター関数が呼ばれていることがわかりました。

最新のものが呼ばれるのではなく、最新じゃないほうが呼ばれるという動きでした。

なぜこうなったか

Durable Functions はストレージアカウント内に作られた「タスクハブ」の中で動作をします。

https://docs.microsoft.com/ja-jp/azure/azure-functions/durable/media/durable-functions-task-hubs/task-hubs-storage.png

Durable Functionsのタスクハブ名は、デフォルトで DurableFunctionsTaskHub です。
2つのアプリを作ったとしても、同じストレージアカウントの中に作ると、デフォルトのタスクハブに両方のアプリが入ることになってしまう、ということでした。

Durable Functionsは複雑な処理を普通のプログラムのように記述できる点が魅力ですが、だからといって完全に普通のプログラミングと完全に同じ感覚でやってはいけませんね。
たしかに同じ場所に同じ名前のもの入れるなって話ですが、普通の感覚だと別アプリ/別プロジェクト/別namespaceだったら同じ名前でも問題ない…となりますよね。

でもDurable Functionsはそもそも関数間のコールを関数名の文字列で行うので、そりゃ衝突してしまいます…。

対処法

ということなので、タスクハブが共通にならないよう、片方(今回は通知版)のタスクハブ名をhost.jsonで変更しました。
これで、どちらも動くようになりました!
(もちろん、別のストレージアカウントを使うのでもOK)

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "hubName": "ClovaLongRunCmplLinePushNotificationSampleTaskHub"
    }
  }
}

でも、だったら最初からタスクハブ名はアプリごとの固有の名前になってほしいな…と思ったのですが、逆に別のプロジェクトで作った関数を同じタスクハブ内で回して相互に連携…とかも多いのかな…?

「デフォルトをアプリ名にしてくれ!」っていうissueも上がっているみたいなので、そのうち対応されるのかもしれません。 github.com

ということで。
なんとか対処することができました。本当によかった…。

心残り

Application Insightsでいろいろ見たかったのですが、なぜか最初の画面ですぐクラッシュしてしまい見れませんでした…。
後日再挑戦したいと思います。

お礼

かずきさんには勉強会のあとも、メールやTwitterでフォローいただきました。
ありがとうございました!!

おわりに

Durable Functions、楽しいです。

これまで雰囲気で使ってましたが、内部の動作を考えたりドキュメントをじっくり読んだり、ソースを追ったり…ということをすると理解が深まりより楽しいですね。そんなことを再認識しました。

スマスピとの組み合わせも現在いろいろ試しているところなので、どこかでまとめて報告できたらなと思います。

LINE Bot開発にインパクト大!?LINE公式アカウント統合によるプラン・料金体系の変更

まえおき

2019年4月18日から、「LINE@」などの複数あったLINEのビジネス向けサービスが 新しい「LINE公式アカウント」として統合される とのことです。
最近業務でもLINEの公式アカウント導入を検討したりしていて、そのときに見つけた情報なんですが、これ、Bot開発にめちゃくちゃ影響あるんじゃないですか…!?

www.linebiz.com

これまでまったく気づいてなかったので、ちょっと焦ってます…。LINE Bot界隈では周知の事実なんでしょうか。。
→(追記)周知っぽいですね。あとから見て知りましたがSlackでは話題になってました(ちゃんと通知ONにしてウォッチしておこう…)

公式からアナウンスをぱっと見てもよくわからないところもあったので、Bot開発に関係ありそうな部分を自分なりに整理してみます。

公式からのアナウンス

くわしい情報はこの記事にありました。
blog-at.line.me

ここに載っている2つのPDFが最新のアナウンスのようです。

以下、これらのPDFからBot開発者への影響を読み取ってみます。

Bot開発にインパクトがありそうな変更点

料金プランと使用可能な機能

これまでLINE@のプランと、Bot用のプランで料金の表が2つありましたが、これが統合されてシンプルになり、しかも安くなりました。
さらに、 どのプランでもすべての機能が使えるようになる みたいです。

つまり、 フリープランでもMessaging APIのPush APIが使えるようになる のです。
これは大きい!

プランはこんな感じです。

フリープラン ライトプラン スタンダードプラン
月額利用料 無料 5,000円 15,000円
メッセージ配信数(無料分) 1,000通 15,000通 45,000通
追加メッセージ料金 追加購入不可 5円/1通 ~3円/1通

あれ?Developer Trialは…?

Developer Trial プランがなくなる

どうやら、なくなるようです。フリープランへ強制移行とのこと。
資料2のP.8には、「Developer Trialプランは、今年夏頃にフリープランへ強制サービス移行する予定です」と書かれています。

ここで「フリープランでPush APIが使えるならいいじゃん」「むしろ友だち数制限がなくなっていいことしかないじゃん」と思うかもしれません。
…が、どうもそう単純な話ではなさそうです。

Push APIの利用が従量課金制になる

Push APIは課金制になるようです。

じゃあDeveloper Trialがフリープランに移行されると、Pushを使うたびに課金されるの??
と一瞬思いましたが、そんなことないです。フリープランといいつつ勝手に課金されたら怒りますよね…。

さきほどの料金表にある、「メッセージ配信数(無料分)」。

フリープラン ライトプラン スタンダードプラン
月額利用料 無料 5,000円 15,000円
メッセージ配信数(無料分) 1,000通 15,000通 45,000通
追加メッセージ料金 追加購入不可 5円/1通 ~3円/1通

「メッセージ配信」とあるので現行LINE@での「メッセージ配信」のことを指すように思えるのですが、どうやら「課金対象となるメッセージ種別」(つまり有料メッセージ)という区分があるようで、それの配信/送信のことだろうと思われます。有料メッセージに、Push APIも含まれています。

資料2 P.6より f:id:himanago:20190301005319p:plain

各プランのメッセージ配信数(無料分)の数までは無料でPush APIを使った送信が可能で、それを超えた分は「追加メッセージ料金」の額に応じた課金がされる、ということなのだと思われます。

ということなので、たとえばライトプランなら「月額基本料金5000円。Push APIなどの有料メッセージは15000通まで無料で使える。それ以上は1通5円だよ」というプランである、ということですね。

ここで注意が必要なのはフリープランです。1000通まで有料メッセージが送れるものの、それ以上追加で送れません
(Reply APIはこれまでと変わらずどのプランでも無料・無制限)

つまり無料でPushし放題のプランはなくなるということ

  • Developer Trialがなくなる
  • 今後はPush APIが使えるようになった新フリープランを使う
  • ただしPushは1000回まで(追加購入できない)

ということなので、いままでDeveloper Trialプランでできた無料でPushし放題のプランはなくなるというわけです。

ただ、これに関してはBot開発者にとってインパクトはそこまで大きくないような気はしています。
開発・検証で使う分にはフリープランの1000通で十分だし、足りなくなればアカウントを作り直せばいいだけ。
個人利用で趣味のBotを作っていた場合でも、1か月で1000通以上PushしてくるようなBotは現実的にはそんなにない気がするので、あまり問題なさそう。

とはいえ、これまでDeveloper Trialの50人限定でPushを使うBotサービスを展開していた人には影響ありそうです。
50人のユーザーなら、20通ずつのPushで限界です。ちょっと厳しいかもですね。

まぁ、「多人数向けにサービスやるなら、お手軽なライトプランも用意したからお金を払ってね。」ということなんでしょう。

配信数のカウント方法

Bot開発ではいままで意識しなかったメッセージの配信数は、新料金体系では無料枠と課金額に響いてくるので、かなり重要になります。
カウント方法についても変更が入り、これまでのLINE@では吹き出し数で1通と数えていたところを、今後は送信1回につき1通とカウントされるようになります。

資料2 P.10より
f:id:himanago:20190301004929p:plain

現行のMessaging APIだと1回あたり最大5件のメッセージを送れるので、ここの記述がそのままMessaging APIにも適用されるのかは不明ですが、適用されるとすれば、BotからのPushでは同じタイミングで複数メッセージを送るならなるべく1回のPushにまとめたほうが節約できるということになります。

Messaging APIがチャット(旧1:1トーク)と併用できるようになる

ちょっと話は変わりますが、これまた大きい、うれしい変更です。
しかも1:多でもできるようになるとか。そのため、名称が「チャット」に変更になるようです。

ただ、併用と言っても同時に使えるわけではなく、Botのアカウントでチャットを行うには、 Botモードからチャットモードに切り替える ことで使えるようになるみたいです。
このモード切替がどんな仕様かは現時点でわからないですが、

  • Bot全体で切り替える
  • 対象ユーザーごとに切り替える

のどちらなんでしょう。 前者だとチャットモードにしてあるユーザーと会話中、別のユーザーがBotに話しかけても返事をしてくれないなんてことになって気軽に使えなそうなので、後者だといいなぁ。
後者なら、Botが「オペレーターにつなぎます」って言って人による応答に切り替わる、よくあるパターンが実現できそう。

※追記※ 残念ながら前者でした。チャット機能を利用している最中は、全ユーザーに対してBotのWebhookが使えないようです。
これではなかなかBotとの併用は難しそうです。

その他些細なこと

Multicast API って何

上で引用した「課金対象となるメッセージ種別」の中に「Multicast API」という記述がありました。
名称からして、複数ユーザーへ同時にメッセージをPushするってことでしょうか。でもそれPush APIの一機能だよね…?
過去、こんな呼び方見たことないのでびっくりしました(普通に使われている呼び方だったらごめんなさい)。

いちおう、Messaging APIのドキュメントにもある通り、

プッシュメッセージは、ユーザーにいつでも送ることのできるメッセージタイプです。1人のユーザーに送る場合は/message/pushエンドポイントを、複数のユーザーに送る場合は/message/multicastエンドポイントを使用します。

というようにエンドポイントが別なんですよね。今後は区別して呼ぶようになるのかな…?

スタンプ削除

メッセージ送信に使えるスタンプから、一部のスタンプ(昔からあったやつですね)が削除されてしまうそうです。

資料2 P.11より
f:id:himanago:20190301020717p:plain

これがチャット機能のみの話なのか、Messaging APIにも関係ある話なのか、これまたわからないのですが、もしそっちでも削除されてしまうのであれば、過去に作ったBotの改修が必要そうですね…。

※追記※ Botも同じくでした。

まとめ

以上、「LINE公式アカウント」統合でBot開発に影響ありそうなポイントを想像込みでまとめてみました。
なんとなく、公式からのアナウンスはLINE@を利用している人を対象したような書きぶりで、Messaging APIの利用においてはどう解釈していいのか悩む箇所が多いなぁという印象でしたが、おそらく4月18日の統合の前後で、Messaging API側のドキュメントも変更されるのだろうと思います。

今回の統合で、公式アカウントの利用やPush APIを使ったサービスの展開を行うハードルはかなり下がった印象を受けます。

これまでPush APIが使えたプロ(API)プランは月額32,400円でしたが、新料金はそれに比べてかなり安くなりました。
「無制限だけど高額なプラン」一択だったものが、使う分に応じたプランを選ぶことができるようになった、というのが今回の変更のポイントでしょう。

ハードルが下がったことで、これからLINE Bot開発がさらに発展していきそうな気がします。
変更の開発者向けドキュメントへの反映も含め、いまから正式移行がとても待ち遠しいです。

Python3エンジニア認定基礎試験に合格したので振り返り(C#er目線で感想など)

はじめに

タイトルの通りです。昨年末、12/27のことですが、駆け込みで試験を受けてきました。その振り返りです。
Python 3 エンジニア認定基礎試験」です。
www.pythonic-exam.com

経緯

昨今のPythonの人気・需要の高まりから、「Pythonを勉強したい」という声は日に日に多く聞かれるようになり、社内で育成担当をしている身としても、もういい加減無視できなくなってきたわけです。
Pythonに関してはこれまでまったく触ったことがなく、「Pythonはインデントでブロックを定義する」とか「機械学習や統計の分野で多く使われる」とかそんな程度の知識しかない状態。
なんとか効率よく基本を押さえたい…と思い見つけたのがこの試験。

今年の6月あたりだったか、割引キャンペーンで教材のオライリー本が実質無料でついてくるセットがあったので買ったのですが、なかなか時間が取れず、有効期限が年内だったので駆け込みでぎりぎり受けてきた…という経緯です。

学習内容

教材である『Pythonチュートリアル(第3版)』をざざーっと読みました。基本、それだけ。
模擬試験がここで公開されているので、それも本を読む前に1回見ました。
先に見てしまったのは、どんなふうな問題が出るのか確認したうえで本を読むほうが効率がいいと思ったからです。 ぬいぐるみほしさに合格体験記を載せてもらいました。めちゃくちゃ本名ですが、ぬいぐるみのためなら仕方ありません(本名公開が条件)。 www.pythonic-exam.com

ちなみにここでは「Python歴3日」、と書きましたが、模擬試験を見て最初に教材を開いたのが12/25で、試験を受けたのが12/27、その日の夜に合格体験談を書いて送った、というようなスピード感です。そして12/28朝には合格体験談が公開されてました。速い!
模擬試験を見て、この試験では教材の記述、特にサンプルコードがけっこう重視されているということがわかり、その内容をしっかり押さえておけば合格点の7割は簡単にとれるだろう、ということがわかったので、そのつもりで教材を読みました。合計の勉強時間は5~6時間か、もっと少ないかも…。

試験自体はかなり簡単という印象だったものの、合格体験談にも書いたようにこの試験は基礎的な知識を得るためのきっかけ・指針としてかなり役立ちました。やっぱり目標や締め切りがあると、わかりやすくそこに向かっていけるものですからね。

Pythonチュートリアルを読んで印象に残ったこと

せっかくなので、Pythonチュートリアルを読んでPythonの特徴を勉強した中で、印象に残ったことを書いておきます。
教材のページ数と、教材と同内容のWeb版の「Pythonチュートリアル」の章番号、リンクを載せておきます。
※ 前提:これまで経験した主な言語は(学習した順に) C, VBA, Java, JavaScript, C#, Groovy など。
※ 特に最近よく使うのはC#なので、C#との比較も触れられるところは触れます。

リストの内包表記

教材P.44(5.1.3. リストの内包表記)。

squares = []
for x in range(10):
    squares.append(x**2)

squares = [x**2 for x in range(10)]

と書けるというもの。ショートコーディングがはかどりそうで慣れたらきっと便利。
2乗を ** 2 で書けるのを含めてシンプルでいいですね。

ちなみに、C#でも1行。

var squares = Enumerable.Range(0, 10).Select(x => Math.Pow(x, 2));

LINQやっぱりいいなぁ。

ループ変数のスコープ

上記の「リスト内包」の利点として説明されていた、Pythonにおけるループ変数の注意点。
Pythonはループで変数のスコープが閉じないので、さきほどのリスト内包を使わないほうのコードでは変数 x があとまで残ってしまう。そのため、リストの内包表記を使いましょうということだけど、これはうっかり気を抜くと危ないな、と思いました。
特に宣言がないので、スコープがどうなってるのかはちゃんと確認しないとまずそう。 C#とかだと、

for (int i = 0; i < 10; i++)
{
    // iが使える
}
// ここでiは使えない

とやった場合 i のスコープはループ内だけにできる。まぁ、こんな素朴なforはあんまり使うなってことなんでしょうかね(PythonC#も)。
ちなみにスコープ問題でいうとC#でも、forでのブロックとスコープの感覚だと「?」となるのがout-var(outでの変数宣言)のスコープ。

if (int.TryParse(str, out var intVal)
{
    // 当然intValが使える
}
// ここでもintValが使える!

これは、もともと↓と書いていたものと同じ動きをさせたいからこうなってる、と理解してしまえばOK。

int intVal;
if (int.TryParse(str, out intVal)
{
    // 当然intValが使える
}
// ifブロックの外で宣言しているものだから当然intValが使える

タプル

教材P.48(5.3. タプルとシーケンス)。
タプルは最近C#でも入りましたが便利な機能ですね。
Pythonの場合は手軽に

t = 12345, 54321, 'hello!'

でタプルが作れるのと、
要素0のタプルは

empty = ()

で作れるということ、
それから要素1のタプルは

singleton = 'hello',   

のように末尾にカンマを付ければいい、というのがおもしろいなと思いました。

パッキング / アンパッキング

まずは教材P.48(5.3. タプルとシーケンス)あたり。
出てきたのは「引数リストのアンパック」「タプル・パッキング」「シーケンス・アンパッキング」という用語。

タプル・パッキングは、

t = 12345, 54321, 'hello!'

で3つの要素が t へタプルとしてパックされることをいい、シーケンスアンパッキングは

x, y, z = t

t の要素がx, y, zそれぞれに展開されて代入される、という話。

それに関連して、「多重代入はタプルパッキングとシーケンスアンパッキングの組み合わせにすぎない」というのがなかなかおもしろい話だなと思いました。
多重代入はこういうの。

x, y, z = 12345, 54321, 'hello!'

3つの変数に3つの値が代入されるだけだけど、読み解けば右辺がタプルになり、左辺にアンパックされて代入される、ということになるわけですね。

あとは似た用語で引数リストのアンパックとかもありました。
教材P.35(4.7.4. 引数リストのアンパック)あたり。

range(3, 6)

と渡すような関数の引数に、

args = [3, 6]
list(range(*args)) 

というふうにargsに入った要素を展開してそれぞれの引数に渡せる、というもの。 * を左側に書くことで引数リストのアンパックを実現できる。

ディクショナリのループ

教材P.53(5.6. ループのテクニック)。
ディクショナリ(辞書型)ではitems() メソッドを使うと、キーとそれに対応する値を同時に取り出せる。

knights = {'gallahad': 'the pure', 'robin': 'the brave'}
for k, v in knights.items():
    print(k, v)

これはC#でもありますね。KeyValuePairでまわすやりかた。C#だとforeachにそのまま入れるだけ。

var knights = new Dictionary<string, string>
{
    { "gallahad", "the pure" },
    { "robin", "the brave" }
};

foreach (var pair in knights)
{
    Console.WriteLine("{0} {1}", pair.Key, pair.Value);
}

C#で「Dictionaryオブジェクトをforeachで回して」っていうとだいたい上のやり方か、↓みたいなKeysで回す方法のどっちかを書いてきますね。

var knights = new Dictionary<string, string>
{
    { "gallahad", "the pure" },
    { "robin", "the brave" }
};

foreach (var k in knights.Keys)
{
    Console.WriteLine("{0} {1}", k, knights[k]);
}

比較の連鎖

教材P.54(5.7. 条件についてもう少し)。
a < b == c で、「a が b より小さく、かつ b と c が等しいかどうか」がテストできるというもの。
こういう書き方ができるのはおもしろいですね。

比較結果の代入

教材P.55(5.7. 条件についてもう少し)。

string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
non_null = string1 or string2 or string3
non_null

とやると'Trondheim'が出力される。
SQLのCOALESCEみたいな動きができておもしろい。

C#だとこうかな。

string string1 = null;
string string2 = "Trondheim";
string string3 = "Hammer Dance";
string nonNull = string1 ?? string2 ?? string3;
Console.WriteLine(nonNull);

相互参照の相対インポート

教材P.66(6.4.2. パッケージ内参照)。
パッケージ内の相互参照の相対インポートはカレントモジュールからの参照になるので、メインモジュールとして使われるものは絶対インポートで書かないとだめ。

from . import echo
from .. import formats
from ..filters import equalizer

ファイルの読み込み(ループ)

教材P.78(7.2ファイル. を読み書きする)。
for line in f: の書き方が楽でいいなと思いました。

for line in f:
    print(line, end='')

ファイルの読み込み(with)

教材P.79(7.2. ファイルを読み書きする)。
withで自動でクローズしてくれる。

with open('workfile') as f:
    read_data = f.read()

C#のusingとか、Javaのtry-with-resourcesみたいなイメージ。

例外の投げ方

教材P.86(8.4. 例外を送出する)。
例外は raise で投げる。

try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise

raiseはなにもかかないとそのまま上に投げてくれる。
C#のthrowも似たような感じだけど、 C#ではthrow;throw ex; で大きな違いがあってかなりの注意が必要…。
Pythonではそういう気遣いはいらなそう?

あと try-else いいな。 for-else もだけど、 finally と別にこれがあると幅が広がりそう。

def divide(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        print("division by zero!")
    else:
        print("result is", result)
    finally:
        print("executing finally clause")

Pythonは多重継承できる

教材P.93(9.5.1. 多重継承)。
Pythonは多重継承可。そういえば多重継承って使ったことないな…。

クラス変数、インスタンス変数

教材P.102(9.3.5. クラスとインスタンス変数)。
self.~ ってやらないとstaticになる。これも知らないとあぶない。。

class Dog:

    kind = 'canine'         # 全インスタンスで共有

    def __init__(self, name):
        self.name = name    # インスタンスごと

隠蔽の手段がない

教材P.104(9.4. いろいろな注意点)。
Pythonではデータ隠蔽を強制できるものは存在しない、とのこと。

まじかー。
こういう、仕様の穴?を規約とか慣習でカバーするのはあまり好きじゃないなあ。
…まぁ、いま業務では、privateって書いても問答無用でpublicになる言語1を使ってますけど。。

10章の出題密度がすごい

教材P.115から(10. 標準ライブラリミニツアー)。
試験範囲をみると、教材でいうとわずか8ページしかない第10章から4問(全体の10%)も出題されます。
なので、ここは特に真剣に読みましたね。
ここが重視されてるのは、Pythonの使い道を知るうえで大事だから…?

doctestがお手軽ですごい

教材P.120(10.11. 品質管理)。

def average(values):
    """Computes the arithmetic mean of a list of numbers.

    >>> print(average([20, 30, 70]))
    40.0
    """
    return sum(values) / len(values)

import doctest
doctest.testmod()   # automatically validate the embedded tests

こんなかんじでコメントに埋め込みテストが書ける。手軽でいいなぁ。

おわりに

実は試験当日もはじまるまで教材を読んでた(1周目)くらいなので、勉強がうすいところも多々ありました。
それでもスコアは 875/1000(合格点700点)とれて無事合格できました。
試験自体は簡単な印象だったので、Python経験者にはものたりないかな?というかんじ。

自分のように、他の言語での開発経験があって、それとの比較でささっと勉強するうえではチュートリアルは教材としていいし、試験は目標としてもちょうどいいのかなと思いました。
この試験は続編?のデータ分析試験というものが今後始まるようなので、こちらもチェックしておこうと思います。


  1. Groovyのこと

ブログタイトルを変更しました

はじめに

あけましておめでとうございます。

ブログタイトルを変えた

シンプルにIDの文字列そのままのブログタイトルに変更しました。
変えたので、いちおう記録として記事更新。

もともと「ITエンジニア育成担当の研究ブログ」でしたが、

  • そもそも気に入ってなかった
  • 対外的な肩書が「育成担当」じゃなくなった

あたりが理由で変えました。

気に入ってなかったのは、まず長いし、研究って何よ、という感じだったので。

対外的な肩書としては、いま「テクニカルトレーナー」「アーキテクト」を名刺に載せさせてもらってます。
これを載せているのも、育成担当とか教育担当いう響きがなんとなくえらそうというか、かたくるしいイメージがあったからで、会社に相談してカタカナのロールをつけてもらいました。

…まぁ、トレーナーでもおなじように「教える」「鍛える」感があるので同じかもですが、とりあえずブログからは仕事のロールは外しました。

今後の記事投稿

仕事関係なく、技術的な話題を投稿していけたらなと思っています。

このまえのLINE関係の開発もそうですし、C#とかAzureとかのMS系技術など、自分の好きなことを書いていきたいなと思います。

LINEは絵本読み聞かせをリニューアルしてますしその話を。
MS系技術は.NET Everywhereというか、クラウド、モバイル、Webなどせっかくいろんなところで動く.NETをいろいろためしてまとめたいなという欲求があるのでそんなことを書けたらなと。

おわりに

今年もよろしくお願いします。