Mengumpulkan informasi dengan mengajukan pertanyaan adalah salah satu cara utama bot berinteraksi dengan pengguna. Pustaka dialog menyediakan fitur bawaan yang berguna seperti kelas prompt yang memudahkan untuk mengajukan pertanyaan dan memvalidasi respons untuk memastikannya cocok dengan jenis data tertentu atau memenuhi aturan validasi kustom.
Anda dapat mengelola alur percakapan linier dan lebih kompleks menggunakan pustaka dialog. Dalam interaksi linier, bot berjalan melalui urutan langkah tetap, dan percakapan selesai. Dialog berguna ketika bot perlu mengumpulkan informasi dari pengguna.
Artikel ini memperlihatkan cara menerapkan alur percakapan linier dengan membuat perintah dan memanggilnya dari dialog air terjun.
Untuk contoh cara menulis perintah Anda sendiri tanpa menggunakan pustaka dialog, lihat artikel Membuat permintaan Anda sendiri untuk mengumpulkan input pengguna.
Sampel perintah multi-giliran menggunakan dialog air terjun, beberapa perintah, dan dialog komponen untuk membuat interaksi linier yang mengajukan serangkaian pertanyaan kepada pengguna. Kode menggunakan dialog untuk menelusuri langkah-langkah berikut:
Akhirnya, jika mereka menjawab ya, tampilkan informasi yang dikumpulkan; jika tidak, beri tahu pengguna bahwa informasi mereka tidak akan disimpan.
Untuk menggunakan dialog, instal paket NuGet Microsoft.Bot.Builder.Dialogs .
Bot berinteraksi dengan pengguna melalui UserProfileDialog
. Saat membuat kelas bot DialogBot
, UserProfileDialog
diatur sebagai dialog utamanya. Bot kemudian menggunakan metode pembantu Run
untuk mengakses dialog.
Dialog\UserProfileDialog.cs
Mulailah dengan membuat UserProfileDialog
yang berasal dari ComponentDialog
kelas , dan memiliki tujuh langkah.
UserProfileDialog
Di konstruktor, buat langkah-langkah air terjun, perintah dan dialog air terjun, dan tambahkan ke kumpulan dialog. Perintah harus berada dalam kumpulan dialog yang sama di mana mereka digunakan.
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);
}
Selanjutnya, tambahkan langkah-langkah yang digunakan dialog untuk meminta input. Untuk menggunakan perintah, panggil dari langkah dalam dialog Anda dan ambil hasil perintah dalam langkah berikut menggunakan stepContext.Result
. Di balik layar, perintah adalah dialog dua langkah. Pertama, perintah meminta input. Kemudian mengembalikan nilai yang valid, atau dimulai kembali dari awal dengan proses ulang hingga menerima input yang valid.
Anda harus selalu mengembalikan non-null DialogTurnResult
dari langkah air terjun. Jika tidak, dialog Anda mungkin tidak berfungsi seperti yang dirancang. Ditunjukkan di bawah ini adalah implementasi untuk NameStepAsync
dalam dialog air terjun.
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);
}
Dalam AgeStepAsync
, tentukan permintaan coba lagi ketika input pengguna gagal divalidasi, baik karena dalam format yang tidak dapat diurai oleh perintah, atau input gagal dalam kriteria validasi. Dalam hal ini, jika tidak ada permintaan coba lagi yang disediakan, perintah akan menggunakan teks perintah awal untuk memprompexi ulang pengguna untuk 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
Mode transportasi, nama, dan usia pengguna disimpan dalam instans UserProfile
kelas.
public class UserProfile
{
public string Transport { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Attachment Picture { get; set; }
}
Dialog\UserProfileDialog.cs
Pada langkah terakhir, periksa yang stepContext.Result
dikembalikan oleh dialog yang disebut di langkah air terjun sebelumnya. Jika nilai pengembalian benar, aksesor profil pengguna mendapatkan dan memperbarui profil pengguna. Untuk mendapatkan profil pengguna, panggil GetAsync
lalu atur nilai userProfile.Transport
properti , , userProfile.Name
userProfile.Age
dan userProfile.Picture
. Terakhir, ringkas informasi untuk pengguna sebelum memanggil EndDialogAsync
, yang mengakhiri dialog. Mengakhiri dialog akan memunculkannya dari tumpukan dialog dan mengembalikan hasil opsional ke induk dialog. Induk adalah dialog atau metode yang memulai dialog yang baru saja berakhir.
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);
Untuk menggunakan dialog, proyek Anda perlu menginstal paket npm botbuilder-dialogs .
Bot berinteraksi dengan pengguna melalui UserProfileDialog
. Saat membuat bot DialogBot
, UserProfileDialog
diatur sebagai dialog utamanya. Bot kemudian menggunakan metode pembantu run
untuk mengakses dialog.
dialog/userProfileDialog.js
Mulailah dengan membuat UserProfileDialog
yang berasal dari ComponentDialog
kelas , dan memiliki tujuh langkah.
UserProfileDialog
Di konstruktor, buat langkah-langkah air terjun, perintah dan dialog air terjun, dan tambahkan ke kumpulan dialog. Perintah harus berada dalam kumpulan dialog yang sama di mana mereka digunakan.
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;
}
Selanjutnya, tambahkan langkah-langkah yang digunakan dialog untuk meminta input. Untuk menggunakan perintah, panggil dari langkah dalam dialog Anda dan ambil hasil perintah dalam langkah berikut dari konteks langkah, dalam hal ini dengan menggunakan step.result
. Di balik layar, perintah adalah dialog dua langkah. Pertama, perintah meminta input. Kemudian mengembalikan nilai yang valid, atau dimulai kembali dari awal dengan proses ulang hingga menerima input yang valid.
Anda harus selalu mengembalikan non-null DialogTurnResult
dari langkah air terjun. Jika tidak, dialog Anda mungkin tidak berfungsi seperti yang dirancang. Ditunjukkan di bawah ini adalah implementasi untuk nameStep
dalam dialog air terjun.
async nameStep(step) {
step.values.transport = step.result.value;
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
Dalam ageStep
, tentukan permintaan coba lagi ketika input pengguna gagal divalidasi, baik karena dalam format yang tidak dapat diurai oleh perintah, atau input gagal dalam kriteria validasi, yang ditentukan dalam konstruktor di atas. Dalam hal ini, jika tidak ada permintaan coba lagi yang disediakan, perintah akan menggunakan teks perintah awal untuk memprompexi ulang pengguna untuk 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
Mode transportasi, nama, dan usia pengguna disimpan dalam instans UserProfile
kelas.
class UserProfile {
constructor(transport, name, age, picture) {
this.transport = transport;
this.name = name;
this.age = age;
this.picture = picture;
}
}
dialog/userProfileDialog.js
Pada langkah terakhir, periksa yang step.result
dikembalikan oleh dialog yang disebut di langkah air terjun sebelumnya. Jika nilai pengembalian benar, aksesor profil pengguna mendapatkan dan memperbarui profil pengguna. Untuk mendapatkan profil pengguna, panggil get
, lalu atur nilai userProfile.transport
properti , , userProfile.name
dan userProfile.age
userProfile.picture
. Terakhir, ringkas informasi untuk pengguna sebelum memanggil endDialog
, yang mengakhiri dialog. Mengakhiri dialog akan memunculkannya dari tumpukan dialog dan mengembalikan hasil opsional ke induk dialog. Induk adalah dialog atau metode yang memulai dialog yang baru saja berakhir.
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.');
}
Membuat metode ekstensi untuk menjalankan dialog air terjun
Metode run
pembantu, yang ditentukan di dalam userProfileDialog
, digunakan untuk membuat dan mengakses konteks dialog. Di sini, accessor
adalah aksesor properti status untuk properti status dialog, dan this
merupakan dialog komponen profil pengguna. Karena dialog komponen menentukan kumpulan dialog dalam, kumpulan dialog luar harus dibuat yang terlihat oleh kode handler pesan dan digunakan untuk membuat konteks dialog.
Konteks dialog dibuat dengan memanggil createContext
metode , dan digunakan untuk berinteraksi dengan dialog yang diatur dari dalam penangan giliran bot. Konteks dialog mencakup konteks giliran saat ini, dialog induk, dan status dialog, yang menyediakan metode untuk mempertahankan informasi dalam dialog.
Konteks dialog memungkinkan Anda memulai dialog dengan ID string, atau melanjutkan dialog saat ini (seperti dialog air terjun yang memiliki beberapa langkah). Konteks dialog diteruskan ke semua dialog bot dan langkah-langkah air terjun.
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);
}
}
Bot berinteraksi dengan pengguna melalui UserProfileDialog
. Saat membuat kelas bot DialogBot
, UserProfileDialog
diatur sebagai dialog utamanya. Bot kemudian menggunakan metode pembantu Run
untuk mengakses dialog.
UserProfileDialog.java
Mulailah dengan membuat UserProfileDialog
yang berasal dari ComponentDialog
kelas , dan memiliki tujuh langkah.
UserProfileDialog
Di konstruktor, buat langkah-langkah air terjun, perintah dan dialog air terjun, dan tambahkan ke kumpulan dialog. Perintah harus berada dalam kumpulan dialog yang sama di mana mereka digunakan.
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");
}
Selanjutnya, tambahkan langkah-langkah yang digunakan dialog untuk meminta input. Untuk menggunakan perintah, panggil dari langkah dalam dialog Anda dan ambil hasil perintah dalam langkah berikut menggunakan stepContext.getResult()
. Di balik layar, perintah adalah dialog dua langkah. Pertama, perintah meminta input. Kemudian mengembalikan nilai yang valid, atau dimulai kembali dari awal dengan proses ulang hingga menerima input yang valid.
Anda harus selalu mengembalikan non-null DialogTurnResult
dari langkah air terjun. Jika tidak, dialog Anda mungkin tidak berfungsi seperti yang dirancang. Ditunjukkan di bawah ini adalah implementasi untuk nameStep
dalam dialog air terjun.
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);
}
Dalam ageStep
, tentukan permintaan coba lagi ketika input pengguna gagal divalidasi, baik karena dalam format yang tidak dapat diurai oleh perintah, atau input gagal dalam kriteria validasi. Dalam hal ini, jika tidak ada permintaan coba lagi yang disediakan, perintah akan menggunakan teks perintah awal untuk memprompexi ulang pengguna untuk 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
Mode transportasi, nama, dan usia pengguna disimpan dalam instans UserProfile
kelas.
// 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
Pada langkah terakhir, periksa yang stepContext.Result
dikembalikan oleh dialog yang disebut di langkah air terjun sebelumnya. Jika nilai pengembalian benar, aksesor profil pengguna mendapatkan dan memperbarui profil pengguna. Untuk mendapatkan profil pengguna, panggil get
lalu atur nilai userProfile.Transport
properti , , userProfile.Name
userProfile.Age
dan userProfile.Picture
. Terakhir, ringkas informasi untuk pengguna sebelum memanggil endDialog
, yang mengakhiri dialog. Mengakhiri dialog akan memunculkannya dari tumpukan dialog dan mengembalikan hasil opsional ke induk dialog. Induk adalah dialog atau metode yang memulai dialog yang baru saja berakhir.
// 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);
}
}
}
Untuk menggunakan dialog, instal botbuilder-dialogs dan botbuilder-ai PyPI packages dengan menjalankan pip install botbuilder-dialogs
dan pip install botbuilder-ai
dari terminal.
Bot berinteraksi dengan pengguna melalui UserProfileDialog
. Saat kelas bot DialogBot
dibuat, UserProfileDialog
diatur sebagai dialog utamanya. Bot kemudian menggunakan metode pembantu run_dialog
untuk mengakses dialog.
dialogs\user_profile_dialog.py
Mulailah dengan membuat UserProfileDialog
yang berasal dari ComponentDialog
kelas , dan memiliki tujuh langkah.
UserProfileDialog
Di konstruktor, buat langkah-langkah air terjun, perintah dan dialog air terjun, dan tambahkan ke kumpulan dialog. Perintah harus berada dalam kumpulan dialog yang sama di mana mereka digunakan.
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__
Selanjutnya, tambahkan langkah-langkah yang digunakan dialog untuk meminta input. Untuk menggunakan perintah, panggil dari langkah dalam dialog Anda dan ambil hasil perintah dalam langkah berikut menggunakan step_context.result
. Di balik layar, perintah adalah dialog dua langkah. Pertama, perintah meminta input. Kemudian mengembalikan nilai yang valid, atau dimulai kembali dari awal dengan proses ulang hingga menerima input yang valid.
Anda harus selalu mengembalikan non-null DialogTurnResult
dari langkah air terjun. Jika tidak, dialog Anda mungkin tidak berfungsi seperti yang dirancang. Di sini Anda dapat melihat implementasi untuk name_step
dalam dialog air terjun.
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.")),
)
Dalam age_step
, tentukan permintaan coba lagi ketika input pengguna gagal divalidasi, baik karena dalam format yang tidak dapat diurai oleh perintah, atau input gagal dalam kriteria validasi, yang ditentukan dalam konstruktor di atas. Dalam hal ini, jika tidak ada permintaan coba lagi yang disediakan, perintah akan menggunakan teks perintah awal untuk memprompeikan ulang pengguna untuk 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
Mode transportasi, nama, dan usia pengguna disimpan dalam instans UserProfile
kelas.
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
Pada langkah terakhir, periksa yang step_context.result
dikembalikan oleh dialog yang disebut di langkah air terjun sebelumnya. Jika nilai pengembalian benar, aksesor profil pengguna mendapatkan dan memperbarui profil pengguna. Untuk mendapatkan profil pengguna, panggil get
, lalu atur nilai user_profile.transport
properti , , user_profile.name
dan user_profile.age
. Terakhir, ringkas informasi untuk pengguna sebelum memanggil end_dialog
, yang mengakhiri dialog. Mengakhiri dialog akan memunculkannya dari tumpukan dialog dan mengembalikan hasil opsional ke induk dialog. Induk adalah dialog atau metode yang memulai dialog yang baru saja berakhir.
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__,
Membuat metode ekstensi untuk menjalankan dialog air terjun
Metode run_dialog()
pembantu didefinisikan dalam helpers\dialog_helper.py yang digunakan untuk membuat dan mengakses konteks dialog. Di sini, accessor
adalah aksesor properti status untuk properti status dialog, dan dialog
merupakan dialog komponen profil pengguna. Karena dialog komponen menentukan kumpulan dialog dalam, kumpulan dialog luar harus dibuat yang terlihat oleh kode handler pesan dan menggunakannya untuk membuat konteks dialog.
Buat konteks dialog dengan memanggil create_context
, yang digunakan untuk berinteraksi dengan dialog yang diatur dari dalam handler giliran bot. Konteks dialog mencakup konteks giliran saat ini, dialog induk, dan status dialog, yang menyediakan metode untuk mempertahankan informasi dalam dialog.
Konteks dialog memungkinkan Anda memulai dialog dengan ID string, atau melanjutkan dialog saat ini (seperti dialog air terjun yang memiliki beberapa langkah). Konteks dialog diteruskan ke semua dialog bot dan langkah-langkah air terjun.
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)
Sampel ini memperbarui status profil pengguna dari dalam dialog. Praktik ini dapat berfungsi untuk beberapa bot, tetapi tidak akan berfungsi jika Anda ingin menggunakan kembali dialog di seluruh bot.
Ada berbagai opsi untuk menjaga langkah-langkah dialog dan status bot terpisah. Misalnya, setelah dialog mengumpulkan informasi lengkap, Anda dapat: