kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

Autocodeことはじめで、カウンターを作ってみた

以下で紹介されていたローコードプラットフォーム「Autocode」、Voiceflow Dialog Management APIの実行環境として使えるんではないか?ということで、ちょっと試してみた。

目次

まずは触ってみる

上記サイトで紹介されているのをまず一通りやってみた所感としては、

  • いろいろなサービスとの連携がライブラリ化されている実行環境がセットになってるリッチなIDE、という感じ
  • サンプルにもあるDiscordとの連携を試してみた感じ、コード補完が使いやすく、パラメータもGUIで設定できたり、と各サービスのAPIドキュメントを見なくても簡単にかける

ということで、Voiceflow Dialog Management APIを、Autocodeで予め連携が用意されているものとつなげるならば、とても楽にかける気がします。

ただ、Discordも個人的にはあんまり使ってないし、まずは以下で作ったWebサイトチャットボットの普通のバックエンドAPIとして使いたい。

kun432.hatenablog.com

この場合、

  • マルチターンな会話の場合には、cookieを使ったセッション処理が必要になる
  • CORSに対応させる必要になる

というところで、Webサーバ部分の処理をある程度書かないといけないのだけど、このあたりはAutocodeではあまり気にしなくて良さそうな部分っぽくて、ドキュメント見ててもあまりピンとこない。

そこで、いろいろ調べつつまずはよくあるカウンターを書いてみました。

// authenticates you with the API standard library
// type `await lib.` to display API autocomplete
const lib = require('lib')({token: process.env.STDLIB_SECRET_TOKEN});
const cookie = require('cookie');

console.log(JSON.stringify(context.http.headers, null, 2));

let counter = 1;
if (context.http.headers.hasOwnProperty('cookie')){
  let cookies = cookie.parse(context.http.headers.cookie);
  counter = cookies.hasOwnProperty('counter') ? Number(cookies.counter) + 1 : 1;
}

const body = {
  "counter": counter
};

return {
  headers: {
    'Content-Type': 'application/json',
    'Set-Cookie': cookie.serialize('counter', counter)
  },
  statusCode: 200,
  body: Buffer.from(JSON.stringify(body))
};

expressの場合、cookieやセッション周りの処理はexpress-sessionあたりを使えば色々隠蔽されてシンプルに書けるのだけど、Autocodeだと

  • cookieを渡す場合はカスタムレスポンスを使って自分でset-cookieしないといけなさそう。
  • cookieを取り出す場合、リクエストはcontextに全て入っているので、cookieを取り出して、自分でパースしないといけなさそう。

ということで、上記のような感じに。なんていうか、expressにはだいぶ甘やかされてるんだなという気がしたw

単にクライアント側のcookie使ってやり取りしてるだけでサーバーサイドでセッション管理とか何もやってないし、CORSあたりも考えないといけないので、先は長い・・・

参考

HTTPリクエストの取り出し方

docs.autocode.com

カスタムなHTTPレスポンスを返す

docs.autocode.com

npmパッケージも使える

docs.autocode.com

今回はクライアントcookieしか使ってないけど、セッションやるなら多分このへん。アプリ単位じゃなくてアカウント単位ってところはちょっと気をつけないといけないかも。PostgreSQL/Airtable/Google Sheetsあたりを使ってもいいかもしれない。

docs.autocode.com

Voiceflowのユーザーペルソナを試してみる

ひさびさのVoiceflowの新機能のご紹介です。

会話フローのテストを行う場合、ユーザの状態にあわせてテストを行いたいことがあります。

  • 初めての利用なのか?何度も利用しているユーザなのか?
  • スキルがユーザの名前を知っているのか?知らないのか?

このように何らかの「パーソナライズ」を組み込んでいる場合はその条件に合わせてテストを行いたいですよね。そこで新しく追加されたのが「ユーザーペルソナ」です。

目次

サンプルスキル

以下のようなサンプルを用意しました。プロジェクトは「Chat Assitant」タイプです。

https://raw.githubusercontent.com/kun432/voiceflow-samples/main/user-persona/sample-user-persona.vf

※右クリックでダウンロードしてインポートしてください。

以前お伝えしたとおり、Voiceflowのテストツールは、残念ながらAlexa・Google向けプロジェクトでは使用できませんのでご注意ください。

実際には全く使えないというわけではないですが、Alexa開発者コンソールやActions on Google上のテストツールを完全にシミュレートしているわけではないので、きちんとしたテストを行うにはそれぞれのプラットフォーム上のテストツールを使う方が良いということです。)

フローはこんな感じ。

ユーザから都道府県を聞いて天気を答えるというものです。実際には天気APIなどを使って天気予報の結果を取得することになりますが、今回はサンプルなのでランダムに回答するようにしています。ポイントは、

  • 初回利用と2回目以降で、チャットボットの応答が変わる。
    • 1回目は、名前と住んでいる都道府県を聞いて、永続的に記憶した上で、天気を答える。
    • 2回目以降は、永続的に記憶された名前と都道府県を使って、そのまま天気を答える。

というところで、少しパーソナライズ的な要素を入れています。1回目の会話フローはこんな感じです。

普通にテストを行うと、変数は何もない状態、つまり1回目の利用ということになるわけです。

ユーザーペルソナを使う

で、次は当然2回目以降のパターンを試してみたいですよね。テスト画面の左側を見てください。

ここにプロジェクトで使用している変数がすべて表示されています。

これを少しいじってみましょう。

sessionsがVoiceflowが予め持っているユーザの利用回数です。nameregionが今回のプロジェクトで作っているユーザの名前と住んでいる都道府県ですね。1回目の利用時に必要な情報はユーザからすでに取得済み、という想定ですね。

これで再度テストを実行してみるとこうなります。

1回目のテストとは異なり、すでにユーザからは必要な情報を取得済みですので、名前や都道府県を取得するためのやりとりが発生せずに応答を返していることがわかりますね。このようにしていろいろ想定される条件にあわせて変数を書き換えればよいわけですが、毎回毎回変更するのは面倒です。

これを一つのセットとして記憶しておけるのが「ユーザペルソナ」機能です。早速使ってみましょう。

変数が表示されている箇所の上にある「All project variables」をクリックします。

"Add new"をクリックします。

ユーザーペルソナを設定する画面が出てきましたので、設定していきましょう。まずは「初回ユーザ」から。

"Name"にユーザペルソナ名を入力します。"Starting Block"は、会話フローのどこからスタートするか、を選択することができます。通常は"Start"にしておいて最初から行えばよいでしょう。

次に「初回ユーザ」の場合の変数を設定します。今回のフローでは、sessions / name / region が永続的に保持され、初回 or 2回目以降で異なる箇所になりますので、これらにチェックを入れます。

それぞれの変数の入力欄が追加されますので、それぞれ値を入れます。Voiceflowでは自分で作成した変数の初期値は0になりますので、nameregionを0にセットします。予め用意されているsessionについては1が初期値になりますので、1をセットします。最後にSave Stateをクリックして保存します。

これで「初回ユーザ」のユーザーペルソナが作成されました。

同様にして、2回目以降のユーザーペルソナとして「継続ユーザ」を作成しましょう。こんな感じです。

これで2つのユーザーペルソナが作成されました。

以後は、ここでユーザーペルソナを切り替えてテストを行えば、それぞれの条件がセットされたテストが行えるというわけです。また、右側のテスト開始ボタンのところからも切り替えることができます。

プロトタイプ共有でユーザーペルソナを使う

さらに、作成したユーザーペルソナをプロトタイプ共有で使うこともできます。

"Share Prototype"をクリックして、"Variable State"でユーザーペルソナを選択して"Copy Link"をクリックします。

発行されたURLにアクセスすると、ユーザーペルソナに応じた変数が設定された状態で、対話フローが動作します。

まとめ

音声アプリを長く利用してもらうためにはパーソナライズはほぼ必須といってよいと思いますが、実際にやろうと思うと実は結構面倒です。今回のユーザーペルソナ機能を使って予めパターンを用意しておけば、いつでもかんたんに切り替えてテストができるのでとても便利ですね。ぜひご活用ください。

個人的な要望としては、Alexa向けプロジェクトでもこれができるとよいなぁと思ったりしてます・・・

AlexaスキルのテストをBotiumでやってみた②

f:id:kun432:20210131175240p:plain

前回の続き。

テストシナリオの書き方について。

目次

前回のテストシナリオ

前回用意したテストシナリオを一つピックアップします。

tripadvisor_happypath01.convo.txt

トリップアドバイザーのテスト:Happy Path #1

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
京都 かな

#bot
京都 ですね。京都 は清水寺がおすすめです。

会話の内容がそのまま書いてあり、ユーザの発話パターンに応じて別のシナリオにしています。これはこれでいいんですが、色々バリエーションが増えてくると難しい。ということでこれを分離していきましょう。

発話のバリエーション

発話のバリエーションに対応するのがutterances.txtになります。以下のようなファイルを用意します。

happypath01_utt.utterances.txt

HAPPYPATH01_UTT
京都
京都 かな
京都 がいいな
京都 に行きたい

convo.txtの方を修正します。

トリップアドバイザーのテスト:Happy Path #1

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
HAPPYPATH01_UTT

#bot
京都 ですね。京都 は清水寺がおすすめです。

では実行してみましょう。他のテストファイルと分けるためにディレクトリを切って移動します。

$ mkdir happypath01
$ mv tripadvisor_happypath01.convo.txt happypath01/.
$ mv happypath01_utt.utterances.txt happypath01/.

ディレクトリを指定するには--convosを使います。

$ botium-cli run --convos happypath01


  Botium Test-Suite
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L1 (10810ms)
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L2 (10065ms)
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L3 (10429ms)
    ✔ トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L4 (10022ms)


  4 passing (42s)

utterances.txtに記載した4パターンでテストされているようですね。--verboseをつけてみるとよくわかります。

$ botium-cli run --convos happypath01 --verbose
(...snip...)
  botium-core-ScriptingProvider  HAPPYPATH01_UTT ({ convoDir: 'happypath01', filename: 'happypath01_utt.utterances.txt' }): 京都|京都 かな|京都 がいいな|京都 に行きたい +0ms
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L1/Line 12 (Line 9: #me - 京都) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L1 ready, calling done function. +11s
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L2/Line 12 (Line 9: #me - 京都 かな) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L2 ready, calling done function. +10s
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L3/Line 12 (Line 9: #me - 京都 がいいな) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L3 ready, calling done function. +8s
(...snip...)
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L4/Line 12 (Line 9: #me - 京都 に行きたい) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path #1/HAPPYPATH01_UTT-L4 ready, calling done function. +11s
(...snip...)

  4 passing (41s)

もちろんAlexa(というかボット側)からの発話にバリエーションがある場合もutterances.txtは使えるようです。

スロットを変数として扱う

前回作成したもう一つのテストシナリオ見てみましょう。tripadvisor_happypath02.convo.txtのほうです。

トリップアドバイザーのテスト:Happy Path #2

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
奈良 に行きたい

#bot
奈良 ですね。奈良 は東大寺がおすすめです。

ユーザが「奈良」という可変となる値、すなわち「スロット」を発話したのに対して、ボットも繰り返すように「奈良」と答えていますよね。こういう場合には変数が使えます。Botiumでは"Scripting Memory"と呼ばれる機能です。

Scripting Memoryは初期状態では有効化されていないので、まずこれを有効化しましょう。botium.jsonを開いて、botium.Capabilitiesに以下を追加します。

{
  "botium": {
    "Capabilities": {
(...snip...)
      "SCRIPTING_ENABLE_MEMORY": true
    }
  }
}

ではテストシナリオを修正していきます。最初と同じようにディレクトリを作成してconbo.txtを移動しておきましょう。

$ mkdir happypath02
$ mv tripadvisor_happypath02.convo.txt happypath02/.

tripadvisor_happypath02.convo.txtを以下のように書き換えます。

トリップアドバイザーのテスト:Happy Path #2

#begin
SET_SCRIPTING_MEMORY city|奈良

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行
きたい」と言ってみてください。

#me
$city に行きたい

#bot
$city ですね。$city は東大寺がおすすめです。

cityという変数を用意して、最初にSET_SCRIPTING_MEMORYで「奈良」を設定しています。あとのやりとりではその部分はすべて$cityで記載しています。

実行してみます。

$ botium-cli run --convos happypath02 --verbose
(...snip...)
  botium-core-SetScriptingMemoryLogicHook Set scripting memory variable "$city" from "undefined" to "奈良, isGlobal: false, type: onConvoBegin" +0ms
(...snip...)
  botium-core-Convo トリップアドバイザーのテスト:Happy Path #2/Line 12: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "奈良 に行きたい",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +2ms
(...snip...)
  botium-core-ScriptingMemory fill start: {} +5s
  botium-core-ScriptingMemory fill end: { '$city': '奈良' } +1ms
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path #2/Line 15 (Line 12: #me - $city に行きたい) BOT: 奈良 ですね。奈良 は東大寺がおすすめです。 = 奈良 ですね。奈良 は東大寺がおすすめです。 ... +5s
(...snip...)

  1 passing (12s)

対話のやり取りで$cityに当たる部分が、最初に設定した「奈良」で置き換えられて会話が行われているのがわかりますでしょうか。これを使えば、スロットのような可変の値もテストができますね。

上記ではconbo.txtに直接記載して変数を設定しています。

#begin
SET_SCRIPTING_MEMORY city|奈良

シナリオごとにこれを書くのはちょっと意味がないですよね。そして、ユーザの発話に応じておすすめされる「清水寺」や「東大寺」の部分も可変なので変数にしたいですね。ではこれを別のファイルに切り出して、happypath01/02のテストを一つにしてしまいましょう。

新しくディレクトリを用意して、conbo.txtをこんな感じで作ります。

$ mkdir happypath
$ vi happypath/tripadvisor_happypath.conbo.txt
トリップアドバイザーのテスト:Happy Path

#me
トリップアドバイザーを開いて

#bot
はじめまして、トリップアドバイザースキルをご利用いただきありがとうございます。このスキルでは行きたい日本の都市名をいうとおすすめの観光名所を提案します。例えば「京都に行きたい」と言ってみてください。

#me
$city に行きたい

#bot
$city ですね。$city は$placeがおすすめです。

以下の内容でhappypath/tripadvisor_happypath.scriptingmemory.txt を作成します。

      |$city |$place
Case1 |京都  |清水寺
Case2 |奈良  |東大寺

実行してみましょう。scriptingmemory.txtを使う場合は--expandscriptingmemoryが必要なようです。

$ botium-cli run --convos happypath --expandscriptingmemory true --verbose
(...snip...)
  botium-core-ScriptingProvider ReadConvosFromDirectory(happypath) scripting memories:
  botium-core-ScriptingProvider  {
  botium-core-ScriptingProvider   header: { name: 'Case1' },
  botium-core-ScriptingProvider   values: { '$city': '京都', '$place': '清水寺' },
  botium-core-ScriptingProvider   sourceTag: { filename: 'tripadvisor_happypath.scriptingmemory.txt' }
  botium-core-ScriptingProvider }
  botium-core-ScriptingProvider {
  botium-core-ScriptingProvider   header: { name: 'Case2' },
  botium-core-ScriptingProvider   values: { '$city': '奈良', '$place': '東大寺' },
  botium-core-ScriptingProvider   sourceTag: { filename: 'tripadvisor_happypath.scriptingmemory.txt' }
(...snip...)
  botium-core-Convo トリップアドバイザーのテスト:Happy Path.Case1/Line 9: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "京都 に行きたい",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +2ms
(...snip...)
  botium-core-ScriptingMemory fill start: {} +5s
  botium-core-ScriptingMemory fill end: { '$city': '京都', '$place': '清水寺' } +0ms
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path.Case1/Line 12 (Line 9: #me - $city に行きたい) BOT: 京都 ですね。京都 は清水寺がおすすめです。 = 京都 ですね。京都 は清水寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path.Case1 ready, calling done function. +10s
    ✔ トリップアドバイザーのテスト:Happy Path.Case1 (10032ms)
(...snip...)
  botium-core-Convo トリップアドバイザーのテスト:Happy Path.Case2/Line 9: user says (cleaned by binary and base64 data and sourceData) {
  botium-core-Convo   "sender": "me",
  botium-core-Convo   "channel": null,
  botium-core-Convo   "not": false,
  botium-core-Convo   "optional": false,
  botium-core-Convo   "messageText": "奈良 に行きたい",
  botium-core-Convo   "media": null,
  botium-core-Convo   "buttons": null,
  botium-core-Convo   "cards": null,
  botium-core-Convo   "forms": null,
  botium-core-Convo   "attachments": null,
  botium-core-Convo   "asserters": [],
  botium-core-Convo   "userInputs": [],
  botium-core-Convo   "logicHooks": []
  botium-core-Convo } +1ms
(...snip...)
  botium-core-ScriptingMemory fill start: {} +5s
  botium-core-ScriptingMemory fill end: { '$city': '奈良', '$place': '東大寺' } +1ms
  botium-core-ScriptingProvider assertBotResponse トリップアドバイザーのテスト:Happy Path.Case2/Line 12 (Line 9: #me - $city に行きたい) BOT: 奈良 ですね。奈良 は東大寺がおすすめです。 = 奈良 ですね。奈良 は東大寺がおすすめです。 ... +5s
  botium-cli-run トリップアドバイザーのテスト:Happy Path.Case2 ready, calling done function. +11s
    ✔ トリップアドバイザーのテスト:Happy Path.Case2 (10646ms)

  2 passing (22s)

はい、それぞれが展開されて2回テストが行われているのがわかりました。

まとめ

まだまだ触りの部分しか触れていませんが、他にも

  • テストシナリオとなるconvoは、上記の書き方以外にもExcel、CSV、YAML、JSON、Markdownなどでも書ける。
  • シナリオの分割(シナリオから別のシナリオを呼び出す)もできる。
  • Scripting Memoryでは関数も使える。
  • マルチモーダルでのボタンアクションなどにも対応できる

などなど機能はかなり豊富にあるようですので、柔軟なテストシナリオを書くことができそうです。興味があればドキュメントをご覧ください。

botium-docs.readthedocs.io

次回はちょっとBotium Boxを見てみたいと思います。