Coletar informações fazendo perguntas é uma das principais maneiras pelas quais um bot interage com os usuários. A biblioteca de caixas de diálogo fornece recursos internos úteis, como classes de prompt que facilitam fazer perguntas e validar a resposta para garantir que ela corresponda a um tipo de dados específico ou atenda às regras de validação personalizadas.
Você pode gerenciar fluxos de conversação lineares e mais complexos usando a biblioteca de diálogos. Em uma interação linear, o bot executa 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 o fluxo de conversação linear criando prompts e chamando-os a partir de uma caixa de diálogo em cascata.
Para obter exemplos de como escrever seus próprios prompts sem usar a biblioteca de diálogos, consulte o artigo Criar seus próprios prompts para coletar entrada do usuário.
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 linear que faz ao usuário uma série de perguntas. O código usa uma caixa de diálogo para percorrer estas etapas:
Por fim, se responderem sim, exiba as informações coletadas; caso contrário, diga ao usuário que suas informações não serão mantidas.
Para usar caixas de diálogo, instale o pacote NuGet Microsoft.Bot.Builder.Dialogs .
O bot interage com o usuário via UserProfileDialog
. Ao criar a classe do DialogBot
bot, o UserProfileDialog
é definido como sua caixa de diálogo principal. Em seguida, o bot usa um Run
método auxiliar para acessar a caixa de diálogo.
Caixas de diálogo\UserProfileDialog.cs
Comece criando o UserProfileDialog
que deriva da ComponentDialog
classe, e tem sete etapas.
UserProfileDialog
No construtor, crie as etapas em cascata, os prompts e a caixa de diálogo em cascata e adicione-os ao conjunto de diálogos. Os prompts precisam estar no mesmo conjunto de diálogo em que 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,
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);
}
Em seguida, adicione as etapas que a caixa de diálogo usa para solicitar entrada. Para usar um prompt, chame-o a partir de uma etapa na caixa de 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 pede entrada. Em seguida, ele retorna o valor válido ou recomeça desde o início com um novo prompt até receber uma entrada válida.
Você sempre deve retornar um não-nulo DialogTurnResult
de uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetado. Abaixo é mostrada a implementação para NameStepAsync
na caixa de diálogo em 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);
}
No AgeStepAsync
, especifique um prompt de nova tentativa 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 porque a entrada falha em um critério de validação. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto do prompt inicial para solicitar novamente a entrada do 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 UserProfile
classe.
public class UserProfile
{
public string Transport { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Attachment Picture { get; set; }
}
Caixas de diálogo\UserProfileDialog.cs
Na última etapa, marque o stepContext.Result
retorno pela caixa de diálogo chamada na etapa de cascata anterior. Se o valor de retorno for true, o acessador de perfil de usuário obtém e atualiza o perfil de usuário. Para obter o perfil de usuário, chame GetAsync
e defina os valores das userProfile.Transport
propriedades , userProfile.Name
userProfile.Age
e userProfile.Picture
. Por fim, resuma as informações para o usuário antes de chamar EndDialogAsync
, o que encerra a caixa de diálogo. Encerrar a caixa de diálogo a retira da pilha de diálogo e retorna um resultado opcional para o pai da caixa de diálogo. O pai é a caixa de diálogo ou o método que iniciou a caixa de diálogo que acabou de terminar.
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);
Para usar diálogos, seu projeto precisa instalar o pacote npm botbuilder-dialogs .
O bot interage com o usuário por meio de um UserProfileDialog
arquivo . Ao criar o bot, DialogBot
o UserProfileDialog
é definido como sua caixa de diálogo principal. Em seguida, o bot usa um run
método auxiliar para acessar a caixa de diálogo.
diálogos/userProfileDialog.js
Comece criando o UserProfileDialog
que deriva da ComponentDialog
classe, e tem sete etapas.
UserProfileDialog
No construtor, crie as etapas em cascata, os prompts e a caixa de diálogo em cascata e adicione-os ao conjunto de diálogos. Os prompts precisam estar no mesmo conjunto de diálogo em que 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.summaryStep.bind(this),
this.confirmStep.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 na caixa de diálogo e recupere o resultado do prompt na etapa seguinte do contexto da etapa, neste caso usando step.result
. Nos bastidores, os prompts são uma caixa de diálogo em duas etapas. Primeiro, o prompt pede entrada. Em seguida, ele retorna o valor válido ou recomeça desde o início com um novo prompt até receber uma entrada válida.
Você sempre deve retornar um não-nulo DialogTurnResult
de uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetado. Abaixo é mostrada a implementação para a 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 porque 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 do prompt inicial para solicitar novamente a entrada do 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 UserProfile
classe.
class UserProfile {
constructor(transport, name, age, picture) {
this.transport = transport;
this.name = name;
this.age = age;
this.picture = picture;
}
}
diálogos/userProfileDialog.js
Na última etapa, marque o step.result
retorno pela caixa de diálogo chamada na etapa de cascata anterior. Se o valor de retorno for true, o acessador de perfil de usuário obtém e atualiza o perfil de usuário. Para obter o perfil de usuário, chame get
e defina os valores das userProfile.transport
propriedades , userProfile.name
userProfile.age
e userProfile.picture
. Por fim, resuma as informações para o usuário antes de chamar endDialog
, o que encerra a caixa de diálogo. Encerrar a caixa de diálogo a retira da pilha de diálogo e retorna um resultado opcional para o pai da caixa de diálogo. O pai é a caixa de diálogo ou o método que iniciou a caixa de diálogo que acabou de terminar.
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.');
}
Criar o método de extensão para executar a caixa de diálogo em cascata
Um run
método auxiliar, definido dentro userProfileDialog
de , é usado para criar e acessar o contexto de diálogo. Aqui, accessor
é o acessador da propriedade state para a propriedade dialog state e this
é a caixa de diálogo do componente de perfil de usuário. Como as caixas de diálogo dos componentes definem um conjunto de diálogos interno, um conjunto de diálogos externos deve ser criado que seja visível para o código do manipulador de mensagens e usado para criar um contexto de diálogo.
O contexto de diálogo é criado chamando o createContext
método e é usado para interagir com o conjunto de diálogos de dentro do manipulador de turnos do bot. O contexto da caixa de diálogo inclui o contexto de turno atual, a caixa de diálogo pai e o estado da caixa de diálogo, que fornece um método para preservar informações dentro da caixa de diálogo.
O contexto da caixa de diálogo permite iniciar uma caixa de diálogo com a ID da cadeia de caracteres ou continuar a caixa de diálogo atual (como uma caixa de diálogo em cascata com várias etapas). O contexto da caixa de diálogo é passado para todas as caixas de diálogo e etapas em cascata 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 via UserProfileDialog
. Ao criar a classe do DialogBot
bot, o UserProfileDialog
é definido como sua caixa de diálogo principal. Em seguida, o bot usa um Run
método auxiliar para acessar a caixa de diálogo.
UserProfileDialog.java
Comece criando o UserProfileDialog
que deriva da ComponentDialog
classe, e tem sete etapas.
UserProfileDialog
No construtor, crie as etapas em cascata, os prompts e a caixa de diálogo em cascata e adicione-os ao conjunto de diálogos. Os prompts precisam estar no mesmo conjunto de diálogo em que 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 entrada. Para usar um prompt, chame-o a partir de uma etapa na caixa de 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 pede entrada. Em seguida, ele retorna o valor válido ou recomeça desde o início com um novo prompt até receber uma entrada válida.
Você sempre deve retornar um não-nulo DialogTurnResult
de uma etapa de cascata. Se você não fizer isso, sua caixa de diálogo pode não funcionar como projetado. Abaixo é mostrada a implementação para nameStep
na caixa de diálogo em 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);
}
No ageStep
, especifique um prompt de nova tentativa 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 porque a entrada falha em um critério de validação. Nesse caso, se nenhum prompt de nova tentativa foi fornecido, o prompt usará o texto do prompt inicial para solicitar novamente a entrada do 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 UserProfile
classe.
// 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, marque o stepContext.Result
retorno pela caixa de diálogo chamada na etapa de cascata anterior. Se o valor de retorno for true, o acessador de perfil de usuário obtém e atualiza o perfil de usuário. Para obter o perfil de usuário, chame get
e defina os valores das userProfile.Transport
propriedades , userProfile.Name
userProfile.Age
e userProfile.Picture
. Por fim, resuma as informações para o usuário antes de chamar endDialog
, o que encerra a caixa de diálogo. Encerrar a caixa de diálogo a retira da pilha de diálogo e retorna um resultado opcional para o pai da caixa de diálogo. O pai é a caixa de diálogo ou o método que iniciou a caixa de diálogo que acabou de terminar.
// 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 botbuilder-dialogs e botbuilder-ai PyPI executando pip install botbuilder-dialogs
e pip install botbuilder-ai
a partir de um terminal.
O bot interage com o usuário via UserProfileDialog
. Quando a classe do DialogBot
bot é criada, a UserProfileDialog
é definida como sua caixa de diálogo principal. Em seguida, o bot usa um run_dialog
método auxiliar para acessar a caixa de diálogo.
diálogos\user_profile_dialog.py
Comece criando o UserProfileDialog
que deriva da ComponentDialog
classe, e tem sete etapas.
UserProfileDialog
No construtor, crie as etapas em cascata, os prompts e a caixa de diálogo em cascata e adicione-os ao conjunto de diálogos. Os prompts precisam estar no mesmo conjunto de diálogo em que 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.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__
Em seguida, adicione as etapas que a caixa de diálogo usa para solicitar entrada. Para usar um prompt, chame-o a partir de uma etapa na caixa de 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 pede entrada. Em seguida, ele retorna o valor válido ou recomeça desde o início com um novo prompt até receber uma entrada válida.
Você sempre deve retornar um não-nulo DialogTurnResult
de 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 para a 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.")),
)
Em age_step
, 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 porque a entrada falha em um critério de validação, especificado no construtor acima. Nesse caso, se nenhum prompt de repetição foi fornecido, o prompt usará o texto do prompt inicial para solicitar novamente a entrada do 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 UserProfile
classe.
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
diálogos\user_profile_dialog.py
Na última etapa, marque o step_context.result
retorno pela caixa de diálogo chamada na etapa de cascata anterior. Se o valor de retorno for true, o acessador de perfil de usuário obtém e atualiza o perfil de usuário. Para obter o perfil de usuário, chame get
e defina os valores das user_profile.transport
propriedades , user_profile.name
e user_profile.age
. Por fim, resuma as informações para o usuário antes de chamar end_dialog
, o que encerra a caixa de diálogo. Encerrar a caixa de diálogo a retira da pilha de diálogo e retorna um resultado opcional para o pai da caixa de diálogo. O pai é a caixa de diálogo ou o método que iniciou a caixa de diálogo que acabou de terminar.
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__,
Criar o método de extensão para executar a caixa de diálogo em cascata
Um run_dialog()
método auxiliar é definido em helpers\dialog_helper.py que é usado para criar e acessar o contexto de diálogo. Aqui, accessor
é o acessador da propriedade state para a propriedade dialog state e dialog
é a caixa de diálogo do componente de perfil de usuário. Como as caixas de diálogo dos componentes definem um conjunto de diálogos interno, um conjunto de diálogos externos deve ser criado que seja visível para o código do manipulador de mensagens e usá-lo para criar um contexto de diálogo.
Crie o contexto 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 da caixa de diálogo inclui o contexto de turno atual, a caixa de diálogo pai e o estado da caixa de diálogo, que fornece um método para preservar informações dentro da caixa de diálogo.
O contexto da caixa de diálogo permite iniciar uma caixa de diálogo com a ID da cadeia de caracteres ou continuar a caixa de diálogo atual (como uma caixa de diálogo em cascata com várias etapas). O contexto da caixa de diálogo é passado para todas as caixas de diálogo e etapas em cascata 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 do perfil do usuário de dentro da caixa de diálogo. Essa prática pode funcionar para alguns bots, 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 do bot separados. Por exemplo, quando a caixa de diálogo reunir informações completas, você poderá: