public async Task <Activity> ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext turnContext, Activity activity, Action <Activity> tokenRequestHandler = null, Action <Activity> fallbackHandler = null) { if (_streamingTransportClient == null) { // establish websocket connection _streamingTransportClient = new WebSocketClient( EnsureWebSocketUrl(skillManifest.Endpoint.ToString()), new SkillCallingRequestHandler( turnContext, _botTelemetryClient, GetTokenCallback(turnContext, tokenRequestHandler), GetFallbackCallback(turnContext, fallbackHandler), GetHandoffActivityCallback())); } // acquire AAD token MicrosoftAppCredentials.TrustServiceUrl(skillManifest.Endpoint.AbsoluteUri); var token = await serviceClientCredentials.GetTokenAsync(); // put AAD token in the header var headers = new Dictionary <string, string>(); headers.Add("Authorization", $"Bearer {token}"); await _streamingTransportClient.ConnectAsync(headers); // set recipient to the skill var recipientId = activity.Recipient.Id; activity.Recipient.Id = skillManifest.MSAappId; // Serialize the activity and POST to the Skill endpoint var body = new StringContent(JsonConvert.SerializeObject(activity, SerializationSettings.BotSchemaSerializationSettings), Encoding.UTF8, SerializationSettings.ApplicationJson); var request = StreamingRequest.CreatePost(string.Empty, body); // set back recipient id to make things consistent activity.Recipient.Id = recipientId; var stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); await _streamingTransportClient.SendAsync(request); stopWatch.Stop(); _botTelemetryClient.TrackEvent( "SkillWebSocketTurnLatency", new Dictionary <string, string> { { "SkillName", skillManifest.Name }, { "SkillEndpoint", skillManifest.Endpoint.ToString() }, }, new Dictionary <string, double> { { "Latency", stopWatch.ElapsedMilliseconds }, }); return(_handoffActivity); }
public static void TrackQnAMakerEvent(this IBotTelemetryClient telemetryClient, string message, QueryResult[] qnaResults) { var topAnswer = qnaResults.FirstOrDefault(); telemetryClient.TrackEvent("QnAMakerResult", new Dictionary <string, string> { ["MessageContent"] = message, ["TopAnswer"] = topAnswer?.Answer, ["TopAnswerScore"] = topAnswer?.Score.ToString(), ["TopAnswerId"] = topAnswer?.Metadata.SingleOrDefault(x => x.Name == "id")?.Value }); }
//gets response from documentation QnA Maker private async Task ProcessDocumentationQnAAsync(ITurnContext <IMessageActivity> turnContext, CancellationToken cancellationToken) { Logger.LogInformation("ProcessSampleQnAAsync"); var results = await BotServices.DocumentationQnA.GetAnswersAsync(turnContext); if (results.Any()) { await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken); } else { var answerNotFoundProperties = new Dictionary <string, string>(); answerNotFoundProperties.Add( "question", turnContext.Activity.Text); TelemetryClient.TrackEvent("QnANotFound", answerNotFoundProperties); await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken); } }
private void Server_Disconnected(object sender, DisconnectedEventArgs e) { if (_stopWatch.IsRunning) { _stopWatch.Stop(); _botTelemetryClient.TrackEvent("SkillWebSocketOpenCloseLatency", null, new Dictionary <string, double> { { "Latency", _stopWatch.ElapsedMilliseconds }, }); } }
private void LogFeedback(FeedbackRecord record) { var properties = new Dictionary <string, string>() { { nameof(FeedbackRecord.Tag), record.Tag }, { nameof(FeedbackRecord.Feedback), record.Feedback }, { nameof(FeedbackRecord.Comment), record.Comment }, { nameof(FeedbackRecord.Request.Text), record.Request?.Text }, { nameof(FeedbackRecord.Request.Id), record.Request?.Conversation.Id }, { nameof(FeedbackRecord.Request.ChannelId), record.Request?.ChannelId }, }; _telemetryClient.TrackEvent(_traceName, properties); }
/// <summary> /// Sends feedback to be logged. /// </summary> /// <param name="record">FeedbackRecord object to be logged.</param> /// <param name="telemetryClient">IBotTelemetryClient object used to log feedback record<param> public static void LogFeedback(FeedbackRecord record, IBotTelemetryClient telemetryClient) { var properties = new Dictionary <string, string>() { { nameof(FeedbackRecord.Tag), record.Tag }, { nameof(FeedbackRecord.Feedback), record.Feedback }, { nameof(FeedbackRecord.Comment), record.Comment }, { nameof(FeedbackRecord.Request.Text), record.Request?.Text }, { nameof(FeedbackRecord.Request.Id), record.Request?.Conversation.Id }, { nameof(FeedbackRecord.Request.ChannelId), record.Request?.ChannelId }, }; telemetryClient.TrackEvent("Feedback", properties); }
/// <summary> /// Sends activities to the conversation. /// </summary> /// <param name="turnContext">The context object for the turn.</param> /// <param name="activities">The activities to send.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <remarks>If the activities are successfully sent, the task result contains /// an array of <see cref="ResourceResponse"/> objects containing the IDs that /// the receiving channel assigned to the activities.</remarks> /// <seealso cref="ITurnContext.OnSendActivities(SendActivitiesHandler)"/> public override async Task <ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken) { if (turnContext == null) { throw new ArgumentNullException(nameof(turnContext)); } if (activities == null) { throw new ArgumentNullException(nameof(activities)); } if (activities.Length == 0) { throw new ArgumentException("Expecting one or more activities, but the array was empty.", nameof(activities)); } var responses = new ResourceResponse[activities.Length]; /* * NOTE: we're using for here (vs. foreach) because we want to simultaneously index into the * activities array to get the activity to process as well as use that index to assign * the response to the responses array and this is the most cost effective way to do that. */ for (var index = 0; index < activities.Length; index++) { var activity = activities[index]; if (string.IsNullOrWhiteSpace(activity.Id)) { activity.Id = Guid.NewGuid().ToString("n"); } var response = default(ResourceResponse); if (activity.Type == ActivityTypesEx.Delay) { // The Activity Schema doesn't have a delay type build in, so it's simulated // here in the Bot. This matches the behavior in the Node connector. var delayMs = (int)activity.Value; await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); // No need to create a response. One will be created below. } // set SemanticAction property of the activity properly EnsureActivitySemanticAction(turnContext, activity); if (activity.Type != ActivityTypes.Trace || (activity.Type == ActivityTypes.Trace && activity.ChannelId == "emulator")) { var requestPath = $"/activities/{activity.Id}"; var request = StreamingRequest.CreatePost(requestPath); // set callerId to empty so it's not sent over the wire activity.CallerId = null; request.SetBody(activity); _botTelemetryClient.TrackTrace($"Sending activity. ReplyToId: {activity.ReplyToId}", Severity.Information, null); var stopWatch = new Diagnostics.Stopwatch(); try { stopWatch.Start(); response = await SendRequestAsync <ResourceResponse>(request).ConfigureAwait(false); stopWatch.Stop(); } catch (Exception ex) { throw new SkillWebSocketCallbackException($"Callback failed. Verb: POST, Path: {requestPath}", ex); } _botTelemetryClient.TrackEvent("SkillWebSocketSendActivityLatency", null, new Dictionary <string, double> { { "Latency", stopWatch.ElapsedMilliseconds }, }); } // If No response is set, then defult to a "simple" response. This can't really be done // above, as there are cases where the ReplyTo/SendTo methods will also return null // (See below) so the check has to happen here. // Note: In addition to the Invoke / Delay / Activity cases, this code also applies // with Skype and Teams with regards to typing events. When sending a typing event in // these _channels they do not return a RequestResponse which causes the bot to blow up. // https://github.com/Microsoft/botbuilder-dotnet/issues/460 // bug report : https://github.com/Microsoft/botbuilder-dotnet/issues/465 if (response == null) { response = new ResourceResponse(activity.Id ?? string.Empty); } responses[index] = response; } return(responses); }
public static void TrackEventEx(this IBotTelemetryClient telemetryClient, string eventName, Activity activity, string dialogId = null, IDictionary <string, string> properties = null, IDictionary <string, double> metrics = null) { telemetryClient.TrackEvent(eventName, GetFinalProperties(activity, dialogId, properties), metrics); }
/// <summary> /// Records incoming and outgoing activities to the Application Insights store. /// </summary> /// <param name="context">The context object for this turn.</param> /// <param name="nextTurn">The delegate to call to continue the bot middleware pipeline.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects /// or threads to receive notice of cancellation.</param> /// <returns>A task that represents the work queued to execute.</returns> /// <seealso cref="ITurnContext"/> /// <seealso cref="Bot.Schema.IActivity"/> public async Task OnTurnAsync(ITurnContext context, NextDelegate nextTurn, CancellationToken cancellationToken) { BotAssert.ContextNotNull(context); context.TurnState.Add(TelemetryLoggerMiddleware.AppInsightsServiceKey, _telemetryClient); // log incoming activity at beginning of turn if (context.Activity != null) { var activity = context.Activity; // Log the Application Insights Bot Message Received _telemetryClient.TrackEvent(BotMsgReceiveEvent, this.FillReceiveEventProperties(activity)); } // hook up onSend pipeline context.OnSendActivities(async(ctx, activities, nextSend) => { // run full pipeline var responses = await nextSend().ConfigureAwait(false); foreach (var activity in activities) { _telemetryClient.TrackEvent(BotMsgSendEvent, this.FillSendEventProperties(activity)); } return(responses); }); // hook up update activity pipeline context.OnUpdateActivity(async(ctx, activity, nextUpdate) => { // run full pipeline var response = await nextUpdate().ConfigureAwait(false); _telemetryClient.TrackEvent(BotMsgUpdateEvent, this.FillUpdateEventProperties(activity)); return(response); }); // hook up delete activity pipeline context.OnDeleteActivity(async(ctx, reference, nextDelete) => { // run full pipeline await nextDelete().ConfigureAwait(false); var deleteActivity = new Activity { Type = ActivityTypes.MessageDelete, Id = reference.ActivityId, } .ApplyConversationReference(reference, isIncoming: false) .AsMessageDeleteActivity(); _telemetryClient.TrackEvent(BotMsgDeleteEvent, this.FillDeleteEventProperties(deleteActivity)); }); if (nextTurn != null) { await nextTurn(cancellationToken).ConfigureAwait(false); } }
public async override Task <Response> ProcessRequestAsync(ReceiveRequest request, object context = null, ILogger <RequestHandler> logger = null) { if (Bot == null) { throw new ArgumentNullException(nameof(Bot)); } if (SkillWebSocketBotAdapter == null) { throw new ArgumentNullException(nameof(SkillWebSocketBotAdapter)); } var response = new Response(); var body = await request.ReadBodyAsString().ConfigureAwait(false); if (string.IsNullOrEmpty(body) || request.Streams?.Count == 0) { response.StatusCode = (int)HttpStatusCode.BadRequest; response.SetBody("Empty request body."); return(response); } if (request.Streams.Where(x => x.Type != "application/json; charset=utf-8").Any()) { response.StatusCode = (int)HttpStatusCode.NotAcceptable; return(response); } Activity activity = null; try { activity = JsonConvert.DeserializeObject <Activity>(body, Serialization.Settings); } catch (Exception ex) { _botTelemetryClient.TrackException(ex); response.StatusCode = (int)HttpStatusCode.BadRequest; response.SetBody("Request body is not an Activity instance."); return(response); } try { var cancellationTokenSource = new CancellationTokenSource(); _stopWatch.Start(); var invokeResponse = await this.SkillWebSocketBotAdapter.ProcessActivityAsync(activity, new BotCallbackHandler(this.Bot.OnTurnAsync), cancellationTokenSource.Token).ConfigureAwait(false); _stopWatch.Stop(); _botTelemetryClient.TrackEvent("SkillWebSocketProcessRequestLatency", null, new Dictionary <string, double> { { "Latency", _stopWatch.ElapsedMilliseconds }, }); // trigger cancel token after activity is handled. this will stop the typing indicator cancellationTokenSource.Cancel(); if (invokeResponse == null) { response.StatusCode = (int)HttpStatusCode.OK; } else { response.StatusCode = invokeResponse.Status; if (invokeResponse.Body != null) { response.SetBody(invokeResponse.Body); } } } catch (SkillWebSocketCallbackException ex) { _botTelemetryClient.TrackException(ex); response.StatusCode = (int)HttpStatusCode.InternalServerError; response.SetBody(ex.Message); return(response); } catch (Exception ex) { _botTelemetryClient.TrackException(ex); response.StatusCode = (int)HttpStatusCode.InternalServerError; } return(response); }