A coleta de informações por meio da apresentação de perguntas é uma das principais formas de um bot interagir com os usuários. A biblioteca de diálogos fornece recursos internos úteis como classes prompt que tornam fácil fazer perguntas e validar as respostas para que elas correspondam a um tipo de dados específico ou atendam às regras de validação personalizadas.
Você pode gerenciar fluxos de conversa simples e complexos usando a biblioteca de caixas de diálogo. Em uma interação simples, o bot percorre uma sequência fixa de etapas e a conversa termina. Uma caixa de diálogo é útil quando o bot precisa coletar informações do usuário.
Este artigo mostra como implementar um fluxo de conversa simples criando prompts e chamando-os de uma caixa de diálogo em cascata.
O exemplo de prompts de várias voltas usa uma caixa de diálogo em cascata, alguns prompts e uma caixa de diálogo de componente para criar uma interação simples que faça uma série de perguntas ao usuário. O código usa um diálogo para percorrer estas etapas:
Finalmente, se ele responder sim, exibir as informações coletadas; caso contrário, dizer ao usuário que as informações dele não serão mantidas.
Para usar as caixas de diálogo, instale o pacote do NuGet, Microsoft.Bot.Builder.Dialogs.
O bot interage com o usuário por meio de UserProfileDialog. Ao criar a classe do DialogBot bot, ela UserProfileDialog é definida como sua caixa de diálogo principal. O bot, em seguida, usa um método auxiliar Run para acessar o diálogo.

Dialogs\UserProfileDialog.cs
Comece criando o UserProfileDialog que deriva da ComponentDialog classe e tenha 7 etapas.
No construtor UserProfileDialog, crie as etapas de cascata, os prompts e o diálogo de cascata, e adicione-os ao conjunto do diálogo. Os avisos precisam estar no mesmo conjunto do diálogo no qual eles são usados.
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,
ConfirmStepAsync,
SummaryStepAsync,
};
// 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);
}
Em seguida, adicione as etapas que a caixa de diálogo usa para solicitar entrada. Para usar um prompt, chame-o de uma etapa no seu diálogo e recupere o resultado do prompt na etapa seguinte usando stepContext.Result. Nos bastidores, os prompts são uma caixa de diálogo em duas etapas. Primeiro, o prompt solicita entrada. Em seguida, retorna o valor válido ou inicia desde o início com um reprompt até receber uma entrada válida.
Você sempre deve ter um retorno de DialogTurnResult não nulo em uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetada. Mostrado abaixo está a implementação na NameStepAsync caixa de diálogo 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, specify a retry prompt for when the user's input fail to validate, either it's in a format that the prompt can't parse, or the input fail a validation criteria. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto de prompt inicial para voltar a solicitar a entrada ao usuário.
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
O modo de transporte, o nome e a idade do usuário são salvos em uma instância da 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
Na última etapa, verifique o stepContext.Result retornado pela caixa de diálogo chamada na etapa anterior da cascata. Se o valor retornado for verdadeiro, o acessador de perfil de usuário obterá e atualizará o perfil do usuário. Para obter o perfil do usuário, chame GetAsync e defina os valores do e userProfile.NameuserProfile.AgeuserProfile.Picture das userProfile.Transportpropriedades. Por fim, resumir as informações para o usuário antes de chamar EndDialogAsync, o que encerra a caixa de diálogo. O fim do diálogo o remove da pilha de diálogo e retorna um resultado opcional ao pai dele. O pai é o método ou diálogo que iniciou o diálogo recém-terminado.
private async Task<DialogTurnResult> SummaryStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if ((bool)stepContext.Result)
{
// 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);
}
}
}
else
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Thanks. Your profile will not be kept."), 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);
}
Para usar as caixas de diálogo, seu projeto precisa instalar o pacote do npm, botbuilder-dialogs.
O bot interage com o usuário por meio de um UserProfileDialog. Ao criar o bot, DialogBotele UserProfileDialog é definido como sua caixa de diálogo principal. O bot, em seguida, usa um método auxiliar run para acessar o diálogo.

dialogs/userProfileDialog.js
Comece criando o UserProfileDialog que deriva da ComponentDialog classe e tenha 7 etapas.
No construtor UserProfileDialog, crie as etapas de cascata, os prompts e o diálogo de cascata, e adicione-os ao conjunto do diálogo. Os avisos precisam estar no mesmo conjunto do diálogo no qual eles são usados.
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.confirmStep.bind(this),
this.summaryStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
Em seguida, adicione as etapas que a caixa de diálogo usa para solicitar entrada. Para usar um prompt, chame-o de uma etapa no diálogo e recupere o resultado do prompt na etapa a seguir do contexto de etapa; no nosso caso, usando step.result. Nos bastidores, os prompts são uma caixa de diálogo em duas etapas. Primeiro, o prompt solicita entrada. Em seguida, ele retorna o valor válido ou começa desde o início com um prompt novamente até receber uma entrada válida.
Você sempre deve ter um retorno de DialogTurnResult não nulo em uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetada. Mostrado abaixo está a implementação da nameStep caixa de diálogo em cascata.
async nameStep(step) {
step.values.transport = step.result.value;
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
Em ageStep, especifique um prompt de repetição para quando a entrada do usuário não for validada, seja porque está em um formato que o prompt não pode analisar ou a entrada falha em um critério de validação, especificado no construtor acima. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto de prompt inicial para voltar a solicitar a entrada ao usuário.
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
O modo de transporte, o nome e a idade do usuário são salvos em uma instância da classe UserProfile.
class UserProfile {
constructor(transport, name, age, picture) {
this.transport = transport;
this.name = name;
this.age = age;
this.picture = picture;
}
}
dialogs/userProfileDialog.js
Na última etapa, verifique o step.result retornado pela caixa de diálogo chamada na etapa anterior da cascata. Se o valor retornado for verdadeiro, o acessador de perfil de usuário obterá e atualizará o perfil do usuário. Para obter o perfil do usuário, chame gete defina os valores do e userProfile.nameuserProfile.ageuserProfile.picture das userProfile.transportpropriedades. Por fim, resumir as informações para o usuário antes de chamar endDialog, o que encerra a caixa de diálogo. O fim do diálogo o remove da pilha de diálogo e retorna um resultado opcional ao pai dele. O pai é o método ou diálogo que iniciou o diálogo recém-terminado.
async summaryStep(step) {
if (step.result) {
// 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.');
}
}
} else {
await step.context.sendActivity('Thanks. Your profile will not be kept.');
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
return await step.endDialog();
}
Criar o método de extensão para executar o diálogo de cascata
Um run método auxiliar, definido no interior userProfileDialog, é usado para criar e acessar o contexto da caixa de diálogo. Aqui, accessor é o acessador de propriedade de estado para a propriedade de estado do diálogo e this é o diálogo do componente de perfil do usuário. Como as caixas de diálogo de componente definem um conjunto de diálogo interno, um conjunto de diálogo externo deve ser criado visível para o código do manipulador de mensagens e usado para criar um contexto de caixa de diálogo.
O contexto de diálogo é criado com a chamada do método createContext e é usado para interagir com o conjunto do diálogo no manipulador de turno do bot. O contexto do diálogo inclui o contexto do turno atual, o diálogo pai e o estado do diálogo, que fornece um método para preservar informações no diálogo.
O contexto da caixa de diálogo permite que você inicie uma caixa de diálogo com a ID da cadeia de caracteres ou continue a caixa de diálogo atual (como uma caixa de diálogo em cascata que tem várias etapas). O contexto do diálogo é passado para todas as etapas de cascata e diálogos do 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);
}
}
O bot interage com o usuário por meio de UserProfileDialog. Ao criar a classe do DialogBot bot, ela UserProfileDialog é definida como sua caixa de diálogo principal. O bot, em seguida, usa um método auxiliar Run para acessar o diálogo.

UserProfileDialog.java
Comece criando o UserProfileDialog que deriva da ComponentDialog classe e tenha 7 etapas.
No construtor UserProfileDialog, crie as etapas de cascata, os prompts e o diálogo de cascata, e adicione-os ao conjunto do diálogo. Os avisos precisam estar no mesmo conjunto do diálogo no qual eles são usados.
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");
}
Em seguida, adicione as etapas que a caixa de diálogo usa para solicitar a entrada. Para usar um prompt, chame-o de uma etapa no seu diálogo e recupere o resultado do prompt na etapa seguinte usando stepContext.getResult(). Nos bastidores, os prompts são uma caixa de diálogo em duas etapas. Primeiro, o prompt solicita entrada. Em seguida, retorna o valor válido ou começa desde o início com um reprompt até receber uma entrada válida.
Você sempre deve ter um retorno de DialogTurnResult não nulo em uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetado. Mostrado abaixo é a implementação da nameStep caixa de diálogo 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, specify a retry prompt for when the user's input fail to validate, either it's in a format that the prompt can't parse, or the input fail a validation criteria. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto de prompt inicial para voltar a solicitar a entrada ao usuário.
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
O modo de transporte, o nome e a idade do usuário são salvos em uma instância da 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
Na última etapa, verifique o stepContext.Result retornado pela caixa de diálogo chamada na etapa anterior da cascata. Se o valor retornado for verdadeiro, o acessador de perfil de usuário obterá e atualizará o perfil de usuário. Para obter o perfil do usuário, chame get e defina os valores de userProfile.Transport, userProfile.NameuserProfile.Age e userProfile.Picture propriedades. Por fim, resumir as informações do usuário antes de chamar endDialog, o que encerra a caixa de diálogo. O fim do diálogo o remove da pilha de diálogo e retorna um resultado opcional ao pai dele. O pai é o método ou diálogo que iniciou o diálogo recém-terminado.
// 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);
}
}
}
Para usar diálogos, instale os pacotes PyPI botbuilder-dialogs e botbuilder-ai executando pip install botbuilder-dialogs e pip install botbuilder-ai em um terminal.
O bot interage com o usuário por meio de UserProfileDialog. Quando a classe do DialogBot bot é criada, ela UserProfileDialog é definida como sua caixa de diálogo principal. O bot, em seguida, usa um método auxiliar run_dialog para acessar o diálogo.

dialogs\user_profile_dialog.py
Comece criando o UserProfileDialog que deriva da ComponentDialog classe e tenha 7 etapas.
No construtor UserProfileDialog, crie as etapas de cascata, os prompts e o diálogo de cascata, e adicione-os ao conjunto do diálogo. Os avisos precisam estar no mesmo conjunto do diálogo no qual eles são usados.
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.confirm_step,
self.summary_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__
Em seguida, adicione as etapas que a caixa de diálogo usa para solicitar a entrada. Para usar um prompt, chame-o de uma etapa no seu diálogo e recupere o resultado do prompt na etapa seguinte usando step_context.result. Nos bastidores, os prompts são uma caixa de diálogo em duas etapas. Primeiro, o prompt solicita entrada. Em seguida, ele retorna o valor válido ou começa desde o início com um prompt novamente até receber uma entrada válida.
Você sempre deve ter um retorno de DialogTurnResult não nulo em uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetado. Aqui você pode ver a implementação da name_step caixa de diálogo em 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, specify a retry prompt for when the user's input fail to validate, either it's in a format that the prompt not parse, or the input fail a validation criteria, specified in the constructor above. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto de prompt inicial para voltar a solicitar a entrada ao usuário
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
O modo de transporte, o nome e a idade do usuário são salvos em uma instância da 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
Na última etapa, verifique o step_context.result retornado pela caixa de diálogo chamada na etapa anterior da cascata. Se o valor retornado for verdadeiro, o acessador de perfil de usuário obterá e atualizará o perfil de usuário. Para obter o perfil do usuário, chame gete defina os valores de user_profile.transport, user_profile.namee user_profile.age propriedades. Por fim, resumir o usuário antes de chamar end_dialog, o que encerra a caixa de diálogo. O fim do diálogo o remove da pilha de diálogo e retorna um resultado opcional ao pai dele. O pai é o método ou diálogo que iniciou o diálogo recém-terminado.
async def summary_step(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
if step_context.result:
# 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."
)
else:
await step_context.context.send_activity(
MessageFactory.text("Thanks. Your profile will not be kept.")
)
# WaterfallStep always finishes with the end of the Waterfall or with another
# dialog, here it is the end.
return await step_context.end_dialog()
Criar o método de extensão para executar o diálogo de cascata
Um run_dialog() método auxiliar é definido em auxiliares\dialog_helper.py usado para criar e acessar o contexto da caixa de diálogo. Aqui, accessor é o acessador de propriedade de estado para a propriedade de estado do diálogo e dialog é o diálogo do componente de perfil do usuário. Como as caixas de diálogo de componente definem um conjunto de diálogo interno, um conjunto de diálogo externo deve ser criado visível para o código do manipulador de mensagens e usá-lo para criar um contexto de caixa de diálogo.
Crie o contexto da caixa de diálogo chamando o create_context, que é usado para interagir com o conjunto de diálogos de dentro do manipulador de turnos do bot. O contexto do diálogo inclui o contexto do turno atual, o diálogo pai e o estado do diálogo, que fornece um método para preservar informações no diálogo.
O contexto do diálogo permite iniciar um diálogo com a ID da cadeia de caracteres ou continuar o diálogo atual (por exemplo, um diálogo em cascata que tem várias etapas). O contexto do diálogo é passado para todas as etapas de cascata e diálogos do 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)
Este exemplo atualiza o estado de perfil do usuário a partir do diálogo. Essa prática pode funcionar para um bot simples, mas não funcionará se você quiser reutilizar uma caixa de diálogo entre bots.
Há várias opções para manter as etapas de diálogo e o estado de bot separados. Por exemplo, após o diálogo reunir todas as informações, você pode: