会話を期限切れにする

この記事の対象: SDK v4

ボットは、最初から会話を再開する必要がある場合があります。 たとえば、ユーザーが一定期間後に応答しない場合などです。 この記事では、会話の有効期限を切るための 2 つの方法について説明します。

  • ユーザーからメッセージが最後に受信された時刻を追跡し、ユーザーから次のメッセージを受信したときに、時刻が事前設定された期間を超える場合は状態をクリアします。 詳細については、「ユーザー対話の有効期限」セクションを参照してください。
  • Cosmos DB Time To Live (TTL) などのストレージ レイヤー機能を使用して、事前設定された時間の後に状態を自動的にクリアします。 詳細については、「ストレージの有効期限」セクションを参照してください。

Note

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

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

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

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

前提条件

このサンプルについて

この記事のサンプル コードは、マルチターン ボットの構造で始まり、そのボットの機能を追加コードによって拡張しています (次のセクションで説明します)。 この拡張コードは、一定の期間が経過した後に会話状態をクリアする方法を示しています。

ユーザー対話の有効期限

この種類の期限切れとなる会話は、最終アクセス タイムプロパティをボットの会話状態に追加することによって実現されます。 このプロパティ値は、アクティビティを処理する前に、アクティビティ ハンドラー内の現在の時刻と比較されます。

Note

この例では、このパターンを簡単にテストするために 30 秒のタイムアウトを使用します。

appsettings.json

まず、appsettings.json に ExpireAfterSeconds 設定を追加します。

{
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "ExpireAfterSeconds": 30
}

Bots\DialogBot.cs

次に、ボット クラスにフィールド ExpireAfterSecondsLastAccessedTimePropertyDialogStateProperty を追加し、ボットのコンストラクターで初期化します。 また、IConfiguration 値を取得するコンストラクターに ExpireAfterSeconds パラメーターを 追加します。

OnMessageActivityAsync メソッドでダイアログ状態プロパティ アクセサーをインラインで作成する代わりに、初期化時に作成して記録します。 ボットには、ダイアログを実行するだけでなく、ダイアログの状態をクリアするためにも、状態プロパティ アクセサーが必要になります。

protected readonly int ExpireAfterSeconds;
protected readonly IStatePropertyAccessor<DateTime> LastAccessedTimeProperty;
protected readonly IStatePropertyAccessor<DialogState> DialogStateProperty;

// Existing fields omitted...

public DialogBot(IConfiguration configuration, ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
    ConversationState = conversationState;
    UserState = userState;
    Dialog = dialog;
    Logger = logger;

    ExpireAfterSeconds = configuration.GetValue<int>("ExpireAfterSeconds");
    DialogStateProperty = ConversationState.CreateProperty<DialogState>(nameof(DialogState));
    LastAccessedTimeProperty = ConversationState.CreateProperty<DateTime>(nameof(LastAccessedTimeProperty));
}

最後に、会話の経過時間が長すぎる場合にダイアログの状態をクリアするコードをボットの OnTurnAsync メソッドに追加します。

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
    // Retrieve the property value, and compare it to the current time.
    var lastAccess = await LastAccessedTimeProperty.GetAsync(turnContext, () => DateTime.UtcNow, cancellationToken).ConfigureAwait(false);
    if ((DateTime.UtcNow - lastAccess) >= TimeSpan.FromSeconds(ExpireAfterSeconds))
    {
        // Notify the user that the conversation is being restarted.
        await turnContext.SendActivityAsync("Welcome back!  Let's start over from the beginning.").ConfigureAwait(false);

        // Clear state.
        await ConversationState.ClearStateAsync(turnContext, cancellationToken).ConfigureAwait(false);
    }

    await base.OnTurnAsync(turnContext, cancellationToken).ConfigureAwait(false);

    // Set LastAccessedTime to the current time.
    await LastAccessedTimeProperty.SetAsync(turnContext, DateTime.UtcNow, cancellationToken).ConfigureAwait(false);

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

ストレージの有効期限

Azure Cosmos DB は、Time to Live (TTL) を使用して、一定の期間が経過したらアイテムをコンテナーから自動的に削除する機能を提供します。 これは、Azure portal 内またはコンテナーの作成時 (言語固有の Cosmos DB SDK を使用) から構成できます。

Bot Framework SDK では、TTL 構成設定は公開されません。 ただし、コンテナーの初期化はオーバーライドでき、Cosmos DB SDK を使用して、Bot Framework ストレージの初期化前に TTL を構成できます。

マルチターン プロンプト サンプルの新しいコピーから開始し、Microsoft.Bot.Builder.Azure NuGet パッケージをプロジェクトに追加します。

appsettings.json

cosmos DB ストレージ オプションを含むように appsettings.json を更新します。

{
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",

  "CosmosDbTimeToLive": 30,
  "CosmosDbEndpoint": "<endpoint-for-your-cosmosdb-instance>",
  "CosmosDbAuthKey": "<your-cosmosdb-auth-key>",
  "CosmosDbDatabaseId": "<your-database-id>",
  "CosmosDbUserStateContainerId": "<no-ttl-container-id>",
  "CosmosDbConversationStateContainerId": "<ttl-container-id>"
}

2 つの ContainerId に注目してください。1 つは UserState 用で、もう 1 つは ConversationState 用です。 ConversationState コンテナーには既定の TTL が設定されますが、UserState には設定されません。

CosmosDbStorageInitializerHostedService.cs

次に、Time To Live が構成されたコンテナーを作成する CosmosDbStorageInitializerHostedService クラスを作成します。

// Add required using statements...

public class CosmosDbStorageInitializerHostedService : IHostedService
{
    readonly CosmosDbPartitionedStorageOptions _storageOptions;
    readonly int _cosmosDbTimeToLive;

    public CosmosDbStorageInitializerHostedService(IConfiguration config)
    {
        _storageOptions = new CosmosDbPartitionedStorageOptions()
        {
            CosmosDbEndpoint = config["CosmosDbEndpoint"],
            AuthKey = config["CosmosDbAuthKey"],
            DatabaseId = config["CosmosDbDatabaseId"],
            ContainerId = config["CosmosDbConversationStateContainerId"]
        };

        _cosmosDbTimeToLive = config.GetValue<int>("CosmosDbTimeToLive");
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        using (var client = new CosmosClient(
            _storageOptions.CosmosDbEndpoint,
            _storageOptions.AuthKey,
            _storageOptions.CosmosClientOptions ?? new CosmosClientOptions()))
        {
            // Create the contaier with the provided TTL
            var containerResponse = await client
                .GetDatabase(_storageOptions.DatabaseId)
                .DefineContainer(_storageOptions.ContainerId, "/id")
                .WithDefaultTimeToLive(_cosmosDbTimeToLive)
                .WithIndexingPolicy().WithAutomaticIndexing(false).Attach()
                .CreateIfNotExistsAsync(_storageOptions.ContainerThroughput)
                .ConfigureAwait(false);
        }
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

Startup.cs

最後に、Startup.cs をストレージ初期化子を使用するように更新し、Cosmos DB を次の状態に更新します。

// Existing code omitted...

// commented out MemoryStorage, since we are using CosmosDbPartitionedStorage instead
// services.AddSingleton<IStorage, MemoryStorage>();

// Add the Initializer as a HostedService (so it's called during the app service startup)
services.AddHostedService<CosmosDbStorageInitializerHostedService>();

// Create the storage options for User state
var userStorageOptions = new CosmosDbPartitionedStorageOptions()
{
    CosmosDbEndpoint = Configuration["CosmosDbEndpoint"],
    AuthKey = Configuration["CosmosDbAuthKey"],
    DatabaseId = Configuration["CosmosDbDatabaseId"],
    ContainerId = Configuration["CosmosDbUserStateContainerId"]
};

// Create the User state. (Used in this bot's Dialog implementation.)
services.AddSingleton(new UserState(new CosmosDbPartitionedStorage(userStorageOptions)));

// Create the storage options for Conversation state
var conversationStorageOptions = new CosmosDbPartitionedStorageOptions()
{
    CosmosDbEndpoint = Configuration["CosmosDbEndpoint"],
    AuthKey = Configuration["CosmosDbAuthKey"],
    DatabaseId = Configuration["CosmosDbDatabaseId"],
    ContainerId = Configuration["CosmosDbConversationStateContainerId"]
};

// Create the Conversation state. (Used by the Dialog system itself.)
services.AddSingleton(new ConversationState(new CosmosDbPartitionedStorage(conversationStorageOptions)));

// Existing code omitted...

これで、Cosmos DB では、30 秒の非アクティブ状態の後、会話状態レコードが自動的に削除されます。

詳細については、「Azure Cosmos DB で有効期限を構成する」を参照してください。

ボットをテストする

  1. Bot Framework Emulator をインストールします (まだインストールしていない場合)。
  2. ご自身のマシンを使ってローカルでサンプルを実行します。
  3. エミュレーターを起動し、ボットに接続して、メッセージを送信します。
  4. いずれかのプロンプトが表示されたら、30 秒待ってから応答します。