Regarding the Microsoft Bot Framework, we all know the samples given by Microsoft. Those samples, however, normally have "one single purpose", that is, the Pizzabot is only for ordering Pizzas, and so on.
Thing is, I was hoping on creating a more complex Bot that actually answers a series of things. For this I am creating a "lobby" dialog where all the messages go, using this MessageController:
return await Conversation.SendAsync(message, () => new LobbyDialog());
On that "Lobby" dialog I have a series of LUIS intents for different things, and since it picks the Task based on the intent, it works nicely.
However, for more complex operations, I was hoping on using the FormFlow mechanism so I can have forms like in the PizzaBot sample. The problem is that all of the "form bots" that are sampled always use this message controller type:
return Chain.From(() => new PizzaOrderDialog(BuildForm)
And in the same MessagesController stablishes the builder flow, like this:
var builder = new FormBuilder<PizzaOrder>();
ActiveDelegate<PizzaOrder> isBYO = (pizza) => pizza.Kind == PizzaOptions.BYOPizza;
ActiveDelegate<PizzaOrder> isSignature = (pizza) => pizza.Kind == PizzaOptions.SignaturePizza;
ActiveDelegate<PizzaOrder> isGourmet = (pizza) => pizza.Kind == PizzaOptions.GourmetDelitePizza;
ActiveDelegate<PizzaOrder> isStuffed = (pizza) => pizza.Kind == PizzaOptions.StuffedPizza;
return builder
// .Field(nameof(PizzaOrder.Choice))
.Field(nameof(PizzaOrder.Size))
.Field(nameof(PizzaOrder.Kind))
.Field("BYO.Crust", isBYO)
.Field("BYO.Sauce", isBYO)
.Field("BYO.Toppings", isBYO)
.Field(nameof(PizzaOrder.GourmetDelite), isGourmet)
.Field(nameof(PizzaOrder.Signature), isSignature)
.Field(nameof(PizzaOrder.Stuffed), isStuffed)
.AddRemainingFields()
.Confirm("Would you like a {Size}, {BYO.Crust} crust, {BYO.Sauce}, {BYO.Toppings} pizza?", isBYO)
.Confirm("Would you like a {Size}, {&Signature} {Signature} pizza?", isSignature, dependencies: new string[] { "Size", "Kind", "Signature" })
.Confirm("Would you like a {Size}, {&GourmetDelite} {GourmetDelite} pizza?", isGourmet)
.Confirm("Would you like a {Size}, {&Stuffed} {Stuffed} pizza?", isStuffed)
.Build()
;
My big question here is, is it possible to start the conversation with the MessagesController that I used and then in the LobbyDialog, use an Intent that fires a Form and returns it? That is, start a flow from a dialog? Or is better to use DialogChains for that?
Because, from what I tried, it appears that I can ONLY do forms if they are called from teh MessagesController class with the methods I described, that is, how Microsoft sampled it in the Pizzabot.
I appreciate any help or input on the matter. Thanks for your time.
Sure you can! Instantiating a form from a dialog is a pretty common scenario. To accomplish that you can do the following inside the LUIS intent method:
var form = new FormDialog<YourFormModel>(
<ExistingModel>,
<TheMethodThatBuildTheForm>,
FormOptions.PromptInStart,
result.Entities);
context.Call(form, <ResumeAfterCallback>);
using the PizzaBot sample, it should looks like:
var form = new FormDialog<PizzaOrder>(
null,
BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call(form, <ResumeAfterCallback>);
In the ResumeAfterCallback you will usually get the result of the form, catch exceptions and perform a context.Wait so the dialog can keep receiving messages. Below a quick example:
private async Task ResumeAfterCallback(IDialogContext context,
IAwaitable<PizzaOrder> result)
{
try
{
var pizzaOrder = await result;
// do something with the pizzaOrder
context.Wait(this.MessageReceived);
}
catch (FormCanceledException<PizzaOrder> e)
{
string reply;
if (e.InnerException == null)
{
reply = "You have canceled the operation. What would you like to do next?";
}
else
{
reply = $"Oops! Something went wrong :(. Technical Details: {e.InnerException.Message}";
}
await context.PostAsync(reply);
context.Wait(this.MessageReceived);
}
}
Related
In the screenshot below, I have got an utterance conflict, which is obvious because I am using similar patterns of samples in both the utterances.
My question is, the skill I am developing requires similar kind of patterns in multiple utterances and I cannot force users to say something like “Yes I want to continue”, or “I want to store…”, something like this.
In such a scenario what is the best practice to avoid utterance conflicts and that too having the multiple similar patterns?
I can use a single utterance and based on what a user says, I can decide what to do.
Here is an example of what I have in my mind:
User says something against {note}
In the skill I check this:
if(this$inputs.note.value === "no") {
// auto route to stop intent
} else if(this$inputs.note.value === "yes") {
// stays inside the same intent
} else {
// does the database stuff and saves the value.
// then asks the user whether he wants to continue
}
The above loop continues until the user says “no”.
But is this the right way to do it? If not, what is the best practice?
Please suggest.
The issue is really that for those two intents you have slots with no context around them. I'm also assuming you're using these slots as catch-all slots meaning you want to capture everything the person says.
From experience: this is very difficult/annoying to implement and will not result in a good user experience.
For the HaveMoreNotesIntent what you want to do is have a separate YesIntent and NoIntent and then route the user to the correct function/intent based on the intent history (aka context). You'll have to just enable this in your config file.
YesIntent() {
console.log(this.$user.$context.prev[0].request.intent);
// Check if last intent was either of the following
if (
['TutorialState.TutorialStartIntent', 'TutorialLearnIntent'].includes(
this.$user.$context.prev[0].request.intent
)
) {
return this.toStateIntent('TutorialState', 'TutorialTrainIntent');
} else {
return this.toStateIntent('TutorialState', 'TutorialLearnIntent');
}
}
OR if you are inside a state you can have yes and no intents inside that state that will only work in that state.
ISPBuyState: {
async _buySpecificPack() {
console.log('_buySpecificPack');
this.$speech.addText(
'Right now I have a "sports expansion pack". Would you like to hear more about it?'
);
return this.ask(this.$speech);
},
async YesIntent() {
console.log('ISPBuyState.YesIntent');
this.$session.$data.productReferenceName = 'sports';
return this.toStatelessIntent('buy_intent');
},
async NoIntent() {
console.log('ISPBuyState.NoIntent');
return this.toStatelessIntent('LAUNCH');
},
async CancelIntent() {
console.log('ISPBuyState.CancelIntent()');
return this.toStatelessIntent('LAUNCH');
}
}
I hope this helps!
I've got a prompt for an SMS bot in which the user can make multiple choices. I'm looking for a pattern for a ChoicePrompt that allows me to do this:
show multiple selections
then after the user selects and answer, re-prompt them to answer again
Remove their previous choice(s) and add an "exit" option to move on
Automatically end the step if they've selected everything.
I'd like to avoid creating a new prompt w/switch cases for each answer tier, as this pattern needs to be implemented in a lot of places...
Example:
bot: User, what do you do to relax?
Exercise
Read a book
Nothing
user: Exercise
bot: Exercise, cool. What else?
Read a book
Nothing else
user: Read a book
bot: OK, you've done everything so we're moving on!
The botframework don't have a ListPrompt that I can see, at least for v4. They do however, have Suggested Actions you can use for this!!! The Botbuilder-Samples repo has a Suggested Action sample that shows a list of three colors:
async onTurn(turnContext) {
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
if (turnContext.activity.type === ActivityTypes.Message) {
const text = turnContext.activity.text;
// Create an array with the valid color options.
const validColors = ['Red', 'Blue', 'Yellow'];
// If the `text` is in the Array, a valid color was selected and send agreement.
if (validColors.includes(text)) {
await turnContext.sendActivity(`I agree, ${ text } is the best color.`);
} else {
await turnContext.sendActivity('Please select a color.');
}
// After the bot has responded send the suggested actions.
await this.sendSuggestedActions(turnContext);
} else if (turnContext.activity.type === ActivityTypes.ConversationUpdate) {
await this.sendWelcomeMessage(turnContext);
} else {
await turnContext.sendActivity(`[${ turnContext.activity.type } event detected.]`);
}
}
An option would be to programatically create the array (in the example above, it's "const validColors") and if the reply is in the list of colors, recreate the array however you want without the chosen option.
I have a custom module that uses Kentico API (DocumentHelper) to update certain fields of my document and then publish but I do not want it to trigger the event handlers that are linked to my document page type. I tried adding comments to .Publish("admin_edit") hoping that I can catch it from the WorkflowEventargs parameter but the VersionComment property always return null. Is there a way to accomplish this in Kentico?
update field:
var document = DocumentHelper.GetDocument(documentID, tree);
var workflowManager = WorkflowManager.GetInstance(tree);
var workflow = workflowManager.GetNodeWorkflow(document);
if (workflow != null)
{
document.CheckOut();
document.SetValue("SomeFIeld", "some value");
document.Update(true);
document.CheckIn();
document.Publish("admin_edit");
}
event handler:
public override void Init()
{
WorkflowEvents.Publish.After += Publish_After;
}
private void Publish_After(object sender, WorkflowEventArgs e)
{
if (!string.IsNullOrEmpty(e.VersionComment) &&
e.VersionComment.Contains("admin_edit"))
return;
}
You always get null for Version information, because that is related to the 'Page versioning' events, specially for 'SaveVersion'. You can find more about that on this link. If you expand 'Properties' you will see which properties are populated for the specific event. In your case, you can try something like this, to add your message for last version and then check for that comment on 'Publish_After' event, see code bellow:
var document = DocumentHelper.GetDocument(documentID, tree);
var workflowManager = WorkflowManager.GetInstance(tree);
var workflow = workflowManager.GetNodeWorkflow(document);
if (workflow != null)
{
document.CheckOut();
document.SetValue("SomeFIeld", "some value");
document.Update(true);
document.CheckIn(versionComment: "admin_edit");
document.Publish();
}
and then, in event handler, take last version and check for comment like this:
if (e.PublishedDocument?.VersionHistory?.Count > 0)
{
var lastVersion = e.PublishedDocument.VersionHistory[0] as VersionHistoryInfo;
if (lastVersion.VersionComment.Equals("admin_edit"))
{
return;
}
}
NOTE: In case that you have a lot of concurrent content editors, there is a chance that your last version is not version from API (someone changed content and saved it right after your API call made change). There is a low chance for that, but still is possible. If this is something that you will use often, you must take it in consideration. This code is tested for Kentico 11.
if (!meeting.location) {
builder.Prompts.text(session, 'where is the location');
} else {
next();
}
This is a part of my node.js code where my bot tries to identify the location in the first message , if he doesn't find it he takes as location any value the user gives, might be as random as "83748yhgsdh".
My question is how can i allow my system to check the user input at each step and only take reasonable ones.
You can probably manually call the LUIS recognizer
e.g:
var recognizer = new builder.LuisRecognizer(model);
recognizer.recognize(
"hello",
model,
function (err, intents, entities) {
console.log(intents);
}
)
I'm trying to make my MVC4-website check to see if people should be alerted with an email because they haven't done something.
I'm having a hard time figuring out how to approach this. I checked if the shared hosting platform would allow me to activate some sort of cronjob, but this is not available.
So now my idea is to perform this check on each page-request, which already seems suboptimal (because of the overhead). But I thought that with using an async it would not be in the way of people just visiting the site.
I first tried to do this in the Application_BeginRequest method in Global.asax, but then it gets called multiple times per page-request, so that didn't work.
Next I found that I can make a Global Filter which executes on OnResultExecuted, which would seemed promising, but still it's no go.
The problem I get there is that I'm using MVCMailer to send the mails, and when I execute it I get the error: {"Value cannot be null.\r\nParameter name: httpContext"}
This probably means that mailer needs the context.
The code I now have in my global filter is the following:
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
HandleEmptyProfileAlerts();
}
private void HandleEmptyProfileAlerts()
{
new Thread(() =>
{
bool active = false;
new UserMailer().AlertFirst("bla#bla.com").Send();
DB db = new DB();
DateTime CutoffDate = DateTime.Now.AddDays(-5);
var ProfilesToAlert = db.UserProfiles.Where(x => x.CreatedOn < CutoffDate && !x.ProfileActive && x.AlertsSent.Where(y => y.AlertType == "First").Count() == 0).ToList();
foreach (UserProfile up in ProfilesToAlert)
{
if (active)
{
new UserMailer().AlertFirst(up.UserName).Send();
up.AlertsSent.Add(new UserAlert { AlertType = "First", DateSent = DateTime.Now, UserProfileID = up.UserId });
}
else
System.Diagnostics.Debug.WriteLine(up.UserName);
}
db.SaveChanges();
}).Start();
}
So my question is, am I going about this the right way, and if so, how can I make sure that MVCMailer gets the right context?
The usual way to do this kind of thing is to have a single background thread that periodically does the checks you're interested in.
You would start the thread from Application_Start(). It's common to use a database to queue and store work items, although it can also be done in memory if it's better for your app.