Raccogliere informazioni ponendo domande è uno dei modi principali con cui un bot interagisce con gli utenti. La raccolta dialoghi offre funzionalità integrate utili, ad esempio classi prompt che semplificano le domande e convalidano la risposta per verificare che corrisponda a un tipo di dati specifici o che soddisfi le regole di convalida personalizzata.
È possibile gestire flussi di conversazione lineari e più complessi usando la libreria dialogs. In un'interazione lineare, il bot viene eseguito attraverso una sequenza fissa di passaggi e la conversazione termina. Una finestra di dialogo è utile quando il bot deve raccogliere informazioni dall'utente.
Questo articolo illustra come implementare il flusso di conversazione lineare creando prompt e chiamandoli da un dialogo a cascata.
Per esempi su come scrivere le proprie richieste senza usare la raccolta di dialoghi, vedere l'articolo Creare richieste personalizzate per raccogliere input utente.
L'esempio di richieste a più turni usa un dialogo a cascata, alcune richieste e un dialogo componente per creare un'interazione lineare che pone all'utente una serie di domande. Il codice usa un dialogo per scorrere questi passaggi:
Infine, se hanno risposto sì, visualizzare le informazioni raccolte; in caso contrario, indicare all'utente che le informazioni non verranno mantenute.
Per usare i dialoghi, installare il pacchetto NuGet Microsoft.Bot.Builder.Dialogs.
Il bot interagisce con l'utente tramite UserProfileDialog
. Quando si crea la classe del DialogBot
bot, viene UserProfileDialog
impostato come finestra di dialogo principale. Il bot usa quindi un metodo helper Run
per accedere al dialogo.
Dialogs\UserProfileDialog.cs
Iniziare creando l'oggetto UserProfileDialog
che deriva dalla ComponentDialog
classe e ha sette passaggi.
Nel costruttore UserProfileDialog
creare i passaggi a cascata, le richieste e il dialogo a cascata e aggiungerli al set di dialoghi. I prompt devono trovarsi nello stesso set di dialoghi in cui vengono usati.
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);
}
Aggiungere quindi i passaggi usati dalla finestra di dialogo per richiedere l'input. Per usare una richiesta, chiamarla da un passaggio del dialogo e recuperarne il risultato nel passaggio seguente usando stepContext.Result
. Le richieste non sono altro che un dialogo in due passaggi. Prima di tutto, il prompt richiede l'input. Restituisce quindi il valore valido o inizia dall'inizio con una nuova richiesta fino a quando non riceve un input valido.
È opportuno che un risultato DialogTurnResult
restituito da un passaggio a cascata sia sempre non Null. In caso contrario, il dialogo potrebbe non funzionare come progettato. Di seguito è illustrata l'implementazione di NameStepAsync
nella finestra di dialogo a cascata.
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);
}
In AgeStepAsync
specificare una richiesta di ripetizione dei tentativi quando l'input dell'utente non riesce a convalidare, perché è in un formato che il prompt non è in grado di analizzare o l'input non riesce a eseguire un criterio di convalida. In questo caso, se non è stata fornita alcuna richiesta di ripetizione dei tentativi, il prompt userà il testo del prompt iniziale per riprovare l'utente per l'input.
private async Task<DialogTurnResult> AgeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if ((bool)stepContext.Result)
{
// User said "yes" so we will be prompting for the age.
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
var promptOptions = new PromptOptions
{
Prompt = MessageFactory.Text("Please enter your age."),
RetryPrompt = MessageFactory.Text("The value entered must be greater than 0 and less than 150."),
};
return await stepContext.PromptAsync(nameof(NumberPrompt<int>), promptOptions, cancellationToken);
}
else
{
// User said "no" so we will skip the next step. Give -1 as the age.
return await stepContext.NextAsync(-1, cancellationToken);
}
}
UserProfile.cs
Il mezzo di trasporto, il nome e l'età dell'utente vengono salvati in un'istanza della 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
Nell'ultimo passaggio controllare l'oggetto stepContext.Result
restituito dal dialogo chiamato nel passaggio a cascata precedente. Se il valore restituito è true, la funzione di accesso del profilo utente ottiene e aggiorna il profilo utente. Per ottenere il profilo utente, chiamare GetAsync
e quindi impostare i valori delle userProfile.Transport
proprietà , userProfile.Name
userProfile.Age
e userProfile.Picture
. Riepilogare infine le informazioni per l'utente prima di chiamare EndDialogAsync
, che termina la finestra di dialogo. Quando si termina un dialogo, questo viene estratto dallo stack e viene restituito un risultato facoltativo all'elemento padre del dialogo. Per elemento padre si intende il dialogo o il metodo che ha avviato il dialogo appena terminato.
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);
Per usare i dialoghi, è necessario installare il pacchetto npm botbuilder-dialogs per il progetto.
Il bot interagisce con l'utente tramite una classe UserProfileDialog
. Quando si crea il bot, DialogBot
viene UserProfileDialog
impostato come finestra di dialogo principale. Il bot usa quindi un metodo helper run
per accedere al dialogo.
dialogs/userProfileDialog.js
Iniziare creando l'oggetto UserProfileDialog
che deriva dalla ComponentDialog
classe e ha sette passaggi.
Nel costruttore UserProfileDialog
creare i passaggi a cascata, le richieste e il dialogo a cascata e aggiungerli al set di dialoghi. I prompt devono trovarsi nello stesso set di dialoghi in cui vengono usati.
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;
}
Aggiungere quindi i passaggi usati dalla finestra di dialogo per richiedere l'input. Per usare una richiesta, chiamarla da un passaggio del dialogo e recuperarne il risultato nel passaggio seguente del contesto del passaggio, usando in questo caso step.result
. Le richieste non sono altro che un dialogo in due passaggi. Prima di tutto, il prompt richiede l'input. Restituisce quindi il valore valido o inizia dall'inizio con una nuova richiesta fino a quando non riceve un input valido.
È opportuno che un risultato DialogTurnResult
restituito da un passaggio a cascata sia sempre non Null. In caso contrario, il dialogo potrebbe non funzionare come progettato. Di seguito è illustrata l'implementazione di nameStep
nella finestra di dialogo a cascata.
async nameStep(step) {
step.values.transport = step.result.value;
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
In ageStep
specificare una richiesta di ripetizione dei tentativi quando l'input dell'utente non riesce a convalidare, perché è in un formato che il prompt non è in grado di analizzare oppure l'input non supera un criterio di convalida, specificato nel costruttore precedente. In questo caso, se non è stata fornita alcuna richiesta di ripetizione dei tentativi, il prompt userà il testo del prompt iniziale per riprovare l'utente per l'input.
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
Il mezzo di trasporto, il nome e l'età dell'utente vengono salvati in un'istanza della classe UserProfile
.
class UserProfile {
constructor(transport, name, age, picture) {
this.transport = transport;
this.name = name;
this.age = age;
this.picture = picture;
}
}
dialogs/userProfileDialog.js
Nell'ultimo passaggio controllare l'oggetto step.result
restituito dal dialogo chiamato nel passaggio a cascata precedente. Se il valore restituito è true, la funzione di accesso del profilo utente ottiene e aggiorna il profilo utente. Per ottenere il profilo utente, chiamare get
e quindi impostare i valori delle userProfile.transport
proprietà , userProfile.name
userProfile.age
e userProfile.picture
. Riepilogare infine le informazioni per l'utente prima di chiamare endDialog
, che termina la finestra di dialogo. Quando si termina un dialogo, questo viene estratto dallo stack e viene restituito un risultato facoltativo all'elemento padre del dialogo. Per elemento padre si intende il dialogo o il metodo che ha avviato il dialogo appena terminato.
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.');
}
Creare il metodo di estensione per eseguire il dialogo a cascata
Un run
metodo helper, definito all'interno userProfileDialog
di , viene usato per creare e accedere al contesto del dialogo. In questo caso, accessor
è la funzione di accesso alle proprietà di stato per la proprietà di stato del dialogo e this
è il dialogo componente del profilo utente. Poiché i dialoghi dei componenti definiscono un set di dialoghi interno, è necessario creare un set di dialoghi esterno visibile al codice del gestore messaggi e usato per creare un contesto di dialogo.
Il contesto del dialogo viene creato chiamando il metodo createContext
e viene usato per interagire con il set di dialoghi dall'interno del gestore dei turni del bot. Il contesto del dialogo include il contesto del turno corrente, il dialogo padre e lo stato del dialogo, che fornisce un metodo per preservare le informazioni all'interno del dialogo.
Il contesto del dialogo consente di avviare un dialogo con l'ID stringa o di continuare il dialogo corrente, ad esempio un dialogo a cascata con più passaggi. Il contesto del dialogo viene passato a tutti i dialoghi e passaggi a cascata del 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);
}
}
Il bot interagisce con l'utente tramite UserProfileDialog
. Quando si crea la classe del DialogBot
bot, viene UserProfileDialog
impostato come finestra di dialogo principale. Il bot usa quindi un metodo helper Run
per accedere al dialogo.
UserProfileDialog.java
Iniziare creando l'oggetto UserProfileDialog
che deriva dalla ComponentDialog
classe e ha sette passaggi.
Nel costruttore UserProfileDialog
creare i passaggi a cascata, le richieste e il dialogo a cascata e aggiungerli al set di dialoghi. I prompt devono trovarsi nello stesso set di dialoghi in cui vengono usati.
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");
}
Aggiungere quindi i passaggi usati dalla finestra di dialogo per richiedere l'input. Per usare una richiesta, chiamarla da un passaggio del dialogo e recuperarne il risultato nel passaggio seguente usando stepContext.getResult()
. Le richieste non sono altro che un dialogo in due passaggi. Prima di tutto, il prompt richiede l'input. Restituisce quindi il valore valido o inizia dall'inizio con una nuova richiesta fino a quando non riceve un input valido.
È opportuno che un risultato DialogTurnResult
restituito da un passaggio a cascata sia sempre non Null. In caso contrario, il dialogo potrebbe non funzionare come progettato. Di seguito è illustrata l'implementazione di nameStep
nella finestra di dialogo a cascata.
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);
}
In ageStep
specificare una richiesta di ripetizione dei tentativi quando l'input dell'utente non riesce a convalidare, perché è in un formato che il prompt non è in grado di analizzare o l'input non riesce a eseguire un criterio di convalida. In questo caso, se non è stata fornita alcuna richiesta di ripetizione dei tentativi, il prompt userà il testo del prompt iniziale per riprovare l'utente per l'input.
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
Il mezzo di trasporto, il nome e l'età dell'utente vengono salvati in un'istanza della 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
Nell'ultimo passaggio controllare l'oggetto stepContext.Result
restituito dal dialogo chiamato nel passaggio a cascata precedente. Se il valore restituito è true, la funzione di accesso del profilo utente ottiene e aggiorna il profilo utente. Per ottenere il profilo utente, chiamare get
e quindi impostare i valori delle userProfile.Transport
proprietà , userProfile.Name
userProfile.Age
e userProfile.Picture
. Riepilogare infine le informazioni per l'utente prima di chiamare endDialog
, che termina la finestra di dialogo. Quando si termina un dialogo, questo viene estratto dallo stack e viene restituito un risultato facoltativo all'elemento padre del dialogo. Per elemento padre si intende il dialogo o il metodo che ha avviato il dialogo appena terminato.
// 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);
}
}
}
Per usare i dialoghi, installare i pacchetti PyPI botbuilder-dialogs e botbuilder-ai eseguendo pip install botbuilder-dialogs
e pip install botbuilder-ai
in un terminale.
Il bot interagisce con l'utente tramite UserProfileDialog
. Quando viene creata la classe del DialogBot
bot, UserProfileDialog
viene impostato come finestra di dialogo principale. Il bot usa quindi un metodo helper run_dialog
per accedere al dialogo.
dialogs\user_profile_dialog.py
Iniziare creando l'oggetto UserProfileDialog
che deriva dalla ComponentDialog
classe e ha sette passaggi.
Nel costruttore UserProfileDialog
creare i passaggi a cascata, le richieste e il dialogo a cascata e aggiungerli al set di dialoghi. I prompt devono trovarsi nello stesso set di dialoghi in cui vengono usati.
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__
Aggiungere quindi i passaggi usati dalla finestra di dialogo per richiedere l'input. Per usare una richiesta, chiamarla da un passaggio del dialogo e recuperarne il risultato nel passaggio seguente usando step_context.result
. Le richieste non sono altro che un dialogo in due passaggi. Prima di tutto, il prompt richiede l'input. Restituisce quindi il valore valido o inizia dall'inizio con una nuova richiesta fino a quando non riceve un input valido.
È opportuno che un risultato DialogTurnResult
restituito da un passaggio a cascata sia sempre non Null. In caso contrario, il dialogo potrebbe non funzionare come progettato. Qui è possibile visualizzare l'implementazione di nella name_step
finestra di dialogo a cascata.
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.")),
)
In age_step
specificare una richiesta di ripetizione dei tentativi quando l'input dell'utente non riesce a convalidare, perché è in un formato che il prompt non è in grado di analizzare oppure l'input non supera un criterio di convalida, specificato nel costruttore precedente. In questo caso, se non è stata fornita alcuna richiesta di ripetizione dei tentativi, il prompt userà il testo della richiesta iniziale per riprovare l'utente per l'input
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
Il mezzo di trasporto, il nome e l'età dell'utente vengono salvati in un'istanza della 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
Nell'ultimo passaggio controllare l'oggetto step_context.result
restituito dal dialogo chiamato nel passaggio a cascata precedente. Se il valore restituito è true, la funzione di accesso del profilo utente ottiene e aggiorna il profilo utente. Per ottenere il profilo utente, chiamare get
e quindi impostare i valori delle user_profile.transport
proprietà , user_profile.name
e user_profile.age
. Riepilogare infine le informazioni per l'utente prima di chiamare end_dialog
, che termina la finestra di dialogo. Quando si termina un dialogo, questo viene estratto dallo stack e viene restituito un risultato facoltativo all'elemento padre del dialogo. Per elemento padre si intende il dialogo o il metodo che ha avviato il dialogo appena terminato.
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__,
Creare il metodo di estensione per eseguire il dialogo a cascata
Un run_dialog()
metodo helper viene definito in helpers\dialog_helper.py usato per creare e accedere al contesto del dialogo. In questo caso, accessor
è la funzione di accesso alle proprietà di stato per la proprietà di stato del dialogo e dialog
è il dialogo componente del profilo utente. Poiché i dialoghi dei componenti definiscono un set di dialoghi interno, è necessario creare un set di dialoghi esterno visibile al codice del gestore messaggi e usarlo per creare un contesto di dialogo.
Creare il contesto del dialogo chiamando , create_context
che viene usato per interagire con il set di dialoghi dall'interno del gestore dei turni del bot. Il contesto del dialogo include il contesto del turno corrente, il dialogo padre e lo stato del dialogo, che fornisce un metodo per preservare le informazioni all'interno del dialogo.
Il contesto del dialogo consente di iniziare un dialogo con il relativo ID stringa o di continuare il dialogo corrente, ad esempio un dialogo a cascata con più passaggi. Il contesto del dialogo viene passato a tutti i dialoghi e passaggi a cascata del 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)
Questo esempio consente di aggiornare lo stato del profilo utente all'interno del dialogo. Questa procedura può funzionare per alcuni bot, ma non funzionerà se si vuole riutilizzare un dialogo tra bot.
Sono disponibili diverse opzioni per mantenere separati lo stato del dialogo e lo stato del bot. Ad esempio, dopo che il dialogo ha raccolto le informazioni complete, è possibile: