I am working on using the form flow example found Here
The example uses formFlow to help the user pick out the toppings they want on their sandwich.
I'm trying to add a verification step that checks if each of the toppings they add is in stock and if it isn't send an apology message and prompt the user to enter a different topping instead. A code example is seen below:
public static IForm<SandwichOrder> BuildForm()
{
return new FormBuilder<SandwichOrder>()
.Message("Welcome to the sandwich order bot!")
.Field(nameof(Sandwich))
.Field(nameof(Length))
.Field(nameof(Bread))
.Field(nameof(Cheese))
.Field(nameof(Topping),
validate: async (state, value) =>
{
foreach(var t in Topping)
{
if (!isToppinginStock)
{
// Apology message
//Code to ask topping question again
}
}
})
.Message("For sandwich toppings you have selected {Toppings}.")
.Build();
}
If anyone can help or point me in the right direction I would really appreciate it.
Something like the following should work for your scenerio:
.Field(nameof(Toppings),
validate: async (state, value) =>
{
var values = ((List<object>)value).OfType<ToppingOptions>();
var notInStock = GetOutOfStockToppings(values);
if(notInStock.Any())
return new ValidateResult { IsValid = false, Value = null, Feedback = $"These are not in stock: {string.Join(",", notInStock.ToArray())} Please choose again." };
return new ValidateResult { IsValid = true, Value = values};
})
static IEnumerable<ToppingOptions> NotInStock = new[] { ToppingOptions.Lettuce, ToppingOptions.Pickles };
private static IEnumerable<ToppingOptions> GetOutOfStockToppings(IEnumerable<ToppingOptions> toppings)
{
List<ToppingOptions> result = new List<ToppingOptions>();
foreach(var topping in toppings)
{
if (NotInStock.Contains(topping))
result.Add(topping);
}
return result;
}
Related
I'm creating a chatbot in .Net C# using BotFramework. In one of my dialog when i start to fill a form flow i cannot exit from flowform till in the moment i will fill all the flow . Exist any possibility to exit and to leave form ?
This is my code :
LuisDialog.cs :
[LuisIntent("balance")]
public async Task balance(IDialogContext context, LuisResult result)
{
var balanca = new FormDialog<BalanceForm>(
new BalanceForm(),
BalanceForm.BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call<BalanceForm>(balanca, BalanceCompleted);
BalanceForm.cs
namespace BasicMultiDialog
{
[Serializable]
public class BalanceForm
{
[Prompt("What is your contract number?")]
public string contract;
public static IForm<BalanceForm> BuildForm()
{
OnCompletionAsyncDelegate<BalanceForm> wrapUpRequest = async
(context, state) =>
{
string wrapUpMessage = "Dear " + house.Firstname + "," + "your balance is " + house.Balance;
await context.PostAsync(wrapUpMessage);
}
};
return new FormBuilder<BalanceForm>().Message
("We have to ask you some information")
.Field(nameof(contract), validate: async (state, response) =>
{
var result = new ValidateResult();
return result;
}
})
.OnCompletion(wrapUpRequest)
//.Confirm("Are you sure: Yes or No ")
.Build();
}
}
}
Actually it's quite easy to cancel a form. If you type "help" or "choices" you can see a list of builtin form commands, and one of these is "quit." There are many terms you can use to quit such as "finish" or "bye." If you want to define your own terms, you can can configure the form commands like this:
var builder = new FormBuilder<BalanceForm>().Message
("We have to ask you some information")
.Field(nameof(contract), validate: async (state, response) =>
{
var result = new ValidateResult();
return result;
})
.OnCompletion(wrapUpRequest)
// Set the command term configuration on its own line
builder.Configuration.Commands[FormCommand.Quit].Terms = new[] { "exit", "cancel" };
return builder.Build();
Keep in mind that when a form is canceled, a FormCanceledException<T> is thrown. If you don't want this to display a message like "Sorry, my bot code is having an issue," you can catch the exception like this:
var balanca = new FormDialog<BalanceForm>(
new BalanceForm(),
BalanceForm.BuildForm,
FormOptions.PromptInStart,
result.Entities)
.Catch<BalanceForm, FormCanceledException<BalanceForm>>((dialog, ex) =>
{
// Handle the cancellation here and return an IDialog<BalanceForm>
});
I have some 10 properties in a class and based on those properties I have a form that asks for user input. I want to know if there's any mechanism that after initial 4-5 questions I ask user if he/she wants to ans more and if reply is yes then next set of fields/questions are asked.
I tried doing it with SetDefine but issue with SetDefine is that its called with each field so it asks the user to confirm with each fiels but I want it to only confirm with 1st optional field and based on the answer either skip all or get all.
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync($"Welcome to the Order helper!");
var orderFormDialog = FormDialog.FromForm(BuildOrderAdvanceStepSearchForm, FormOptions.PromptInStart);
context.Call(orderFormDialog, ResumeAfterOrdersFormDialog);
}
private IForm<OrderSearchQuery> BuildOrderAdvanceStepSearchForm()
{
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(OrderSearchQuery.ItemNumber))
.Field(nameof(OrderSearchQuery.Draft))
.Field(nameof(OrderSearchQuery.Dispatched))
.Field(nameof(OrderSearchQuery.InTransit))
.Field(nameof(OrderSearchQuery.Delivered))
//.Confirm("Do you want to search with more options?.")
//.Field(nameof(OrderSearchQuery.AddField1))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField1))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField2))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField3))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField4))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField5))
.SetDefine(OrderAdvanceStepConfirmation))
.Build();
}
private static async Task<bool> OrderAdvanceStepConfirmation(OrderSearchQuery state, Field<OrderSearchQuery> field)
{
field.SetPrompt(new PromptAttribute($"Do you want to search with more options?."));
return true;
}
private async Task ResumeAfterordersFormDialog(IDialogContext context, IAwaitable<OrderSearchQuery> result)
{
try
{
var searchQuery = await result;
await context.PostAsync($"Ok. Searching for orders...");
var count = 100;
if (count > 1)
{
await context.PostAsync($"I found total of 100 orders");
await context.PostAsync($"To get order details, you will need to provide more info...");
}
else
{
await context.PostAsync($"I found the order you were looking for...");
await context.PostAsync($"Now I can provide you information related to Consumer Package, Multi-Pack, Shelf Tray & Unit Load for this order.");
}
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the order Search";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
//context.Done<object>(null);
}
}
I want it to only confirm with 1st optional field and based on the answer either skip all or get all.
You can use SetNext of FieldReflector.
For example create a enum for confirmation for example like this:
public enum Confirmation
{
Yes,
No
}
public Confirmation? Corfirm { get; set; }
And then you can build the Form like this:
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(ItemNumber))
.Field(nameof(Draft))
.Field(new FieldReflector<OrderSearchQuery>(nameof(Corfirm))
.SetNext((value, state) =>
{
var selection = (Confirmation)value;
if (selection == Confirmation.Yes)
{
//go to step 1
return new NextStep();
}
else
{
//skip the following steps
state.Stpe1 = null;
state.Step2 = null;
return new NextStep(StepDirection.Complete);
}
})
)
.Field(nameof(Stpe1))
.Field(nameof(Step2)).Build();
I need to import customer related data from legacy DB and perform several transformations during the process. This means a single entry needs to perform additional "events" (synchronize products, create invoices, etc.).
My initial solution was a simple parallel approach.
It works okay, but sometimes it has issues. If the currently processed customers need to wait for the same type of events, their processing queues might got stuck and eventually time out, causing every underlying events to fail too (they depend on the one which failed). It doesn't happen all the time, yet it's annoying.
So I got another idea, work in batches. I mean not only limiting the number of customers being processed at the same time, but also the number of the events which are broadcasted to the queues. While searching around for ideas, I found this answer, which points to the TPL DataFlow.
I made a skeleton to get familiar with it. I set up a simple pipeline, but I'm a bit confused about the usage of Complete() and awaiting Completion().
The steps are the following
Make a list of numbers (the ids of the customers to be imported) - this is outside the import logic, it just there to be able to trigger the rest of the logic
Create a BatchBlock (to be able to limit the number of customers to be processed at the same time)
Create a single MyClass1 item based on the id (TransformBlock<int, MyClass1>)
Perform some logic and generate a collection of MyClass2 (TransformManyBlock<MyClass1, MyClass2>) - as example, sleep for 1 second
Perform some logic on every item of the collection (ActionBlock<MyClass2>) - as example, sleep for 1 second
Here's the full code:
public static class Program
{
private static void Main(string[] args)
{
var batchBlock = new BatchBlock<int>(2);
for (var i = 1; i < 10; i++)
{
batchBlock.Post(i);
}
batchBlock.Complete();
while (batchBlock.TryReceive(null, out var ids))
{
var transformBlock = new TransformBlock<int, MyClass1>(delegate (int id)
{
Console.WriteLine($"TransformBlock(id: {id})");
return new MyClass1(id, "Star Wars");
});
var transformManyBlock = new TransformManyBlock<MyClass1, MyClass2>(delegate (MyClass1 myClass1)
{
Console.WriteLine($"TransformManyBlock(myClass1: {myClass1.Id}|{myClass1.Value})");
Thread.Sleep(1000);
return GetMyClass22Values(myClass1);
});
var actionBlock = new ActionBlock<MyClass2>(delegate (MyClass2 myClass2)
{
Console.WriteLine($"ActionBlock(myClass2: {myClass2.Id}|{myClass2.Value})");
Thread.Sleep(1000);
});
transformBlock.LinkTo(transformManyBlock);
transformManyBlock.LinkTo(actionBlock);
foreach (var id in ids)
{
transformBlock.Post(id);
}
// this is the point when I'm not 100% sure
//transformBlock.Complete();
//transformManyBlock.Complete();
//transformManyBlock.Completion.Wait();
actionBlock.Complete();
actionBlock.Completion.Wait();
}
Console.WriteLine();
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
private static IEnumerable<MyClass2> GetMyClass22Values(MyClass1 myClass1)
{
return new List<MyClass2>
{
new MyClass2(1, myClass1.Id+ " did this"),
new MyClass2(2, myClass1.Id+ " did that"),
new MyClass2(3, myClass1.Id+ " did this again")
};
}
}
public class MyClass1
{
public MyClass1(int id, string value)
{
Id = id;
Value = value;
}
public int Id { get; set; }
public string Value { get; set; }
}
public class MyClass2
{
public MyClass1(int id, string value)
{
Id = id;
Value = value;
}
public int Id { get; set; }
public string Value { get; set; }
}
So the point I struggle with is the end, where I'd need to call Complete() or wait for Completion. I can't seem to find the right combination. I'd like to see an output as follows:
TransformBlock(id: 1)
TransformBlock(id: 2)
TransformManyBlock(myClass1: 1|Star Wars)
TransformManyBlock(myClass1: 2|Star Wars)
ActionBlock(myClass2: 1|1 did this)
ActionBlock(myClass2: 2|1 did that)
ActionBlock(myClass2: 3|1 did this again)
ActionBlock(myClass2: 1|2 did this)
ActionBlock(myClass2: 2|2 did that)
ActionBlock(myClass2: 3|2 did this again)
TransformBlock(id: 3)
TransformBlock(id: 4)
TransformManyBlock(myClass1: 3|Star Wars)
TransformManyBlock(myClass1: 4|Star Wars)
ActionBlock(myClass2: 1|3 did this)
ActionBlock(myClass2: 2|3 did that)
ActionBlock(myClass2: 3|3 did this again)
ActionBlock(myClass2: 1|4 did this)
ActionBlock(myClass2: 2|4 did that)
ActionBlock(myClass2: 3|4 did this again)
[the rest of the items]
Press any key to exit...
Anyone can point me to the right direction?
You're almost there, you need to call Complete on the first block in the pipeline then await Completion on the last block. Then in your links you need to propagate completion like this:
private async static void Main(string[] args) {
var transformBlock = new TransformBlock<int, MyClass1>(delegate (int id)
{
Console.WriteLine($"TransformBlock(id: {id})");
return new MyClass1(id, "Star Wars");
});
var transformManyBlock = new TransformManyBlock<MyClass1, MyClass2>(delegate (MyClass1 myClass1)
{
Console.WriteLine($"TransformManyBlock(myClass1: {myClass1.Id}|{myClass1.Value})");
Thread.Sleep(1000);
return GetMyClass22Values(myClass1);
});
var actionBlock = new ActionBlock<MyClass2>(delegate (MyClass2 myClass2)
{
Console.WriteLine($"ActionBlock(myClass2: {myClass2.Id}|{myClass2.Value})");
Thread.Sleep(1000);
});
//propagate completion
transformBlock.LinkTo(transformManyBlock, new DataflowLinkOptions() { PropagateCompletion = true });
transformManyBlock.LinkTo(actionBlock, new DataflowLinkOptions() { PropagateCompletion = true});
foreach(var id in ids) {
transformBlock.Post(id);
}
//Complete the first block
transformBlock.Complete();
//wait for completion to flow to the last block
await actionBlock.Completion;
}
You can also incorporate the batch block into your pipeline and remove the need for the TryRecieve call but that seems like another part of your flow.
Edit
Example of propagating completion to multiple blocks:
public async static void Main(string[] args) {
var sourceBlock = new BufferBlock<int>();
var processBlock1 = new ActionBlock<int>(i => Console.WriteLine($"Block1 {i}"));
var processBlock2 = new ActionBlock<int>(i => Console.WriteLine($"Block2 {i}"));
sourceBlock.LinkTo(processBlock1);
sourceBlock.LinkTo(processBlock2);
var sourceBlockCompletion = sourceBlock.Completion.ContinueWith(tsk => {
if(!tsk.IsFaulted) {
processBlock1.Complete();
processBlock2.Complete();
} else {
((IDataflowBlock)processBlock1).Fault(tsk.Exception);
((IDataflowBlock)processBlock2).Fault(tsk.Exception);
}
});
//Send some data...
sourceBlock.Complete();
await Task.WhenAll(sourceBlockCompletion, processBlock1.Completion, processBlock2.Completion);
}
We're experimenting with the MS Bot Framework and haven't quite worked out how to do this scenario:
We have a LUIS Dialog (type <object>), which is working correctly and is trained properly. To use the common sandwich example, the basics of what LUIS intent is looking for is the user asking for the status of an order. If the order number was provided in the question ("What is the status of order 1234?"), then the LUIS dialog does the lookup and reports the status directly (which is all currently working).
However, if the user just triggers the intent without providing the order number ("I'd like to look up the status of an order."), I'd like to launch another dialog/form to ask the user if they'd like to look up the order by address or order number, and then do the appropriate DB lookup based on how they answer.
I'm just not sure how to configure the Form/Dialog (or even which is best in this case) to do a different lookup based on if they choose address or number lookup.
Here's the intent so far:
private readonly BuildFormDelegate<OrderStatusDialog> OrderStatusDelegate;
[LuisIntent(nameof(LuisIntents.OrderStatus))]
public async Task OrderStatus(IDialogContext context, LuisResult result)
{
// Order number(s) were provided
if (result.Entities.Any(Entity => Entity.Type == nameof(LuisEntityTypes.OrderNumber)))
{
// Loop in case they asked about multiple orders
foreach (var entity in result.Entities.Where(Entity => Entity.Type == nameof(LuisEntityTypes.OrderNumber)))
{
var orderNum = entity.Entity;
// Call webservice to check status
var request = new RestRequest(Properties.Settings.Default.GetOrderByNum, Method.GET);
request.AddUrlSegment("num", orderNum);
var response = await RestHelper.SendRestRequestAsync(request);
var parsedResponse = JObject.Parse(response);
if ((bool)parsedResponse["errored"])
{
await context.PostAsync((string)parsedResponse["errMsg"]);
continue;
}
// Grab status from returned JSON
var status = parsedResponse["orderStatus"].ToString();
await context.PostAsync($"The status of order {orderNum} is {status}");
}
context.Wait(MessageReceived);
}
// Order number was not provided
else
{
var orderStatusForm = new FormDialog<OrderStatusDialog>(new OrderStatusDialog(), OrderStatusDelegate,
FormOptions.PromptInStart);
context.Call<OrderStatusDialog>(orderStatusForm, CallBack);
}
}
private async Task CallBack(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceived);
}
And the form:
public enum OrderStatusLookupOptions
{
Address,
OrderNumber
}
[Serializable]
public class OrderStatusDialog
{
public OrderStatusLookupOptions? LookupOption;
public static IForm<OrderStatusDialog> BuildForm()
{
return new FormBuilder<OrderStatusDialog>()
.Message("In order to look up the status of a order, we will first need either the order number or your delivery address.")
.Build();
}
}
The FormFlow route is a valid option. What is missing in your form flow is asking for the address/order number after the lookup option is selected.
What you can do in that case is adding two more fields to the OrderStatusDialog class: OrderNumber and DeliveryAddress.
Then you need to use the selected OrderStatusLookupOptions to activate/deactivate the next field.
The code, from the top of my head, would be something like:
[Serializable]
public class OrderStatusDialog
{
public OrderStatusLookupOptions? LookupOption;
public int OrderNumber;
public string DeliveryAddress
public static IForm<OrderStatusDialog> BuildForm()
{
return new FormBuilder<OrderStatusDialog>()
.Message("In order to look up the status of a order, we will first need either the order number or your delivery address.")
.Field(nameof(OrderStatusDialog.LookupOption))
.Field(new FieldReflector<OrderStatusDialog>(nameof(OrderStatusDialog.OrderNumber))
.SetActive(state => state.LookupOption == OrderStatusLookupOptions.OrderNumber))
.Field(new FieldReflector<OrderStatusDialog>(nameof(OrderStatusDialog.DeliveryAddress))
.SetActive(state => state.LookupOption == OrderStatusLookupOptions.Address))
.Build();
}
}
Then on your Callback method you will receive the form filled and you can do the DB lookup.
Alternatively, you can just use PromptDialogs and guide the user through the same experience. Take a look to the MultiDialogs sample to see the different alternatives.
I added a working sample on this here.
I have created a bot, having a FormFlow in it. Now if you type I want to launch a product, LUIS will tell which dialog it has to go to :
internal static IDialog<AssesmentHelper> CreateProduct()
{
return Chain.From(() => FormDialog.FromForm(AssesmentHelper.BuildForm))
.Do(async (context, profileForm) =>
{
try
{
var completed = await profileForm;
}
catch (FormCanceledException<AssesmentHelper> e)
{
string reply;
if (e.InnerException == null)
{
reply = $"You quit on {e.Last}--maybe you can finish next time!";
}
else
{
reply = Responses.ShortCircuit;
}
await context.PostAsync(reply);
}
});
}
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
stLuis = await LuisHelper.ParseUserInput(activity.Text);
switch (stLuis.topScoringIntent.intent)
{
case "CreateProduct":
await Conversation.SendAsync(activity, CreateProduct);
break;
case "EditProduct":
await Conversation.SendAsync(activity, EditProduct);
break;
case "None":
break;
default:
break;
}
}
Now, once it enters dialog, it asks user to select numbers:
Please select numbers:
Azure
Windows
Now if I reply 1,2. Luis returns it as None intent, so my message does not go to the corresponding dialog. It always go to None case.
The code for dialog is:
public static IForm<AssesmentHelper> BuildForm()
{
return new FormBuilder<AssesmentHelper>()
.Message(Responses.NumberSelection)
.Field(nameof(Program))
.Field(nameof(Product))
.Build();
}
Enum for program and product:
[Serializable]
public enum Program
{
None = 0,
A = 1,
};
[Serializable]
public enum Product
{
None = 0,
Azure = 1,
Windows = 2
};
As soon as I enter this dialog, It asks me to select number for selecting program. Now if i select 1,2. Luis returns it as None intent. So Case "None", is executed.
What I want is, redirect the control to same dialog. I have similar dialog for Edit Product as well. That's why I cannot train my luis app to understand numbers as Product Intent. Otherwise whenever i select number for Edit Product, it will go to different case.
Earlier it was identifying correct intents somehow, but today i republished my luis app and it stopped identifying.