Bot Framework Composer でボット開発 : その 6 メモリオブジェクト

[前回](https://qiita.com/kenakamu/items/26570ca0866923a184c0) の記事では割り込みの方法について紹介しました。今回はボットのメモリオブジェクトについて見ていきます。尚ここでいうメモリオブジェクトはハードウェアとしてのメモリに限定されるものでは無く、Bot Framework Composer が開発者に対して情報を提供する仕組みであり、情報はデータベースに保存されている場合もあります。

# メモリオブジェクト

[公式の Conversation flow and memory](https://docs.microsoft.com/en-us/composer/concept-memory)ドキュメントにもある通り、ボットのメモリオブジェクトは会話フローを成り立たせるために重要な役割を果たします。

- ユーザーのプロファイルや設定などを保持
- 会話レベルでユーザーからの入力や処理の内容を保持
- ダイアログ間での情報の受け渡し

メモリオブジェクトへのアクセスは、user.name のように **スコープ.プロパティ** 形式となります。
Bot Framework Composer では以下のスコープを提供します。

- user
- conversation
- dialog
- turn
- settings
- this

user と conversation データは **BotState** オブジェクトの一部として扱われ、既定で Cosmos DB に永続化されます。

## user スコープ

ユーザー単位の情報を保持するために提供されるスコープで、明示的に削除しない限りユーザー単位で保持されます。ユーザー名や設定など会話の範囲を超えた情報はユーザースコープに保存します。

## conversation スコープ

会話単位の情報を保持するために提供されるスコープです。会話には複数のユーザーが存在する可能性があるため、特定の会話において、会話に参加するユーザーからアクセスできるべき情報を保存します。また新しい会話が作成されると新規のメモリオブジェクトが提供されます。

## dialog スコープ

現在アクティブなダイアログおよびその親や子ダイアログの情報を保持します。特定ダイアログ内でのみ利用する情報を保存します。

## turn スコープ

ターンとはユーザーが 1 つのメッセージを送ってから、ボットから応答を受け取るまでの一連の流れです。ターンスコープにはユーザーのメッセージやボットの応答が含まれ、メッセージが送られるたびに新しく作成されます。

## settings スコープ

appsettings.json の設定を保持するメモリオブジェクト。階層のものはプロパティとしてアクセス。

```shell
settings.luis.appID
settings.qna.kbId
```
## This スコープ

処理の場所によってコンテキストが変わるオブジェクト。

# 各スコープに含まれるデータの確認

各データはエミュレーターを使うことで確認が可能です。

## ターンスコープに含まれるデータの確認

基本的にターンスコープの情報は自動生成されます。

既存のボットを起動してエミュレーターから接続します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/658165c3-2ae3-1a97-f5be-17b33e5ec2b4.png)

エミュレーターの Bot State から turn を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/0c48c0fc-a9b5-c6da-7b48-d1b9367fe353.png)

turn には主に以下の自動生成された情報が含まれます。

- activity: ユーザーやボットからのメッセージ (アクティビティ)
- recognized: Recognizer で取得した結果
    - intent: 取得したインテント
    - entities: 取得したエンティティ
- dialogEvent: 現在のダイアログの状態やイベントの情報

この他にも複数選択肢の回答を保持する turn.choice など状況に応じてプロパティが増えます。

## ダイアログスコープに含まれるデータの確認

ダイアログスコープの情報も自動生成され、アクティブなダイアログの情報や呼び出し元のダイアログの情報などが含まれます。またターンスコープの dialogEvent.Name もダイアログの状態を確認するために使います。

プロファイルダイアログに入った直後のダイアログスコープには以下の情報が含まれます。ここでは最後のインテント情報や、ダイアログのスタック情報が見て取れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/b26f74e5-ba96-2a3a-8d07-a0fc81ff6414.png)

2 つ目のアクティビティである、「猫は何匹いますか？」の後では、ダイアログスコープのデータは以下のように変化します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/d58a698c-01a1-7b2d-061d-9bd09178f19f.png)

ダイアログが完了した直後ではダイアログスコープはありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/5fa79108-c977-838c-4078-8646efbcbb63.png)

またダイアログ内でループ処理をした場合などは、```dialog.foreach.value``` にてループされたアイテムにアクセスする事ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/bf13941f-7ace-9ba5-a11f-a9047aae2047.png)

## 会話スコープに含まれるデータの確認

会話スコープにも自動生成されたデータとして、現在のダイアログの情報が含まれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/7a8fafdd-43fe-0844-93e4-d4ec302a8d71.png)

## ユーザースコープに含まれるデータの確認

ユーザースコープには自動生成されるデータはありません。明示的に追加したものだけが含まれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/4dee2404-8deb-5dd4-65ec-3c60d04a3eaa.png)

# プロパティの追加

各スコープのプロパティに対して値を設定する方法はいくつかありますが、既に [ユーザー入力の理解](https://qiita.com/kenakamu/items/11bae087ca93f5a6d997#%E3%82%A4%E3%83%B3%E3%83%97%E3%83%83%E3%83%88%E3%81%B8%E3%81%AE%E5%AF%BE%E5%BF%9C) で紹介したように、入力インプットをプロパティに保存する方法が良く使われます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/df2d0547-e922-7c72-8de8-0470445aca18.png)



# プロパティの管理

明示的にプロパティを管理できるよう、専用のアクションが提供されます。

- 単一プロパティに値を設定
- 複数プロパティに値を一括設定
- 単一プロパティを削除
- 複数プロパティを一括削除
- 配列データのプロパティを編集
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/e259639e-66cc-e81c-8144-8027117222fc.png)

初めの 4 つは言葉の通りで、操作も明らかであるため、ここでは配列データの操作について紹介します。

## 配列データのプロパティを編集

プロパティで配列データを持たせる場合、以下の操作が可能です。

- Push: 値を追加
- Pop: 配列の最後から値を取り出す
- Take: 配列の最初から値を取り出す
- Remote: 指定したアイテムを削除する
- Clear: 配列を空にする

アクションのプロパティペインは操作によって動的に変化しないため、どのアクションで何を使うか理解しておく必要があります。

**Push**

- Items property: 配列を保存するプロパティ
- Value: 追加する値

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/1ee9d213-a02e-b5a7-6e00-090e510f098b.png"/>

**Pop/Take**

- Items property: 配列を保持するプロパティ
- Result Property: Pop/Take の結果を保持するプロパティ

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/3689724e-5405-5d27-ea3d-82c1330dfe23.png"/>

**remove**

- Items property: 配列を保持するプロパティ
- Value: 削除するアイテム

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/48d416ec-895b-0888-c865-1a0001db2610.png"/>

# メモリオブジェクトへのショートカット

よく使うものはショートが用意されています。

記号	| 例	| 元のパス	| 説明
:--|:--|:--|:--
$	| \$userName <br/> \$lastIntent	|dialog.userName<br/> dialog.lastIntent	| $ はダイアログスコープを表すショートカットです。
\#	|#intentName	|turn.recognized.intents.intentName	| LUIS などにより取得されたインテントは  # で表現できます。
@	|<span>@</span>entityName	|turn.recognized.entities.entityName	|LUIS などにより取得されたエンティティのうち初めのエンティティの名前が取れます。
@@	|<span>@@</span>entityName	|turn.recognized.entities.entityName	|LUIS などにより取得されたエンティティのうち初めのエンティティの値が取れます。

[前回の記事で LUIS の閾値を設定](https://qiita.com/kenakamu/items/26570ca0866923a184c0#luis-%E9%96%BE%E5%80%A4%E3%81%AE%E8%A8%AD%E5%AE%9A)した際も、# ショートカットを使いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214116/dcc0724b-747a-80bd-2716-6f22378dea86.png)

# まとめ

メモリオブジェクトは高度なボットを作成する上で重要な知識です。適切なスコープに必要な値を保持できるよう、色々試してください。次回は LUIS を使ったエンティティ取得と利用について見ていきます。

[次の記事へ](https://qiita.com/kenakamu/items/ba22b09ad587fa259e0a)
[目次へ戻る](https://qiita.com/kenakamu/items/6dc043cfc1f199032883#bot-framework-composer)