まくろぐ

チャットボット: Bot Builder SDK で会話の状態を保存する (BotState)

更新:
作成:

BotState クラス

Bot Builder SDK の BotState クラス は、ボットとの会話内の特定のコンテキストにおける状態を保持するためのクラスです。

というと難しいですが、簡単に言うと、会話ごとの状態保存や、ユーザーごとの状態保存を行うための便利クラスです。

BotState クラスには次のようなサブクラスが定義されています。

これらのクラスは、内部で Storage オブジェクトを利用します。

/p/6wtzzq4/state-001.svg
生成コード📖

Storage がグローバルに状態保存を行っていたのに対し、BotState はネームスペースを考慮して状態保存を行うものだと考えることができます。 実際に、ConversationState クラスや UserState クラスの実装を覗いてみると、getStorageKey() というメソッドでストレージ用の保存キーを作成しており、それぞれ次のように構成しています。

  • ConversationState が使用する保存キー
    • ${ channelId }/conversations/${ conversationId }/${ this.namespace }
  • UserState が使用する保存キー
    • ${ channelId }/users/${ userId }/${ this.namespace }
  • PrivateConversationState が使用するキー
    • ${ channelId }/conversations/${ conversationId }/users/${ userId }/${ this.namespace }

実用的なボットの状態管理を行うには、Storage インタフェースをそのまま使うのではなく、ConversationState / UserState / PrivateConversationState などを使うことになるでしょう。

ConversationState と UserState クラスを使用する

インスタンス化

ConversationStateUserState のインスタンスを作成するには、コンストラクタに Storage オブジェクトを渡します。

const { ConversationState, UserState, MemoryStorage } = require('botbuilder');

const storage = new MemoryStorage();
const convState = new ConversationState(storage);
const userState = new UserState(storage);

ここでは、ストレージとして MemoryStorage を使用していますが、CosmosDbStorage などを使用することもできます。

UserState クラスでカウンタを保存する

下記のボットクラスでは、UserState オブジェクトを使って、ユーザーがメッセージを送った回数をカウントしています。 UserState に対して値を読み書きするには、UserState#createProperty() を使ってプロパティアクセサーを作成します。 ここでは、counter という名前のプロパティアクセサーを作成しています。

mybot.js
const { ActivityHandler, BotFrameworkAdapter, UserState } = require('botbuilder');

// ボット実装
class MyBot extends ActivityHandler {
  constructor(storage) {
    super();
    this._createStateObjects(storage);

    // イベントハンドラの登録
    this.onMessage(async (context, next) => {
      const user = context.activity.from.name;
      const text = context.activity.text;

      // カウンタープロパティの値を読み取る(初期値は 1)
      const cnt = await this.counterProp.get(context, 1);

      // メッセージを返信
      await context.sendActivity(`${user}さん、${cnt}回目のメッセージですね: ${text}`);

      // カウンタープロパティの値を更新(+1 する)
      await this.counterProp.set(context, cnt + 1);

      // UserState をすべてストレージに保存する
      await this.userState.saveChanges(context);
    });
  }

  // 状態管理用のオブジェクトを作成
  _createStateObjects(storage) {
    this.userState = new UserState(storage);

    // UserState としてカウンターを保存するプロパティへのアクセサーを作成
    this.counterProp = this.userState.createProperty('counter');
  }
}

exports.MyBot = MyBot;

これで、ユーザーごとに、ボットと会話した回数をカウントできるようになります。

上記のボットクラス実装では、UserState が使用する Storage オブジェクトをコンストラクタで受け取るようにしています。 index.js など、ボットクラスを生成する側で MemoryStorageCosmosDbStorage などのインスタンスを渡すようにしてください。

const myBot = new MyBot(new MemoryStorage());

ConversationState クラスでカウンタを保存する

上記の例では、UserState を使ってカウント値を保存していたので、ユーザーごとにカウント値がインクリメントされていきます。 new UserState という部分を、new ConversationState に変えるだけで、会話ごとにカンウント値を管理できるようになります。

例えば、Slack で考えてみるとわかりやすいです。

/p/6wtzzq4/state-002.png

UserState にカウント値を保存した場合は、ユーザーがチャンネルを移動しながら会話をしたときに、カウント値が引き継がれてインクリメントされていきます。

Channel1: 1 → 2 → 7
Channel2: 3 → 5 → 9
Channel3: 4 → 6 → 8

一方、ConversationState にカウント値を保存した場合は、チャンネルごとにカウント値が管理されるので、各チャンネルごとに初期値の 1 からインクリメントされていきます。 ただし、このカウント値はチャンネルの中で一意なものになるので、ユーザー間で共有されます。

Channel1: 1 → 2 → 3
Channel2: 1 → 2 → 3
Channel3: 1 → 2 → 3
まくろぐ
サイトマップまくへのメッセージ