I'm starting a project for a ChatBot in C# with the bot framework.
I choose the ContosoFlowers example for learning about the use of bot framework. In the AddressDialog users cannot quit the dialog after they enter to it without providing an address.
How can I update the code so when users reply "Cancel" or "Abort" or "B" or "Back" they quit the dialog?
namespace ContosoFlowers.BotAssets.Dialogs
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Extensions;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using Properties;
using Services;
[Serializable]
public class AddressDialog : IDialog<string>
{
private readonly string prompt;
private readonly ILocationService locationService;
private string currentAddress;
public AddressDialog(string prompt, ILocationService locationService)
{
this.prompt = prompt;
this.locationService = locationService;
}
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync(this.prompt);
context.Wait(this.MessageReceivedAsync);
}
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
var addresses = await this.locationService.ParseAddressAsync(message.Text);
if (addresses.Count() == 0)
{
await context.PostAsync(Resources.AddressDialog_EnterAddressAgain);
context.Wait(this.MessageReceivedAsync);
}
else if (addresses.Count() == 1)
{
this.currentAddress = addresses.First();
PromptDialog.Choice(context, this.AfterAddressChoice, new[] { Resources.AddressDialog_Confirm, Resources.AddressDialog_Edit }, this.currentAddress);
}
else
{
var reply = context.MakeMessage();
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
foreach (var address in addresses)
{
reply.AddHeroCard(Resources.AddressDialog_DidYouMean, address, new[] { new KeyValuePair<string, string>(Resources.AddressDialog_UseThisAddress, address) });
}
await context.PostAsync(reply);
context.Wait(this.MessageReceivedAsync);
}
}
private async Task AfterAddressChoice(IDialogContext context, IAwaitable<string> result)
{
try
{
var choice = await result;
if (choice == Resources.AddressDialog_Edit)
{
await this.StartAsync(context);
}
else
{
context.Done(this.currentAddress);
}
}
catch (TooManyAttemptsException)
{
throw;
}
}
}
}
You just need to handle the quit conditions at the top of the MessageReceivedAsync method.
At the top of the dialog you can add something like
private static IEnumerable<string> cancelTerms = new[] { "Cancel", "Back", "B", "Abort" };
And also add this method:
public static bool IsCancel(string text)
{
return cancelTerms.Any(t => string.Equals(t, text, StringComparison.CurrentCultureIgnoreCase));
}
Then is just a matter of understanding if the message sent by the user matches any of the cancelation terms. In the MessageReceivedAsync method do something like:
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
if (IsCancel(message.Text))
{
context.Done<string>(null);
}
// rest of the code of this method..
}
You can also go a bit more generic and create a CancelableIDialog similar to what was done in the CancelablePromptChoice.
Related
I've tried to define a gRPC service where client can subscribe to receive broadcasted messages and they can also send them.
syntax = "proto3";
package Messenger;
service MessengerService {
rpc SubscribeForMessages(User) returns (stream Message) {}
rpc SendMessage(Message) returns (Close) {}
}
message User {
string displayName = 1;
}
message Message {
User from = 1;
string message = 2;
}
message Close {}
My idea was that when a client requests to subscribe to the messages, the response stream would be added to a collection of response streams, and when a message is sent, the message is sent through all the response streams.
However, when my server attempts to write to the response streams, I get an exception System.InvalidOperationException: 'Response stream has already been completed.'
Is there any way to tell the server to keep the streams open so that new messages can be sent through them? Or is this not something that gRPC was designed for and a different technology should be used?
The end goal service would be allows multiple types of subscriptions (could be to new messages, weather updates, etc...) through different clients written in different languages (C#, Java, etc...). The different languages part is mainly the reason I chose gRPC to try this, although I intend on writing the server in C#.
Implementation example
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Messenger;
namespace SimpleGrpcTestStream
{
/*
Dependencies
Install-Package Google.Protobuf
Install-Package Grpc
Install-Package Grpc.Tools
Install-Package System.Interactive.Async
Install-Package System.Linq.Async
*/
internal static class Program
{
private static void Main()
{
var messengerServer = new MessengerServer();
messengerServer.Start();
var channel = Common.GetNewInsecureChannel();
var client = new MessengerService.MessengerServiceClient(channel);
var clientUser = Common.GetUser("Client");
var otherUser = Common.GetUser("Other");
var cancelClientSubscription = AddCancellableMessageSubscription(client, clientUser);
var cancelOtherSubscription = AddCancellableMessageSubscription(client, otherUser);
client.SendMessage(new Message { From = clientUser, Message_ = "Hello" });
client.SendMessage(new Message { From = otherUser, Message_ = "World" });
client.SendMessage(new Message { From = clientUser, Message_ = "Whoop" });
cancelClientSubscription.Cancel();
cancelOtherSubscription.Cancel();
channel.ShutdownAsync().Wait();
messengerServer.ShutDown().Wait();
}
private static CancellationTokenSource AddCancellableMessageSubscription(
MessengerService.MessengerServiceClient client,
User user)
{
var cancelMessageSubscription = new CancellationTokenSource();
var messages = client.SubscribeForMessages(user);
var messageSubscription = messages
.ResponseStream
.ToAsyncEnumerable()
.Finally(() => messages.Dispose());
messageSubscription.ForEachAsync(
message => Console.WriteLine($"New Message: {message.Message_}"),
cancelMessageSubscription.Token);
return cancelMessageSubscription;
}
}
public static class Common
{
private const int Port = 50051;
private const string Host = "localhost";
private static readonly string ChannelAddress = $"{Host}:{Port}";
public static User GetUser(string name) => new User { DisplayName = name };
public static readonly User ServerUser = GetUser("Server");
public static readonly Close EmptyClose = new Close();
public static Channel GetNewInsecureChannel() => new Channel(ChannelAddress, ChannelCredentials.Insecure);
public static ServerPort GetNewInsecureServerPort() => new ServerPort(Host, Port, ServerCredentials.Insecure);
}
public sealed class MessengerServer : MessengerService.MessengerServiceBase
{
private readonly Server _server;
public MessengerServer()
{
_server = new Server
{
Ports = { Common.GetNewInsecureServerPort() },
Services = { MessengerService.BindService(this) },
};
}
public void Start()
{
_server.Start();
}
public async Task ShutDown()
{
await _server.ShutdownAsync().ConfigureAwait(false);
}
private readonly ConcurrentDictionary<User, IServerStreamWriter<Message>> _messageSubscriptions = new ConcurrentDictionary<User, IServerStreamWriter<Message>>();
public override async Task<Close> SendMessage(Message request, ServerCallContext context)
{
await Task.Run(() =>
{
foreach (var (_, messageStream) in _messageSubscriptions)
{
messageStream.WriteAsync(request);
}
}).ConfigureAwait(false);
return await Task.FromResult(Common.EmptyClose).ConfigureAwait(false);
}
public override async Task SubscribeForMessages(User request, IServerStreamWriter<Message> responseStream, ServerCallContext context)
{
await Task.Run(() =>
{
responseStream.WriteAsync(new Message
{
From = Common.ServerUser,
Message_ = $"{request.DisplayName} is listening for messages!",
});
_messageSubscriptions.TryAdd(request, responseStream);
}).ConfigureAwait(false);
}
}
public static class AsyncStreamReaderExtensions
{
public static IAsyncEnumerable<T> ToAsyncEnumerable<T>(this IAsyncStreamReader<T> asyncStreamReader)
{
if (asyncStreamReader is null) { throw new ArgumentNullException(nameof(asyncStreamReader)); }
return new ToAsyncEnumerableEnumerable<T>(asyncStreamReader);
}
private sealed class ToAsyncEnumerableEnumerable<T> : IAsyncEnumerable<T>
{
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
=> new ToAsyncEnumerator<T>(_asyncStreamReader, cancellationToken);
private readonly IAsyncStreamReader<T> _asyncStreamReader;
public ToAsyncEnumerableEnumerable(IAsyncStreamReader<T> asyncStreamReader)
{
_asyncStreamReader = asyncStreamReader;
}
private sealed class ToAsyncEnumerator<TEnumerator> : IAsyncEnumerator<TEnumerator>
{
public TEnumerator Current => _asyncStreamReader.Current;
public async ValueTask<bool> MoveNextAsync() => await _asyncStreamReader.MoveNext(_cancellationToken);
public ValueTask DisposeAsync() => default;
private readonly IAsyncStreamReader<TEnumerator> _asyncStreamReader;
private readonly CancellationToken _cancellationToken;
public ToAsyncEnumerator(IAsyncStreamReader<TEnumerator> asyncStreamReader, CancellationToken cancellationToken)
{
_asyncStreamReader = asyncStreamReader;
_cancellationToken = cancellationToken;
}
}
}
}
}
The problem you're experiencing is due to the fact that MessengerServer.SubscribeForMessages returns immediately. Once that method returns, the stream is closed.
You'll need an implementation similar to this to keep the stream alive:
public class MessengerService : MessengerServiceBase
{
private static readonly ConcurrentDictionary<User, IServerStreamWriter<Message>> MessageSubscriptions =
new Dictionary<User, IServerStreamWriter<Message>>();
public override async Task SubscribeForMessages(User request, IServerStreamWriter<ReferralAssignment> responseStream, ServerCallContext context)
{
if (!MessageSubscriptions.TryAdd(request))
{
// User is already subscribed
return;
}
// Keep the stream open so we can continue writing new Messages as they are pushed
while (!context.CancellationToken.IsCancellationRequested)
{
// Avoid pegging CPU
await Task.Delay(100);
}
// Cancellation was requested, remove the stream from stream map
MessageSubscriptions.TryRemove(request);
}
}
As far as unsubscribing / cancellation goes, there are two possible approaches:
The client can hold onto a CancellationToken and call Cancel() when it wants to disconnect
The server can hold onto a CancellationToken which you would then store along with the IServerStreamWriter in the MessageSubscriptions dictionary via a Tuple or similar. Then, you could introduce an Unsubscribe method on the server which looks up the CancellationToken by User and calls Cancel on it server-side
Similar to Jon Halliday's answer, an indefinately long Task.Delay(-1) could be used and passed the context's cancellation token.
A try catch can be used to remove end the server's response stream when the task is cancelled.
public override async Task SubscribeForMessages(User request, IServerStreamWriter<Message> responseStream, ServerCallContext context)
{
if (_messageSubscriptions.ContainsKey(request))
{
return;
}
await responseStream.WriteAsync(new Message
{
From = Common.ServerUser,
Message_ = $"{request.DisplayName} is listening for messages!",
}).ConfigureAwait(false);
_messageSubscriptions.TryAdd(request, responseStream);
try
{
await Task.Delay(-1, context.CancellationToken);
}
catch (TaskCanceledException)
{
_messageSubscriptions.TryRemove(request, out _);
}
}
I'm creating a bot using Bot Framework, in web channel and direct line.
i Have a luis dialog which call some forms ( flow form ) .
I have to ask for each form the contract number and some other data . How to store just one time contract number and to go ahead to other field and that to response the right answer for that contract number of the user.
i was trying to ask in the begging the contract number and name of the person and to check if they match..if yes to let the user to use the bot...but i cannot find the right logic code.
Can you help me please with any idea how to ask just one time the contract number and to use in all my dialog and forms ?
Below is my code :
MessagesController.cs :
public class MessagesController : ApiController
{
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isTypingReply = activity.CreateReply();
isTypingReply.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isTypingReply);
if (activity.Type == ActivityTypes.Message)
{
activity.Locale = "en-US";
Helpers.SaveActivityDataToDB(activity);
await Conversation.SendAsync(activity, () => new LuisDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Luis.cs
[LuisIntent("Greeting")]
public async Task Greeting(IDialogContext context, LuisResult result)
{
await context.SayAsync(text: "Welcome");
}
[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);
}
private async Task BalanceCompleted(IDialogContext context, IAwaitable<BalanceForm> result)
{
BalanceForm form = null;
try
{
form = await result;
}
catch (OperationCanceledException)
{
}
if (form == null)
{
await context.PostAsync("Try again please!");
}
else
{
//call the LossForm service to complete the form fill
var message = $"Thnx";
await context.PostAsync(message);
}
context.Wait(this.MessageReceived);
}
BalanceForm.cs
[Serializable]
public class BalanceForm
{
[Prompt("What is your contract number?")]
public string contract;
public static IForm<BalanceForm> BuildForm()
{
OnCompletionAsyncDelegate<BalanceForm> wrapUpRequest = async
(context, state) =>
{
using (BotModelDataContext BotDb = new BotModelDataContext())
{
//search in database
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))
.OnCompletion(wrapUpRequest)
//.Confirm("Are you sure: Yes or No ")
.Build();
}
}
}
Can you help me please with any idea how to ask just one time the contract number and to use in all my dialog and forms ?
Based on your requirement, I create a sample to prompt user for contract number and save the value in UserData so that I can get the contract number that user provided from UserData in child dialog(s). The following sample code is for your reference.
In RootDialog:
[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;
// calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
var contractnumber = "";
if (!context.UserData.TryGetValue<string>("contract_number", out contractnumber))
{
//prompt for contract number
PromptDialog.Text(
context: context,
resume: AfterGetContractNumber,
prompt: "Please provide your contract number.",
retry: "Please try again."
);
}
else
{
await context.Forward(new BaiscLuisDialog(), AfterLuisDialog, context.Activity, CancellationToken.None);
}
}
private async Task AfterGetContractNumber(IDialogContext context, IAwaitable<string> result)
{
string contractnumber = await result;
context.UserData.SetValue<string>("contract_number", contractnumber);
await context.PostAsync($"OK, received your contract number: {contractnumber}.");
context.Done(this);
}
private async Task AfterLuisDialog(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceivedAsync);
}
}
In BaiscLuisDialog:
[LuisIntent("balance")]
public async Task BalanceIntent(IDialogContext context, LuisResult result)
{
//call BalanceForm
//and pass contract number that user provided to BalanceForm contract_number
var balanca = new Microsoft.Bot.Builder.FormFlow.FormDialog<BalanceForm>(new BalanceForm() { contract_number = context.UserData.GetValue<string>("contract_number") },
BalanceForm.BuildForm,
Microsoft.Bot.Builder.FormFlow.FormOptions.PromptInStart,
result.Entities);
context.Call<BalanceForm>(balanca, BalanceCompleted);
}
private async Task BalanceCompleted(IDialogContext context, IAwaitable<BalanceForm> result)
{
context.Wait(MessageReceived);
}
In BalanceForm:
[Serializable]
public class BalanceForm
{
public string contract_number;
[Prompt("What is your contract number?")]
public string contract;
public string your_other_field;
public static IForm<BalanceForm> BuildForm()
{
OnCompletionAsyncDelegate<BalanceForm> wrapUpRequest = async
(context, state) =>
{
//using (BotModelDataContext BotDb = new BotModelDataContext())
//{
// //search in database
// string wrapUpMessage = "Dear " + house.Firstname + "," + "your balance is " + house.Balance;
// await context.PostAsync(wrapUpMessage);
//};
//your code logic here
var contractnumber = "";
context.UserData.TryGetValue<string>("contract_number", out contractnumber);
string wrapUpMessage = " Form completed! Your contract number is " + contractnumber;
var replymes = context.MakeMessage();
replymes.Text = wrapUpMessage;
await context.PostAsync(replymes);
};
return new FormBuilder<BalanceForm>()
.Message("We have to ask you some information")
.Field(new FieldReflector<BalanceForm>(nameof(contract)).SetActive(state => state.contract_number == null))
.Field(nameof(your_other_field))
.OnCompletion(wrapUpRequest)
//.Confirm("Are you sure: Yes or No ")
.Build();
}
}
Test result:
I am trying to create a bot wherein based on luis intent i need to ask customer to feed in his details and feedback. For example, if customer is unhappy i want his details to allow a callback. My luis intent identifies the dialog but i am not able to fire up a form. My luis dialog code is
namespace Microsoft.Bot.Sample.Luisbot
{
[Serializable]
public class FeedbackForm
{
[Prompt(new string[] { "Name?" })]
public string Name { get; set; }
[Prompt("Contact Number")]
public string Contact { get; set; }
[Prompt("Query")]
public string Query { get; set; }
public static IForm<FeedbackForm> BuildForm()
{
return new FormBuilder<FeedbackForm>()
.Build();
}
}
[Serializable]
class BasicLuisDialog : LuisDialog<object>
{
public BasicLuisDialog() : base(new LuisService(new LuisModelAttribute(ConfigurationManager.AppSettings["LuisAppId"], ConfigurationManager.AppSettings["LuisAPIKey"])))
[LuisIntent("Greetings")]
public async Task GreetingsIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Hi. Please share your query");
context.Wait(MessageReceived);
}
[LuisIntent("Critical")]
public async Task CriticalIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Thank you for your response.To help you better I will arrange a call back from our customer care team. Please provide following details");
var feedbackForm = new FormDialog<FeedbackForm>(new FeedbackForm(), FeedbackForm.BuildForm, FormOptions.PromptInStart,result.Entities);
context.Call(feedbackForm, FeedbackFormComplete);
context.Wait(MessageReceived);
}
}
}
And my messagecontroller code is
namespace Microsoft.Bot.Sample.LuisBot
{
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Connector;
[BotAuthentication]
public class MessagesController : ApiController
{
private static IForm<FeedbackForm> BuildForm()
internal static IDialog<FeedbackForm> MakeRoot()
{
return Chain.From(() => new BasicLuisDialog(BuildForm));
}
[ResponseType(typeof(void))]
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new BasicLuisDialog());
}
else
{
await this.HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
if (message.MembersAdded.Any(o => o.Id == message.Recipient.Id))
{
ConnectorClient client = new ConnectorClient(new Uri(message.ServiceUrl));
var reply = message.CreateReply();
reply.Text = "Welcome to RB Customer Care";
await client.Conversations.ReplyToActivityAsync(reply);
}
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
}
}
}
Can anyone help me understand what is wrong here. I am not a pro at C#
I have a bot with a root LuisDialog and 4 more LuisDialogs each one with a different LUIS model. Following the conversation started here I've implemented a similar DialogFactory strategy.
When a user sends a question that matches "None" intent in my root dialog, I evaluate the rest of dialogs until I find a match and then forward the message to the "winner".
The problem I'm facing is that I'm getting the http error: 429 (Too Many Requests) when querying LUIS (BaseDialog class).
Any ideas about how to face this?
The "None" intent in my root dialog:
[LuisIntent("None")]
public async Task None(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var activity = await message;
var factory = new DialogFactory();
BaseDialog<object> dialog = await factory.Create(result.Query);
if (dialog != null)
{
await context.Forward(dialog, EndDialog, activity, CancellationToken.None);
}
else
{
await context.PostAsync("No results!");
}
}
public static async Task EndDialog(IDialogContext context, IAwaitable<object> result)
{
//...
}
The DialogFactory class:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading.Tasks;
namespace CodeBot.Dialogs
{
public class DialogFactory
{
private static object _lock = new object();
private static List<BaseDialog<object>> Dialogs { get; set; }
public async Task<BaseDialog<object>> Create(string query)
{
query = query.ToLowerInvariant();
EnsureDialogs();
foreach (var dialog in Dialogs)
{
if (await dialog.CanHandle(query))
{
return dialog;
}
}
return null;
}
private void EnsureDialogs()
{
if (Dialogs == null || (Dialogs.Count != 4))
{
lock (_lock)
{
if (Dialogs == null)
{
Dialogs = new List<BaseDialog<object>>();
}
else if (Dialogs.Count != 4)
{
Dialogs.Clear();
}
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog1));
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog2));
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog3));
Dialogs.Add((BaseDialog<object>)Activator.CreateInstance(typeof(Dialog4));
}
}
}
}
}
And finally, the BaseDialog class (where I'm getting the error):
using Microsoft.Bot.Builder.Dialogs;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Luis;
using System;
namespace CodeBot.Dialogs
{
[Serializable]
public class BaseDialog<R> : LuisDialog<R>
{
public LuisModelAttribute Luis_Model { get; private set; }
public BaseDialog(LuisModelAttribute luisModel) : base(new LuisService(luisModel))
{
Luis_Model = luisModel;
}
public async Task<bool> CanHandle(string query)
{
try
{
var tasks = services.Select(s => s.QueryAsync(query, CancellationToken.None)).ToArray();
var results = await Task.WhenAll(tasks); <-- Error!!!
var winners = from result in results.Select((value, index) => new { value, index })
let resultWinner = BestIntentFrom(result.value)
where resultWinner != null
select new LuisServiceResult(result.value, resultWinner, this.services[result.index]);
var winner = this.BestResultFrom(winners);
return winner != null && !winner.BestIntent.Intent.Equals(Constants.NONE, StringComparison.InvariantCultureIgnoreCase);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine($"CanHandle error: {e.Message}");
return false;
}
}
}
}
The 429 error is caused by your application (key) hitting the LUIS API too heavily.
You need to either throttle your requests to ensure you stay below the threshold of the free tier, or upgrade to the Basic plan which allows 50 requests a second.
I've updated my NuGet packages to use the version 1.2.0.1 of the Microsoft Bot Framework.
Some breaking changes were reported here, and I managed to fix the build errors. But the application is not working anymore..
I have two problems:
The code throws an InvalidIntentHandlerException when I send a message an utterance to the controller.
In my 'intent' method (decorated with the LuisIntent attribute) it was possible to read the value of the entities. Like so:
[Serializable]
[LuisModel("xxxxx", "xxxx")]
public class BookFlightDialog : LuisDialog<BookFlightForm>
{
private readonly BuildFormDelegate<BookFlightForm> BuildForm;
internal BookFlightDialog(BuildFormDelegate<BookFlightForm> buildForm)
{
BuildForm = buildForm;
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
await context.PostAsync("I'm sorry. I didn't understand you.");
context.Wait(MessageReceived);
}
[LuisIntent("BookAFlight")]
public async Task BookAFlight(IDialogContext context, LuisResult result)
{
var form = new BookFlightForm();
// var entities = new List<EntityRecommendation>(result.Entities);
var locations = result.Entities.Where(e => e.Type.Equals("builtin.geography") || e.Type.Equals("builtin.geography.city")).OrderBy(e => e.StartIndex);
if (locations.Any())
{
form.LocationFrom = locations.First().Name;
if (locations.Count() == 2)
{
form.LocationTo = locations.Skip(1).First().Name;
}
}
var date = result.Entities.FirstOrDefault(e => e.Type == "builtin.datetime.date");
if (date != null) form.DepartureDate = DateTime.Parse(date.Name);
var formDialog = new FormDialog<BookFlightForm>(form, BuildForm, FormOptions.PromptInStart);
context.Call(formDialog, OnComplete);
}
private async Task OnComplete(IDialogContext context, IAwaitable<BookFlightForm> result)
{
BookFlightForm booking;
try
{
booking = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("Ok, see you later.");
return;
}
if (booking != null)
{
var service = new SkyScannerService();
var possibilities = await service.Search(booking);
await context.PostAsync(possibilities);
}
else
{
await context.PostAsync("Form returned empty response!");
}
context.Wait(MessageReceived);
}
}
How do I fix the exception and how do I read the value of the entities?
Thanks once again!
This is because you are not using inbuilt LuisResult class by having using LuisResult = Bots.Results.LuisResult;. Replace it with using Microsoft.Bot.Builder.Luis.Models;.