ユーザーと会話データを保存する

この記事の対象: SDK v4

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

Note

Bot Framework JavaScript SDK、C#、Python SDK は引き続きサポートされますが、Java SDK については、最終的な長期サポートは 2023 年 11 月に終了する予定です。

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

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

詳細については、「The future of bot building」をご覧ください。

前提条件

  • ボットの基本、およびボットによる状態の管理方法に関する知識が必要です。
  • この記事のコードは、状態管理ボット サンプルをベースにしています。 サンプルのコピー (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;
}

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

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

Startup.cs

// {
//     TypeNameHandling = TypeNameHandling.All,
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state");

// With a custom JSON SERIALIZER, use this instead.
// var storage = new BlobsStorage("<blob-storage-connection-string>", "bot-state", jsonSerializer);

/* END AZURE BLOB STORAGE */

Bots/StateManagementBot.cs

private BotState _conversationState;
private BotState _userState;

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

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

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

Bots/StateManagementBot.cs

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

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

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

  • userProfile.Name が空で、conversationData.PromptedUserForNametrue の場合、指定されたユーザー名を取得し、ユーザーの状態内に格納します。
  • userProfile.Name が空で、conversationData.PromptedUserForNamefalse の場合、ユーザー名を要求します。
  • 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#JavaScriptJavaPython の README を参照してください。
  3. エミュレーターを使用してサンプル ボットをテストする

追加情報

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

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

次のステップ

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