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.
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:
Falls ja, werden die erfassten Informationen angezeigt. Falls nicht, wird dem Benutzer mitgeteilt, dass seine Informationen nicht gespeichert werden.
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.
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);
In Ihrem Projekt muss das npm-Paket botbuilder-dialogs installiert werden, damit Dialoge verwendet werden können.
Der Bot interagiert mit dem Benutzer über UserProfileDialog
. Bei der Erstellung von DialogBot
des Bots wird UserProfileDialog
als Hauptdialog festgelegt. Der Bot verwendet dann eine Hilfsmethode vom Typ run
, um auf den Dialog zuzugreifen.
dialogs/userProfileDialog.js
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.
constructor(userState) {
super('userProfileDialog');
this.userProfile = userState.createProperty(USER_PROFILE);
this.addDialog(new TextPrompt(NAME_PROMPT));
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
this.addDialog(new NumberPrompt(NUMBER_PROMPT, this.agePromptValidator));
this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT, this.picturePromptValidator));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.transportStep.bind(this),
this.nameStep.bind(this),
this.nameConfirmStep.bind(this),
this.ageStep.bind(this),
this.pictureStep.bind(this),
this.summaryStep.bind(this),
this.confirmStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
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 das Ergebnis der Eingabeaufforderung aus dem Schrittkontext ab (in diesem Fall mithilfe von step.result
). 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 nameStep
im Wasserfalldialog dargestellt.
async nameStep(step) {
step.values.transport = step.result.value;
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
Geben Sie in ageStep
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 im obigen Konstruktor angegebenes 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.
async ageStep(step) {
if (step.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.
const promptOptions = { prompt: 'Please enter your age.', retryPrompt: 'The value entered must be greater than 0 and less than 150.' };
return await step.prompt(NUMBER_PROMPT, promptOptions);
} else {
// User said "no" so we will skip the next step. Give -1 as the age.
return await step.next(-1);
}
}
userProfile.js
Transportmittel, Name und Alter des Benutzers werden in einer Instanz der Klasse UserProfile
gespeichert.
class UserProfile {
constructor(transport, name, age, picture) {
this.transport = transport;
this.name = name;
this.age = age;
this.picture = picture;
}
}
dialogs/userProfileDialog.js
Überprüfen Sie im letzten Schritt das Ergebnis des Schrittkontexts (step.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 get
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 endDialog
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.
await step.context.sendActivity(msg);
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
return await step.endDialog();
}
async summaryStep(step) {
step.values.picture = step.result && step.result[0];
// Get the current profile object from user state.
const userProfile = await this.userProfile.get(step.context, new UserProfile());
userProfile.transport = step.values.transport;
userProfile.name = step.values.name;
userProfile.age = step.values.age;
userProfile.picture = step.values.picture;
let 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 step.context.sendActivity(msg);
if (userProfile.picture) {
try {
await step.context.sendActivity(MessageFactory.attachment(userProfile.picture, 'This is your profile picture.'));
} catch {
await step.context.sendActivity('A profile picture was saved but could not be displayed here.');
}
Erstellen der Erweiterungsmethode zum Ausführen des Wasserfalldialogs
Eine in userProfileDialog
definierte run
-Hilfsmethode wird verwendet, um den Dialogkontext zu erstellen und darauf zuzugreifen. Hier ist accessor
der Zustandseigenschaftenaccessor für die Dialogzustandseigenschaft und this
der Benutzerprofil-Komponentendialog. Da Komponentendialoge einen inneren Dialogsatz definieren, muss ein für den Code des Meldungshandlers sichtbarer äußerer Dialogsatz erstellt werden und dieser zur Erstellung eines Dialogkontexts verwendet werden.
Der Dialogkontext wird durch Aufrufen der Methode createContext
erstellt und für die Interaktion mit dem Dialog innerhalb des Turn-Handlers des Bots verwendet. Der Dialogkontext enthält den aktuellen Turn-Kontext, den übergeordneten Dialog und den Dialogzustand. Dadurch steht eine Methode zum Speichern von Informationen innerhalb des Dialogs zur Verfügung.
Der Dialogkontext ermöglicht es Ihnen, einen Dialog mit der Zeichenfolgen-ID zu starten oder den aktuellen Dialog fortzusetzen (etwa bei einem Wasserfalldialog mit mehreren Schritten). Der Dialogkontext wird an alle Dialoge und Wasserfallschritte des Bots weitergegeben.
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
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.
UserProfileDialog.java
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 withUserState) {
super("UserProfileDialog");
userProfileAccessor = withUserState.createProperty("UserProfile");
WaterfallStep[] waterfallSteps = {
UserProfileDialog::transportStep,
UserProfileDialog::nameStep,
this::nameConfirmStep,
this::ageStep,
UserProfileDialog::pictureStep,
this::confirmStep,
this::summaryStep
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps)));
addDialog(new TextPrompt("TextPrompt"));
addDialog(new NumberPrompt<Integer>("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class));
addDialog(new ChoicePrompt("ChoicePrompt"));
addDialog(new ConfirmPrompt("ConfirmPrompt"));
addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator));
// The initial child Dialog to run.
setInitialDialogId("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.getResult()
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 nameStep
im Wasserfalldialog dargestellt.
private static CompletableFuture<DialogTurnResult> nameStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue());
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your name."));
return stepContext.prompt("TextPrompt", promptOptions);
}
Geben Sie in ageStep
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 CompletableFuture<DialogTurnResult> ageStep(WaterfallStepContext stepContext) {
if ((Boolean)stepContext.getResult()) {
// 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.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your age."));
promptOptions.setRetryPrompt(MessageFactory.text("The value entered must be greater than 0 and less than 150."));
return stepContext.prompt("NumberPrompt", promptOptions);
}
// User said "no" so we will skip the next step. Give -1 as the age.
return stepContext.next(-1);
}
UserProfile.java
Transportmittel, Name und Alter des Benutzers werden in einer Instanz der Klasse UserProfile
gespeichert.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.sample.multiturnprompt;
import com.microsoft.bot.schema.Attachment;
/**
* This is our application state.
*/
public class UserProfile {
public String transport;
public String name;
public Integer age;
public Attachment picture;
}
UserProfileDialog.java
Ü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 get
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 endDialog
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.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.sample.multiturnprompt;
import com.microsoft.bot.builder.MessageFactory;
import com.microsoft.bot.builder.StatePropertyAccessor;
import com.microsoft.bot.builder.UserState;
import com.microsoft.bot.connector.Channels;
import com.microsoft.bot.dialogs.ComponentDialog;
import com.microsoft.bot.dialogs.DialogTurnResult;
import com.microsoft.bot.dialogs.WaterfallDialog;
import com.microsoft.bot.dialogs.WaterfallStep;
import com.microsoft.bot.dialogs.WaterfallStepContext;
import com.microsoft.bot.dialogs.choices.ChoiceFactory;
import com.microsoft.bot.dialogs.choices.FoundChoice;
import com.microsoft.bot.dialogs.prompts.AttachmentPrompt;
import com.microsoft.bot.dialogs.prompts.ChoicePrompt;
import com.microsoft.bot.dialogs.prompts.ConfirmPrompt;
import com.microsoft.bot.dialogs.prompts.NumberPrompt;
import com.microsoft.bot.dialogs.prompts.PromptOptions;
import com.microsoft.bot.dialogs.prompts.PromptValidatorContext;
import com.microsoft.bot.dialogs.prompts.TextPrompt;
import com.microsoft.bot.schema.Attachment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
public class UserProfileDialog extends ComponentDialog {
private final StatePropertyAccessor<UserProfile> userProfileAccessor;
public UserProfileDialog(UserState withUserState) {
super("UserProfileDialog");
userProfileAccessor = withUserState.createProperty("UserProfile");
WaterfallStep[] waterfallSteps = {
UserProfileDialog::transportStep,
UserProfileDialog::nameStep,
this::nameConfirmStep,
this::ageStep,
UserProfileDialog::pictureStep,
this::confirmStep,
this::summaryStep
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps)));
addDialog(new TextPrompt("TextPrompt"));
addDialog(new NumberPrompt<Integer>("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class));
addDialog(new ChoicePrompt("ChoicePrompt"));
addDialog(new ConfirmPrompt("ConfirmPrompt"));
addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator));
// The initial child Dialog to run.
setInitialDialogId("WaterfallDialog");
}
private static CompletableFuture<DialogTurnResult> transportStep(WaterfallStepContext stepContext) {
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
// Running a prompt here means the next WaterfallStep will be run when the user's response is received.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your mode of transport."));
promptOptions.setChoices(ChoiceFactory.toChoices("Car", "Bus", "Bicycle"));
return stepContext.prompt("ChoicePrompt", promptOptions);
}
private static CompletableFuture<DialogTurnResult> nameStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue());
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your name."));
return stepContext.prompt("TextPrompt", promptOptions);
}
private CompletableFuture<DialogTurnResult> nameConfirmStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("name", stepContext.getResult());
// We can send messages to the user at any point in the WaterfallStep.
return stepContext.getContext().sendActivity(MessageFactory.text(String.format("Thanks %s.", stepContext.getResult())))
.thenCompose(resourceResponse -> {
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Would you like to give your age?"));
return stepContext.prompt("ConfirmPrompt", promptOptions);
});
}
private CompletableFuture<DialogTurnResult> ageStep(WaterfallStepContext stepContext) {
if ((Boolean)stepContext.getResult()) {
// 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.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your age."));
promptOptions.setRetryPrompt(MessageFactory.text("The value entered must be greater than 0 and less than 150."));
return stepContext.prompt("NumberPrompt", promptOptions);
}
// User said "no" so we will skip the next step. Give -1 as the age.
return stepContext.next(-1);
}
private static CompletableFuture<DialogTurnResult> pictureStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("age", (Integer) stepContext.getResult());
String msg = (Integer)stepContext.getValues().get("age") == -1
? "No age given."
: String.format("I have your age as %d.", (Integer)stepContext.getValues().get("age"));
// We can send messages to the user at any point in the WaterfallStep.
return stepContext.getContext().sendActivity(MessageFactory.text(msg))
.thenCompose(resourceResponse -> {
if (StringUtils.equals(stepContext.getContext().getActivity().getChannelId(), Channels.MSTEAMS)) {
// This attachment prompt example is not designed to work for Teams attachments, so skip it in this case
return stepContext.getContext().sendActivity(MessageFactory.text("Skipping attachment prompt in Teams channel..."))
.thenCompose(resourceResponse1 -> stepContext.next(null));
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please attach a profile picture (or type any message to skip)."));
promptOptions.setRetryPrompt(MessageFactory.text("The attachment must be a jpeg/png image file."));
return stepContext.prompt("AttachmentPrompt", promptOptions);
});
}
private CompletableFuture<DialogTurnResult> confirmStep(WaterfallStepContext stepContext) {
List<Attachment> attachments = (List<Attachment>)stepContext.getResult();
stepContext.getValues().put("picture", attachments == null ? null : attachments.get(0));
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Is this ok?"));
return stepContext.prompt("ConfirmPrompt", promptOptions);
}
private CompletableFuture<DialogTurnResult> summaryStep(WaterfallStepContext stepContext) {
if ((Boolean)stepContext.getResult()) {
// Get the current profile object from user state.
return userProfileAccessor.get(stepContext.getContext(), () -> new UserProfile())
.thenCompose(userProfile -> {
userProfile.transport = (String) stepContext.getValues().get("transport");
userProfile.name = (String) stepContext.getValues().get("name");
userProfile.age = (Integer) stepContext.getValues().get("age");
userProfile.picture = (Attachment) stepContext.getValues().get("picture");
String msg = String.format(
"I have your mode of transport as %s and your name as %s",
userProfile.transport, userProfile.name
);
if (userProfile.age != -1) {
msg += String.format(" and your age as %s", userProfile.age);
}
msg += ".";
return stepContext.getContext().sendActivity(MessageFactory.text(msg))
.thenApply(resourceResponse -> userProfile);
})
.thenCompose(userProfile -> {
if (userProfile.picture != null) {
try {
return stepContext.getContext().sendActivity(
MessageFactory.attachment(userProfile.picture,
"This is your profile picture."
));
} catch(Exception ex) {
return stepContext.getContext().sendActivity(
MessageFactory.text(
"A profile picture was saved but could not be displayed here."
));
}
}
return stepContext.getContext().sendActivity(
MessageFactory.text("A profile picture wasn't attached.")
);
})
.thenCompose(resourceResponse -> stepContext.endDialog());
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
return stepContext.getContext().sendActivity(MessageFactory.text("Thanks. Your profile will not be kept."))
.thenCompose(resourceResponse -> stepContext.endDialog());
}
private static CompletableFuture<Boolean> agePromptValidator(
PromptValidatorContext<Integer> promptContext
) {
// This condition is our validation rule. You can also change the value at this point.
return CompletableFuture.completedFuture(
promptContext.getRecognized().getSucceeded()
&& promptContext.getRecognized().getValue() > 0
&& promptContext.getRecognized().getValue() < 150);
}
private static CompletableFuture<Boolean> picturePromptValidator(
PromptValidatorContext<List<Attachment>> promptContext
) {
if (promptContext.getRecognized().getSucceeded()) {
List<Attachment> attachments = promptContext.getRecognized().getValue();
List<Attachment> validImages = new ArrayList<>();
for (Attachment attachment : attachments) {
if (StringUtils.equals(
attachment.getContentType(), "image/jpeg") || StringUtils.equals(attachment.getContentType(), "image/png")
) {
validImages.add(attachment);
}
}
promptContext.getRecognized().setValue(validImages);
// If none of the attachments are valid images, the retry prompt should be sent.
return CompletableFuture.completedFuture(!validImages.isEmpty());
}
else {
// We can return true from a validator function even if Recognized.Succeeded is false.
return promptContext.getContext().sendActivity("No attachments received. Proceeding without a profile picture...")
.thenApply(resourceResponse -> true);
}
}
}
Um Dialoge zu verwenden, installieren Sie die PyPI-Pakete botbuilder-dialogs und botbuilder-ai, indem Sie über ein Terminal pip install botbuilder-dialogs
und pip install botbuilder-ai
ausführen.
Der Bot interagiert mit dem Benutzer über UserProfileDialog
. Wenn die DialogBot
-Klasse des Bots erstellt wird, wird der UserProfileDialog
als Hauptdialog festgelegt. Der Bot verwendet dann eine Hilfsmethode vom Typ run_dialog
, um auf den Dialog zuzugreifen.
dialogs\user_profile_dialog.py
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.
def __init__(self, user_state: UserState):
super(UserProfileDialog, self).__init__(UserProfileDialog.__name__)
self.user_profile_accessor = user_state.create_property("UserProfile")
self.add_dialog(
WaterfallDialog(
WaterfallDialog.__name__,
[
self.transport_step,
self.name_step,
self.name_confirm_step,
self.age_step,
self.picture_step,
self.summary_step,
self.confirm_step,
],
)
)
self.add_dialog(TextPrompt(TextPrompt.__name__))
self.add_dialog(
NumberPrompt(NumberPrompt.__name__, UserProfileDialog.age_prompt_validator)
)
self.add_dialog(ChoicePrompt(ChoicePrompt.__name__))
self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__))
self.add_dialog(
AttachmentPrompt(
AttachmentPrompt.__name__, UserProfileDialog.picture_prompt_validator
)
)
self.initial_dialog_id = WaterfallDialog.__name__
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 step_context.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. Hier sehen Sie die Implementierung für name_step
im Wasserfalldialog.
async def name_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
step_context.values["transport"] = step_context.result.value
return await step_context.prompt(
TextPrompt.__name__,
PromptOptions(prompt=MessageFactory.text("Please enter your name.")),
)
Geben Sie in age_step
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 im obigen Konstruktor angegebenes 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
async def age_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
if step_context.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.
return await step_context.prompt(
NumberPrompt.__name__,
PromptOptions(
prompt=MessageFactory.text("Please enter your age."),
retry_prompt=MessageFactory.text(
"The value entered must be greater than 0 and less than 150."
),
),
)
# User said "no" so we will skip the next step. Give -1 as the age.
return await step_context.next(-1)
data_models\user_profile.py
Transportmittel, Name und Alter des Benutzers werden in einer Instanz der Klasse UserProfile
gespeichert.
class UserProfile:
"""
This is our application state. Just a regular serializable Python class.
"""
def __init__(self, name: str = None, transport: str = None, age: int = 0, picture: Attachment = None):
self.name = name
self.transport = transport
self.age = age
self.picture = picture
dialogs\user_profile_dialog.py
Überprüfen Sie im letzten Schritt das Ergebnis des Schrittkontexts (step_context.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 get
auf und legen Sie die Werte der Eigenschaften user_profile.transport
, user_profile.name
und user_profile.age
fest. Abschließend fassen Sie die Informationen für den Benutzer zusammen und rufen schließlich end_dialog
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.
async def summary_step(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
step_context.values["picture"] = (
None if not step_context.result else step_context.result[0]
)
# Get the current profile object from user state. Changes to it
# will saved during Bot.on_turn.
user_profile = await self.user_profile_accessor.get(
step_context.context, UserProfile
)
user_profile.transport = step_context.values["transport"]
user_profile.name = step_context.values["name"]
user_profile.age = step_context.values["age"]
user_profile.picture = step_context.values["picture"]
msg = f"I have your mode of transport as {user_profile.transport} and your name as {user_profile.name}."
if user_profile.age != -1:
msg += f" And age as {user_profile.age}."
await step_context.context.send_activity(MessageFactory.text(msg))
if user_profile.picture:
await step_context.context.send_activity(
MessageFactory.attachment(
user_profile.picture, "This is your profile picture."
)
)
else:
await step_context.context.send_activity(
"A profile picture was saved but could not be displayed here."
)
# WaterfallStep always finishes with the end of the Waterfall or with another
# dialog, here it is the end.
return await step_context.prompt(
ConfirmPrompt.__name__,
Erstellen der Erweiterungsmethode zum Ausführen des Wasserfalldialogs
Eine run_dialog()
-Hilfsmethode wird in helpers\dialog_helper.py definiert, die für die Erstellung und den Zugriff auf den Dialogkontext verwendet wird. Hier ist accessor
der Zustandseigenschaftenaccessor für die Dialogzustandseigenschaft und dialog
der Benutzerprofil-Komponentendialog. Da Komponentendialoge einen inneren Dialogsatz definieren, muss ein für den Code des Meldungshandlers sichtbarer äußerer Dialogsatz erstellt werden und dieser zur Erstellung eines Dialogkontexts verwendet werden.
Erstellen Sie den Dialogkontext, indem Sie create_context
aufrufen, das verwendet wird, um mit dem Dialogsatz aus dem Turn-Handler des Bots zu interagieren. Der Dialogkontext enthält den aktuellen Turn-Kontext, den übergeordneten Dialog und den Dialogzustand. Dadurch steht eine Methode zum Speichern von Informationen innerhalb des Dialogs zur Verfügung.
Der Dialogkontext ermöglicht es Ihnen, einen Dialog mit der Zeichenfolgen-ID zu starten oder den aktuellen Dialog fortzusetzen (etwa bei einem Wasserfalldialog mit mehreren Schritten). Der Dialogkontext wird an alle Dialoge und Wasserfallschritte des Bots weitergegeben.
class DialogHelper:
@staticmethod
async def run_dialog(
dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor
):
dialog_set = DialogSet(accessor)
dialog_set.add(dialog)
dialog_context = await dialog_set.create_context(turn_context)
results = await dialog_context.continue_dialog()
if results.status == DialogTurnStatus.Empty:
await dialog_context.begin_dialog(dialog.id)
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: