Getting LUIS entities inside a child dialog - c#

I'm developing a bot using Bot Framework, LUIS and ActionBinding.
In one of my intent handlers I call a new Dialog which has the methods StartAsync(IDialogContext context) and ReceiveMessageAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
As I understand it, the messages typed by the user while waiting (with context.Wait(ReceiveMessageAsync)) won't be sent to LUIS, right?
So if I need to understand what the user is saying without having to parse the string, which are my options? Could call the ILuisService.QueryAsync with the message.Text for each message be an option?
I want to be able to detect entities typed by the user so I can map them to missing fields. For example in this conversation:
User: I want to book a flight. // LUIS detects intent
Bot: Ok. Can you tell me more about your flight? // child dialog is called to handle the rest of the conversation
User: I want to go to Madrid.
Bot: To fly to Madrid you can choose between company A, B or C.
User: I want to go with A tomorrow night
Bot: Ok, searching for available tickets for tomorrow night in A...
In this case there are no initial entities when the intent is detected, but there could be, and in that case the bot would not ask for the already given information.
For my project, a simple Form with one to one question-answer is not enough. I also need to make more validations and confirmations on previously set parameters if the user wants to change one or more parameters (i.e., I need to go back to all parameters and check if the changed parameter affects them). For example:
User: Wait, I want to fly to Barcelona instead.
Bot: Company A does not fly to Barcelona. You can choose between C and D.
User: Ok I want to fly with C.
Bot: There are tickets available for tomorrow night in company C. Keep the flight for tomorrow night?
User: yes.
Any tips or guidance for best practices would help a lot.
Thanks in advance.
Edit:
With the Sub Action solution, where would my validators operate? On the FulfillAsync method? I'd need to validate and then send a question to the user and understand the reply he sent (parsing entities). Would that be possible inside a LuisAction?
I'd like to use the QueryValueFromLuisAsync but after looking at it, I'd need to pass the paramName, which is one of the action properties (if i'm not mistaken) and that is what I'm trying to avoid. I don't want to map one answer (i.e., message.Text) to one field, i want to map one answer to multiple fields.
Let's say i need to fill a model that has 6 properties. If the bot asks one question to the user and in his reply there are 3 entities I want to map those entities to 3 fields and only make questions about the remaining non mapped fields afterwards.

My first reaction to this is to avoid using a custom child dialog and go with SubActions and create your own validators if you want to have complex logic there or even override the IsValid method from the sub action.
However, if that's not a possibility, then I would consider reusing the QueryValueFromLuisAsync method, where the action should be the model you want to interact with. That function will end up calling LUIS and will try to assign the result or return another intent/action depending on the scenario. I would give this a try.

Related

Best way to encapsulate creation logic

I think I have an amateur architecture question, but this is something that I've been struggling to figure out for quite a while.
I have a C# web project that creates users in several places like this:
var user = /*create user somehow*/;
_userRepository.Add(user);
_userRepository.SaveChanges();
Now I need to add logic that sends email notifications every time a user is created:
var user = /*create user somehow*/;
_userRepository.Add(user);
_userRepository.SaveChanges();
_notificationService.SendUserCreatedNotification(user);
The problem with this is I wouldn't like to add the same line of code to all the places where new user is created (DRY!).
Now, I could wrap up the Add/Save/SendUserCreatedNotification logic in a separate service:
var user = /*create user somehow*/;
_userCreationService.AddAndSave(user);
But:
the purpose of this service would be logically weird (add user to
repo, save changes to repo, send notifications); I can't even think
of a good name for this service & method
The service method would only have 3 lines of code: Add/Save/SendUserCreatedNotification
How do you usually solve such tasks? Is approach 2 the best way to go? Or maybe there exists a better approach 3?
One possible solution would be to send this notification from the _userRepository.SaveChanges(); method.
You would check your UoW change tracker for all the user entities that are in the 'Created' state and send these notifications after committing.
However, using this approach, the notification sending will be hidden in your infrastructure/data access/... layer. This means the notification sending logic will not be part of your domain/core logic. For someone to become aware of this part of the logic, they would have to dive into the implementation details of your repository (or UoW).
Instead, you could fire an event from the _userRepository.SaveChanges(); and subscribe to that event in your core logic.
The approach I would take would be the following:
In _userRepository.SaveChanges(); for every created user, fire a UserCreatedEvent event that will contain the information about the user.
Subscribe to that event in your core logic and call _notificationService.SendUserCreatedNotification(user); from the event handler.
This way, you would also decouple your user creation logic from the notification sending logic.

Botframework, LUIS: Is there an intents history?

I'm trying to create a bot that integrates LUIS whose purpose is to search for recipes and products, but I'm struggling to understand a few concepts.
To begin with, I have an issue regarding the flow of the conversation. Let's say the user asks for a recipe but doesn't specify what products he would like to filter. The bot will check if there are any entities (products) in this utterance and if not, it will reply asking for the specific products. So something like:
User: "I want to see recipes"
Bot: "Please specify the ingredients"
User: "Bananas"
This is where I find my first problem. How will the bot be able to understand that the user's last utterance (bananas) is directed to the Recipes Intent and not the Products one?
To try and work around this, I've trained LUIS to direct these entities to go to the None Intent. I've also created a flag that allows me to detect which was the last Intent the user went through. From this I can redirect the bot to the correct intent.
I feel like there must be a better solution than this. Am I missing something here? Is there a way to keep track of the history of intents used? I've also tried using context.Wait, but I believe the method doesn't receive (or return) a LuisResult, which makes it impossible for me to later detect if there are any entities in the user's message.
My second question is, if it is possible for the user to send a message that won't enter any intent and will just be directed to a certain method?
[LuisIntent("aa")]
[LuisIntent("bb")]
public async Task AaIntentSpecified(IDialogContext context, LuisResult result)
{
}
[LuisIntent("")]
public async Task IntentNotSpecified(IDialogContext context, LuisResult result)
{
}
If you left a method like this in luis dialog, then any intents that not has a function mapped will go to this function. The intent "aa" and "bb", will go to AaaIntentSpecified, any other intents like "cc", "dd" ... will go to IntentNotSpecified.

Create Case with Activity through Rest API

I am trying to import cases from our old ticketing system into Acumatica using a C# console application. I have the old tickets loaded, and I am trying to use the REST API to create the cases.
I created a custom web service endpoint to load the cases, but I would also like to create message activities from the posts in our old system. If I use the Cases screen under Organization, I can add a Detail entity for activities. However, there does not appear to be a way to add the Activity Details field, which is the body of the activity. Here is a screenshot of the current endpoint setup showing the top-level Case entity I created:
The above image shows the Case entity, which does not appear to have the ActivityDetails field. However, if I use the Activity screen from the Hidden site map folder, the ActivityDetails is present. Here is a screenshot of the Activity entity I created, which does have ActivityDetails:
I hope this makes sense, but I would like the ActivityDetails field to be available from the Cases top-level entity so I can insert a complete case including activities and their detail. Any help would be greatly appreciated.
Thank you.
This is not a behavior that is possible.
The reason for this is that when you go on that screen using the UI, there is no possibility to add new Event, Task or Activity directly from that screen. The action button that are there only serve as to open the other screens a already create a link to the case from where the action was clicked on.
Since the APIs work by dealing with one screen at the time, it is not possible from the Case screen to create an Activity.
So to create an Activity for a Case, you will first have to create the Activity and then link it to the Case.
In order to do so, you must first add some field to both the Case entity and the Activity Entity.
These fields must be added manually as they are not part of the autocompletion.
For the Case entity, you need to add the following field:
Field Name = "NoteID"
Mapped Object = "Case Summary"
Mapped Field = "NoteID"
Field Value = "GuidValue"
enter code here
For the Activity entity please add the following field:enter code here
Field Name = "RefNoteID"
Mapped Object = "Activities"
Mapped Field = "RefNoteID"
Field Value = "GuidValue"
Once these two fields have been added, you can start adding the activity to the case.
In order to do so:
1) Retrieve the Case on which you want to add the activity using A GET call. You will need to use the value from the NoteID field that has just been added.
2) Create the Activity like you normally would, using a PUT call, but instead of trying to add a value in the RelatedEntityDescription field, add the NoteID value you just retrieved from the Case to the RefNoteID field you just added to the Activity entity. In the response you will be able to see that the Activity was linked to the case by checking the RelatedEntityDescription field.

Can I add custom logic to a Bot Framework PromptDialog for handling invalid answers?

In my Bot Framework project, I'm using a PromptDialog to show a predefined set of valid options, with the below code:
var pickListOptions = new List<Option>();
pickListOptions.AddRange(
_currentQuestion.validValues.Select(x => Option.CreateOption(x)));
PromptDialog.Choice(context, choiceSelected,
pickListOptions,
_currentQuestion.label,
"Sorry, I didn't get that", 3, PromptStyle.Keyboard);
When a free-form answer is typed in which is not in the list of valid values is entered, the "Sorry, I didn't get that" message is automatically displayed and the question is reprompted. However, I want to potentially handle certain invalid answers with a different dialog (i.e. if the user is asking for help). Is there any way to override the automatic reprompt with custom logic?
There are likely two valid answers for this question:
If you want to change the validation, you can inherit from the PromptChoice and override the TryParse or the MessageReceivedAsync methods. For example, the CancelablePromptChoice
For handling global commands, like help, instead of doing what I explained in #1, you might want to consider using Scorables. Take a look to the GlobalMessagesHandlers sample to understand more.

DDD update via REST

I am new to DDD, and I am trying to figure out a way to update aggregate by using a PUT verb.
If all properties in the aggregate have private setters, then it's obvious I need to have set of functionality for every business requirement. For an example
supportTicket.Resolve();
It's clear for me that I can achieve this with an endpoint such as /api/tickets/5/resolve, but what if i want to provide a way to update whole ticket atomically?
As an example, user can make a PUT request to /api/tickets/5 with a following body
{"status" : "RESOLVED", "Title":"Some crazy title"}
Do I need to do something like this in the ApplicationSercvice
if(DTO.Status != null && dto.Status == "RESOLVED")
supportTicket.Resolve();
if(DTO.Title != null)
supportTicket.setNewTitle(DTO.title);
If that's the case and changing ticket title has some business logic to prevent changing it if the ticket is resolved, should I consider some kind of prioritizing when updating aggregate, or I am looking at this entirely wrong?
Domain Driven Design for RESTful Systems -- Jim Webber
what if i want to provide a way to update whole ticket atomically?
If you want to update the whole ticket atomically, ditch aggregates; aggregates are the wrong tool in your box if what you really want is a key value store with CRUD semantics.
Aggregates only make sense when their are business rules for the domain to enforce. Don't build a tractor when all you need is a shovel.
As an example, user can make a PUT request to /api/tickets/5
That's going to make a mess. In a CRUD implementation, replacing the current state of a resource by sending it a representation of a new state is appropriate. But that doesn't really fit for aggregates at all, because the state of the aggregate is not under the control of you, the client/publisher.
The more appropriate idiom is to publish a message onto a bus, which when handled by the domain will have the side effect of achieving the changes you want.
PUT /api/tickets/5/messages/{messageId}
NOW your application service looks at the message, and sends commands to the aggregate
if(DTO.Status != null && dto.Status == "RESOLVED")
supportTicket.Resolve();
if(DTO.Title != null)
supportTicket.setNewTitle(DTO.title);
This is OK, but in practice its much more common to make the message explicit about what is to be done.
{ "messageType" : "ResolveWithNewTitle"
, "status" : "RESOLVED"
, "Title":"Some crazy title"
}
or even...
[
{ "messageType" : "ChangeTitle"
, "Title" : "Some crazy title"
}
, { "messageType" : "ResolveTicket"
}
]
Basically, you want to give the app enough context that it can do real message validation.
let's say I had aggregates which encapsulated needed business logic, but besides that there is a new demand for atomic update functionality and I am trying to understand a best way to deal with this.
So the right way to deal with this is first to deal with it on the domain level -- sit down with your domain experts, make sure that everybody understands the requirement and how to express it in the ubiquitous language, etc.
Implement any new methods that you need in the aggregate root.
Once you have the use case correctly supported in the domain, then you can start worrying about your resources following the previous pattern - the resource just takes the incoming request, and invokes the appropriate commands.
Is changing the Title a requirement of Resolving a ticket? If not, they should not be the same action in DDD. You wouldn't want to not resolve the ticket if the new name was invalid, and you wouldn't want to not change the name if the ticket was not resolvable.
Make 2 calls to perform the 2 separate actions. This also allows for flexibility such as, the Title can be changed immediately, but perhaps "resolving" the ticket will kick off some complex and time consuming (asyncronous) work flow before the ticket is actually resolved. Perhaps it needs to have a manager sign off? You don't want the call to change "title" tied up in that mix.
If needs be, create something to orchestrate multiple commands as per #VoiceOfUnreason's comment.
Wherever possible, keep things separate, and code to use cases as opposed to minimizing interacitons with entities.
You're probably right. But it's probably wiser to encapsulate such logic inside the ticket it self, by making a "change()" method, receiving a changeCommandModel (or something like this), so you can define the business rules inside your domain object.
if (DTO.Status != null && dto.Status == "RESOLVED")
supportTicket.Resolve(DTO.title);
I will change the underlying method to take title as parameter, this clarify the resolve action. That second if and validation you want in the domain method. It's really preference, more importantly is the message and I agree with #VoiceOfUnreason second option.

Categories