Pour interagir avec les utilisateurs, un bot collecte généralement des informations en posant des questions. La bibliothèque de dialogues fournit des fonctionnalités intégrées telles que les classes d’invite qui vous permettent de poser des questions facilement et de valider la réponse pour vous assurer qu’elle correspond à un type de données spécifique ou répond aux règles de validation personnalisées.
Vous pouvez gérer des flux de conversation linéaires et plus complexes avec la bibliothèque de dialogues. Dans une interaction linéaire, le bot s'exécute via une séquence fixe d'étapes, et la conversation se termine. Un dialogue est utile quand le bot doit recueillir des informations auprès de l'utilisateur.
Cet article explique comment implémenter un flux de conversation linéaire en créant des invites et en les appelant à partir d'un dialogue en cascade.
Pour obtenir des exemples montrant comment écrire vos propres invites sans utiliser la bibliothèque de dialogues, consultez l’article Créer vos propres invites pour collecter des entrées utilisateur.
L'exemple d'invites multitours utilise une boîte de dialogue en cascade, quelques invites et une boîte de dialogue de composant pour créer une interaction linéaire qui pose une série de questions à l'utilisateur. Le code utilise un dialogue pour suivre ces étapes :
Enfin, si l'utilisateur a répondu oui, affichez les informations collectées ; dans le cas contraire, indiquez-lui que ses informations ne seront pas conservées.
Pour utiliser les dialogues, installez le package NuGet Microsoft.Bot.Builder.Dialogs.
Le bot interagit avec l’utilisateur par le biais du UserProfileDialog
. Lors de la création de la classe DialogBot
du bot, le UserProfileDialog
est défini comme son dialogue principal. Le bot utilise ensuite une méthode d’assistance Run
pour accéder au dialogue.
Dialogs\UserProfileDialog.cs
Créez d'abord le UserProfileDialog
qui dérive de la classe ComponentDialog
et comporte sept étapes.
Dans le constructeur UserProfileDialog
, créez les étapes en cascade, les invites et le dialogue en cascade, puis ajoutez-les au jeu de dialogues. Les invites doivent se trouver dans le jeu de dialogues où elles sont utilisées.
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);
}
Ensuite, ajoutez les étapes que le dialogue utilise pour demander l'entrée. Pour utiliser une invite, appelez-la à partir d’une étape dans votre dialogue et récupérez le résultat de l’invite à l’étape suivante avec stepContext.Result
. Dans les coulisses, les invites constituent une boîte de dialogue en deux étapes. Tout d'abord, l'invite demande une entrée. Ensuite, elle retourne la valeur valide ou redémarre depuis le début avec une nouvelle invite jusqu'à ce qu'elle reçoive une entrée valide.
Vous devez toujours retourner une valeur DialogTurnResult
non Null à partir d’une étape en cascade. À défaut, votre dialogue risque de ne pas fonctionner comme prévu. L'exemple ci-dessous illustre l'implémentation pour NameStepAsync
dans le dialogue en cascade.
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);
}
Dans AgeStepAsync
, spécifiez une nouvelle invite au cas où la validation de l'entrée de l'utilisateur échouerait, soit parce que son format ne peut pas être analysé par l'invite, soit parce que l'entrée ne remplit pas un critère de validation. Dans ce cas, si aucune nouvelle invite n'a été fournie, l'invite utilise le texte d'invite initial pour redemander à l'utilisateur d'effectuer une entrée
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
Le mode de transport, le nom et l’âge de l’utilisateur sont enregistrés dans une instance de la classe 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
Au cours de la dernière étape, vérifiez le stepContext.Result
retourné par le dialogue appelé à l'étape précédente en cascade. Si la valeur renvoyée est true, l'accesseur au profil utilisateur obtient et met à jour le profil de l'utilisateur. Pour obtenir le profil utilisateur, appelez GetAsync
, puis définissez les valeurs des propriétés userProfile.Transport
, userProfile.Name
, userProfile.Age
et userProfile.Picture
. Enfin, récapitulez les informations à l'attention de l'utilisateur avant d'appeler EndDialogAsync
, qui met fin au dialogue. La fin du dialogue se traduit par son retrait de la pile des dialogues et le retour d’un résultat facultatif à son parent. Le parent est le dialogue ou la méthode ayant démarré le dialogue qui vient de prendre fin.
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);
Pour utiliser des dialogues, votre projet doit installer le package npm botbuilder-dialogs.
Le bot interagit avec l’utilisateur par le biais d’un UserProfileDialog
. Lors de la création du DialogBot
du bot, le UserProfileDialog
est défini comme son dialogue principal. Le bot utilise ensuite une méthode d’assistance run
pour accéder au dialogue.
dialogs/userProfileDialog.js
Créez d'abord le UserProfileDialog
qui dérive de la classe ComponentDialog
et comporte sept étapes.
Dans le constructeur UserProfileDialog
, créez les étapes en cascade, les invites et le dialogue en cascade, puis ajoutez-les au jeu de dialogues. Les invites doivent se trouver dans le jeu de dialogues où elles sont utilisées.
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;
}
Ensuite, ajoutez les étapes que le dialogue utilise pour demander l'entrée. Pour utiliser une invite, appelez-la à partir d’une étape dans votre dialogue et récupérez le résultat de l’invite à l’étape suivante à partir du contexte de l’étape, en l’occurrence avec step.result
. Dans les coulisses, les invites constituent une boîte de dialogue en deux étapes. Tout d'abord, l'invite demande une entrée. Ensuite, elle retourne la valeur valide ou redémarre depuis le début avec une nouvelle invite jusqu'à ce qu'elle reçoive une entrée valide.
Vous devez toujours retourner une valeur DialogTurnResult
non Null à partir d’une étape en cascade. À défaut, votre dialogue risque de ne pas fonctionner comme prévu. Vous pouvez consultez ici l'implémentation pour le nameStep
dans le dialogue en cascade.
async nameStep(step) {
step.values.transport = step.result.value;
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
Dans ageStep
, spécifiez une nouvelle invite au cas où la validation de l'entrée de l'utilisateur échouerait, soit parce que son format ne peut pas être analysé par l'invite ou parce que l'entrée ne remplit pas un critère de validation, spécifié dans le constructeur ci-dessus. Dans ce cas, si aucune nouvelle invite n'a été fournie, l'invite utilise le texte d'invite initial pour redemander à l'utilisateur d'effectuer une entrée
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
Le mode de transport, le nom et l’âge de l’utilisateur sont enregistrés dans une instance de la classe UserProfile
.
class UserProfile {
constructor(transport, name, age, picture) {
this.transport = transport;
this.name = name;
this.age = age;
this.picture = picture;
}
}
dialogs/userProfileDialog.js
Au cours de la dernière étape, vérifiez le step.result
retourné par le dialogue appelé à l'étape précédente en cascade. Si la valeur renvoyée est true, l'accesseur au profil de l'utilisateur obtient et met à jour le profil de l'utilisateur. Pour obtenir le profil utilisateur, appelez get
, puis définissez les valeurs des propriétés userProfile.transport
, userProfile.name
, userProfile.age
et userProfile.picture
. Enfin, récapitulez les informations à l'attention de l'utilisateur avant d'appeler endDialog
, qui met fin au dialogue. La fin du dialogue se traduit par son retrait de la pile des dialogues et le retour d’un résultat facultatif à son parent. Le parent est le dialogue ou la méthode ayant démarré le dialogue qui vient de prendre fin.
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.');
}
Créer la méthode d’extension pour exécuter le dialogue en cascade
Une méthode d'assistance run
, définie dans userProfileDialog
, est utilisée pour créer et accéder au contexte de dialogue. Ici, accessor
est l’accesseur de la propriété d’état du dialogue, et this
est le dialogue composant du profil utilisateur. Étant donné que les dialogues de composants définissent un ensemble de dialogues interne, un ensemble de dialogues externe doit être créé, visible par le code du gestionnaire de message et utilisé pour créer un contexte de dialogue.
Vous créez le contexte du dialogue en appelant la méthode createContext
et l’utilisez pour interagir avec le jeu de dialogues à partir du gestionnaire de tours du bot. Le contexte du dialogue inclut le contexte du tour actuel, le dialogue parent et l’état du dialogue, ce qui offre une méthode permettant de conserver les informations au sein du dialogue.
Le contexte du dialogue vous permet de démarrer un dialogue avec l'ID de chaîne ou de continuer le dialogue actuel (par exemple, un dialogue en cascade qui comporte plusieurs étapes). Le contexte du dialogue est transmis à la totalité des dialogues et des étapes en cascade du bot.
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);
}
}
Le bot interagit avec l’utilisateur par le biais du UserProfileDialog
. Lors de la création de la classe DialogBot
du bot, le UserProfileDialog
est défini comme son dialogue principal. Le bot utilise ensuite une méthode d’assistance Run
pour accéder au dialogue.
UserProfileDialog.java
Créez d'abord le UserProfileDialog
qui dérive de la classe ComponentDialog
et comporte sept étapes.
Dans le constructeur UserProfileDialog
, créez les étapes en cascade, les invites et le dialogue en cascade, puis ajoutez-les au jeu de dialogues. Les invites doivent se trouver dans le jeu de dialogues où elles sont utilisées.
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");
}
Ensuite, ajoutez les étapes que le dialogue utilise pour demander l'entrée. Pour utiliser une invite, appelez-la à partir d’une étape dans votre dialogue et récupérez le résultat de l’invite à l’étape suivante avec stepContext.getResult()
. Dans les coulisses, les invites constituent une boîte de dialogue en deux étapes. Tout d'abord, l'invite demande une entrée. Ensuite, elle retourne la valeur valide ou redémarre depuis le début avec une nouvelle invite jusqu'à ce qu'elle reçoive une entrée valide.
Vous devez toujours retourner une valeur DialogTurnResult
non Null à partir d’une étape en cascade. À défaut, votre dialogue risque de ne pas fonctionner comme prévu. L'exemple ci-dessous illustre l'implémentation pour nameStep
dans le dialogue en cascade.
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);
}
Dans ageStep
, spécifiez une nouvelle invite au cas où la validation de l'entrée de l'utilisateur échouerait, soit parce que son format ne peut pas être analysé par l'invite, soit parce que l'entrée ne remplit pas un critère de validation. Dans ce cas, si aucune nouvelle invite n'a été fournie, l'invite utilise le texte d'invite initial pour redemander à l'utilisateur d'effectuer une entrée
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
Le mode de transport, le nom et l’âge de l’utilisateur sont enregistrés dans une instance de la classe UserProfile
.
// 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
Au cours de la dernière étape, vérifiez le stepContext.Result
retourné par le dialogue appelé à l'étape précédente en cascade. Si la valeur renvoyée est true, l'accesseur au profil utilisateur obtient et met à jour le profil de l'utilisateur. Pour obtenir le profil utilisateur, appelez get
, puis définissez les valeurs des propriétés userProfile.Transport
, userProfile.Name
, userProfile.Age
et userProfile.Picture
. Enfin, récapitulez les informations à l'attention de l'utilisateur avant d'appeler endDialog
, qui met fin au dialogue. La fin du dialogue se traduit par son retrait de la pile des dialogues et le retour d’un résultat facultatif à son parent. Le parent est le dialogue ou la méthode ayant démarré le dialogue qui vient de prendre fin.
// 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);
}
}
}
Pour utiliser les dialogues, installez les packages PyPI botbuilder-dialogs et botbuilder-ai en exécutant pip install botbuilder-dialogs
et pip install botbuilder-ai
à partir d’un terminal.
Le bot interagit avec l’utilisateur par le biais du UserProfileDialog
. Lorsque la classe DialogBot
du bot est créée, le UserProfileDialog
est défini comme son dialogue principal. Le bot utilise ensuite une méthode d’assistance run_dialog
pour accéder au dialogue.
dialogs\user_profile_dialog.py
Créez d'abord le UserProfileDialog
qui dérive de la classe ComponentDialog
et comporte sept étapes.
Dans le constructeur UserProfileDialog
, créez les étapes en cascade, les invites et le dialogue en cascade, puis ajoutez-les au jeu de dialogues. Les invites doivent se trouver dans le jeu de dialogues où elles sont utilisées.
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__
Ensuite, ajoutez les étapes que le dialogue utilise pour demander l'entrée. Pour utiliser une invite, appelez-la à partir d’une étape dans votre dialogue et récupérez le résultat de l’invite à l’étape suivante avec step_context.result
. Dans les coulisses, les invites constituent une boîte de dialogue en deux étapes. Tout d'abord, l'invite demande une entrée. Ensuite, elle retourne la valeur valide ou redémarre depuis le début avec une nouvelle invite jusqu'à ce qu'elle reçoive une entrée valide.
Vous devez toujours retourner une valeur DialogTurnResult
non Null à partir d’une étape en cascade. À défaut, votre dialogue risque de ne pas fonctionner comme prévu. Vous trouverez ici l'implémentation de la name_step
dans le dialogue en cascade.
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.")),
)
Dans age_step
, spécifiez une nouvelle invite au cas où la validation de l'entrée de l'utilisateur échouerait, soit parce que son format ne peut pas être analysé par l'invite ou parce que l'entrée ne remplit pas un critère de validation, spécifié dans le constructeur ci-dessus. Dans ce cas, si aucune nouvelle invite n'a été fournie, l'invite utilise le texte d'invite initial pour redemander à l'utilisateur d'effectuer une entrée
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
Le mode de transport, le nom et l’âge de l’utilisateur sont enregistrés dans une instance de la classe UserProfile
.
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
Au cours de la dernière étape, vérifiez le step_context.result
retourné par le dialogue appelé à l'étape précédente en cascade. Si la valeur renvoyée est true, l'accesseur au profil de l'utilisateur obtient et met à jour le profil de l'utilisateur. Pour obtenir le profil utilisateur, appelez get
, puis définissez les valeurs des propriétés user_profile.transport
, user_profile.name
, et user_profile.age
. Enfin, récapitulez les informations à l'attention de l'utilisateur avant d'appeler end_dialog
, qui met fin au dialogue. La fin du dialogue se traduit par son retrait de la pile des dialogues et le retour d’un résultat facultatif à son parent. Le parent est le dialogue ou la méthode ayant démarré le dialogue qui vient de prendre fin.
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__,
Créer la méthode d’extension pour exécuter le dialogue en cascade
Une méthode d'assistance run_dialog()
est définie dans le fichier helpers\dialog_helper.py et est utilisée pour créer et accéder au contexte de dialogue. Ici, accessor
est l’accesseur de la propriété d’état du dialogue, et dialog
est le dialogue composant du profil utilisateur. Dans la mesure où les dialogues de composants définissent un ensemble de dialogues interne, un ensemble de dialogues externe visible par le code du gestionnaire de message doit être créé et utilisé pour créer un contexte de dialogue.
Créer le contexte de dialogue en appelant la create_context
, qui est utilisée pour interagir avec le jeu de dialogues à partir du gestionnaire de tour du bot. Le contexte du dialogue inclut le contexte du tour actuel, le dialogue parent et l’état du dialogue, ce qui offre une méthode permettant de conserver les informations au sein du dialogue.
Le contexte du dialogue vous permet de démarrer un dialogue avec l’ID de chaîne ou de continuer le dialogue actuel (par exemple, un dialogue en cascade qui comporte plusieurs étapes). Le contexte du dialogue est transmis à la totalité des dialogues et des étapes en cascade du bot.
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)
Cet exemple met à jour l’état du profil utilisateur dans le dialogue. Cette pratique peut fonctionner pour certains bots, mais elle ne fonctionnera pas si vous souhaitez réutiliser un dialogue entre bots.
Il existe différentes options pour séparer les étapes de dialogue de l’état du bot. Par exemple, une fois que votre dialogue a collecté des informations complètes, vous pouvez effectuer les actions suivantes :