봇과 사용자 간의 대화에는 사용자에게 정보를 요청(확인)하고, 사용자의 응답을 구문 분석한 다음, 해당 정보에 대한 작업을 수행하는 경우가 많습니다. 봇은 대화의 컨텍스트를 추적하여 해당 동작을 관리하고 이전의 질문에 대한 답변을 기억할 수 있어야 합니다. 봇의 상태는 들어오는 메시지에 적절하게 응답하기 위해 추적하는 정보입니다.
샘플 봇은 사용자에게 일련의 질문을 하고, 몇 가지 답변의 유효성을 검사하고, 입력을 저장합니다. 다음 다이어그램은 봇, 사용자 프로필 및 대화 흐름 클래스 간의 관계를 보여 줍니다.
사용자 상태는 사용자의 이름, 연령 및 선택한 날짜를 추적하고 대화 상태는 사용자에게 마지막으로 요청한 내용을 추적합니다.
이 봇을 배포할 계획이 없으므로 메모리 스토리지를 사용하도록 사용자 및 대화 상태를 구성합니다.
봇의 메시지 턴 처리기와 사용자 및 대화 상태 속성을 사용하여 대화 흐름 및 입력 컬렉션을 관리합니다. 봇에서 메시지 턴 처리기를 반복할 때마다 받은 상태 속성 정보를 기록합니다.
시작할 때 사용자 및 대화 상태 개체를 만들고 봇 생성자의 종속성 주입을 통해 사용합니다.
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>();
Bots/CustomPromptBot.cs
private readonly BotState _userState;
private readonly BotState _conversationState;
public CustomPromptBot(ConversationState conversationState, UserState userState)
{
_conversationState = conversationState;
_userState = userState;
}
index.js에서 사용자 및 대화 상태 개체를 만들고 봇 생성자에서 사용합니다.
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
봇/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;
Spring 컨테이너에서 제공하는 ConversationState 및 UserState 인스턴스를 사용하여 getBot 메서드에서 CustomPromptBot을 생성합니다. CustomPromptBot의 생성자는 시작 중에 제공된 ConversationState 및 UserState에 대한 참조를 저장합니다.
Application.java
@Bean
public Bot getBot(
ConversationState conversationState,
UserState userState
) {
return new CustomPromptBot(conversationState, userState);
}
CustomPromptBot.java
private final BotState userState;
private final BotState conversationState;
public CustomPromptBot(ConversationState conversationState, UserState userState) {
this.conversationState = conversationState;
this.userState = userState;
app.py 사용자 및 대화 상태 개체를 만들고 봇 생성자에서 사용합니다.
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
사용자 프로필 및 대화 흐름 속성에 대한 속성 접근자를 만든 다음, GetAsync
을 호출하여 상태에서 속성 값을 검색합니다.
Bots/CustomPromptBot.cs
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);
턴이 끝나기 전에 스토리지에 상태 변경 내용을 기록하도록 호출 SaveChangesAsync
합니다.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}
사용자 프로필 및 대화 흐름 속성에 대한 속성 접근자를 만든 다음, get
을 호출하여 상태에서 속성 값을 검색합니다.
봇/customPromptBot.js
this.onMessage(async (turnContext, next) => {
const flow = await this.conversationFlow.get(turnContext, { lastQuestionAsked: question.none });
const profile = await this.userProfile.get(turnContext, {});
턴이 끝나기 전에 스토리지에 상태 변경 내용을 기록하도록 호출 saveChanges
합니다.
/**
* 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);
}
사용자 프로필 및 대화 흐름 속성에 대한 속성 접근자를 만든 다음, get
을 호출하여 상태에서 속성 값을 검색합니다.
CustomPromptBot.java
@Override
protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
StatePropertyAccessor<ConversationFlow> conversationStateAccessors =
conversationState.createProperty("ConversationFlow");
StatePropertyAccessor<UserProfile> userStateAccessors = userState.createProperty("UserProfile");
return userStateAccessors.get(turnContext, () -> new UserProfile()).thenCompose(profile -> {
return conversationStateAccessors.get(turnContext, () -> new ConversationFlow()).thenCompose(flow -> {
return fillOutUserProfile(flow, profile, turnContext);
});
턴이 끝나기 전에 스토리지에 상태 변경 내용을 기록하도록 호출 saveChanges
합니다.
})
.thenCompose(result -> conversationState.saveChanges(turnContext))
.thenCompose(result -> userState.saveChanges(turnContext));
생성자에서 상태 속성 접근자를 만들고 대화에 대한 상태 관리 개체(위에서 만든)를 설정합니다.
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)
턴이 끝나기 전에 스토리지에 상태 변경 내용을 기록하도록 호출 SaveChangesAsync
합니다.
# Save changes to UserState and ConversationState
await self.conversation_state.save_changes(turn_context)
await self.user_state.save_changes(turn_context)
메시지 작업을 처리할 때 메시지 처리기는 도우미 메서드를 사용하여 대화를 관리하고 사용자에게 메시지를 표시합니다. 도우미 메서드는 다음 섹션에 설명되어 있습니다.
Bots/CustomPromptBot.cs
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);
}
봇/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
@Override
protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) {
StatePropertyAccessor<ConversationFlow> conversationStateAccessors =
conversationState.createProperty("ConversationFlow");
StatePropertyAccessor<UserProfile> userStateAccessors = userState.createProperty("UserProfile");
return userStateAccessors.get(turnContext, () -> new UserProfile()).thenCompose(profile -> {
return conversationStateAccessors.get(turnContext, () -> new ConversationFlow()).thenCompose(flow -> {
return fillOutUserProfile(flow, profile, turnContext);
});
})
.thenCompose(result -> conversationState.saveChanges(turnContext))
.thenCompose(result -> userState.saveChanges(turnContext));
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)
봇은 이전 턴에서 봇이 요청한 질문에 따라 사용자에게 정보를 묻는 메시지를 표시합니다. 입력은 유효성 검사 방법을 사용하여 구문 분석됩니다.
각 유효성 검사 메서드는 유사한 디자인을 따릅니다.
유효성 검사 메서드는 다음 섹션에 설명되어 있습니다.
Bots/CustomPromptBot.cs
{
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;
}
}
}
봇/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
private static CompletableFuture<Void> fillOutUserProfile(ConversationFlow flow,
UserProfile profile,
TurnContext turnContext) {
String input = "";
if (StringUtils.isNotBlank(turnContext.getActivity().getText())) {
input = turnContext.getActivity().getText().trim();
}
switch (flow.getLastQuestionAsked()) {
case None:
return turnContext.sendActivity("Let's get started. What is your name?", null, null)
.thenRun(() -> {flow.setLastQuestionAsked(ConversationFlow.Question.Name);});
case Name:
Triple<Boolean, String, String> nameValidationResult = validateName(input);
if (nameValidationResult.getLeft()) {
profile.setName(nameValidationResult.getMiddle());
return turnContext.sendActivity(String.format("Hi %s.", profile.getName()), null, null)
.thenCompose(result -> turnContext.sendActivity("How old are you?", null, null))
.thenRun(() -> { flow.setLastQuestionAsked(ConversationFlow.Question.Age); });
} else {
if (StringUtils.isNotBlank(nameValidationResult.getRight())) {
return turnContext.sendActivity(nameValidationResult.getRight(), null, null)
.thenApply(result -> null);
} else {
return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null)
.thenApply(result -> null);
}
}
case Age:
Triple<Boolean, Integer, String> ageValidationResult = ValidateAge(input);
if (ageValidationResult.getLeft()) {
profile.setAge(ageValidationResult.getMiddle());
return turnContext.sendActivity(String.format("I have your age as %d.", profile.getAge()), null, null)
.thenCompose(result -> turnContext.sendActivity("When is your flight?", null, null))
.thenRun(() -> { flow.setLastQuestionAsked(ConversationFlow.Question.Date); });
} else {
if (StringUtils.isNotBlank(ageValidationResult.getRight())) {
return turnContext.sendActivity(ageValidationResult.getRight(), null, null)
.thenApply(result -> null);
} else {
return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null)
.thenApply(result -> null);
}
}
case Date:
Triple<Boolean, String, String> dateValidationResult = ValidateDate(input);
AtomicReference<UserProfile> profileReference = new AtomicReference<UserProfile>(profile);
if (dateValidationResult.getLeft()) {
profile.setDate(dateValidationResult.getMiddle());
return turnContext.sendActivity(
String.format("Your cab ride to the airport is scheduled for %s.",
profileReference.get().getDate()))
.thenCompose(result -> turnContext.sendActivity(
String.format("Thanks for completing the booking %s.", profileReference.get().getDate())))
.thenCompose(result -> turnContext.sendActivity("Type anything to run the bot again."))
.thenRun(() -> {
flow.setLastQuestionAsked(ConversationFlow.Question.None);
profileReference.set(new UserProfile());
});
} else {
if (StringUtils.isNotBlank(dateValidationResult.getRight())) {
return turnContext.sendActivity(dateValidationResult.getRight(), null, null)
.thenApply(result -> null);
} else {
return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null)
.thenApply(result -> null);
}
}
default:
return CompletableFuture.completedFuture(null);
}
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
봇은 다음 조건을 사용하여 입력의 유효성을 검사합니다.
Bots/CustomPromptBot.cs
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;
}
봇/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
private static Triple<Boolean, String, String> validateName(String input) {
String name = null;
String message = null;
if (StringUtils.isEmpty(input)) {
message = "Please enter a name that contains at least one character.";
} else {
name = input.trim();
}
return Triple.of(StringUtils.isBlank(message), name, message);
}
private static Triple<Boolean, Integer, String> ValidateAge(String input) {
int age = 0;
String 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.
List<ModelResult> results = NumberRecognizer.recognizeNumber(input, PromptCultureModels.ENGLISH_CULTURE);
for (ModelResult result : results) {
// The result resolution is a dictionary, where the "value" entry contains the processed String.
Object value = result.resolution.get("value");
if (value != null) {
age = Integer.parseInt((String) value);
if (age >= 18 && age <= 120) {
return Triple.of(true, age, "");
}
}
}
message = "Please enter an age between 18 and 120.";
}
catch (Throwable th) {
message = "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120.";
}
return Triple.of(StringUtils.isBlank(message), age, message);
}
private static Triple<Boolean, String, String> ValidateDate(String input) {
String date = null;
String 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 {
List<ModelResult> results = DateTimeRecognizer.recognizeDateTime(input, PromptCultureModels.ENGLISH_CULTURE);
// 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.
LocalDateTime earliest = LocalDateTime.now().plus(1, ChronoUnit.HOURS);
for (ModelResult result : results) {
// The result resolution is a dictionary, where the "values" entry contains the processed input.
List<Map<String, Object>> resolutions = (List<Map<String, Object>>) result.resolution.get("values");
for (Map<String, Object> resolution : 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.
String dateString = (String) resolution.get("value");
if (StringUtils.isBlank(dateString)) {
dateString = (String) resolution.get("start");
}
if (StringUtils.isNotBlank(dateString)){
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime candidate;
try {
candidate = LocalDateTime.from(f.parse(dateString));
} catch (DateTimeParseException err) {
// If the input is a date, it will throw an exception and it will create a datetime
// with the MIN localtime
DateTimeFormatter d = DateTimeFormatter.ofPattern("yyyy-MM-dd");
candidate = LocalDateTime.of(LocalDate.parse(dateString, d), LocalDateTime.MIN.toLocalTime());
}
if (earliest.isBefore(candidate)) {
DateTimeFormatter dateformat = DateTimeFormatter.ofPattern("MM-dd-yyyy");
date = candidate.format(dateformat);
return Triple.of(true, date, message);
}
}
}
}
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.",
)