Implementieren eines sequenziellen Konversationsablaufs

GILT FÜR: SDK v4

Das Sammeln von Informationen durch das Stellen von Fragen ist eine der Hauptvorgehensweisen, mit denen ein Bot mit Benutzern interagiert. Die Dialogbibliothek bietet nützliche integrierte Features wie prompt-Klassen zum einfachen Stellen von Fragen und Überprüfen der Antworten, um sicherzustellen, dass sie einen bestimmten Datentyp aufweisen oder benutzerdefinierte Validierungsregeln erfüllen.

Mit der Dialogbibliothek können Sie lineare und komplexere Konversationsflüsse verwalten. In einer linearen Interaktion wird der Bot über eine feste Sequenz von Schritten ausgeführt, und die Konversation endet. Ein Dialog ist hilfreich, wenn der Bot Informationen vom Benutzer benötigt.

Dieser Artikel zeigt, wie Sie Eingabeaufforderungen erstellen und über einen Wasserfalldialog aufrufen, um einen linearen Konversationsfluss zu implementieren. Beispiele dafür, wie Sie Ihre eigenen Eingabeaufforderungen schreiben können, ohne die Dialogbibliothek zu verwenden, finden Sie im Artikel Erstellen eigener Eingabeaufforderungen zum Erfassen von Benutzereingaben.

Hinweis

Die Bot Framework-JavaScript-, C#- und Python-SDKs werden weiterhin unterstützt, das Java SDK wird jedoch eingestellt und der langfristige Support endet im November 2023. Es werden nur kritische Sicherheits- und Programmfehlerbehebungen innerhalb dieses Repositorys durchgeführt.

Bestehende Bots, die mit dem Java SDK erstellt wurden, werden weiterhin funktionieren.

Wenn Sie einen neuen Bot erstellen möchten, sollten Sie den Einsatz von Power Virtual Agents in Betracht ziehen und sich über die Auswahl der richtigen Chatbot-Lösung informieren.

Weitere Informationen finden Sie unter Die Zukunft des Bot-Buildings.

Voraussetzungen

Informationen zu diesem Beispiel

Das Beispiel für Eingabeaufforderungen mit mehreren Turns verwendet einen Wasserfalldialog, einige Eingabeaufforderungen und einen Komponentendialog, um eine lineare Interaktion zu erstellen, bei der dem Benutzer eine Reihe von Fragen gestellt wird. Der Code durchläuft die folgenden Schritte in Form eines Dialogs:

Schritte Art der Eingabeaufforderung
Transportmittel des Benutzers erfragen Auswahleingabeaufforderung
Name des Benutzers erfragen Texteingabeaufforderung
Benutzer fragen, ob er sein Alter angeben möchte Bestätigungseingabeaufforderung
Falls ja, Alter des Benutzers erfragen Zahleneingabeaufforderung mit Überprüfung, damit nur Altersangaben zwischen Null und 150 (jeweils ausschließlich) akzeptiert werden
Wenn Microsoft Teams nicht verwendet wird, nach Profilbild fragen Eingabeaufforderung mit Überprüfung, um eine fehlende Anlage zuzulassen
Fragen, ob die erfassten Informationen korrekt sind Erneute Bestätigungseingabeaufforderung

Falls ja, werden die erfassten Informationen angezeigt. Falls nicht, wird dem Benutzer mitgeteilt, dass seine Informationen nicht gespeichert werden.

Erstellen des Hauptdialogs

Installieren Sie das NuGet-Paket Microsoft.Bot.Builder.Dialogs, um Dialoge verwenden zu können.

Der Bot interagiert mit dem Benutzer über UserProfileDialog. Bei der Erstellung der Klasse DialogBot des Bots wird UserProfileDialog als Hauptdialog festgelegt. Der Bot verwendet dann eine Hilfsmethode vom Typ Run, um auf den Dialog zuzugreifen.

Class diagram for the C# sample.

Dialogs\UserProfileDialog.cs

Erstellen Sie zunächst den Benutzerprofildialog (UserProfileDialog). Er wird von der Klasse ComponentDialog abgeleitet und umfasst sieben Schritte.

Erstellen Sie im Konstruktor UserProfileDialog die Wasserfallschritte, die Eingabeaufforderungen und den Wasserfalldialog, und fügen Sie sie dem Dialogsatz hinzu. Die Eingabeaufforderungen müssen sich in dem Dialogsatz befinden, in dem sie verwendet werden.

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

Fügen Sie als Nächstes die Schritte hinzu, mit denen das Dialogfeld zur Eingabeaufforderung aufgefordert wird. Rufen Sie in einem Schritt Ihres Dialogs eine Eingabeaufforderung auf, um sie zu verwenden, und rufen Sie im nächsten Schritt mithilfe von stepContext.Result das Ergebnis der Eingabeaufforderung ab. Im Hintergrund sind Eingabeaufforderungen ein aus zwei Schritten bestehender Dialog. Zuerst fordert die Eingabeaufforderung die Eingabe an. Dann wird entweder der gültige Wert zurückgegeben, oder der Vorgang wird mit einer erneuten Eingabeaufforderung wiederholt, bis eine gültige Eingabe erfolgt.

Von einem Wasserfallschritt sollte immer ein Dialog-Turn-Ergebnis (DialogTurnResult) zurückgegeben werden, das nicht NULL ist. Andernfalls funktioniert Ihr Dialog möglicherweise nicht wie vorgesehen. Nachfolgend ist die Implementierung für NameStepAsync im Wasserfalldialog dargestellt.

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

Geben Sie in AgeStepAsync eine erneute Eingabeaufforderung für den Fall an, dass bei der Überprüfung der Benutzereingabe ein Fehler auftritt – entweder, weil sie in einem Format vorliegt, das die Eingabeaufforderung nicht analysieren kann, oder weil die Eingabe ein Validierungskriterium nicht erfüllt. Ohne Angabe einer erneuten Eingabeaufforderung verwendet die Eingabeaufforderung den Text der ersten Eingabeaufforderung, um den Benutzer erneut zu einer Eingabe aufzufordern.

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

Transportmittel, Name und Alter des Benutzers werden in einer Instanz der Klasse UserProfile gespeichert.

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

Überprüfen Sie im letzten Schritt das Ergebnis des Schrittkontexts (stepContext.Result), das vom im vorherigen Wasserfallschritt aufgerufenen Dialog zurückgegeben wurde. Lautet der Rückgabewert „true“, ruft der Benutzerprofilaccessor das Benutzerprofil ab und aktualisiert es. Zum Abrufen des Benutzerprofils rufen Sie GetAsync auf und legen Sie die Werte der Eigenschaften userProfile.Transport, userProfile.Name, userProfile.Age und userProfile.Picture fest. Abschließend fassen Sie die Informationen für den Benutzer zusammen und rufen schließlich EndDialogAsync auf, um den Dialog zu beenden. Der beendete Dialog wird aus dem Dialogstapel entfernt und gibt ein optionales Ergebnis an das übergeordnete Element des Dialogs zurück. Das übergeordnete Element ist der Dialog oder die Methode, der bzw. die den soeben beendeten Dialog gestartet hat.

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

Ausführen des Dialogs

Bots\DialogBot.cs

Der Handler OnMessageActivityAsync verwendet die RunAsync-Methode, um den Dialog zu starten oder fortzusetzen. OnTurnAsync verwendet die Zustandsverwaltungsobjekte des Bots, um Zustandsänderungen zu speichern. Die ActivityHandler.OnTurnAsync-Methode ruft die verschiedenen Aktivitätshandlermethoden auf – beispielsweise OnMessageActivityAsync. Dadurch wird der Zustand nach Abschluss des Nachrichtenhandlers, aber noch vor Abschluss des Turns gespeichert.

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

Registrieren von Diensten für den Bot

Dieser Bot verwendet die folgenden Dienste:

  • Grundlegende Dienste für einen Bot: Anmeldeinformationsanbieter, Adapter und Botimplementierung
  • Dienste für die Zustandsverwaltung: Speicher, Benutzerzustand und Konversationszustand
  • Der vom Bot verwendete Dialog

Startup.cs

Registrieren Sie Dienste für den Bot in Startup. Diese Dienste sind für andere Teile des Codes per Abhängigkeitsinjektion verfügbar.

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

Hinweis

Arbeitsspeicher wird nur für Testzwecke genutzt und ist nicht für die Verwendung in der Produktion vorgesehen. Verwenden Sie für einen Produktionsbot unbedingt einen permanenten Speicher.

Bot testen

  1. Installieren Sie Bot Framework Emulator, sofern noch nicht geschehen.
  2. Führen Sie das Beispiel lokal auf Ihrem Computer aus.
  3. Starten Sie den Emulator, stellen Sie eine Verbindung mit Ihrem Bot her, und senden Sie Nachrichten wie unten dargestellt.

An example transcript of a conversation with the multi-turn prompt bot.

Weitere Informationen

Informationen zu Dialogen und zum Botzustand

In diesem Bot sind zwei Zustandseigenschaftenaccessoren definiert:

  • Ein Accessor wurde im Konversationszustand für die Dialogzustandseigenschaft erstellt. Der Dialogzustand verfolgt nach, an welcher Stelle sich der Benutzer innerhalb der Dialoge eines Dialogsatzes befindet, und wird vom Dialogkontext aktualisiert (z. B. wenn die beginDialog- oder continueDialog-Methoden aufgerufen werden).
  • Ein Accessor wurde im Benutzerzustand für die Benutzerprofileigenschaft erstellt. Damit verfolgt der Bot die Informationen zum Benutzer nach. Diesen Zustand müssen Sie explizit im Dialogcode verwalten.

Die get- und set-Methoden eines Zustandseigenschaftenaccessors rufen den Wert der Eigenschaft ab und legen ihn im Cache des Zustandsverwaltungsobjekts fest. Der Cache wird aufgefüllt, wenn der Wert einer Zustandseigenschaft erstmals in einem Turn angefordert wird, er muss jedoch explizit gespeichert werden. Um Änderungen an diesen beiden Zustandseigenschaften beizubehalten, wird ein Anruf der saveChanges-Methode des entsprechenden Zustandsverwaltungsobjekts durchgeführt.

In diesem Beispiel wird der Zustand des Benutzerprofils innerhalb des Dialogs aktualisiert. Diese Vorgehensweise kann bei einigen Bots funktionieren, aber nicht, wenn Sie einen Dialog in mehreren Bots wiederverwenden möchten.

Zum Trennen der Dialogschritte und des Botzustands stehen verschiedene Optionen zur Verfügung. Nachdem der Dialog vollständige Informationen erfasst hat, können Sie beispielsweise wie folgt vorgehen:

  • Stellen Sie die erfassten Daten mithilfe der endDialog-Methode als Rückgabewert für den übergeordneten Kontext bereit. Hierbei kann es sich um den Turn-Handler des Bots oder einen früheren aktiven Dialog im Dialogstapel handeln und so sind auch die Prompt-Klassen aufgebaut.
  • Generieren Sie eine Anforderung an einen geeigneten Dienst. Dies kann gut funktionieren, wenn Ihr Bot als Front-End für einen umfangreicheren Dienst fungiert.

Definition einer Eingabeaufforderungen-Validierungssteuerelement-Methode

UserProfileDialog.cs

Nachstehend finden Sie ein Beispiel für einen Validierungscode für die AgePromptValidatorAsync-Methodendefinition. promptContext.Recognized.Value enthält den analysierten Wert, bei dem es sich hier um eine ganze Zahl für die Zahleneingabeaufforderung handelt. promptContext.Recognized.Succeeded gibt an, ob die Eingabeaufforderung die Eingabe des Benutzers analysieren konnte. Das Validierungssteuerelement sollte „false“ zurückgeben, um anzugeben, dass der Wert nicht akzeptiert wurde, und der Eingabeaufforderungsdialog sollte den Benutzer erneut auffordern. Andernfalls wird „true“ zurückgegeben, um die Eingabe zu akzeptieren und zum Eingabeaufforderungsdialog zurückzukehren. Sie können den Wert im Validierungssteuerelement gemäß Ihrem Szenario ändern.

    }

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

Nächste Schritte