Vytvoření vlastních výzev ke shromáždění uživatelských vstupů
Článek
PLATÍ PRO: SDK v4
Konverzace mezi robotem a uživatelem často zahrnuje dotaz (vyzvání) uživatele k zadání informací, parsování odpovědi uživatele a následnému jednání s těmito informacemi. Robot by měl sledovat kontext konverzace, aby mohl spravovat své chování a pamatovat si odpovědi na předchozí otázky. Stav robota je informace, které sleduje, aby správně reagoval na příchozí zprávy.
Tip
Knihovna dialogových oken poskytuje předdefinované výzvy, které poskytují více funkcí, které můžou uživatelé používat. Příklady těchto výzev lze nalézt v článku Implementace sekvenčního toku konverzace.
Poznámka:
Sady SDK služby Bot Framework JavaScript, C# a Python budou nadále podporovány, ale sada Java SDK se vyřazuje s konečnou dlouhodobou podporou končící v listopadu 2023.
Stávající roboti sestavení pomocí sady Java SDK budou i nadále fungovat.
Kód v tomto článku je založený na ukázce s výzvou k zadání vstupních údajů uživateli. Budete potřebovat kopii ukázky jazyka C#, ukázky JavaScriptu, ukázky Java nebo ukázky Pythonu.
Ukázkový robot se zeptá uživatele na řadu otázek, ověří některé odpovědi a uloží svůj vstup. Následující diagram ukazuje vztah mezi třídami botu, profilu uživatele a toku konverzace.
UserProfile Třída pro informace o uživateli, které robot bude shromažďovat.
Třída ConversationFlow , která má řídit stav naší konverzace při shromažďování informací o uživateli.
Vnitřní ConversationFlow.Question výčet pro sledování vaší pozice v konverzaci.
userProfile Třída pro informace o uživateli, které robot bude shromažďovat.
Třída conversationFlow , která má řídit stav naší konverzace při shromažďování informací o uživateli.
Vnitřní conversationFlow.question výčet pro monitorování, ve kterém bodě se nacházíte v konverzaci.
UserProfile Třída pro informace o uživateli, které robot bude shromažďovat.
Třída ConversationFlow , která má řídit stav naší konverzace při shromažďování informací o uživateli.
Vnitřní ConversationFlow.Question výčet pro sledování, kde se nacházíte v konverzaci.
UserProfile Třída pro informace o uživateli, které robot bude shromažďovat.
Třída ConversationFlow , která má řídit stav naší konverzace při shromažďování informací o uživateli.
Vnitřní ConversationFlow.Question výčet pro sledování místa, kde jste v konverzaci.
Stav uživatele bude sledovat jméno uživatele, věk a zvolené datum a stav konverzace bude sledovat, co jste naposledy požádali uživatele.
Vzhledem k tomu, že tento robot neplánujete nasadit, nakonfigurujete stav uživatele a konverzace tak, aby používaly úložiště paměti.
Pomocí obslužné rutiny pro zpracování zpráv robota a vlastností stavu konverzace a uživatele můžete spravovat tok konverzace a sběr vstupů. V robotovi zaznamenáte informace o vlastnosti stavu přijaté během každé iterace obslužné rutiny otáčení zprávy.
Vytvořte objekty stavu uživatele a konverzace při spuštění a spotřebujte je prostřednictvím injektáže závislostí v konstruktoru robota.
Startup.cs
// Create the Bot Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
services.AddSingleton<IStorage, MemoryStorage>();
// Create the User state.
services.AddSingleton<UserState>();
// Create the Conversation state.
services.AddSingleton<ConversationState>();
Vytvořte objekty stavu uživatele a konverzace v index.js a spotřebujte je v konstruktoru robota.
index.js
// Catch-all for errors.
adapter.onTurnError = async (context, error) => {
// This check writes out errors to console log .vs. app insights.
// NOTE: In production environment, you should consider logging this to Azure
bots/customPromptBot.js
class CustomPromptBot extends ActivityHandler {
constructor(conversationState, userState) {
super();
// The state management objects for the conversation and user.
this.conversationState = conversationState;
this.userState = userState;
Pomocí instancí ConversationState a UserState poskytovaných kontejnerem Spring vytvořte CustomPromptBot v metodě getBot. Konstruktor CustomPromptBot bude ukládat odkazy na ConversationState a UserState poskytnuté během spuštění.
Application.java
Varování
Vypadá to, že ukázka, kterou hledáte, se přesunula. Ujišťujeme vás, že na vyřešení tohoto problému pracujeme.
CustomPromptBot.java
Varování
Vypadá to, že ukázka, kterou hledáte, byla přesunuta. Ujišťujeme vás, že pracujeme na vyřešení tohoto problému.
Vytvořte objekty stavu uživatele a konverzace v app.py a spotřebujte je v konstruktoru robota.
app.py
CONVERSATION_STATE = ConversationState(MEMORY)
# Create Bot
BOT = CustomPromptBot(CONVERSATION_STATE, USER_STATE)
# Listen for incoming requests on /api/messages.
bots/custom_prompt_bot.py
class CustomPromptBot(ActivityHandler):
def __init__(self, conversation_state: ConversationState, user_state: UserState):
if conversation_state is None:
raise TypeError(
"[CustomPromptBot]: Missing parameter. conversation_state is required but None was given"
)
if user_state is None:
raise TypeError(
"[CustomPromptBot]: Missing parameter. user_state is required but None was given"
)
self.conversation_state = conversation_state
self.user_state = user_state
Před koncem kola zavolejte saveChanges, aby se změny stavu uložily do úložiště.
/**
* Override the ActivityHandler.run() method to save state changes after the bot logic completes.
*/
async run(context) {
await super.run(context);
// Save any state changes. The load happened during the execution of the Dialog.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
}
Vytvořte přístupové objekty vlastností pro profil uživatele a vlastnosti toku konverzace a potom voláním get načtěte hodnotu vlastnosti ze stavu.
CustomPromptBot.java
Varování
Vypadá to, že se ukázka, kterou hledáte, přesunula. Buďte ujištěni, že na vyřešení tohoto problému pracujeme.
Před koncem tahu zavolejte saveChanges, aby se zapsaly všechny změny stavu do úložiště.
Varování
Vypadá to, že ukázka, kterou hledáte, se přesunula. Buďte ujištěni, že na řešení tohoto problému pracujeme.
V konstruktoru vytvoříte přístupové objekty stavových vlastností a nastavíte objekty správy stavu (vytvořené výše) pro naši konverzaci.
bots/custom_prompt_bot.py
async def on_message_activity(self, turn_context: TurnContext):
# Get the state properties from the turn context.
profile = await self.profile_accessor.get(turn_context, UserProfile)
flow = await self.flow_accessor.get(turn_context, ConversationFlow)
Než kolo skončí, zavolejte SaveChangesAsync, aby se změny stavu zapsaly do úložiště.
# Save changes to UserState and ConversationState
await self.conversation_state.save_changes(turn_context)
await self.user_state.save_changes(turn_context)
Zpracovatel otáčení zprávy
Při zpracování zprávových aktivit používá obslužná rutina zprávy pomocnou metodu ke správě konverzace a podněcování uživatele. Pomocná metoda je popsaná v následující části.
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var conversationStateAccessors = _conversationState.CreateProperty<ConversationFlow>(nameof(ConversationFlow));
var flow = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationFlow(), cancellationToken);
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
var profile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile(), cancellationToken);
await FillOutUserProfileAsync(flow, profile, turnContext, cancellationToken);
// Save changes.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}
bots/customPromptBot.js
this.onMessage(async (turnContext, next) => {
const flow = await this.conversationFlow.get(turnContext, { lastQuestionAsked: question.none });
const profile = await this.userProfile.get(turnContext, {});
await CustomPromptBot.fillOutUserProfile(flow, profile, turnContext);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
CustomPromptBot.java
Varování
Zdá se, že ukázka, kterou hledáte, se přesunula! Ujišťujeme vás, že pracujeme na vyřešení této záležitosti.
bots/custom_prompt_bot.py
async def on_message_activity(self, turn_context: TurnContext):
# Get the state properties from the turn context.
profile = await self.profile_accessor.get(turn_context, UserProfile)
flow = await self.flow_accessor.get(turn_context, ConversationFlow)
await self._fill_out_user_profile(flow, profile, turn_context)
# Save changes to UserState and ConversationState
await self.conversation_state.save_changes(turn_context)
await self.user_state.save_changes(turn_context)
Vyplnění profilu uživatele
Robot vyzve uživatele k zadání informací podle toho, na jaké otázce se robot v předchozím kroku zeptal. Vstup se analyzuje pomocí metody ověřování.
Každá metoda ověřování má podobný návrh:
Vrácená hodnota označuje, jestli je vstup platnou odpovědí na tuto otázku.
Pokud ověření projde, vytvoří parsovanou a normalizovanou hodnotu, která se uloží.
Pokud se ověření nezdaří, vytvoří zprávu, se kterou může robot znovu požádat o informace.
Metody ověřování jsou popsány v následující části.
{
var input = turnContext.Activity.Text?.Trim();
string message;
switch (flow.LastQuestionAsked)
{
case ConversationFlow.Question.None:
await turnContext.SendActivityAsync("Let's get started. What is your name?", null, null, cancellationToken);
flow.LastQuestionAsked = ConversationFlow.Question.Name;
break;
case ConversationFlow.Question.Name:
if (ValidateName(input, out var name, out message))
{
profile.Name = name;
await turnContext.SendActivityAsync($"Hi {profile.Name}.", null, null, cancellationToken);
await turnContext.SendActivityAsync("How old are you?", null, null, cancellationToken);
flow.LastQuestionAsked = ConversationFlow.Question.Age;
break;
}
else
{
await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
break;
}
case ConversationFlow.Question.Age:
if (ValidateAge(input, out var age, out message))
{
profile.Age = age;
await turnContext.SendActivityAsync($"I have your age as {profile.Age}.", null, null, cancellationToken);
await turnContext.SendActivityAsync("When is your flight?", null, null, cancellationToken);
flow.LastQuestionAsked = ConversationFlow.Question.Date;
break;
}
else
{
await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
break;
}
case ConversationFlow.Question.Date:
if (ValidateDate(input, out var date, out message))
{
profile.Date = date;
await turnContext.SendActivityAsync($"Your cab ride to the airport is scheduled for {profile.Date}.");
await turnContext.SendActivityAsync($"Thanks for completing the booking {profile.Name}.");
await turnContext.SendActivityAsync($"Type anything to run the bot again.");
flow.LastQuestionAsked = ConversationFlow.Question.None;
profile = new UserProfile();
break;
}
else
{
await turnContext.SendActivityAsync(message ?? "I'm sorry, I didn't understand that.", null, null, cancellationToken);
break;
}
}
}
bots/customPromptBot.js
// Manages the conversation flow for filling out the user's profile.
static async fillOutUserProfile(flow, profile, turnContext) {
const input = turnContext.activity.text;
let result;
switch (flow.lastQuestionAsked) {
// If we're just starting off, we haven't asked the user for any information yet.
// Ask the user for their name and update the conversation flag.
case question.none:
await turnContext.sendActivity("Let's get started. What is your name?");
flow.lastQuestionAsked = question.name;
break;
// If we last asked for their name, record their response, confirm that we got it.
// Ask them for their age and update the conversation flag.
case question.name:
result = this.validateName(input);
if (result.success) {
profile.name = result.name;
await turnContext.sendActivity(`I have your name as ${ profile.name }.`);
await turnContext.sendActivity('How old are you?');
flow.lastQuestionAsked = question.age;
break;
} else {
// If we couldn't interpret their input, ask them for it again.
// Don't update the conversation flag, so that we repeat this step.
await turnContext.sendActivity(result.message || "I'm sorry, I didn't understand that.");
break;
}
// If we last asked for their age, record their response, confirm that we got it.
// Ask them for their date preference and update the conversation flag.
case question.age:
result = this.validateAge(input);
if (result.success) {
profile.age = result.age;
await turnContext.sendActivity(`I have your age as ${ profile.age }.`);
await turnContext.sendActivity('When is your flight?');
flow.lastQuestionAsked = question.date;
break;
} else {
// If we couldn't interpret their input, ask them for it again.
// Don't update the conversation flag, so that we repeat this step.
await turnContext.sendActivity(result.message || "I'm sorry, I didn't understand that.");
break;
}
// If we last asked for a date, record their response, confirm that we got it,
// let them know the process is complete, and update the conversation flag.
case question.date:
result = this.validateDate(input);
if (result.success) {
profile.date = result.date;
await turnContext.sendActivity(`Your cab ride to the airport is scheduled for ${ profile.date }.`);
await turnContext.sendActivity(`Thanks for completing the booking ${ profile.name }.`);
await turnContext.sendActivity('Type anything to run the bot again.');
flow.lastQuestionAsked = question.none;
profile = {};
break;
} else {
// If we couldn't interpret their input, ask them for it again.
// Don't update the conversation flag, so that we repeat this step.
await turnContext.sendActivity(result.message || "I'm sorry, I didn't understand that.");
break;
}
}
}
CustomPromptBot.java
Varování
Vypadá to, že ukázka, kterou hledáte, se přesunula. Ujišťujeme vás, že pracujeme na vyřešení této záležitosti.
bots/custom_prompt_bot.py
async def _fill_out_user_profile(
self, flow: ConversationFlow, profile: UserProfile, turn_context: TurnContext
):
user_input = turn_context.activity.text.strip()
# ask for name
if flow.last_question_asked == Question.NONE:
await turn_context.send_activity(
MessageFactory.text("Let's get started. What is your name?")
)
flow.last_question_asked = Question.NAME
# validate name then ask for age
elif flow.last_question_asked == Question.NAME:
validate_result = self._validate_name(user_input)
if not validate_result.is_valid:
await turn_context.send_activity(
MessageFactory.text(validate_result.message)
)
else:
profile.name = validate_result.value
await turn_context.send_activity(
MessageFactory.text(f"Hi {profile.name}")
)
await turn_context.send_activity(
MessageFactory.text("How old are you?")
)
flow.last_question_asked = Question.AGE
# validate age then ask for date
elif flow.last_question_asked == Question.AGE:
validate_result = self._validate_age(user_input)
if not validate_result.is_valid:
await turn_context.send_activity(
MessageFactory.text(validate_result.message)
)
else:
profile.age = validate_result.value
await turn_context.send_activity(
MessageFactory.text(f"I have your age as {profile.age}.")
)
await turn_context.send_activity(
MessageFactory.text("When is your flight?")
)
flow.last_question_asked = Question.DATE
# validate date and wrap it up
elif flow.last_question_asked == Question.DATE:
validate_result = self._validate_date(user_input)
if not validate_result.is_valid:
await turn_context.send_activity(
MessageFactory.text(validate_result.message)
)
else:
profile.date = validate_result.value
await turn_context.send_activity(
MessageFactory.text(
f"Your cab ride to the airport is scheduled for {profile.date}."
)
)
await turn_context.send_activity(
MessageFactory.text(
f"Thanks for completing the booking {profile.name}."
)
)
await turn_context.send_activity(
MessageFactory.text("Type anything to run the bot again.")
)
flow.last_question_asked = Question.NONE
Parsování a ověření vstupu
Robot k ověření vstupu používá následující kritéria.
Název musí být neprázdný řetězec. Normalizuje se odstraněním nadbytečných mezer.
Věk musí být mezi 18 a 120. Normalizuje se vrácením celého čísla.
Datum musí být libovolné datum nebo čas aspoň hodinu v budoucnu.
Normalizuje se vrácením jenom části analyzovaného vstupu s datem.
Poznámka:
Pro vstup stáří a data se v ukázce používají knihovny Microsoft/Recognizers-Text k provedení počáteční analýzy.
Toto je jen jeden způsob, jak analyzovat vstup. Další informace o těchto knihovnách najdete v souboru README projektu.
private static bool ValidateName(string input, out string name, out string message)
{
name = null;
message = null;
if (string.IsNullOrWhiteSpace(input))
{
message = "Please enter a name that contains at least one character.";
}
else
{
name = input.Trim();
}
return message is null;
}
private static bool ValidateAge(string input, out int age, out string message)
{
age = 0;
message = null;
// Try to recognize the input as a number. This works for responses such as "twelve" as well as "12".
try
{
// Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on.
// The recognizer returns a list of potential recognition results, if any.
var results = NumberRecognizer.RecognizeNumber(input, Culture.English);
foreach (var result in results)
{
// The result resolution is a dictionary, where the "value" entry contains the processed string.
if (result.Resolution.TryGetValue("value", out var value))
{
age = Convert.ToInt32(value);
if (age >= 18 && age <= 120)
{
return true;
}
}
}
message = "Please enter an age between 18 and 120.";
}
catch
{
message = "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120.";
}
return message is null;
}
private static bool ValidateDate(string input, out string date, out string message)
{
date = null;
message = null;
// Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "9pm", "tomorrow", "Sunday at 5pm", and so on.
// The recognizer returns a list of potential recognition results, if any.
try
{
var results = DateTimeRecognizer.RecognizeDateTime(input, Culture.English);
// Check whether any of the recognized date-times are appropriate,
// and if so, return the first appropriate date-time. We're checking for a value at least an hour in the future.
var earliest = DateTime.Now.AddHours(1.0);
foreach (var result in results)
{
// The result resolution is a dictionary, where the "values" entry contains the processed input.
var resolutions = result.Resolution["values"] as List<Dictionary<string, string>>;
foreach (var resolution in resolutions)
{
// The processed input contains a "value" entry if it is a date-time value, or "start" and
// "end" entries if it is a date-time range.
if (resolution.TryGetValue("value", out var dateString)
|| resolution.TryGetValue("start", out dateString))
{
if (DateTime.TryParse(dateString, out var candidate)
&& earliest < candidate)
{
date = candidate.ToShortDateString();
return true;
}
}
}
}
message = "I'm sorry, please enter a date at least an hour out.";
}
catch
{
message = "I'm sorry, I could not interpret that as an appropriate date. Please enter a date at least an hour out.";
}
return false;
}
bots/customPromptBot.js
// Validates name input. Returns whether validation succeeded and either the parsed and normalized
// value or a message the bot can use to ask the user again.
static validateName(input) {
const name = input && input.trim();
return name !== undefined
? { success: true, name: name }
: { success: false, message: 'Please enter a name that contains at least one character.' };
};
// Validates age input. Returns whether validation succeeded and either the parsed and normalized
// value or a message the bot can use to ask the user again.
static validateAge(input) {
// Try to recognize the input as a number. This works for responses such as "twelve" as well as "12".
try {
// Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on.
// The recognizer returns a list of potential recognition results, if any.
const results = Recognizers.recognizeNumber(input, Recognizers.Culture.English);
let output;
results.forEach(result => {
// result.resolution is a dictionary, where the "value" entry contains the processed string.
const value = result.resolution.value;
if (value) {
const age = parseInt(value);
if (!isNaN(age) && age >= 18 && age <= 120) {
output = { success: true, age: age };
return;
}
}
});
return output || { success: false, message: 'Please enter an age between 18 and 120.' };
} catch (error) {
return {
success: false,
message: "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120."
};
}
}
// Validates date input. Returns whether validation succeeded and either the parsed and normalized
// value or a message the bot can use to ask the user again.
static validateDate(input) {
// Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "today at 9pm", "tomorrow", "Sunday at 5pm", and so on.
// The recognizer returns a list of potential recognition results, if any.
try {
const results = Recognizers.recognizeDateTime(input, Recognizers.Culture.English);
const now = new Date();
const earliest = now.getTime() + (60 * 60 * 1000);
let output;
results.forEach(result => {
// result.resolution is a dictionary, where the "values" entry contains the processed input.
result.resolution.values.forEach(resolution => {
// The processed input contains a "value" entry if it is a date-time value, or "start" and
// "end" entries if it is a date-time range.
const datevalue = resolution.value || resolution.start;
// If only time is given, assume it's for today.
const datetime = resolution.type === 'time'
? new Date(`${ now.toLocaleDateString() } ${ datevalue }`)
: new Date(datevalue);
if (datetime && earliest < datetime.getTime()) {
output = { success: true, date: datetime.toLocaleDateString() };
return;
}
});
});
return output || { success: false, message: "I'm sorry, please enter a date at least an hour out." };
} catch (error) {
return {
success: false,
message: "I'm sorry, I could not interpret that as an appropriate date. Please enter a date at least an hour out."
};
}
}
CustomPromptBot.java
Varování
Vypadá to, že ukázka, kterou hledáte, se přesunula. Buďte ujištěni, že pracujeme na řešení této situace.
bots/custom_prompt_bot.py
def _validate_name(self, user_input: str) -> ValidationResult:
if not user_input:
return ValidationResult(
is_valid=False,
message="Please enter a name that contains at least one character.",
)
return ValidationResult(is_valid=True, value=user_input)
def _validate_age(self, user_input: str) -> ValidationResult:
# Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on.
# The recognizer returns a list of potential recognition results, if any.
results = recognize_number(user_input, Culture.English)
for result in results:
if "value" in result.resolution:
age = int(result.resolution["value"])
if 18 <= age <= 120:
return ValidationResult(is_valid=True, value=age)
return ValidationResult(
is_valid=False, message="Please enter an age between 18 and 120."
)
def _validate_date(self, user_input: str) -> ValidationResult:
try:
# Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "9pm",
# "tomorrow", "Sunday at 5pm", and so on. The recognizer returns a list of potential recognition results,
# if any.
results = recognize_datetime(user_input, Culture.English)
for result in results:
for resolution in result.resolution["values"]:
if "value" in resolution:
now = datetime.now()
value = resolution["value"]
if resolution["type"] == "date":
candidate = datetime.strptime(value, "%Y-%m-%d")
elif resolution["type"] == "time":
candidate = datetime.strptime(value, "%H:%M:%S")
candidate = candidate.replace(
year=now.year, month=now.month, day=now.day
)
else:
candidate = datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
# user response must be more than an hour out
diff = candidate - now
if diff.total_seconds() >= 3600:
return ValidationResult(
is_valid=True,
value=candidate.strftime("%m/%d/%y"),
)
return ValidationResult(
is_valid=False,
message="I'm sorry, please enter a date at least an hour out.",
)
except ValueError:
return ValidationResult(
is_valid=False,
message="I'm sorry, I could not interpret that as an appropriate "
"date. Please enter a date at least an hour out.",
)