Good day everyone,
I'm creating a chatbot for my company and I started with the samples on github and the framework docs.
We decided to host it on Azure and added LUIS and Table Storage to it. The Bot runs fine locally in Botframework Emulator, but on Azure (WebChat, Telegram) it will only run for approximatly an hour to an hour and fifteen minutes, if no one tries to communicate with the bot. After this period of time, the bot will just run into an internal server error. When you ask the bot something, you can stretch this time window (For how long I don't know and why I don't know either, sorry).
In Azure "Always On" is set to true.
I'm really frustrated at this point, because I cannot find the problem and I'm pretty sure there must be something wrong with my code, because I don't properly understand the framework. I'm still a beginner with Azure, C# and Bot Framework.
Also I have already read everything on "internal server error's" on here and github. Also tried Debugging, even with extra Debbug options in VS. We have not tried Application Insights yet.
At the moment I'm doing everything with the LUIS Dialog which calls / Forwards to other IDialogs:
[LuisIntent(Intent_Existens)]
public async Task ExistensOf(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var existens = new ExistensDialog();
var messageToForward = await message;
if (result.Entities.Count == 1)
{
messageToForward.Value = result.Entities[0].Entity;
await context.Forward(existens, AfterDialog, messageToForward);
}
else
{
context.Wait(this.MessageReceived);
}
}
I know that "Value" is for CardActions, but I don't know how else I could pass Entities to the child dialog.
[Serializable]
public class ExistensDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
if (message.Text.Contains("specificWord"))
{
await context.Forward(new ExistensHU(), AfterDialog, message);
}
else
{
await context.Forward(new ExistensBin(), AfterDialog, message);
}
}
private async Task AfterDialog(IDialogContext context, IAwaitable<object> result)
{
context.Done<object>(null);
}
}
then:
[Serializable]
internal class ExistensHU : IDialog<object>
{
private Renamer renamer = new Renamer(); // Just for renaming
private ExternalConnection ec = new ExternalConnection(); //Just a HTTP connection to a WebApp to get data from it
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
const string apiCallURL = "some/API/"; // ExternalConnection...
var message = await result;
string nameHU = renamer.RemoveBlanks(message.Value.ToString());
StringBuilder answerBuilder = new StringBuilder();
var name = ec.CreateSingleAPIParameter("name", nameHU);
Dictionary<string, string> wms = await ec.APIResultAsDictionary(apiCallURL, name);
foreach (var item in wms)
{
if (item.Key.Equals("none") && item.Value.Equals("none"))
{
answerBuilder.AppendLine($"Wrong Answer");
}
else
{
answerBuilder.AppendLine($"Correct Answer");
}
}
await context.PostAsync(answerBuilder.ToString());
context.Done<object>(null);
}
}
That's basically every Dialog in my project.
Also I have an IDialog which looks like this:
[Serializable]
public class VerificationDialog : IDialog<object>
{
[NonSerializedAttribute]
private readonly LuisResult _luisResult;
public VerificationDialog(LuisResult luisResult)
{
_luisResult = luisResult;
}
public async Task StartAsync(IDialogContext context)
{
var message = _luisResult.Query;
if (!message.StartsWith("Wie viele"))
{
context.Call(new ByVerificationDialog(_luisResult), AfterDialog);
}
else
{
context.Call(new CountBinsByVerification(_luisResult), AfterDialog);
}
}
private async Task AfterDialog(IDialogContext context, IAwaitable<object> result)
{
context.Done<object>(null);
}
}
I don't know if I'm allowed to pass the luisResult like this from BasicLuisDialog. This could be the issue or one of the issues.
Basically that's it and I'm still getting used to the framework. I'm not expecting an absolute answer. Just hints/tips and advice how to make everything better.
Thanks in advance!
If you are using the .NET SDK version 3.14.0.7. There is currently a bug we are tracking in this version. There has been a number of reports and we are actively investigating. Please try downgrading to 3.13.1. This should fix the issue for you until we can release a new version.
for reference we are tracking the issue on these GitHub issues:
https://github.com/Microsoft/BotBuilder/issues/4322
https://github.com/Microsoft/BotBuilder/issues/4321
Update 3/21/2018:
We have pushed a new version of the SDK which includes a fix for this issue https://www.nuget.org/packages/Microsoft.Bot.Builder/3.14.1.1
Internal error usually means exceptions in .NET application.
Use AppDomain.CurrentDomain.UnhandledException to receive all unhandled exceptions and log them somewhere (consider using Application Insights).
After you investigate logged information fix that.
Related
I been writting alot of Webjobs with similar functionallity where similar code works just fine. But with Azure Functions instead I get error sometimes.
[FunctionName("Alert")]
public static async void Alert([ServiceBusTrigger(Topic.Alert, Subscription.Sql, AccessRights.Listen, Connection = "servicebusconnectionstring")] BrokeredMessage message, TraceWriter log)
{
using (var stream = message.GetBody<Stream>())
{
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
var json = Encoding.UTF8.GetString(memoryStream.ToArray());
try
{
throw new Exception("Test-Exception");
}
catch (Exception e)
{
EventLogger.LoggException("Function.Sql.Alert", e, new Dictionary<string, string>() { { "Messsage", json } });
if (message.DeliveryCount >= 5)
{
EventLogger.LoggEvent("DeadLetterQueue", new Dictionary<string, string>() { { "Function", "Function.Sql.Alert" }, { "Messsage", json } });
await QueueService.SendAsync(Queue.Deadletter, JsonConvert.DeserializeObject<CloudAlert>(json));
await message.CompleteAsync();
}
else
await message.AbandonAsync();
}
}
}
await message.CompleteAsync();
}
The issue is when I call message.AbandonAsync() or message.CompleteAsync it sometimes throw
System.ObjectDisposedException: BrokeredMessage has been disposed.
If I don't call message.CompleteAsync() at the end the message is still marked as completed. I could live with that, but I want to be able to Abandon the message as well and that function dosn't always work either.
Someone done something similar and have a solution? Using .NET Standard 2.0 and following NuGet package for ServiceBus: Microsoft.Azure.ServiceBus v2.0.0
You should not Complete or Abandon messages manually. Azure Functions runtime will do it for you based on success of failure (exception) of your function execution.
So, throw an exception if you want to Abandon. It will be auto-retried again up until Max Delivery Count limit.
I have a question... Unfortunately all the samples on the web are too shallow and don't really cover this well:
I have a RootDialog that extends the LuisDialog. This RootDialog is responsible for figuring out what the user wants to do. It could be multiple things, but one of them would be initiating a new order. For this, the RootDialog would forward the call to the NewOrderDialog, and the responsibility of the NewOrderDialog would be to figure out some basic details (what does the user want to order, which address does he like to use) and finally it will confirm the order and return back to the RootDialog.
The code for the RootDialog is very simple:
[Serializable]
public class RootDialog : LuisDialog<object>
{
public RootDialog() : base(new LuisService(new LuisModelAttribute(ConfigurationManager.AppSettings["LuisAppId"], ConfigurationManager.AppSettings["LuisAPIKey"], domain: "westus.api.cognitive.microsoft.com")))
{
}
[LuisIntent("Order.Place")]
public async Task PlaceOrderIntent(IDialogContext context, LuisResult result)
{
await context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, context.Activity, CancellationToken.None);
context.Wait(MessageReceived);
}
private async Task OnPlaceOrderIntentCompleted(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync("Your order has been placed. Thank you for shopping with us.");
context.Wait(MessageReceived);
}
}
I also had some code in mind for the NewOrderDialog:
[Serializable]
public class NewOrderDialog : LuisDialog<object>
{
private string _product;
private string _address;
public NewOrderDialog() : base(new LuisService(new LuisModelAttribute(ConfigurationManager.AppSettings["LuisAppId"], ConfigurationManager.AppSettings["LuisAPIKey"], domain: "westus.api.cognitive.microsoft.com")))
{
}
[LuisIntent("Order.RequestedItem")]
public async Task RequestItemIntent(IDialogContext context, LuisResult result)
{
EntityRecommendation item;
if (result.TryFindEntity("Item", out item))
{
_product = item.Entity;
await context.PostAsync($"Okay, I understood you want to order: {_product}.");
}
else
{
await context.PostAsync("I couldn't understand what you would like to buy. Can you try it again?");
}
context.Wait(MessageReceived);
}
[LuisIntent("Order.AddedAddress")]
public async Task AddAddressIntent(IDialogContext context, LuisResult result)
{
EntityRecommendation item;
if (result.TryFindEntity("Address", out item))
{
_address = item.Entity;
await context.PostAsync($"Okay, I understood you want to ship the item to: {_address}.");
}
else
{
await context.PostAsync("I couldn't understand where you would like to ship the item. Can you try it again?");
}
context.Wait(MessageReceived);
}
}
The code as listed doesn't work. Upon entering the Order.Place intent, it immediately executes the 'success' callback, and then throws this exception:
Exception: IDialog method execution finished with multiple resume handlers specified through IDialogStack.
[File of type 'text/plain']
So I have a few questions:
How do I solve the error that I get?
How can I, upon entering the NewOrderDialog, check if we already know what the product and address is, and if not prompt them for the correct info?
Why does the NewOrderDialog get closed even though I don't call anything like context.Done()? I only want it to be closed when all the info has been gathered and the order has been confirmed.
So the first issue is that you are doing a context.Forward and a context.Wait in "Order.Place", which by definition is wrong. You need to choose: forward to a new dialog or wait in the current. Based on your post, you want to forward, so just remove the Wait call.
Besides that, you have 1 LUIS dialog and you are trying to forward to a new LUIS dialog... I have my doubts if that will work on not; I can imagine those are two different LUIS models otherwise it will be just wrong.
Based on your comment, I now understand what you are trying to do with the second dialog. The problem (and this is related to your second question) is that using LUIS in tha way might be confusing. Eg:
user: I want to place an order
bot => Forward to new dialog. Since it's a forward, the activity.Text will likely go to LUIS again (to the model of the second dialog) and nothing will be detected. The second dialog will be in Wait state, for user input.
Now, how the user will know that he needs to enter an address or a product? Where are you prompting the user for them? See the issue?
Your third question I suspect is a side effect of the error you are having in #1, which I already provided the solution for.
If you clarify a bit more I might be even more helpful. What you are trying to do with LUIS in the second dialog it doesn't look ok, but maybe with an explanation might have sense.
A usual scenario would be: I get the intent from LUIS ("Order.Place") and then I start a FormFlow or a set of Prompts to get the info to place the order (address, product, etc) or if you want to keep using LUIS you might want to check Luis Action Binding. You can read more on https://blog.botframework.com/2017/04/03/luis-action-binding-bot/.
Do you know about the Bing Location Control for Microsoft Bot Framework? It can be used to handle the 'which address does he like to use' part in obtaining and validating the user's address.
Here is some sample code:
[LuisModel("xxx", "yyy")]
[Serializable]
public class RootDialog : LuisDialog<object>
{
...
[LuisIntent("Find Location")]
public async Task FindLocationIntent(IDialogContext context, LuisResult result)
{
try
{
context.Call(new FindUserLocationDialog(), ResumeAfterLocationDialog);
}
catch (Exception e)
{
// handle exceptions
}
}
public async Task ResumeAfterLocationDialog(IDialogContext context, IAwaitable<object> result)
{
var resultLocation = await result;
await context.PostAsync($"Your location is {resultLocation}");
// do whatever you want
context.Wait(this.MessageReceived);
}
}
And for "FindUserLocationDialog()", you can follow the sample from Line 58 onwards.
Edit:
1) Can you try using:
[LuisIntent("Order.Place")]
public async Task PlaceOrderIntent(IDialogContext context, LuisResult result)
{
context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, context.Activity, CancellationToken.None);
// or this
// context.Call(new NewOrderDialog(), OnPlaceOrderIntentCompleted);
}
2) I would say it depends on how you structured your intent. Does your "Order.Place" intent include entities? Meaning to say, if your user said "I want to make an order of Product X addressed to Address Y", does your intent already pick up those entities?
I would suggest that you check the product and address under your "Order.Place" intent. After you obtained and validated the product and address, you can forward it to another Dialog (non-LUIS) to handle the rest of the order processing.
[LuisIntent("Order.Place")]
public async Task PlaceOrderIntent(IDialogContext context, LuisResult result)
{
EntityRecommendation item;
if (result.TryFindEntity("Item", out item))
{
_product = item.Entity;
}
if (result.TryFindEntity("Address", out item))
{
_address = item.Entity;
}
if (_product == null)
{
PromptDialog.Text(context, this.MissingProduct, "Enter the product");
}
else if (_address == null)
{
PromptDialog.Text(context, this.MissingAddress, "Enter the address");
}
// both product and address present
context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, **object with _product and _address**, CancellationToken.None);
}
private async Task MissingProduct(IDialogContext context, IAwaitable<String> result)
{
_product = await result;
// perform some validation
if (_address == null)
{
PromptDialog.Text(context, this.MissingAddress, "Enter the address");
}
else
{
// both product and address present
context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, **object with _product and _address**, CancellationToken.None);
}
}
private async Task MissingAddress(IDialogContext context, IAwaitable<String> result)
{
_address = await result;
// perform some validation
// both product and address present
context.Forward(new NewOrderDialog(), OnPlaceOrderIntentCompleted, **object with _product and _address**, CancellationToken.None);
}
Something along these lines. Might need to include try/catch.
3) I think it has got to do with your 'context.Wait(MessageReceived)' in 'Order.Place' LUIS intent
Bot Framework suddenly stopped rendering html, I have attached screenshot. What can be wrong? I am using v3.5.31 which is the latest one. I have used Bot Application template which is bydefault available when creating new BOT project.
Below is the code i have written
[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)
{
PromptDialog.Text(context, resumeAfter, "Enter your name", "Didn't get that",
3
);
}
private async Task resumeAfter(IDialogContext context, IAwaitable<string> result)
{
string answer = await result;
await context.PostAsync($"Hello <i><b>{answer}</b></i>");
}
}
This was reported here. and it looks like it's by design in the WebChat control (that is what the Emulator uses behind the scenes). See the change in the WebChat control: https://github.com/Microsoft/BotFramework-WebChat/commit/a5cd8cffb7a97fb925e4078dbe1085694fa92f80
I've tried to come up with something from the example in the WebJobsSDK gitHub
var eventHubConfig = new EventHubConfiguration();
string eventHubName = "MyHubName";
eventHubConfig.AddSender(eventHubName,"Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=SendRule;SharedAccessKey=xxxxxxxx");
eventHubConfig.AddReceiver(eventHubName, "Endpoint=sb://test.servicebus.windows.net/;SharedAccessKeyName=ReceiveRule;SharedAccessKey=yyyyyyy");
config.UseEventHub(eventHubConfig);
JobHost host = new JobHost(config);
But I'm afraid that's not far enough for someone of my limited "skillset"!
I can find no instance of JobHostConfiguration that has a UseEventHub property (using the v1.2.0-alpha-10291 version of the Microsoft.AzureWebJobs package), so I can't pass the EventHubConfiguration to the JobHost.
I've used EventHub before, not within the WebJob context. I don't see if the EventHostProcessor is still required if using the WebJob triggering...or does the WebJob trigger essentially act as the EventHostProcessor?
Anyway, if anyone has a more complete example for a simpleton like me that would be really sweet! Thanks
From the documentation here, you should have all the information you need.
What you are missing is a reference of the Microsoft.Azure.WebJobs.ServiceBus.1.2.0-alpha-10291 nuget package.
The UseEventHub is an extension method that is declared in this package.
Otherwise your configuration seems ok.
Here is an example on how to receive or send messages from/to an EventHub:
public class BasicTest
{
public class Payload
{
public int Counter { get; set; }
}
public static void SendEvents([EventHub("MyHubName")] out Payload x)
{
x = new Payload { Counter = 100 };
}
public static void Trigger(
[EventHubTrigger("MyHubName")] Payload x,
[EventHub("MyHubName")] out Payload y)
{
x.Counter++;
y = x;
}
}
EventProcessorHost is still required, as the WebJob just provides the hosting environment for running it. As far as I know, EventProcessorHost is not integrated so deeply into WebJob, so its triggering mechanism cannot be used for processing EventHub messages. I use WebJob for running EventProcessorHost continuously:
public static void Main()
{
RunAsync().Wait();
}
private static async Task RunAsync()
{
try
{
using (var shutdownWatcher = new WebJobsShutdownWatcher())
{
await Console.Out.WriteLineAsync("Initializing...");
var eventProcessorHostName = "eventProcessorHostName";
var eventHubName = ConfigurationManager.AppSettings["eventHubName"];
var consumerGroupName = ConfigurationManager.AppSettings["eventHubConsumerGroupName"];
var eventHubConnectionString = ConfigurationManager.ConnectionStrings["EventHub"].ConnectionString;
var storageConnectionString = ConfigurationManager.ConnectionStrings["EventHubStorage"].ConnectionString;
var eventProcessorHost = new EventProcessorHost(eventProcessorHostName, eventHubName, consumerGroupName, eventHubConnectionString, storageConnectionString);
await Console.Out.WriteLineAsync("Registering event processors...");
var processorOptions = new EventProcessorOptions();
processorOptions.ExceptionReceived += ProcessorOptions_ExceptionReceived;
await eventProcessorHost.RegisterEventProcessorAsync<CustomEventProcessor>(processorOptions);
await Console.Out.WriteLineAsync("Processing...");
await Task.Delay(Timeout.Infinite, shutdownWatcher.Token);
await Console.Out.WriteLineAsync("Unregistering event processors...");
await eventProcessorHost.UnregisterEventProcessorAsync();
await Console.Out.WriteLineAsync("Finished.");
}
catch (Exception ex)
{
await HandleErrorAsync(ex);
}
}
}
private static async void ProcessorOptions_ExceptionReceived(object sender, ExceptionReceivedEventArgs e)
{
await HandleErrorAsync(e.Exception);
}
private static async Task HandleErrorAsync(Exception ex)
{
await Console.Error.WriteLineAsync($"Critical error occured: {ex.Message}{ex.StackTrace}");
}
I have an Azure Web Job built using the Azure SDK whose only job is to call a web service (Web API) and then log a response based on the return value (a class). The problem is that as soon as it calls the HttpClient PostAsJsonAsync method to call the service, it exits out of the web job without executing any of the response handling. My code is:
public class Result
{
// Properties ---------------------------------------------------------
public bool Success { get; set; }
public string Error { get; set; }
}
public class Functions
{
// This function will be triggered based on the schedule you have set for this WebJob
// This function will enqueue a message on an Azure Queue called queue
[NoAutomaticTrigger]
public async static void ManualTrigger(TextWriter log, int value)
{
using (var client = new HttpClient())
{
var rootUrl = ConfigurationManager.AppSettings.Get("WebJobTargetUrl");
client.BaseAddress = new System.Uri(rootUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Console.WriteLine("Call service");
var response = await client.PostAsJsonAsync("api/Reminder/ProcessDueReminders", new { ItemID = 1 });
Console.WriteLine("After service");
var result = await response.Content.ReadAsAsync<Result>();
Console.WriteLine("After result");
if (result.Success)
Console.WriteLine("Reminders Processed");
else
Console.WriteLine("Reminder process error: " + result.Error);
}
}
}
and the execution logs from the portal are:
I believe it has something to do with the asynchronous operation but I can't figure out a pattern that will work. Any help would be appreciated.
You must define the return value of your own async method as Task instead of void.
On a related note, you should suffix the name of your method with Async. That's not going to solve the problem, but it indicates that you're using the async/await pattern.
There is probably an exception in your PostAsJsonAsync call. Try to put a try catch around it to and log the error:
try {
var response = await client.PostAsJsonAsync("api/Reminder/ProcessDueReminders", new { ItemID = 1 });
} catch (Exception ex){
Console.WriteLine("Exception: "+ ex);
}
Console.WriteLine("After service");