ユーザーおよび会話データを保存する

適用対象: SDK v4

ボットは本質的にステートレスです。 いったんご自身のボットがデプロイされると、ターンをまたいで、そのボットを同じプロセスやマシンで実行することはできません。 しかし、お使いのボットでは、会話のコンテキストの追跡が必要になることがあります。これにより、自身の動作を管理し、以前の質問に対する回答を記憶できるようになります。 Bot Framework SDK の状態とストレージの機能を使用すると、状態をお使いのボットに追加できます。 ボットは状態管理およびストレージ オブジェクトを使用して状態を管理維持します。 この状態マネージャーは、基になるストレージの種類には関係なく、プロパティ アクセサーを使用して状態プロパティにアクセスできる抽象化レイヤーを提供します。

Note

Bot Framework JavaScript と C# SDK は引き続きサポートされますが、Python SDK と Java SDK は廃止され、2023 年 11 月に終了する最終的な長期サポートが終了します。 このリポジトリ内の重要なセキュリティとバグの修正のみが行われます。

これらの SDK を使用して構築された既存のボットは引き続き機能します。

新しいボットの構築については、 Power Virtual Agents の 使用を検討し、 適切なチャットボット ソリューションの選択に関する記事を参照してください。

詳細については、「 ボット構築の未来」を参照してください。

前提条件

  • ボットの基本、およびボットによる状態の管理方法に関する知識が必要です。
  • この記事のコードは、状態管理ボット サンプルをベースにしています。 サンプルのコピーは、 C#JavaScriptJava、または Python のいずれかで必要になります。

このサンプルについて

このサンプルでは、ユーザー入力を受け取ったときに、保存済み会話状態を確認して、ユーザーが以前に名前を入力するように求められたかどうかを確かめます。 求められていない場合は、ユーザーの名前が要求され、入力した内容はユーザー状態に格納されます。 その場合は、ユーザー状態に格納されている名前を使用して、ユーザーとその入力データと、受信した時刻と入力チャネル ID がユーザーに返されます。 時刻とチャネル ID の値は、ユーザーの会話データから取得され、会話状態に保存されます。 次の図は、ボット、ユーザー プロファイル、および会話データ クラスの間の関係を示しています。

クラスの定義

状態管理を設定するには、まず、ユーザーと会話状態で管理する情報を含むクラスを定義します。 この記事で使用している例では、次のクラスを定義しています。

  • UserProfile.cs では、ボットがUserProfile収集するユーザー情報のクラスを定義します。
  • ConversationData.cs では、ユーザー情報のConversationData収集中に会話の状態を制御するクラスを定義します。

次のコード例は、UserProfileConversationData クラスの定義を示しています。

UserProfile.cs

public class UserProfile
{
    public string Name { get; set; }
}

ConversationData.cs

public class ConversationData
{
    // The time-stamp of the most recent incoming message.
    public string Timestamp { get; set; }

    // The ID of the user's channel.
    public string ChannelId { get; set; }

    // Track whether we have already asked the user's name
    public bool PromptedUserForName { get; set; } = false;
}

会話およびユーザー状態オブジェクトの作成

次に、 オブジェクトと ConversationState オブジェクトの作成UserStateに使用される を登録MemoryStorageします。 ユーザーおよび会話状態オブジェクトは Startup で作成され、依存関係がボット コンストラクターに挿入されます。 ボット用のサービスとして他に資格情報プロバイダー、アダプター、およびボット実装が登録されています。

Startup.cs

var storage = new MemoryStorage();

// Create the User state passing in the storage layer.
var userState = new UserState(storage);
services.AddSingleton(userState);

// Create the Conversation state passing in the storage layer.
var conversationState = new ConversationState(storage);
services.AddSingleton(conversationState);

Bots/StateManagementBot.cs

private BotState _conversationState;
private BotState _userState;

public StateManagementBot(ConversationState conversationState, UserState userState)
{
    _conversationState = conversationState;
    _userState = userState;
}

状態プロパティ アクセサーの追加

次に、 オブジェクトへのハンドルを提供する メソッドを CreateProperty 使用して、プロパティ アクセサーを BotState 作成します。 各状態プロパティ アクセサーを使用すると、関連付けられている状態プロパティの値を取得または設定できます。 状態プロパティを使用する前に、各アクセサーを使用してストレージからプロパティを読み込み、状態キャッシュから取得します。 state プロパティに関連付けられている適切なスコープのキーを取得するには、 メソッドを GetAsync 呼び出します。

Bots/StateManagementBot.cs

var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));

お使いのボットから状態にアクセスする

前のセクションでは、状態プロパティ アクセサーをボットに追加するための、初期化時の手順について説明しました。 これで、実行時にこれらのアクセサーを使用して、状態情報の読み取りと書き込みを行うことができます。 次のサンプル コードでは次のロジック フローを使用します。

  • が空で conversationData.PromptedUserForNametrue の場合userProfile.Nameは、指定されたユーザー名を取得し、これをユーザー状態に格納します。
  • が空で conversationData.PromptedUserForNamefalse の場合userProfile.Nameは、ユーザーの名前を要求します。
  • 以前に格納されていた場合 userProfile.Name は、ユーザー入力からメッセージ時間とチャネル ID を取得し、すべてのデータをユーザーにエコーバックし、取得したデータを会話状態に格納します。

Bots/StateManagementBot.cs

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    // Get the state properties from the turn context.

    var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
    var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());

    var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
    var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());

    if (string.IsNullOrEmpty(userProfile.Name))
    {
        // First time around this is set to false, so we will prompt user for name.
        if (conversationData.PromptedUserForName)
        {
            // Set the name to what the user provided.
            userProfile.Name = turnContext.Activity.Text?.Trim();

            // Acknowledge that we got their name.
            await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");

            // Reset the flag to allow the bot to go through the cycle again.
            conversationData.PromptedUserForName = false;
        }
        else
        {
            // Prompt the user for their name.
            await turnContext.SendActivityAsync($"What is your name?");

            // Set the flag to true, so we don't prompt in the next turn.
            conversationData.PromptedUserForName = true;
        }
    }
    else
    {
        // Add message details to the conversation data.
        // Convert saved Timestamp to local DateTimeOffset, then to string for display.
        var messageTimeOffset = (DateTimeOffset)turnContext.Activity.Timestamp;
        var localMessageTime = messageTimeOffset.ToLocalTime();
        conversationData.Timestamp = localMessageTime.ToString();
        conversationData.ChannelId = turnContext.Activity.ChannelId.ToString();

        // Display state data.
        await turnContext.SendActivityAsync($"{userProfile.Name} sent: {turnContext.Activity.Text}");
        await turnContext.SendActivityAsync($"Message received at: {conversationData.Timestamp}");
        await turnContext.SendActivityAsync($"Message received from: {conversationData.ChannelId}");
    }
}

ターン ハンドラーを終了する前に、状態管理オブジェクトの SaveChangesAsync() メソッドを使用して、すべての状態変更をストレージに書き戻します。

Bots/StateManagementBot.cs

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    await base.OnTurnAsync(turnContext, cancellationToken);

    // Save any state changes that might have occurred during the turn.
    await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}

ボットをテストする

  1. 最新の Bot Framework Emulator をダウンロードしてインストールします
  2. ご自身のマシンを使ってローカルでサンプルを実行します。 手順が必要な場合は、 C#JavaScriptJava、または Python の README を参照してください。
  3. エミュレーターを使用してサンプル ボットをテストします。

関連情報

この記事では、ボットに状態を追加する方法について説明しました。 関連トピックの詳細については、次の表を参照してください。

トピック Notes
プライバシー ユーザーの個人データを格納する場合は、一般データ保護規則に関するコンプライアンスを確保する必要があります。
状態管理 すべての状態管理呼び出しが非同期で行われます。これらの呼び出しは既定で Last-Writer-Wins です。 実際の場面では、状態の取得、設定、および保存は、ご自身のボット内で、できるだけ近いタイミングで行う必要があります。 オプティミスティック ロックを実装する方法については、「 ボットのカスタム ストレージを実装する」を参照してください。
重要なビジネス データ ボットの状態を使用して、優先順位、ユーザー名、または最後に注文したものを格納しますが、重要なビジネス データの格納には使用しないでください。 重要なデータについては、独自のストレージ コンポーネントを作成するか、ストレージに直接書き込んでください。
Recognizer-Text サンプルでは、ユーザー入力の解析および検証に Microsoft/Recognizers-Text ライブラリが使用されます。 詳細については、概要に関するページを参照してください。

次のステップ

ユーザーに一連の質問をし、回答を検証し、入力を保存する方法について説明します。