My bot right now is using local memory, the goal is whenever a conversation end. I want to delete everything from local memory about this user. So I tried this onEndOfConversation.
Apparently this error shows up saying that onEndOfConversation is not a function.
This is my code :
const { CardFactory } = require('botbuilder');
const { DialogBot } = require('./dialogBot');
const WelcomeCard = require('./resources/welcomeCard.json');
class DialogAndWelcomeBot extends DialogBot {
constructor(conversationState, userState, dialog) {
super(conversationState, userState, dialog);
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; cnt++) {
if (membersAdded[cnt].id !== context.activity.recipient.id) {
//const welcomeCard = CardFactory.adaptiveCard(WelcomeCard);
//await context.sendActivity({ attachments: [welcomeCard] });
await dialog.run(context, conversationState.createProperty('DialogState'));
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
this.onEndOfConversation(async (context, next) => {
console.log("END!");
await conversationState.delete(context);
await userState.delete(context);
});
}
}
module.exports.DialogAndWelcomeBot = DialogAndWelcomeBot;
So how should I do this? If onEndOfConversation isn't recognize, what alternatives I can do to clear user and conversation from the memory after a dialogue ends.
The endOfConversation activity handler is used internally when a bot is also coupled with a skill. When a conversation is ended by the user, the bot then sends this activity type to the skill notifying it that the conversation has ended with the user.
There are different ways you could attack this. The method I use is component dialogs. Modeled after the cancelAndHelpDialog design, when a user types "cancel" or "quit", the user is brought to an exit dialog where feedback is gathered, etc.
As part of the exiting process, you could call conversationState.delete() within the dialog followed by cancelAllDialogs(true).
Hope of help!
Related
I am try to implement Swapping context between dialog flows .Assume i am in middle of one dialog flow and i wants to move to another functionality or dialog flow with new utterance..Here bot should prompt Do you want to move to another flow ?...However we have implemented Global Message Scorables here.... Please help me any one. Thanks in advance
i am trying to get the result, i am created one more method in core bot and try to check the luis score again and redirect to new dialog flow based on luis score
enter code here
var msg = stepContext.Context.Activity.Text;
var recognizerResult=await
_services.BasicBotLuisApplication.RecognizeAsync(stepContext.Context, cancellationToken);
var topScoreIntent = recognizerResult?.GetTopScoringIntent();
elseif(topScoreIntent.Value.score>double.Parse(appSettings.Value.LuisScore))
{
var luisRes = recognizerResult.Properties["luisResult"] as LuisResult;
return await stepContext.BeginDialogAsync(nameof(CreateDialog), luisRes,
cancellationToken);
}
it is working normal flows or type any other keywords like help, cancel, stop. but i give it any dialog flow at that time it's not working.
I'm not sure why this isn't working, as the logic seems sound. But you say that the other interrupts (which I think in latest core-bot sample are specific utterances) are working. Have you tried checking intent in the interrupt function? Here is what I have done with my nodejs bot, hopefully this will help in your case. Instead of checking for utterances, I'm checking intent. Cancel and Help just provide text, but Expedite and Escalate start new dialogs.
async isTurnInterrupted(dc, luisResults) {
const topIntent = LuisRecognizer.topIntent(luisResults);
const topIntentScore = luisResults.intents[topIntent].score;
// see if there are any conversation interrupts we need to handle
if (topIntent === CANCEL_INTENT & topIntentScore > 0.6) {
if (dc.activeDialog) {
// cancel all active dialog (clean the stack)
await dc.cancelAllDialogs();
await dc.context.sendActivity('Ok. I\'ve cancelled our last activity.');
} else {
await dc.context.sendActivity('I don\'t have anything to cancel. If you\'re not trying to cancel something, please ask your question again.');
}
return true; // this is an interruption
}
if (topIntent === HELP_INTENT & topIntentScore > 0.5) {
await dc.context.sendActivity('Let me try to provide some help.');
await dc.context.sendActivity('Right now I am trained to help you with order status and tracking. If you are stuck in a conversation, type "Cancel" to start over.');
return true; // this is an interruption
}
if (topIntent === EXPEDITE_INTENT & topIntentScore > 0.5) {
await dc.beginDialog(INTERRUPT_DIALOG, topIntent);
return false; // pushing new dialog so not an interruption
}
if (topIntent === ESCALATE_INTENT & topIntentScore > 0.5) {
await dc.beginDialog(INTERRUPT_DIALOG, topIntent);
return false; // pushing new dialog so not an interruption
}
return false; // this is not an interruption
}
I am using the virtual assistant template provided via Microsoft's botframework-solutions Github repository, and I am having trouble maintaining the active dialog between our main entry point -- a bot developed that implements the dispatch model to determine which skill to send the user's utterance for further handling -- and the individual skill processes the user's input.
I have our dispatch model bot listening to POSTs made to localhost:3979/api/messages using the route method in mainDialog.ts to determine the relevant skill to pass the users' utterance to, which takes that utterance and determines which dialog within the skill should be used to handle the users' utterance. When we begin a dialog that implements a multi-step waterfall dialog, the dispatch model bot is not keeping track of the dialog that is active within the skill bot, and therefore the skill bot doesn't know how to route the users' utterance to the already active dialog. When we are POSTing directly to the skill bot and foregoing the dispatch model bot, the skill bot is able to keep track of the active dialog, which leads me to believe that we aren't properly managing state between the dispatch bot and the skill bot.
I've noticed that bot the virtual assistant and skill templates we're using from the botframework-solutions templates instantiate a new instance of AutoSaveStateMiddleWare within their defaultAdapter.ts, so it appears that reading and writing of conversation and user state should already be automatically managed.
mainDialog.ts within the dispatch bot, which routes utterances to the appropriate skill
protected async route(dc: DialogContext): Promise<void> {
// Get cognitive models for locale
const locale: string = i18next.language.substring(0, 2);
const cognitiveModels: ICognitiveModelSet | undefined = this.services.cognitiveModelSets.get(locale);
if (cognitiveModels === undefined) {
throw new Error('There is no value in cognitiveModels');
}
// Check dispatch result
const dispatchResult: RecognizerResult = await cognitiveModels.dispatchService.recognize(dc.context);
const intent: string = LuisRecognizer.topIntent(dispatchResult);
if (this.settings.skills === undefined) {
throw new Error('There is no skills in settings value');
}
// Identify if the dispatch intent matches any Action within a Skill if so, we pass to the appropriate SkillDialog to hand-off
const identifiedSkill: ISkillManifest | undefined = SkillRouter.isSkill(this.settings.skills, intent);
if (identifiedSkill !== undefined) {
// We have identified a skill so initialize the skill connection with the target skill
await dc.beginDialog(identifiedSkill.id);
// Pass the activity we have
const result: DialogTurnResult = await dc.continueDialog();
if (result.status === DialogTurnStatus.complete) {
await this.complete(dc);
}
} else if (intent === 'l_NOVA_general') {
// If dispatch result is general luis model
const luisService: LuisRecognizerTelemetryClient | undefined = cognitiveModels.luisServices.get(this.luisServiceGeneral);
if (luisService === undefined) {
throw new Error('The specified LUIS Model could not be found in your Bot Services configuration.');
} else {
const result: RecognizerResult = await luisService.recognize(dc.context);
if (result !== undefined) {
const generalIntent: string = LuisRecognizer.topIntent(result);
// switch on general intents
switch (generalIntent) {
case 'Escalate': {
// start escalate dialog
await dc.beginDialog(EscalateDialog.name);
break;
}
case 'None':
default: {
// No intent was identified, send confused message
await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
}
}
}
}
} else {
// If dispatch intent does not map to configured models, send 'confused' response.
await this.responder.replyWith(dc.context, MainResponses.responseIds.confused);
}
}
The following code from the skill bot's dialogBot.ts which listens to all turn events
public async turn(turnContext: TurnContext, next: () => Promise<void>): Promise<any> {
// Client notifying this bot took to long to respond (timed out)
if (turnContext.activity.code === EndOfConversationCodes.BotTimedOut) {
this.telemetryClient.trackTrace({
message: `Timeout in ${ turnContext.activity.channelId } channel: Bot took too long to respond`,
severityLevel: Severity.Information
});
return;
}
const dc: DialogContext = await this.dialogs.createContext(turnContext);
if (dc.activeDialog !== undefined) {
const result: DialogTurnResult = await dc.continueDialog();
} else {
await dc.beginDialog(this.rootDialogId);
}
await next();
}
The mainDialog.ts within the skill bot that routes to the claims_claimsStatus waterfall dialog
protected async route(dc: DialogContext): Promise<void> {
// get current activity locale
const locale: string = i18next.language.substring(0, 2);
const localeConfig: Partial<ICognitiveModelSet> | undefined = this.services.cognitiveModelSets.get(locale);
// Populate state from SkillContext slots as required
await this.populateStateFromSkillContext(dc.context);
if (localeConfig === undefined) {
throw new Error('There is no cognitiveModels for the locale');
}
// Get skill LUIS model from configuration
if (localeConfig.luisServices !== undefined) {
const luisService: LuisRecognizerTelemetryClient | undefined = localeConfig.luisServices.get(this.solutionName);
if (luisService === undefined) {
throw new Error('The specified LUIS Model could not be found in your Bot Services configuration.');
} else {
let turnResult: DialogTurnResult = Dialog.EndOfTurn;
const result: RecognizerResult = await luisService.recognize(dc.context);
const intent: string = LuisRecognizer.topIntent(result);
switch (intent) {
case 'claims_claimStatus': {
turnResult = await dc.beginDialog(StatusDialog.name);
break;
}
.
.
.
Expected results: When using the dispatch bot to route to the skill bot, after beginning a waterfall dialog within the claims_claimStatus dialog, the dialogContext.activeDialog should be statusDialog for further steps of the waterfall dialog
Actual results: When using the dispatch bot to route to the skill bot, after beginning a waterfall dialog within the claims_claimStatus dialog, the dialogContext.activeDialog should is undefined for further steps of the waterfall dialog
I have a SMS / Twilio Channel that I'm using to send out a Proactive message to the user. To send the Proactive message I'm calling a API method passing in MySpecialId which is used later in the conversation.
I want to save this MySpecialId into the conversation but at the point I have the MySpecialId the conversation doesn't exist yet, and I don't have a turnContext, so I can't really save it yet.
Is there a way to pass this Id from my API method into the BotCallback? I created a quick example. (Here is the original example I'm using https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/16.proactive-messages)
Thanks
[HttpGet("{number}")]
public async Task<IActionResult> Get(string MySpecialId)
{
//For Twillio Channel
MicrosoftAppCredentials.TrustServiceUrl("https://sms.botframework.com/");
var NewConversation = new ConversationReference
{
User = new ChannelAccount { Id = $"+1{PHONENUMBERTOTEXTHERE}" },
Bot = new ChannelAccount { Id = "+1MYPHONENUMBERHERE" },
Conversation = new ConversationAccount { Id = $"+1{PHONENUMBERTOTEXTHERE}" },
ChannelId = "sms",
ServiceUrl = "https://sms.botframework.com/"
};
BotAdapter ba = (BotAdapter)_HttpAdapter;
await ((BotAdapter)_HttpAdapter).ContinueConversationAsync(_AppId, NewConversation, BotCallback, default(CancellationToken));
return new ContentResult()
{
Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
};
}
private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
try
{
var MyConversationState = _ConversationState.CreateProperty<MyConversationData>(nameof(MyConversationData));
var ConversationState = await MyConversationState.GetAsync(turnContext, () => new MyConversationData(), cancellationToken);
//********************************************************************************************************************
ConversationState.MySpecialId = //HOW DO I GET MySpecialId FROM THE GET METHOD ABOVE HERE?
//********************************************************************************************************************
await _ConversationState.SaveChangesAsync((turnContext, false, cancellationToken);
await turnContext.SendActivityAsync("Starting proactive message bot call back");
}
catch (Exception ex)
{
this._Logger.LogError(ex.Message);
}
}
I don't believe that you can. Normally, you would do something like this by passing values through Activity.ChannelData, but ChannelData doesn't exist on ConversationReference.
Per comments below, #Ryan pointed out that you can pass data on ConversationAccount.Properties. Note, however, this is currently only available in the C# SDK. I've opened an issue to bring this into the Node SDK, but ETA is unknown at this point.
Instead, I'd suggest using a something more native to C#, like:
Create a ConcurrentDictionary
private ConcurrentDictionary<string, string> _idMap;
Map MySpecialId to Conversation.Id (in your Get function)
_idMap.AddOrUpdate(conversationReference.Conversation.Id, MySpecialId, (key, newValue) => MySpecialId);
Access the MySpecialId from the Activity.Conversation.Id (in BotCallback)
var ConversationState = await MyConversationState.GetAsync(turnContext, () => new MyConversationData(), cancellationToken);
ConversationState.MySpecialId = _idMap.GetValueOrDefault(turnContext.Activity.Conversation.Id);
Save ConversationState
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
There's other ways you could do this and some validation checks you'll need to add, but this should get you started.
I'm trying to send an event activity from the bot to the client when I'm in a certain dialog, but I just can't get it to work... Any suggestions or code samples I can look at?
Also I already tried to make a back channel and it works but on the bot side as far as I could tell it only works in the message controller.
EDIT*
I'm sorry, for not providing any details, I was in a hurry last week.
So i'm making a bot that will fill a "report" for a user, the bot asks questions, and the user gives the answers. For the first question i have to call a function in my angular app when i'm in the first dialog, that is after the root dialog, that will check the user input and return an "Account" object to the bot if the account exists, if it doesn't then it returns null... (and i know it would be easier to just make an API and connect directly to the bot, but i have to use angular)
I'm using the back channel like so:
botConnection.activity$
.filter( activity => {
// tslint:disable-next-line:max-line-length
return (activity.type === 'message' && activity.from['id'] === 'VisitReportV3' && activity.text === 'Please select an account...') ||
(activity.type === 'message' && activity.from['id'] === 'user' && this.accountFlag)
})
.subscribe(activity => {
if (activity.from['id'] === 'VisitReportV3' && activity.text === 'Please select an account...') {
console.log('"account" received');
this.accountFlag = true;
postAccountInfo();
} else if (activity.from['id'] === 'user' && this.accountFlag) {
console.log('"account" flag recieved');
this.accountFlag = false;
}
});
It's probably completely wrong but i didn't know how else to do it..
tl;dr:
So in short, i need to check if i'm in the first dialog (or the one that asks me for the account) and if I am then wait for the next user input and call the angular function to check the input and see if there are any accounts that match, if not return null if they do return the serialized object to the bot for some more processing... I hope this explains some more.
In your messages controller you are probably only forwarding activities with a type of Message to your dialogs. If you used the default bot template, you probably have something like
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.GetActivityType() == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
that you need to change to
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.GetActivityType() == ActivityTypes.Message || activity.GetActivityType() == ActivityTypes.Event)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
That will send both messages and events to your dialogs. Keep in mind though that you need to check for both of them in each of you dialog methods now especially when you are expecting events as the user can type and send a message before your event gets sent.
EDIT
You can't get the dialog name from activity.from['id']. However, in your dialog you can create an Activity reply and set the name to be something else. For example in your dialog,
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
Activity activity = await result as Activity;
Activity reply = activity.CreateReply();
reply.Text = "Please select an account...";
reply.Name = "VisitReportV3";
await context.PostAsync(reply);
}
then in your back channel JavaScript you would change from checking if activity.from['id'] === "VisitReportV3" to activity.name === "VisitReportV3".
How can I send a message to the user without the user sending me a message? Like for example CNN bot is sending messages every day in the morning by itself. How can I do that in the bot framework?
See this.
In fact, you do not strictly need to receive a message from the user first, but addressing manually can be error-prone (you have to know the user's and bot's channel account, the service URL, etc.)
And in turn (per #thegaram's message), that only works for some channels. For example, Skype requires that the user contact the bot before the bot can message the user.
Once contacted, you can store the user's channelAccount data once they contact you and use that to send them proactive messages. For example if the user has subscribed to hear sports scores for a particular team over time.
Any sort of unsolicited spam messages of course are prohibited by the policies of the Bot Framework (and most of the channels).
Yes you can do that. We called it Greeting from Bot. I have done it and sharing a sample code with you.
Write this code in your messageController or first dialog used in bot.
if (activity.Text == null)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
Activity isActivityTyping = activity.CreateReply();
isActivityTyping.Type = ActivityTypes.Typing;
await connector.Conversations.ReplyToActivityAsync(isActivityTyping);
await Conversation.SendAsync(activity, () => new Dialogs.GreetDialog());
}
after this code you need to create a dialog GreetDialog. Below is the cs file code for your reference.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace GPP.Bot.Dialogs
{
[Serializable]
internal class GreetDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(Greeting);
}
private async Task Greeting(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
if (string.IsNullOrEmpty(message.Text))
{
// Hero Card
var cardMsg = context.MakeMessage();
var attachment = BotWelcomeCard("Hello, I am a bot. Right now I am on training and in a prototype state", "");
cardMsg.Attachments.Add(attachment);
await context.PostAsync(cardMsg);
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
else
{
context.Call<object>(new ActionDialog(), AfterGreetingDialogCompleted);
}
}
private static Attachment BotWelcomeCard(string responseFromQNAMaker, string userQuery)
{
var heroCard = new HeroCard
{
Title = userQuery,
Subtitle = "",
Text = responseFromQNAMaker,
Images = new List<CardImage> { new CardImage("https://i2.wp.com/lawyerist.com/lawyerist/wp-content/uploads/2016/08/docubot.gif?fit=322%2C294&ssl=1") },
Buttons = new List<CardAction> { new CardAction(ActionTypes.ImBack, "Show Menu", value: "Show Bot Menu") }
};
return heroCard.ToAttachment();
}
private async Task AfterGreetingDialogCompleted(IDialogContext context, IAwaitable<object> result)
{
context.Done<object>(new object());
}
}
}
this is a working code. Do let me know in case you face ant issue.
~cheers :)