사용자 및 대화 데이터 저장Save user and conversation data

적용 대상: SDK v4APPLIES TO: SDK v4

봇은 기본적으로 상태 비저장입니다.A bot is inherently stateless. 일단 봇이 배포되면 동일한 프로세스 또는 동일한 머신에서 한 턴에서 다음 턴으로 실행되지 않을 수 있습니다.Once your bot is deployed, it may not run in the same process or on the same machine from one turn to the next. 그러나 봇은 대화의 컨텍스트를 추적하여 해당 동작을 관리하고 이전 질문에 대한 답변을 기억해야 할 수도 있습니다.However, your bot may need to track the context of a conversation so that it can manage its behavior and remember answers to previous questions. Bot Framework SDK의 상태 및 스토리지 기능을 사용하면 상태를 봇에 추가할 수 있습니다.The state and storage features of the Bot Framework SDK allow you to add state to your bot. 봇은 상태 관리 및 스토리지 개체를 사용하여 상태를 관리하고 유지합니다.Bots use state management and storage objects to manage and persist state. 상태 관리자는 기본 스토리지의 유형에 관계없이 속성 접근자를 사용하여 상태 속성에 액세스할 수 있는 추상화 레이어를 제공합니다.The state manager provides an abstraction layer that lets you access state properties using property accessors, independent of the type of underlying storage.

사전 요구 사항Prerequisites

이 샘플 정보About this sample

사용자 입력을 수신할 때 이 샘플은 저장된 대화 상태를 확인하여 이 사용자에게 해당 사용자의 이름을 제공하라는 프롬프트를 표시했는지 확인합니다.Upon receiving user input, this sample checks the stored conversation state to see if this user has previously been prompted to provide their name. 프롬프트를 표시하지 않은 경우 사용자의 이름을 요청하며 해당 입력은 사용자 상태 내에 저장됩니다.If not, the user's name is requested and that input is stored within user state. 프롬프트를 표시한 경우 사용자 상태 내에 저장된 이름을 사용하여 사용자와 대화하며 사용자의 입력 데이터를 수신된 시간 및 입력 채널 ID와 함께 사용자에게 다시 반환합니다.If so, the name stored within user state is used to converse with the user and their input data, along with the time received and input channel Id, is returned back to the user. 시간 및 채널 ID 값은 사용자 대화 데이터에서 검색된 후 대화 상태에 저장됩니다.The time and channel Id values are retrieved from the user conversation data and then saved to conversation state. 다음 다이어그램은 봇, 사용자 프로필 및 대화 데이터 클래스 간의 관계를 보여줍니다.The following diagram shows the relationship between the bot, user profile, and conversation data classes.

클래스 정의Define classes

상태 관리를 설정하는 첫 번째 단계는 사용자 및 대화 상태에서 관리하려는 모든 정보를 포함하는 클래스를 정의하는 것입니다.The first step in setting up state management is to define the classes containing the information to manage in the user and conversation state. 이 문서에 사용된 예제에서는 다음 클래스를 정의합니다.The example used in this article, defines the following classes:

  • UserProfile 에서 UserProfile 봇이 수집할 사용자 정보에 대 한 클래스를 정의 합니다.In UserProfile.cs, you define a UserProfile class for the user information that the bot will collect.
  • ConversationData 에서는 ConversationData 사용자 정보를 수집 하는 동안 대화 상태를 제어 하는 클래스를 정의 합니다.In ConversationData.cs, you define a ConversationData class to control our conversation state while gathering user information.

다음 코드 예제에서는 UserProfileConversationData 클래스에 대한 정의를 보여줍니다.The following code examples show the definitions for the UserProfile and ConversationData classes.

UserProfile.csUserProfile.cs

// Defines a state property used to track information about the user.
public class UserProfile
{
    public string Name { get; set; }
}

ConversationData.csConversationData.cs

// Defines a state property used to track conversation data.
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;
}

대화 및 사용자 상태 개체 만들기Create conversation and user state objects

다음에는 MemoryStorage 및 개체를 만드는 데 사용 되는를 등록 UserState ConversationState 합니다.Next, you register MemoryStorage that is used to create UserState and ConversationState objects. 사용자 및 대화 상태 개체는 Startup에 만들어지며 종속성은 봇 생성자에 주입됩니다.The user and conversation state objects are created at Startup and dependency injected into the bot constructor. 등록된 봇에 대한 다른 서비스는 자격 증명 공급자, 어댑터 및 봇 구현입니다.Other services for a bot that are registered are: a credential provider, an adapter, and the bot implementation.

Startup.csStartup.cs

// Create the storage we'll be using for User and Conversation state.
// (Memory is great for testing purposes - examples of implementing storage with
// Azure Blob Storage or Cosmos DB are below).
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.csBots/StateManagementBot.cs

private BotState _conversationState;
private BotState _userState;

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

상태 속성 접근자 추가Add state property accessors

이제 개체에 대한 핸들을 제공하는 메서드를 사용하여 속성 CreateProperty 접근자를 만듭니다. BotStateNow you create property accessors using the CreateProperty method that provides a handle to the BotState object. 각 상태 속성 접근자를 사용하면 연결된 상태 속성의 값을 가져오거나 설정할 수 있습니다.Each state property accessor allows you to get or set the value of the associated state property. 상태 속성을 사용하기 전에 각 접근자를 사용하여 스토리지에서 속성을 로드하고 상태 캐시에서 속성을 얻습니다.Before you use the state properties, use each accessor to load the property from storage and get it from the state cache. 상태 속성과 연결된 적절한 범위의 키를 얻으려면 GetAsync 메서드를 호출합니다.To get the properly scoped key associated with the state property, you call the GetAsync method.

Bots/StateManagementBot.csBots/StateManagementBot.cs

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

봇에서 상태 액세스Access state from your bot

앞의 섹션에서는 상태 속성 접근자를 봇에 추가하는 초기화 시간 단계에 대해 다루고 있습니다.The preceding section covers the initialization-time steps to add state property accessors to our bot. 이제 런타임에 이러한 접근자를 사용하여 상태 정보를 읽고 쓸 수 있습니다.Now, you can use those accessors at run-time to read and write state information. 아래 샘플 코드는 다음 논리 흐름을 사용합니다.The sample code below uses the following logic flow:

  • userProfile.Name 비어 있고 conversationData.PromptedUserForName이 true이면 제공된 사용자 이름을 검색하여 사용자 상태 내에 저장합니다.If userProfile.Name is empty and conversationData.PromptedUserForName is true, you retrieve the user name provided and store this within user state.
  • userProfile.Name 비어 있고 conversationData.PromptedUserForName이 false이면 사용자 이름을 요청합니다.If userProfile.Name is empty and conversationData.PromptedUserForName is false, you ask for the user's name.
  • userProfile.Name 이전에 저장된 경우 사용자 입력에서 메시지 시간 및 채널 ID를 검색하고, 모든 데이터를 사용자에게 다시 에코하고, 검색된 데이터를 대화 상태 내에 저장합니다.If userProfile.Name was previously stored, you retrieve message time and channel Id from the user input, echo all data back to the user, and store the retrieved data within conversation state.

Bots/StateManagementBot.csBots/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() 메서드를 사용하여 모든 상태 변경 내용을 스토리지에 다시 씁니다.Before you exit the turn handler, you use the state management objects' SaveChangesAsync() method to write all state changes back to storage.

Bots/StateManagementBot.csBots/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);
}

봇 테스트Test the bot

최신 Bot Framework Emulator를 다운로드하고 설치합니다.Download and install the latest Bot Framework Emulator

  1. 샘플을 머신에서 로컬로 실행합니다.Run the sample locally on your machine. 지침이 필요한 경우 C# 샘플 또는 JS 샘플에 대한 추가 정보 파일을 참조하세요.If you need instructions, refer to the README file for C# Sample or JS Sample.
  2. Emulator를 사용하여 아래와 같이 봇을 테스트합니다.Use the Emulator to test the bot as shown below.

상태 봇 샘플 테스트

추가 리소스Additional resources

개인 정보: 사용자의 개인 데이터를 저장하려면 GDPR(일반 데이터 보호 규정)을 준수해야 합니다.Privacy: If you intend to store user's personal data, you should ensure compliance with General Data Protection Regulation.

상태 관리: 상태 관리 호출은 모두 비동기적이며, 기본적으로 최종 작성자 인정(last-writer-wins)입니다.State management: All of the state management calls are asynchronous, and last-writer-wins by default. 실제로 봇의 상태를 최대한 가깝게 가져오고, 설정하고, 저장해야 합니다.In practice, you should get, set, and save state as close together in your bot as possible.

중요 비즈니스 데이터: 봇 상태를 사용하여 기본 설정, 사용자 이름 또는 요청한 마지막 항목을 저장하지만, 중요 비즈니스 데이터를 저장하는 데는 사용하지 않습니다.Critical business data: Use bot state to store preferences, user name, or the last thing they ordered, but do not use it to store critical business data. 중요한 데이터의 경우 사용자 고유의 스토리지 구성 요소를 만들거나스토리지에 직접 씁니다.For critical data, create your own storage components or write directly to storage.

Recognizer-Text: 샘플에서는 Microsoft/Recognizers-Text 라이브러리를 사용하여 사용자 입력을 구문 분석하고 유효성을 검사합니다.Recognizer-Text: The sample uses the Microsoft/Recognizers-Text libraries to parse and validate user input. 자세한 내용은 개요 페이지를 참조하세요.For more information, see the overview page.

다음 단계Next steps

이제 스토리지에서 봇 데이터를 읽고 쓰는 데 도움이 되는 상태를 구성하는 방법을 알아보았으므로, 사용자에게 일련의 질문을 하고 답변을 확인하고 입력을 저장하는 방법을 알아보겠습니다.Now that you know how to configure state to help you read and write bot data to storage, let's learn how ask the user a series of questions, validate their answers, and save their input.