適用対象: SDK v4
ボット内の状態は、最新の Web アプリケーションと同じパラダイムに従います。また、Bot Framework SDK には、状態の管理を容易にするための抽象化概念がいくつか用意されています。
Web アプリと同様、ボットは本質的にはステートレスです。つまり、お使いのボットの別のインスタンスで、会話の任意のターンを処理できます。 一部のボットでは、このシンプルさが推奨されます。ボットは追加情報なしで動作するか、必要な情報が受信メッセージ内に存在することが保証されます。 他のユーザーの場合、ボットが有用な会話を行うためには、状態 (会話が中断した場所やユーザーに関して以前に受信したデータなど) が必要です。
状態が必要な理由
状態を維持すると、ユーザーまたは会話に関する特定の事柄を覚えておくことで、ボットはより意味のある会話を行えます。 たとえば、以前にユーザーと話したことがある場合は、ユーザーに関する以前の情報を保存して、もう一度要求する必要がないようにすることができます。 また、状態は現在のターンよりも長くデータを保持するため、ボットは複数ターンの会話の過程で情報を保持します。
ボットに関連するため、状態の使用にはいくつかのレイヤーがあります。ストレージ レイヤー、状態管理 (下の図のボットの状態に含まれています)、状態プロパティ アクセサーです。 次の図は、メソッド呼び出しを表す実線の矢印と、応答を表す破線 (戻り値の有無にかかわらず) を含む、これらのレイヤー間の相互作用シーケンスの一部を示しています。
この図のフローについては、以下のセクションで、これらの各レイヤーの詳細について説明します。
ストレージ レイヤー
バックエンドから始めて、状態情報が実際に格納されるのが ストレージ レイヤーです。 これは、インメモリ、Azure、サード パーティのサーバーなどの物理ストレージと考えることができます。
Bot Framework SDK には、ストレージ レイヤーの実装がいくつか含まれています。
- メモリ ストレージ は、テスト目的でメモリ内ストレージを実装します。 メモリ内データ ストレージは、このストレージが揮発性で一時的であるため、ローカル テストのみを目的としています。 データは、ボットが再起動されるたびにクリアされます。
- Azure Blob Storage は 、Azure Blob Storage オブジェクト データベースに接続します。
- Azure Cosmos DB パーティション分割ストレージは 、パーティション分割された Cosmos DB NoSQL データベースに接続します。
Von Bedeutung
Cosmos DB ストレージ クラスは非推奨となりました。 もともと CosmosDbStorage で作成されたコンテナーにはパーティション キーが設定されておらず、既定のパーティション キー "/_partitionKey" が与えられました。
Cosmos DB ストレージで作成されたコンテナーは、Cosmos DB パーティション分割ストレージと共に使用できます。 詳細については、Azure Cosmos DB でのパーティション分割に関するページを参照してください。
また、従来の Cosmos DB ストレージとは異なり、Cosmos DB パーティション分割ストレージでは、Cosmos DB アカウント内にデータベースが自動的に作成されません。 新しいデータベースを手動で作成する必要がありますが、CosmosDbPartitionedStorage によってコンテナーが作成されるため、コンテナーの手動作成はスキップしてください。
他のストレージ オプションに接続する方法については、ストレージ への直接書き込みを参照してください。
ステート管理
状態管理 では、基になるストレージ レイヤーに対するボットの状態の読み取りと書き込みが自動化されます。 状態は 状態プロパティとして格納されます。これは、実際には、ボットが状態管理オブジェクトを介して読み書きできるキーと値のペアであり、基になる特定の実装について心配する必要はありません。 これらの状態プロパティは、その情報の格納方法を定義します。 たとえば、特定のクラスまたはオブジェクトとして定義したプロパティを取得すると、そのデータがどのように構造化されるかがわかります。
これらの状態プロパティは、範囲が設定された「バケット」としてまとめられます。これは、これらのプロパティを整理するのに役立つ単なるコレクションです。 SDK には、次の 3 つの "バケット" が含まれています。
- ユーザー状態
- 会話の状態
- プライベート会話の状態
これらのバケットはすべて ボット状態 クラスのサブクラスであり、派生して、異なるスコープを持つ他の種類のバケットを定義できます。
これらの定義済みバケットのスコープは、バケットに応じて特定の可視性に設定されます。
- ユーザーの状態は、会話に関係なく、ボットがそのチャネルでそのユーザーと会話している任意のターンで使用できます。
- 会話の状態は、グループ会話など、ユーザーに関係なく、特定の会話で任意のターンで使用できます
- プライベート会話の状態は、特定の会話とそのユーザーに限定されます。
ヒント
ユーザーと会話の両方の状態は、チャネルによってスコープされます。 異なるチャネルを使用してボットにアクセスする同じユーザーは、チャネルごとに 1 つずつ、それぞれ異なるユーザー状態で表示されます。
これらの定義済みの各バケットに使用されるキーは、ユーザーと会話、またはその両方に固有です。 状態プロパティの値を設定する場合、キーは内部的に定義され、各ユーザーまたは会話が正しいバケットとプロパティに確実に配置されるように、ターン コンテキストに情報が含まれます。 具体的には、キーは次のように定義されます。
- ユーザー状態では、チャネル ID および FROM ID を使用してキーが作成されます。 たとえば、{Activity.ChannelId} / users / {Activity.From.Id} # YourPropertyName
- 会話状態では、チャネル ID と会話ID を使用してキーが作成されます。 たとえば、 {Activity.ChannelId}/conversations/{Activity.Conversation.Id}#YourPropertyName
- プライベート会話状態では、チャネル ID、送信者 ID、および会話 IDを使用してキーを作成します。 たとえば、 {Activity.ChannelId}/conversations/{Activity.Conversation.Id}/users/{Activity.From.Id}#YourPropertyName
各種類の状態を使用する場合
会話の状態は、次のような会話のコンテキストを追跡する場合に適しています。
- ボットがユーザーに質問したかどうか、およびどの質問が
- 会話の現在のトピックとは何か、または最後のトピックは何でしたか
ユーザーの状態は、次のようなユーザーに関する情報を追跡する場合に適しています。
- 重要でないユーザー情報 (名前と基本設定、アラーム設定、アラート設定など)
- ボットとの最後の会話に関する情報
- たとえば、製品サポート ボットは、ユーザーが質問した製品を追跡できます。
プライベート会話の状態は、グループ会話をサポートしているが、ユーザー固有の情報と会話固有の情報の両方を追跡するチャネルに適しています。 たとえば、クラスルーム クリッカー ボットがある場合は、次のようになります。
- ボットは、特定の質問に対する学生の回答を集計して表示できます。
- ボットはセッションの終了時に各学生のパフォーマンスを集計し、個別に通知できます。
これらの定義済みバケットの使い方の詳細については、状態に関するハウツー記事を参照してください。
複数のデータベースへの接続
ボットが複数のデータベースに接続する必要がある場合は、データベースごとにストレージ レイヤーを作成します。 セキュリティ、コンカレンシー、またはデータの場所のニーズが異なる情報をボットが収集する場合は、複数のデータベースを使用できます。
ストレージ レイヤーごとに、状態プロパティをサポートするために必要な状態管理オブジェクトを作成します。
State プロパティ アクセサー
状態プロパティ アクセサーは、実際に状態プロパティの 1 つを読み書きし、ターン内から状態プロパティにアクセスするための get、set、delete メソッドを提供するために使用されます。 アクセサーを作成するには、プロパティ名を指定する必要があります。これは通常、ボットを初期化するときに行われます。 その後、そのアクセサーを使用して、ボットの状態のそのプロパティを取得して操作できます。
アクセサーを使用すると、SDK は基になるストレージから状態を取得し、ボットの 状態キャッシュ を更新できます。 状態キャッシュは、状態オブジェクトを格納するボットによって管理されるローカル キャッシュであり、基になるストレージにアクセスせずに読み取りと書き込みの操作を許可します。 キャッシュにまだ存在しない場合は、アクセサーの get メソッドを呼び出すと状態が取得され、キャッシュにも配置されます。 取得した状態プロパティは、ローカル変数と同様に操作できます。
アクセサーの delete メソッドは、キャッシュからプロパティを削除し、基になるストレージからも削除します。
Von Bedeutung
アクセサーの get メソッドへの最初の呼び出しでは、あなたの状態にオブジェクトがまだ存在しない場合、作成するファクトリメソッドを指定する必要があります。 ファクトリ メソッドが指定されていない場合は、例外が発生します。 ファクトリ メソッドの使用方法の詳細については、ステート方法に関する記事を参照してください。
アクセサーから取得した状態プロパティに加えた変更を保持するには、状態キャッシュ内のプロパティを更新する必要があります。 これを行うには、アクセサー セット メソッドを呼び出して行うことができます。このメソッドはキャッシュ内のプロパティの値を設定し、その後で読み取りまたは更新する必要がある場合に使用できます。 そのデータを基になるストレージに実際に保持する (したがって、現在のターン後に使用できるようにするには) 状態 を保存する必要があります。
状態プロパティにおけるアクセサーメソッドのしくみ
アクセサーメソッドは、ボットが状態とやり取りする主要な手段です。 各動作のしくみと、基になるレイヤーの相互作用は次のとおりです。
- アクセサーの get メソッド:
- アクセサーは、状態キャッシュからプロパティを要求します。
- プロパティがキャッシュ内にある場合は、それを返します。 それ以外の場合は、状態管理オブジェクトから取得します。 (まだ状態にない場合は、アクセサー get 呼び出しで提供されるファクトリ メソッドを使用します)。
- アクセサーの set メソッド:
- 状態キャッシュを新しいプロパティ値で更新します。
- 状態管理オブジェクトの save changes メソッド:
- 状態キャッシュ内のプロパティに対する変更を確認します。
- そのプロパティをストレージに書き込みます。
ダイアログの現在の状態
ダイアログ ライブラリは、ボットの会話状態で定義されたダイアログ状態プロパティ アクセサーを使用して、会話内のダイアログの場所を保持します。 ダイアログ状態プロパティを使用すると、各ダイアログでターン間に一時的な情報を格納することもできます。
アダプティブ ダイアログには、より複雑なメモリ スコープ構造があるため、構成や認識の結果などに簡単にアクセスできます。 ダイアログ マネージャーは、ユーザーと会話の状態管理オブジェクトを使用して、これらのメモリ スコープを提供します。
ダイアログ ライブラリの詳細については、 ダイアログ ライブラリ の記事を参照してください。
- これらの種類のダイアログに固有の情報については、 コンポーネント ダイアログとウォーターフォール ダイアログについて 参照してください。
- アダプティブ ダイアログに固有の情報については、アダプティブ ダイアログの概要と、アダプティブ ダイアログの状態の管理に関する記事を参照してください。
状態の保存
アクセサーの set メソッドを呼び出して更新された状態を記録すると、その状態プロパティはまだ永続化されたストレージに保存されておらず、代わりにボットの状態キャッシュにのみ保存されます。 状態キャッシュの変更を永続化された状態に保存するには、状態管理オブジェクトの save changes メソッドを呼び出す必要があります。これは、前述のボット状態クラス (ユーザー状態や会話状態など) の実装で使用できます。
状態管理オブジェクト (上記のバケットなど) に対して save changes メソッドを呼び出すと、そのバケットのその時点まで設定したすべてのプロパティが状態キャッシュに保存されますが、ボットの状態にある可能性のある他のバケットには保存されません。
ヒント
ボットの状態では、"最後の書き込み優先" 動作が実装されます。最後の書き込みでは、以前に書き込まれた状態にスタンプが付けられます。 これは多くのアプリケーションで機能する可能性がありますが、特にスケールアウトシナリオでは、ある程度のコンカレンシーや待機時間が発生する可能性があります。
ターン ハンドラーの完了後に状態を更新する可能性のあるカスタム ミドルウェアがある場合は、 ミドルウェアで状態を処理することを検討してください。