How to add a Dialog in a bot using SDK4.0 - botframework

I am trying to implement a bot which uses Qna services and Azure search.
I am taking help of the C# QnA Maker sample github code.
It is using a BotServices.cs class which is taking a QnA service in its constructor. This Botservice object is being passed to the QnABot class constructor.
I want to use Dialog set in QnABot's constructor which need accessors to be added. I really didn't understand how to add accessor class and use them in the startup.cs
I tried to copy some code from other samples but didn't work.
Please help me to add an accessor to the BotServices constructor so that I can use dialog sets inside of it.
I would like to extend the QnA sample for my purpose.

Can you tell us why you want to pass a dialog set tot the botservices class? this class is only used to reference external services such as QnAMaker and LUIS. If you want to start a Dialog, do so in the OnTurnAsync method of the QnABot.cs class. keep in mind that the this method as it is created in this specific sample will send a response on every message the user sends even if they are working through a dialog. You could change the OnTurnAsync in such a way that the first step in the dialog is to check the QnAMaker. See the enterpriseBot sample to see how to start a dialog as well as adding an accessor to a child Dialog. see the following snipped from the MainDialog.cs class how they added the accessor:
protected override async Task OnStartAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
var onboardingAccessor = _userState.CreateProperty<OnboardingState>(nameof(OnboardingState));
var onboardingState = await onboardingAccessor.GetAsync(innerDc.Context, () => new OnboardingState());
var view = new MainResponses();
await view.ReplyWith(innerDc.Context, MainResponses.Intro);
if (string.IsNullOrEmpty(onboardingState.Name))
{
// This is the first time the user is interacting with the bot, so gather onboarding information.
await innerDc.BeginDialogAsync(nameof(OnboardingDialog));
}
}

Related

With Elsa Workflow 2.0 how to create an activity based on HttpEndpoint

We're trying to use Elsa for a project, but we're facing some difficulties now, so need suggestions badly. One thing we're trying to do is to create an Activity based on existing HttpEndpoint. However, with the source code got from https://github.com/elsa-workflows/elsa-core, after googled some docs and samples, we haven't been able to figure it out.
Here is what exactly we're attempting to do.
create a new Activity based on HttpEndpoint
make the Path include WorkflowInstanceId by default
a little bit more customizations needed in our scenario
Looking forward to suggestions and guidance. Thanks!
You could do something like what the Webhooks module is doing. Instead of
inheriting from HttpEndpoint, it uses an IActivityTypeProvider implementation that dynamically yields new activity types that reuse HttpEndpoint.
In your case, your activity type provider would only have to yield a single activity type (e.g. MyEndpoint) that pre-configures any and all aspects that you want, including the default Path property value.
Deriving a new activity type from HttpEndpoint directly works too. You will have to implement your own bookmark provider that provides HttpEndpointBookmark objects - the HTTP middleware for the HttpEndpoint activity relies on that.
Example:
public class MyEndpointBookmarkProvider : BookmarkProvider<HttpEndpointBookmark>
{
public override bool SupportsActivity(BookmarkProviderContext context) => context.ActivityType.TypeName == nameof(MyEndpoint);
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken)
{
var path = await context.ReadActivityPropertyAsync<MyEndpoint, PathString>(x => x.Path, cancellationToken);
var methods = (await context.ReadActivityPropertyAsync<MyEndpoint, HashSet<string>>(x => x.Methods, cancellationToken))?.Select(ToLower) ?? Enumerable.Empty<string>();
BookmarkResult CreateBookmark(string method) => Result(new(path, method), nameof(HttpEndpoint));
return methods.Select(CreateBookmark);
}
private static string ToLower(string s) => s.ToLowerInvariant();
}
The above bookmark provider provides bookmarks for your custom activity when the activity being indexed is of type "MyEndpoint".
Alternatively, you might go a different route altogether and simply implement an API endpoint (an ASP.NET Core controller, middleware or route endpoint) that triggers workflows based on your custom activity. I've written some documentation about that process here: https://elsa-workflows.github.io/elsa-core/docs/next/guides/guides-blocking-activities

Dynamics CRM Getting Account Details

I'm having a problem with the following thing.
In Dynamics CRM I have a Custom Button that created a new Order, the button works great, however, I wanted to make it work as the OOB one and prefilled some of the Account information such as Account Name and Price List (form example).
example
In Ribbon Workbench I've added the following parameter for my button
ribbon settins
The next thing that I've done was to create a new Jscript web resource and add the following code.
references: https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/customize-dev/pass-dynamics-365-data-page-parameter-ribbon-actions
Web resource name: new_getorder
Code: function XrmCore.Commands.Open.openNewRecord(primaryControl) {
var formContext = primaryControl;
}
I've then added the web resource on the Order form where is supposed to trigger when clicking on the custom button from the account form, but I'm getting a script error (which is not a surprise for me)
form properies
I've also enabled Pass execution context as first parameter
Web resource method does not exist: XrmCore.Commands.Open.openNewRecord
new order button from account ribbon
Error when loading the Order form: Web resource method does not exist: XrmCore.Commands.Open.openNewRecord
Instead of custom code, why don't you try Map Entity Field feature?
You can map attributes between entities that have an entity
relationship. This lets you set default values for a record that is
created in the context of another record.
In your scenario, after mapping Account and Price list fields between Account and Order tables, when you will go to the Related > Orders section and try to create an Order from there, those two fields should be pre-filled on the new Order form.
And I think this feature would work also when you have a Orders sub grid on account form and try to create Order from new button on the sub grid.
You may go through the documentation to understand this concept and to check steps to create mapping.
Anyway, I've managed to find my solution by using this script in a custom web resource
enter code here
var entityFormOptions = {};
entityFormOptions["entityName"] = "salesorder";
entityFormOptions["createFromEntity"] = currentRecordRef;;
// Open the form.
Xrm.Navigation.openForm(entityFormOptions).then(
function (success) {
console.log(success);
},
function (error) {
console.log(error);
});

New intent in LUIS for multi turn dialogs

I am going to create a multi-turn dialog. I didn't get how it should be connected with LUIS models. I checked out documentation, but there are samples with only one turn dialogs. Also, I use Virtual Assistant template.
I want to do something like this.
User: I want to book a flight
Bot: What is the destination?
User: London
Bot: When?
User: 21st of September.
Bot: The ticket was bought.
The questions are what happens on the second step? Should I check out dispatcher? Should I add all possible phrases for all steps inside the intent?
General LUIS stuff
For your LUIS model you will need your intents - BookFlight and None. Under your BookFlight intent you will have your Utterances - all the phrases you want to be able to trigger the BookFlight intent.
MyLuisApp
--BookFlight
----I want to book a flight
----Book a flight
----I need a plane ticket
----etc
--None
----Utterances that don't match any of your intents
The none intent is VERY important as per this documentation.
Adding this functionality to a new bot or the core bot template
There are a couple different samples provided on how you could achieve this, but the best way is using Dialogs. What you want is a Waterfall Dialog. Inside this Dialog you can define each stage in the waterfall e.g. Ask for destination, ask for date etc.
In order to trigger the BookFlight waterfall you would have a MainDialog that handled every request, and checks with the LUIS dispatcher link1 and link2 to find out the users intent as per this example. If the intent is BookFlight then you would start the BookFlightDialog which contains the book flight waterfall.
...
// Check dispatch result
var dispatchResult = await cognitiveModels.DispatchService.RecognizeAsync<DispatchLuis>(dc.Context, CancellationToken.None);
var intent = dispatchResult.TopIntent().intent;
if (intent == "BookFlight")
{
// Start BookFlightDialog
await dc.BeginDialogAsync(nameof(BookFlightDialog));
}
General Waterfall Dialog stuff
You'd define your steps as something like:
var waterfallSteps = new WaterfallStep[]
{
AskDestinationAsync,
AskDepartureDateAsync,
ConfirmStepAsync,
FinishDialogAsync,
};
For your scenario there is actually a sample that has already been created with the BookFlight intent available here. There is a full guide on how to get this setup and working in the official documentation. So you can test to see how everything works then modify it as you need.
Other interesting links:
Custom prompt sample - roll your own.
Multi-turn sample - waterfall dialog.
Virtual Assistant stuff
Once you understand how the above works you will be able to modify the Virtual Assistant template to handle the BookFlight intent by taking the following actions:
Adding a BookFlight intent to your existing LUIS DISPATCH app that is connected to your VA template.
Adding utterances to the BookFlight intent.
Save and train your LUIS app.
Publish your LUIS app.
Running the update_cognitive_models.ps1 script as per step 3 of the instructions here which will pull down the changes (your new intent and utterances).
.\Deployment\Scripts\update_cognitive_models.ps1 -RemoteToLocal
NOTE: This command must be run using PowerShell Core and from the root of your project Directory, i.e. inside your Virtual Assistant folder.
The result of running this script should be a bunch of files created locally, as well as the DispatchLuis.cs file being updated to include your new intent. You should also check the Summary.html file that is created to see that your new intent is there. You will now have to update the VA code to actually do something when your new intent is triggered - add another if/case statement inside the RouteAsync method of the MainDialog.cs file - see here for an example.
Something like this:
MainDialog.cs
protected override async Task RouteAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
// Call to dispatch to get intent
if (intent == DispatchLuis.Intent.bookflight)
{
// Start BookFlightDialog
await dc.BeginDialogAsync(nameof(BookFlightDialog));
}
...
}

Recognize the user input inside the handler function using dialogflow.ai/api.ai and Microsoft Bot Builder

I am building a bot using the given technology stacks:
Microsoft Bot Builder
Node.js
Dialogflow.ai (api.ai)
We used waterfall model to implement a matched intent Dialog, which includes a couple of handler functions and prompts. After following this scenario I need to identify the entities inside an inner handler function, for a user input.
eg:
Bot : Where do you want to fly?
User: Singapore. (For this we added entities like SIN - Singapore,SIN(Synonym), So I need to resolve the value as SIN)
Any help on this scenario is much appreciated.
Here is a post Using api.ai with microsoft bot framework you can refer for your reqirement, and with a sample at https://github.com/GanadiniAkshay/weatherBot/blob/master/api.ai/index.js. The weather api key leveraged in this sample is out of date, but the waterfall and recognizer api key is still working.
Generally speaking:
Use api-ai-recognizer
Instantiate the apiairecognizer and leveraged builder.IntentDialog to include the recognizer:
var recognizer = new apiairecognizer("<api_key>");
var intents = new builder.IntentDialog({
recognizers: [recognizer]
});
In IntentDialogs, use builder.EntityRecognizer.findEntity(args.entities,'<entity>'); to recognize the intent entities.

Prism 2.1 Publish/Subscribe with weak reference?

I am building a Prism 2.1 demo by way of getting up to speed with the technology. I am having a problem with CompositePresentationEvents published and subscribed via the Event Aggregation service. The event subscription works fine if I set a strong reference (KeepSubscriberReferenceAlive = true), but it fails if I set a weak reference (KeepSubscriberReferenceAlive omitted).
I would like to subscribe with a weak reference, so that I don't have to manage unsubscribing from the event. Is there any way to do that? Why is a strong reference required here? Thanks for your help!
Here are the particulars: My demo app is single-threaded and has two regions, Navigator and Workspace, and three modules, NavigatorModule, WorkspaceAModule, and WorkspaceBModule. The NavigatorModule has two buttons, 'Show Workspace A' and 'Show Workspace B'. When one of these buttons is clicked, an ICommand is invoked that publishes a CompositePresentationEvent called ViewRequested. The event carries a string payload that specifies which workspace module should be shown.
Here is the event declaration, from the app's Infrastructure project:
using Microsoft.Practices.Composite.Presentation.Events;
namespace Prism2Demo.Common.Events
{
public class ViewRequestedEvent : CompositePresentationEvent<string>
{
}
}
Here is the event publishing code, from the Navigator module:
// Publish ViewRequestedEvent
var eventAggregator = viewModel.Container.Resolve<IEventAggregator>();
var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
viewRequestedEvent.Publish(workspaceName);
Here is the event subscription code, which each Workspace module includes in its Initialize() method:
// Subscribe to ViewRequestedEvent
var eventAggregator = m_Container.Resolve<IEventAggregator>();
var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, ThreadOption.PublisherThread, true);
The Subscribe() statement is shown with a strong reference.
Thanks again for your help.
A couple of things to check:
Make sure that your EventAggregator instance is being correctly registered with the container or it may itself be garbage collected:
container.RegisterType<IEventAggregator, EventAggregator>(new ContainerControlledLifetimeManager());
Also make sure that you have a strong reference to the subscribed object held somewhere (this in your subscription code).

Resources