How to switch between multiple formflows in Microsoft bot framework? - c#

i have a basic form. Depending on the choices of the user, it will direct it to various formflows. But i am unable to achieve this. It repeats first formFlow again and again in Microsoft BOT framework ?
//These are two forms that i have initiated. If a count is 1 then it must open first formflow otherwise the second formflow.
internal static IDialog<General> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(General.BuildForm));
}
internal static IDialog<ProfileForm> MakeRootDialog1()
{
return Chain.From(() => FormDialog.FromForm(ProfileForm.BuildForm));
}
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message && General.count == 0)
{
await Conversation.SendAsync(activity, MakeRootDialog);
General.count = 1;
}
else if(activity.Type == ActivityTypes.Message && General.count >= 1)
{
await Conversation.SendAsync(activity, MakeRootDialog1);
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}

I was able to repro the problem but I'm still thinking how that could be solved using Chains in the MessageController.
My suggestion to unblock you is to move the "IF" logic for the Forms into a separate dialog. Something like the following:
Controller
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
RootDialog
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (General.count == 0)
{
General.count = 1;
context.Call(FormDialog.FromForm<General>(General.BuildForm, FormOptions.PromptInStart), async (ctx, formResult) => ctx.Wait(this.MessageReceivedAsync));
}
else if (General.count >= 1)
{
context.Call(FormDialog.FromForm<ProfileForm>(ProfileForm.BuildForm, FormOptions.PromptInStart), async (ctx, formResult) => ctx.Wait(this.MessageReceivedAsync));
}
}
}
This is a personal opinion => I prefer to use Dialogs since once the bot start to grow it's easier to follow the logic and to separate the components.

Related

How can I access an objects properties that is wrapped in a Task<>?

I am trying to access a property on the user within UpdateUser(userModelFromRepo), when I change the parameters to accept Task I cannot access the User.modifiedAt property. Is there a way to do so? Alternatively, is there a way to just return the user object back and remain async from GetUserById()? await _repository.UpdateUser(userModelFromRepo) says that it cannot convert from Task User to User.
[HttpPut("{id}")]
public async Task<ActionResult> UpdateUser(int id, UserUpdateDto userUpdateDto)
{
var userModelFromRepo = _repository.GetUserById(id);
if (userModelFromRepo == null)
{
return NotFound();
}
await _mapper.Map(userUpdateDto, userModelFromRepo);
await _repository.UpdateUser(userModelFromRepo);
await _repository.SaveChanges();
return NoContent();
}
public async Task<User> GetUserById(int id)
{
return await _context.User.FirstOrDefaultAsync(u => u.Id == id);
}
public async Task UpdateUser(User userModelFromRepo)
{
await Task.Run(() =>
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
user.ModifiedAt = DateTime.UtcNow;
});
}
You need to await the call like this
var userModelFromRepo = await _repository.GetUserById(id);
Full code:
[HttpPut("{id}")]
public async Task<ActionResult> UpdateUser(int id, UserUpdateDto userUpdateDto)
{
var userModelFromRepo = await _repository.GetUserById(id);
if (userModelFromRepo == null)
{
return NotFound();
}
await _mapper.Map(userUpdateDto, userModelFromRepo);
await _repository.UpdateUser(userModelFromRepo);
await _repository.SaveChanges();
return NoContent();
}
Also, if you don't do anything else in your UpdateUser method you can remove the Task.Run stuff, it doesn't add any value.
public Task UpdateUser(User userModelFromRepo)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
user.ModifiedAt = DateTime.UtcNow;
return Task.CompletedTask;
}

Luis choosing between two intent

If the luis highest intent score for a conversation is 0.15, and the second is 0.14, would it be possible for the bot to ask the user if they meant the first intent or the second intent? If yes how to do so? I've been searching in the documentation samples and there doesn't seem to be any solution except just making more and more utterances so this does not happen; is that correct?
If the luis highest intent score for a conversation is 0.15, and the second is 0.14, would it be possible for the bot to ask the user if they meant the first intent or the second intent? If yes how to do so?
Yes, we can achieve this requirement. The following sample code work for me, you can refer to it.
[Serializable]
public class MyLuisDialog : LuisDialog<object>
{
public MyLuisDialog() : base(new LuisService(new LuisModelAttribute("xxxxxxx",
"xxxxxxx",
domain: "westus.api.cognitive.microsoft.com")))
{
}
//modify Luis request to make it return all intents instead of just the topscoring intent
protected override LuisRequest ModifyLuisRequest(LuisRequest request)
{
request.Verbose = true;
return request;
}
protected override async Task DispatchToIntentHandler(IDialogContext context, IAwaitable<IMessageActivity> item, IntentRecommendation bestIntent, LuisResult result)
{
if (bestIntent.Intent == "FindFood" || bestIntent.Intent == "BuyFood")
{
if (result.Intents[0].Score - result.Intents[1].Score < 0.1)
{
bestIntent.Intent = "FindOrBuyFood";
bestIntent.Score = 1;
}
}
await base.DispatchToIntentHandler(context, item, bestIntent, result);
}
[LuisIntent("Greeting")]
public async Task GreetingIntent(IDialogContext context, LuisResult result)
{
await this.ShowLuisResult(context, result);
}
//...
//other intent handlers
//...
[LuisIntent("FindFood")]
[LuisIntent("BuyFood")]
public async Task FoodIntent(IDialogContext context, LuisResult result)
{
await this.ShowLuisResult(context, result);
}
[LuisIntent("FindOrBuyFood")]
public async Task FindOrBuyFoodIntent(IDialogContext context, LuisResult result)
{
var food = "food";
if (result.Entities.Count() > 0)
{
food = result.Entities[0].Entity;
}
List<string> options = new List<string>() { $"Find {food}", $"Buy {food}" };
PromptDialog.Choice(
context: context,
resume: ChoiceReceivedAsync,
options: options,
prompt: "Hi. Please Select one option :",
retry: "Please try again.",
promptStyle: PromptStyle.Auto
);
}
private async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var option = await result;
//your code logic here
await context.PostAsync($"You selected the '{option}'");
context.Wait(MessageReceived);
}
private async Task ShowLuisResult(IDialogContext context, LuisResult result)
{
await context.PostAsync($"You have reached {result.Intents[0].Intent} intent.");
context.Wait(MessageReceived);
}
}
Test Result:

How to pass the parameter from controller to FormDialog state model

Requirement
FormStateModel already contains FIRST input that users types.
Code
Simply I want to put the string that is in activity.Text inside FormStateModel:
private IDialog<FormStateModel> MakeRootDialog(string input)
{
return Chain.From(() => new FormDialog<FormStateModel>(
new FormStateModel() { Question = input },
ContactDetailsForm.BuildForm,
FormOptions.None));
}
=
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(
toBot: activity,
MakeRoot: () => this.MakeRootDialog(activity.Text));
}
else
{
await HandleSystemMessageAsync(activity);
}
var response = this.Request.CreateResponse(HttpStatusCode.OK);
return response;
}
On ConversationUpdate I start conversation simply by asking "Please type your Question:"
private static async Task<Activity> HandleSystemMessageAsync(Activity message)
{
switch (message.Type)
{
case ActivityTypes.DeleteUserData:
break;
case ActivityTypes.ConversationUpdate:
await Welcome(message);
break;
(...)
In that way:
private static async Task Welcome(Activity activity)
{
(...)
reply.Text = string.Format("Hello, how can we help you today? Please type your Question:");
await client.Conversations.ReplyToActivityAsync(reply);
(...)
}
But I can not find a way how to pass it. In this case this exception occurs:
anonymous method closures that capture the environment are not serializable, consider removing environment capture or using a reflection serialization surrogate:
Is there any way around that to populate state model at this step?
Solved by calling RootDialog inside MessagesController, then Calling new FormDialog by context.Call(form, (...));
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
await Conversation.SendAsync(activity, () => new LayerDialog());
}
LayerDialog:
[Serializable]
public class LayerDialog: IDialog<IMessageActivity>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.OnMessageReceivedAsync);
}
private async Task OnMessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var awaited = await result;
FormStateModel model = new FormStateModel();
model.Value = awaited.Text;
var form = new FormDialog<FormStateModel >(model ,
BuildForm , FormOptions.PromptInStart);
context.Call(form , this.AfterResume);
}

Bot Framework Forward Type Arguments Error

I am getting this following error
when trying to use the MS Bot Framework Example to call a different dialog. This is my code:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace ReadMeBot.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (activity != null && activity.Text.ToLower().Contains("what is"))
{
await
context.Forward(new InternetSearchDialog(), this.ResumeAfterInternetSearchDialog, activity, CancellationToken.None);
}
// calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
// return our reply to the user
await context.PostAsync($"You sent {activity.Text} which was {length} characters. Thank you!");
context.Wait(MessageReceivedAsync);
}
private async Task ResumeAfterInternetSearchDialog(IDialogContext context, IAwaitable<string> result)
{
}
}
}
How can I solve this? I googled around and nobody seems to have this issue. What am I doing wrong?
Since you are forwarding to another dialog, you don't need to wait in this dialog. You'll want to call context.Wait in the resume though.
Things should work as expected if you change your code to something like this:
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (activity != null && activity.Text.ToLower().Contains("what is"))
{
await
context.Forward(new InternetSearchDialog(), this.ResumeAfterInternetSearchDialog, activity, CancellationToken.None);
}
else
{
// calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
// return our reply to the user
await context.PostAsync($"You sent {activity.Text} which was {length} characters. Thank you!");
context.Wait(MessageReceivedAsync);
}
}
private async Task ResumeAfterInternetSearchDialog(IDialogContext context, IAwaitable<string> result)
{
context.Wait(MessageReceivedAsync);
}
}

Get first message sent to the Bot when using Chain

In controller
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
I execute
await Conversation.SendAsync(activity, ()=> MakeJsonRootDialog());
Then in implementation how I get first message sent to BOT?
Object completed contains only fields asked during the conversation:
public static IDialog<JObject> MakeJsonRootDialog(string strDirPath)
{
return Chain.From(() => FormDialog.FromForm(preChatInquery.BuildJsonForm))
.Do(async (context, order) =>
{
try
{
var completed = await order;
await context.PostAsync("Processed your order!");
}
catch (FormCanceledException<JObject> e)
{
string reply;
if (e.InnerException == null)
{
reply = $"You quit on {e.Last}--maybe you can finish next time!";
}
else
{
reply = "Sorry, I've had a short circuit. Please try again.";
}
await context.PostAsync(reply);
}
});
`
The very first thing you need to do when using Chain is to Post the message to the chain, using the PostToChain() method.
Here is an example from the EchoChainDialog sample.
public static readonly IDialog<string> dialog = Chain.PostToChain()
.Select(msg => msg.Text)

Categories