순차적 대화 흐름 구현

적용 대상: SDK v4

질문을 제공하여 정보를 수집하는 것은 봇이 사용자와 상호 작용하는 기본 방법 중 하나입니다. 대화 상자 라이브러리는 쉽게 질문하고 응답의 유효성을 검사하여 특정 데이터 형식과 일치하거나 사용자 지정 유효성 검사 규칙을 충족하는지 확인할 수 있는 ‘프롬프트’ 클래스 등의 유용한 기본 제공 기능을 제공합니다.

대화 상자 라이브러리를 사용하여 선형 및 더 복잡한 대화 흐름을 관리할 수 있습니다. 선형 상호 작용에서 봇은 고정된 단계 시퀀스를 통해 실행되고 대화가 완료됩니다. 대화 상자는 봇이 사용자로부터 정보를 수집해야 하는 경우에 유용합니다.

이 문서에서는 프롬프트를 만들고 폭포 대화 상자에서 호출하여 선형 대화 흐름을 구현하는 방법을 보여 줍니다. 대화 상자 라이브러리를 사용하지 않고 사용자 고유의 프롬프트를 작성하는 방법에 대한 예제는 사용자 입력 문서를 수집하기 위한 사용자 고유의 프롬프트 만들기를 참조하세요.

참고 항목

Bot Framework JavaScript, C#및 Python SDK는 계속 지원되지만 Java SDK는 2023년 11월에 종료되는 최종 장기 지원으로 사용 중지됩니다.

Java SDK를 사용하여 빌드된 기존 봇은 계속 작동합니다.

새 봇 빌드의 경우 Power Virtual Agents 사용을 고려하고 올바른 챗봇 솔루션을 선택하는 방법을 읽어 보세요.

자세한 내용은 봇 빌드의 미래를 참조 하세요.

필수 조건

이 샘플 정보

다중 턴 프롬프트 샘플은 폭포 대화 상자, 몇 가지 프롬프트 및 구성 요소 대화 상자를 사용하여 사용자에게 일련의 질문을 하는 선형 상호 작용을 만듭니다. 코드는 대화 상자를 사용하여 다음 단계를 순환합니다.

단계 프롬프트 형식
사용자에게 교통 모드를 요청합니다. 선택 프롬프트
사용자에게 이름을 요청합니다. 텍스트 프롬프트
사용자에게 나이를 제공할지 물어보세요. 확인 프롬프트
'예'라고 대답하면 나이를 물어보세요. 숫자 프롬프트, 0보다 크고 150보다 작은 연령만 허용하도록 유효성 검사
Microsoft Teams를 사용하지 않는 경우 프로필 사진을 요청하세요. 첨부 파일 누락을 허용하는 유효성 검사와 함께 첨부 파일 프롬프트
수집된 정보가 "확인"인지 묻습니다. 확인 프롬프트 다시 사용

마지막으로, '예'라고 대답하면 수집된 정보를 표시합니다. 그렇지 않으면 사용자에게 해당 정보가 유지되지 않는다고 알릴 수 있습니다.

기본 대화 상자 만들기

대화를 사용하려면 Microsoft.Bot.Builder.Dialogs NuGet 패키지를 설치합니다.

봇은 .를 통해 UserProfileDialog사용자와 상호 작용합니다. 봇의 DialogBot 클래스를 만들 때는 UserProfileDialog 기본 대화 상자로 설정됩니다. 그런 다음, 봇은 Run 도우미 메서드를 사용하여 대화 상자에 액세스합니다.

C# 샘플에 대한 클래스 다이어그램.

Dialogs\UserProfileDialog.cs

먼저 클래스에서 파생되는 7단계를 ComponentDialog 만듭니 UserProfileDialog 다.

UserProfileDialog 생성자에서 폭포 단계, 프롬프트 및 폭포 대화 상자를 만들고 대화 집합에 추가합니다. 프롬프트는 사용되는 것과 동일한 대화 상자 집합에 있어야 합니다.

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,
        SummaryStepAsync,
        ConfirmStepAsync,
    };

    // 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);
}

다음으로, 대화 상자에서 입력을 요청하는 데 사용하는 단계를 추가합니다. 프롬프트를 사용하려면 대화의 단계에서 호출하고 다음 단계에서 stepContext.Result를 사용하여 프롬프트 결과를 검색합니다. 백그라운드에서 프롬프트는 2단계 대화 상자입니다. 먼저 프롬프트에서 입력을 요청합니다. 그런 다음 유효한 값을 반환하거나 처음부터 다시 시작하여 유효한 입력을 받을 때까지 다시 시작합니다.

항상 폭포 단계에서 null DialogTurnResult 이 아닌 값을 반환해야 합니다. 그렇지 않으면 대화 상자가 설계된 대로 작동하지 않을 수 있습니다. 아래는 폭포 대화 상자의 구현 NameStepAsync 입니다.

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프롬프트가 구문 분석할 수 없는 형식이거나 입력이 유효성 검사 조건에 실패하기 때문에 사용자 입력의 유효성을 검사하지 못하는 경우에 대한 재시도 프롬프트를 지정합니다. 이 경우 다시 시도 프롬프트가 제공되지 않은 경우 프롬프트는 초기 프롬프트 텍스트를 사용하여 사용자에게 입력을 다시 표시합니다.

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.cs

사용자의 교통 수단, 이름 및 나이가 UserProfile 클래스의 인스턴스에 저장됩니다.

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.cs

마지막 단계에서는 이전 폭포 단계에서 stepContext.Result 호출된 대화 상자에서 반환된 검사. 반환 값이 true이면 사용자 프로필 접근자가 사용자 프로필을 가져오고 업데이트합니다. 사용자 프로필을 얻으려면 호출 GetAsync 한 다음 , userProfile.NameuserProfile.AgeuserProfile.Picture 속성의 userProfile.Transport값을 설정합니다. 마지막으로, 대화 상자를 종료하는 호출 EndDialogAsync하기 전에 사용자의 정보를 요약합니다. 대화 상자를 종료하면 대화 상자 스택에서 팝업되고 선택적 결과가 대화의 부모에 반환됩니다. 부모는 대화 또는 방금 전에 종료된 대화를 시작한 메서드입니다.

    else
    {
        msg += $" Your profile will not be kept.";
    }

    await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), 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);
}

private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    stepContext.Values["picture"] = ((IList<Attachment>)stepContext.Result)?.FirstOrDefault();

    // 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);

대화 상자 실행

Bots\DialogBot.cs

OnMessageActivityAsync 처리기는 메서드를 RunAsync 사용하여 대화 상자를 시작하거나 계속합니다. OnTurnAsync 는 봇의 상태 관리 개체를 사용하여 스토리지에 대한 상태 변경 내용을 유지합니다. ActivityHandler.OnTurnAsync 메서드는 OnMessageActivityAsync와 같은 다양한 작업 처리기 메서드를 호출합니다. 이렇게 하면 메시지 처리기가 완료된 후 턴 자체가 완료되기 전에 상태가 저장됩니다.

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);
}

봇에 대한 서비스 등록

이 봇은 다음 서비스를 사용합니다.

  • 봇 기본 서비스: 자격 증명 공급자, 어댑터 및 봇 구현
  • 상태를 관리하기 위한 서비스: 스토리지, 사용자 상태 및 대화 상태.
  • 봇이 사용할 대화 상자입니다.

Startup.cs

에서 Startup봇에 대한 서비스를 등록합니다. 이러한 서비스는 종속성 주입을 통해 코드의 다른 부분에서 사용할 수 있습니다.

{
    // 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(options =>
        {
            options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
        });

        // Create the Bot Framework Authentication to be used with the Bot Adapter.
        services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();

        // Create the Bot 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>();

참고 항목

메모리 스토리지는 테스트 목적으로만 사용되며 프로덕션 용도로는 사용되지 않습니다. 프로덕션 봇에 영구 스토리지 유형을 사용해야 합니다.

봇 테스트

  1. 아직 설치하지 않은 경우 Bot Framework Emulator설치합니다.
  2. 머신에서 로컬로 샘플을 실행합니다.
  3. 에뮬레이터를 시작하고 봇에 연결한 다음, 아래와 같은 메시지를 보냅니다.

다중 턴 프롬프트 봇과의 대화에 대한 예시 기록입니다.

추가 정보

대화 상자 및 봇 상태 정보

이 봇에서는 두 가지 상태 속성 접근자가 정의됩니다.

  • 대화 상태 속성에 대한 대화 상태 내에서 만든 속성입니다. 대화 상태는 사용자가 대화 집합의 대화 상자 내에 있는 위치를 추적하며 시작 대화 상자 또는 계속 대화 메서드가 호출되는 경우와 같은 대화 컨텍스트에 의해 업데이트됩니다.
  • 사용자 프로필 속성에 대한 사용자 상태 내에서 만든 속성입니다. 봇은 이를 사용하여 사용자에 대한 정보를 추적하며 대화 상자 코드에서 이 상태를 명시적으로 관리해야 합니다.

상태 속성 접근자의 getset 메서드는 상태 관리 개체의 캐시에서 속성의 값을 가져오고 설정합니다. 캐시는 상태 속성의 값이 차례로 요청될 때 처음으로 채워지지만 명시적으로 유지되어야 합니다. 이러한 두 상태 속성의 변경 내용을 유지하기 위해 해당 상태 관리 개체의 변경 내용 저장 메서드 호출이 수행됩니다.

이 샘플은 대화 상자 내에서 사용자 프로필 상태를 업데이트합니다. 이 방법은 일부 봇에서 작동할 수 있지만 봇 간에 대화 상자를 다시 사용하려는 경우에는 작동하지 않습니다.

대화 상자 단계와 봇 상태를 분리하는 다양한 옵션이 있습니다. 예를 들어, 대화 상자가 완전한 정보를 수집하면 다음을 수행할 수 있습니다.

  • end dialog 메서드를 사용하여 수집된 데이터를 부모 컨텍스트에 다시 반환 값으로 제공합니다. 이는 봇의 턴 처리기 또는 대화 상자 스택의 이전 활성 대화 상자일 수 있으며 프롬프트 클래스가 디자인되는 방식입니다.
  • 적절한 서비스에 대한 요청을 생성합니다. 봇이 더 큰 서비스의 프런트 엔드 역할을 하는 경우 이 작업이 잘 작동할 수 있습니다.

프롬프트 유효성 검사기 메서드의 정의

UserProfileDialog.cs

다음은 메서드 정의에 대한 유효성 검사기 코드 예제입니다 AgePromptValidatorAsync . promptContext.Recognized.Value 에는 숫자 프롬프트에 대한 정수인 구문 분석된 값이 포함되어 있습니다. promptContext.Recognized.Succeeded 는 프롬프트가 사용자의 입력을 구문 분석할 수 있는지 여부를 나타냅니다. 유효성 검사기는 false를 반환하여 값이 수락되지 않았음을 나타내고 프롬프트 대화 상자는 사용자를 다시 표시해야 합니다. 그렇지 않으면 true를 반환하여 입력을 수락하고 프롬프트 대화 상자에서 반환합니다. 시나리오에 따라 유효성 검사기의 값을 변경할 수 있습니다.

    }

    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Is this ok?") }, cancellationToken);
}

다음 단계