순차적 대화 흐름 구현Implement sequential conversation flow

적용 대상: SDK v4APPLIES TO: SDK v4

질문을 제시하여 정보를 수집하는 것은 봇에서 사용자와 상호 작용하는 주요 방법 중 하나입니다.Gathering information by posing questions is one of the main ways a bot interacts with users. 대화 상자 라이브러리는 쉽게 질문하고 응답의 유효성을 검사하여 특정 데이터 형식과 일치하거나 사용자 지정 유효성 검사 규칙을 충족하는지 확인할 수 있는 ‘프롬프트’ 클래스 등의 유용한 기본 제공 기능을 제공합니다. The dialogs library provides useful built-in features such as prompt classes that make it easy to ask questions and validate the response to make sure it matches a specific data type or meets custom validation rules.

대화 상자 라이브러리를 사용하여 단순 및 복합 대화 흐름을 관리할 수 있습니다.You can manage simple and complex conversation flows using the dialogs library. 간단한 상호 작용에서 봇은 고정된 일련의 단계를 통해 실행되고 대화가 완료됩니다.In a simple interaction, the bot runs through a fixed sequence of steps, and the conversation finishes. 대화 상자는 봇이 사용자로부터 정보를 수집해야 하는 경우에 유용합니다.A dialog is useful when the bot needs to gather information from the user.

이 문서에서는 프롬프트를 만들고 폭포 대화에서 호출하여 간단한 대화 흐름을 구현하는 방법을 보여줍니다.This article shows how to implement simple conversation flow by creating prompts and calling them from a waterfall dialog.

대화 상자 라이브러리를 사용하지 않고 사용자 고유의 프롬프트를 작성하는 방법의 예제는 사용자 입력을 수집하기 위해 고유한 프롬프트 만들기를 참조하세요.For examples of how to write your own prompts without using the dialogs library, see the Create your own prompts to gather user input article.

사전 요구 사항Prerequisites

이 샘플 정보About this sample

다중 턴 프롬프트 샘플은 폭포 대화 상자, 몇 가지 프롬프트 및 구성 요소 대화 상자를 사용하여 사용자에게 일련의 질문을 하는 간단한 상호 작용을 만듭니다.The multi-turn prompts sample uses a waterfall dialog, a few prompts, and a component dialog to create a simple interaction that asks the user a series of questions. 코드는 대화 상자를 사용하여 다음 단계를 순환합니다.The code uses a dialog to cycle through these steps:

단계Steps 프롬프트 형식Prompt type
사용자에게 사용자의 전송 모드 요청Ask the user for their mode of transportation 선택 항목 프롬프트Choice prompt
사용자에게 사용자의 이름 요청Ask the user for their name 텍스트 프롬프트Text prompt
사용자에게 자신의 나이 제공을 원하는지 여부 질문Ask the user if they want to provide their age 확인 프롬프트Confirm prompt
예라고 대답한 경우 나이를 요청합니다.If they answered yes, ask for their age 0보다 크고 150보다 작은 연령만 허용하는 유효성 검사가 있는 숫자 프롬프트Number prompt, with validation to only accept ages greater than 0 and less than 150
Microsoft 팀을 사용하지 않는 경우 프로필 사진 요청If they're not using Microsoft Teams, ask them for a profile picture 첨부 파일 프롬프트, 누락된 첨부 파일을 허용하는 유효성 검사Attachment prompt, with validation to allow a missing attachment
수집된 정보가 "정상"인지 묻습니다.Ask if the collected information is "ok" 확인 프롬프트 재사용Reuse Confirm prompt

끝으로, 사용자가 예로 답하면 수집된 정보를 표시합니다. 그렇지 않으면 사용자에게 해당 사용자의 정보가 보관되지 않을 것임을 알려줍니다.Finally, if they answered yes, display the collected information; otherwise, tell the user that their information will not be kept.

기본 대화 만들기Create the main dialog

대화를 사용하려면 Microsoft.Bot.Builder.Dialogs NuGet 패키지를 설치합니다.To use dialogs, install the Microsoft.Bot.Builder.Dialogs NuGet package.

봇은 UserProfileDialog를 통해 사용자와 상호 작용합니다.The bot interacts with the user via UserProfileDialog. 봇의 클래스를 만들 때 DialogBot UserProfileDialog 는 기본 대화 상자로 설정됩니다.When creating the bot's DialogBot class, the UserProfileDialog is set as its main dialog. 그러면 봇이 Run 도우미 메서드를 사용하여 대화에 액세스합니다.The bot then uses a Run helper method to access the dialog.

C# 사용자 프로필 대화 상자

Dialogs\UserProfileDialog.csDialogs\UserProfileDialog.cs

먼저 UserProfileDialog ComponentDialog 클래스에서 파생되고 7단계가 있는 를 만듭니다.Begin by creating the UserProfileDialog that derives from the ComponentDialog class, and has 7 steps.

UserProfileDialog 생성자에서 폭포 단계, 프롬프트 및 폭포 대화를 만들어 대화 세트에 추가합니다.In the UserProfileDialog constructor, create the waterfall steps, prompts and the waterfall dialog, and add them to the dialog set. 프롬프트는 프롬프트가 사용되는 대화 세트에 있어야 합니다.The prompts need to be in the same dialog set in which they are used.

public UserProfileDialog(UserState userState)
    : base(nameof(UserProfileDialog))
{
    _userProfileAccessor = userState.CreateProperty<UserProfile>("UserProfile");

    // This array defines how the Waterfall will execute.
    var waterfallSteps = new WaterfallStep[]
    {
        TransportStepAsync,
        NameStepAsync,
        NameConfirmStepAsync,
        AgeStepAsync,
        PictureStepAsync,
        ConfirmStepAsync,
        SummaryStepAsync,
    };

    // Add named dialogs to the DialogSet. These names are saved in the dialog state.
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
    AddDialog(new TextPrompt(nameof(TextPrompt)));
    AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>), AgePromptValidatorAsync));
    AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
    AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt), PicturePromptValidatorAsync));

    // The initial child Dialog to run.
    InitialDialogId = nameof(WaterfallDialog);
}

다음으로 대화 상자에서 입력을 요청하는 데 사용하는 단계를 추가합니다.Next, add the steps that the dialog uses to prompt for input. 프롬프트를 사용하려면 대화의 단계에서 호출하고 다음 단계에서 stepContext.Result를 사용하여 프롬프트 결과를 검색합니다.To use a prompt, call it from a step in your dialog and retrieve the prompt result in the following step using stepContext.Result. 프롬프트는 내부적으로 두 가지 단계로 수행되는 대화입니다.Behind the scenes, prompts are a two-step dialog. 먼저 프롬프트에서 입력을 요청합니다.First, the prompt asks for input. 그런 다음 유효한 값을 반환하거나, 유효한 입력을 받을 때까지 재프롬프트를 통해 처음부터 다시 시작합니다.Then it returns the valid value, or starts over from the beginning with a reprompt until it receives a valid input.

항상 폭포 단계에서 null이 아닌 DialogTurnResult를 반환해야 합니다.You should always return a non-null DialogTurnResult from a waterfall step. 그렇지 않으면 대화 상자가 설계된 대로 작동하지 않을 수 있습니다.If you don't, your dialog may not work as designed. 아래는 폭포 대화 NameStepAsync 상자의 에 대한 구현입니다.Shown below is the implementation for NameStepAsync in the waterfall dialog.

private static async Task<DialogTurnResult> NameStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    stepContext.Values["transport"] = ((FoundChoice)stepContext.Result).Value;

    return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Please enter your name.") }, cancellationToken);
}

에서는 AgeStepAsync 프롬프트가 구문 분석할 수 없는 형식이거나 입력이 유효성 검사 조건에 실패하기 때문에 사용자의 입력이 유효성 검사에 실패하는 경우에 대한 재시도 프롬프트를 지정합니다.In AgeStepAsync, specify a retry prompt for when the user's input fails to validate, either because it's in a format that the prompt can't parse, or the input fails a validation criteria. 이 경우 다시 시도 프롬프트가 제공되지 않으면 프롬프트에서 초기 프롬프트 텍스트를 사용하여 사용자에게 다시 입력하도록 요청하는 프롬프트를 표시합니다.In this case, if no retry prompt was provided, the prompt will use the initial prompt text to re-prompt the user for input.

private async Task<DialogTurnResult> AgeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    if ((bool)stepContext.Result)
    {
        // User said "yes" so we will be prompting for the age.
        // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
        var promptOptions = new PromptOptions
        {
            Prompt = MessageFactory.Text("Please enter your age."),
            RetryPrompt = MessageFactory.Text("The value entered must be greater than 0 and less than 150."),
        };

        return await stepContext.PromptAsync(nameof(NumberPrompt<int>), promptOptions, cancellationToken);
    }
    else
    {
        // User said "no" so we will skip the next step. Give -1 as the age.
        return await stepContext.NextAsync(-1, cancellationToken);
    }
}

UserProfile.csUserProfile.cs

사용자의 교통 수단, 이름 및 나이가 UserProfile 클래스의 인스턴스에 저장됩니다.The user's mode of transportation, name, and age are saved in an instance of the UserProfile class.

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

    public string Name { get; set; }

    public int Age { get; set; }

    public Attachment Picture { get; set; }
}

Dialogs\UserProfileDialog.csDialogs\UserProfileDialog.cs

마지막 stepContext.Result 단계에서는 이전 폭포 단계에서 호출된 대화 상자에서 반환된 를 확인합니다.In the last step, check the stepContext.Result returned by the dialog called in the previous waterfall step. 반환 값이 true이면 사용자 프로필 접근자가 사용자 프로필을 가져오고 업데이트합니다.If the return value is true, the user profile accessor gets and updates the user profile. 사용자 프로필을 얻으려면 를 GetAsync 호출한 다음 , 및 속성의 값을 userProfile.Transport userProfile.Name userProfile.Age userProfile.Picture 설정합니다.To get the user profile, call GetAsync and then set the values of the userProfile.Transport, userProfile.Name, userProfile.Age and userProfile.Picture properties. 마지막으로 대화 상자를 종료하는 를 호출하기 전에 사용자에 대한 정보를 EndDialogAsync 요약합니다.Finally, summarize the information for the user before calling EndDialogAsync, which ends the dialog. 대화를 종료하면 대화 스택이 사라지고 대화 부모에 선택적 결과가 반환됩니다.Ending the dialog pops it off the dialog stack and returns an optional result to the dialog's parent. 부모는 대화 또는 방금 전에 종료된 대화를 시작한 메서드입니다.The parent is the dialog or method that started the dialog that just ended.

private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    if ((bool)stepContext.Result)
    {
        // Get the current profile object from user state.
        var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);

        userProfile.Transport = (string)stepContext.Values["transport"];
        userProfile.Name = (string)stepContext.Values["name"];
        userProfile.Age = (int)stepContext.Values["age"];
        userProfile.Picture = (Attachment)stepContext.Values["picture"];

        var msg = $"I have your mode of transport as {userProfile.Transport} and your name as {userProfile.Name}";

        if (userProfile.Age != -1)
        {
            msg += $" and your age as {userProfile.Age}";
        }

        msg += ".";

        await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);

        if (userProfile.Picture != null)
        {
            try
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(userProfile.Picture, "This is your profile picture."), cancellationToken);
            }
            catch
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("A profile picture was saved but could not be displayed here."), cancellationToken);
            }
        }
    }
    else
    {
        await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thanks. Your profile will not be kept."), cancellationToken);
    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
    return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}

대화 실행Run the dialog

Bots\DialogBot.csBots\DialogBot.cs

OnMessageActivityAsync 처리기는 RunAsync 메서드를 사용하여 대화를 시작하거나 계속합니다.The OnMessageActivityAsync handler uses the RunAsync method to start or continue the dialog. OnTurnAsync bot의 상태 관리 개체를 사용 하 여 저장소에 대 한 상태 변경 내용을 유지 합니다.OnTurnAsync uses the bot's state management objects to persist any state changes to storage. ActivityHandler.OnTurnAsync 메서드는 OnMessageActivityAsync와 같은 다양한 작업 처리기 메서드를 호출합니다.The ActivityHandler.OnTurnAsync method calls the various activity handler methods, such as OnMessageActivityAsync. 이러한 방식으로 메시지 처리기가 완료 된 후에도 상태가 저장 됩니다.In this way, the state is saved after the message handler completes but before the turn itself completes.

public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
    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);
}

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    Logger.LogInformation("Running dialog with Message Activity.");

    // Run the Dialog with the new message Activity.
    await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}

봇용 서비스 등록Register services for the bot

이 봇은 다음 서비스를 사용 합니다.This bot uses the following services:

  • 봇 기본 서비스: 자격 증명 공급자, 어댑터 및 봇 구현Basic services for a bot: a credential provider, an adapter, and the bot implementation.
  • 상태 관리 서비스: 스토리지, 사용자 상태 및 대화 상태Services for managing state: storage, user state, and conversation state.
  • 봇이 사용할 대화The dialog the bot will use.

Startup.csStartup.cs

에서 봇에 대 한 서비스를 등록 Startup 합니다.Register services for the bot in Startup. 해당 서비스는 종속성 주입을 통해 코드의 다른 부분에 사용할 수 있습니다.These services are available to other parts of the code through dependency injection.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient().AddControllers().AddNewtonsoftJson();

    // Create the Bot Framework Adapter with error handling enabled.
    services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

    // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
    services.AddSingleton<IStorage, MemoryStorage>();

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

    // Create the Conversation state. (Used by the Dialog system itself.)
    services.AddSingleton<ConversationState>();

    // The Dialog that will be run by the bot.
    services.AddSingleton<UserProfileDialog>();

    // Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
    services.AddTransient<IBot, DialogBot<UserProfileDialog>>();
}

참고

메모리 스토리지는 테스트 목적으로만 사용되며 프로덕션용이 아닙니다.Memory storage is used for testing purposes only and is not intended for production use. 프로덕션 봇에는 영구 스토리지 형식을 사용해야 합니다.Be sure to use a persistent type of storage for a production bot.

봇 테스트To test the bot

  1. 아직 수행 하지 않은 경우 Bot Framework 에뮬레이터를 설치 합니다.If you haven't done so already, install the Bot Framework Emulator.
  2. 샘플을 머신에서 로컬로 실행합니다.Run the sample locally on your machine.
  3. 에뮬레이터를 시작하고 봇에 연결한 다음, 아래와 같은 메시지를 보냅니다.Start the Emulator, connect to your bot, and send messages as shown below.

다중 턴 프롬프트 대화 상자의 샘플 실행

추가 정보Additional information

대화 상자 및 봇 상태 정보About dialog and bot state

이 봇에서는 두 개의 상태 속성 접근자가 정의 됩니다.In this bot, two state property accessors are defined:

  • 대화 상자 상태 속성에 대해 대화 상태 내에서 만든 접근자입니다.One created within conversation state for the dialog state property. 대화 상자 상태는 사용자가 대화 상자 집합의 대화 상자에 있는 위치를 추적 하며, begin dialog 또는 continue 대화 상자 메서드를 호출 하는 경우와 같이 대화 상자 컨텍스트에 의해 업데이트 됩니다.The dialog state tracks where the user is within the dialogs of a dialog set, and it's updated by the dialog context, such as when the begin dialog or continue dialog methods are called.
  • 사용자 프로필 속성에 대한 사용자 상태 내에서 만든 접근자입니다.One created within user state for the user profile property. Bot는이를 사용 하 여 사용자에 대 한 정보를 추적 하 고, 대화 상자 코드에서이 상태를 명시적으로 관리 해야 합니다.The bot uses this to track information it has about the user, and you must explicitly manage this state in the dialog code.

상태 속성 접근자의 getset 메서드는 상태 관리 개체의 캐시에서 속성의 값을 가져오고 설정합니다.The get and set methods of a state property accessor get and set the value of the property in the state management object's cache. 처음으로 상태 속성의 값이 순서대로 요청되는 경우 캐시가 채워지지만 명시적으로 유지되어야 합니다.The cache is populated the first time the value of a state property is requested in a turn, but it must be persisted explicitly. 이러한 상태 속성에 대 한 변경 내용을 유지 하기 위해 해당 상태 관리 개체의 변경 내용 저장 메서드에 대 한 호출이 수행 됩니다.In order to persist changes to both of these state properties, a call to the save changes method, of the corresponding state management object, is performed.

이 샘플은 대화 상자 내에서 사용자 프로필 상태를 업데이트합니다.This sample updates the user profile state from within the dialog. 이 방법은 간단한 봇에서 사용할 수 있지만 봇에서 대화 상자를 다시 사용 하려는 경우에는 작동 하지 않습니다.This practice can work for a simple bot, but it won't work if you want to reuse a dialog across bots.

대화 상자 단계와 봇 상태를 분리하는 다양한 옵션이 있습니다.There are various options for keeping dialog steps and bot state separate. 예를 들어, 대화 상자가 완전한 정보를 수집하면 다음을 수행할 수 있습니다.For example, once your dialog gathers complete information, you can:

  • 종료 대화 상자 를 사용 하 여 수집 된 데이터를 부모 컨텍스트에 반환 값으로 다시 제공 합니다.Use the end dialog method to provide the collected data as return value back to the parent context. 이것은 봇의 턴 처리기 또는 대화 스택의 이전 활성 대화 상자 이며 프롬프트 클래스를 디자인 하는 방법입니다.This can be the bot's turn handler or an earlier active dialog on the dialog stack and it's how the prompt classes are designed.
  • 적절한 서비스에 요청을 생성합니다.Generate a request to an appropriate service. 봇이 대규모 서비스에 대한 프론트 엔드의 역할을 하는 경우 제대로 작동할 수 있습니다.This might work well if your bot acts as a front end to a larger service.

프롬프트 유효성 검사기 메서드의 정의Definition of a prompt validator method

UserProfileDialog.csUserProfileDialog.cs

다음은 메서드 정의에 대 한 유효성 검사기 코드 예제입니다 AgePromptValidatorAsync .Below is a validator code example for the AgePromptValidatorAsync method definition. promptContext.Recognized.Value에는 구문 분석된 값(숫자 프롬프트의 정수)이 포함됩니다.promptContext.Recognized.Value contains the parsed value, which is an integer here for the number prompt. promptContext.Recognized.Succeeded는 프롬프트에서 사용자의 입력을 구문 분석할 수 있는지 여부를 나타냅니다.promptContext.Recognized.Succeeded indicates whether the prompt was able to parse the user's input or not. 값이 수락되지 않았으며 프롬프트 대화 상자에서 사용자에게 프롬프트를 다시 표시해야 함을 나타내기 위해 유효성 검사기에서 false를 반환해야 합니다. 그렇지 않으면 true를 반환하여 입력을 수락하고 프롬프트 대화 상자에서 이를 반환해야 합니다.The validator should return false to indicate that the value was not accepted and the prompt dialog should reprompt the user; otherwise, return true to accept the input and return from the prompt dialog. 시나리오에 따라 유효성 검사기의 값을 변경할 수 있습니다.Note that you can change the value in the validator per your scenario.

private static Task<bool> AgePromptValidatorAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)
{
    // This condition is our validation rule. You can also change the value at this point.
    return Task.FromResult(promptContext.Recognized.Succeeded && promptContext.Recognized.Value > 0 && promptContext.Recognized.Value < 150);
}

다음 단계Next steps