Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ public QnAMakerDialog([CallerFilePath] string sourceFilePath = "", [CallerLineNu
[JsonIgnore]
public HttpClient HttpClient { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to log personal information that came from the user to telemetry.
/// </summary>
/// <value>If true, personal information is logged to Telemetry; otherwise the properties will be filtered.</value>
public bool LogPersonalInformation { get; set; } = false;

/// <summary>
/// Called when the dialog is started and pushed onto the dialog stack.
/// </summary>
Expand Down Expand Up @@ -229,7 +235,7 @@ protected async virtual Task<IQnAMakerClient> GetQnAMakerClientAsync(DialogConte
KnowledgeBaseId = this.knowledgeBaseId
};
var options = await GetQnAMakerOptionsAsync(dc).ConfigureAwait(false);
return new QnAMaker(endpoint, options, HttpClient);
return new QnAMaker(endpoint, options, HttpClient, this.TelemetryClient, this.LogPersonalInformation);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,13 @@ public LuisAdaptiveRecognizer()
public HttpClientHandler HttpClient { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to log personal information that came from the user to telemetry.
/// Gets or sets the flag to determine if personal information should be logged in telemetry.
/// </summary>
/// <value>If true, personal information is logged to Telemetry; otherwise the properties will be filtered.</value>
public bool LogPersonalInformation { get; set; } = false;
/// <value>
/// The flag to indicate in personal information should be logged in telemetry.
/// </value>
[JsonProperty("logPersonalInformation")]
public BoolExpression LogPersonalInformation { get; set; } = "=settings.telemetry.logPersonalInformation";
Copy link
Contributor

@vishwacsena vishwacsena Apr 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the new property, we need an update to the component schema file as well as re-gen the sdk.schema.


/// <inheritdoc/>
public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialogContext, Activity activity, CancellationToken cancellationToken = default, Dictionary<string, string> telemetryProperties = null, Dictionary<string, double> telemetryMetrics = null)
Expand All @@ -100,7 +103,7 @@ public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialog

var result = await wrapper.RecognizeAsync(tempContext, cancellationToken).ConfigureAwait(false);

this.TelemetryClient.TrackEvent("LuisResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties, dialogContext.Context), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "LuisResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties, dialogContext), telemetryMetrics);

return result;
}
Expand Down Expand Up @@ -134,19 +137,22 @@ public LuisRecognizerOptionsV3 RecognizerOptions(DialogContext dialogContext)
};
}

protected override Dictionary<string, string> FillRecognizerResultTelemetryProperties(RecognizerResult recognizerResult, Dictionary<string, string> telemetryProperties, ITurnContext turnContext)
protected override Dictionary<string, string> FillRecognizerResultTelemetryProperties(RecognizerResult recognizerResult, Dictionary<string, string> telemetryProperties, DialogContext dc)
{
var (logPersonalInfo, error) = this.LogPersonalInformation.TryGetValue(dc.State);
var (applicationId, error2) = this.ApplicationId.TryGetValue(dc.State);

var topTwoIntents = (recognizerResult.Intents.Count > 0) ? recognizerResult.Intents.OrderByDescending(x => x.Value.Score).Take(2).ToArray() : null;

// Add the intent score and conversation id properties
var properties = new Dictionary<string, string>()
{
{ LuisTelemetryConstants.ApplicationIdProperty, ApplicationId.ExpressionText },
{ LuisTelemetryConstants.ApplicationIdProperty, applicationId },
{ LuisTelemetryConstants.IntentProperty, topTwoIntents?[0].Key ?? string.Empty },
{ LuisTelemetryConstants.IntentScoreProperty, topTwoIntents?[0].Value.Score?.ToString("N2") ?? "0.00" },
{ LuisTelemetryConstants.Intent2Property, (topTwoIntents?.Count() > 1) ? topTwoIntents?[1].Key ?? string.Empty : string.Empty },
{ LuisTelemetryConstants.IntentScore2Property, (topTwoIntents?.Count() > 1) ? topTwoIntents?[1].Value.Score?.ToString("N2") ?? "0.00" : "0.00" },
{ LuisTelemetryConstants.FromIdProperty, turnContext.Activity.From.Id },
{ LuisTelemetryConstants.FromIdProperty, dc.Context.Activity.From.Id },
};

if (recognizerResult.Properties.TryGetValue("sentiment", out var sentiment) && sentiment is JObject)
Expand All @@ -166,9 +172,9 @@ protected override Dictionary<string, string> FillRecognizerResultTelemetryPrope
properties.Add(LuisTelemetryConstants.EntitiesProperty, entities);

// Use the LogPersonalInformation flag to toggle logging PII data, text is a common example
if (LogPersonalInformation && !string.IsNullOrEmpty(turnContext.Activity.Text))
if (logPersonalInfo && !string.IsNullOrEmpty(dc.Context.Activity.Text))
{
properties.Add(LuisTelemetryConstants.QuestionProperty, turnContext.Activity.Text);
properties.Add(LuisTelemetryConstants.QuestionProperty, dc.Context.Activity.Text);
}

// Additional Properties can override "stock" properties.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,16 @@ public QnAMakerDialog2([CallerFilePath] string sourceFilePath = "", [CallerLineN
public ArrayExpression<Metadata> StrictFilters { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to call test or prod environment of knowledge base to be called.
/// Gets or sets the flag to determine if personal information should be logged in telemetry.
/// </summary>
/// <value>
/// The flag to indicate in personal information should be logged in telemetry.
/// </value>
[JsonProperty("logPersonalInformation")]
public new BoolExpression LogPersonalInformation { get; set; } = "=settings.telemetry.logPersonalInformation";

/// <summary>
/// Gets or sets a value indicating whether gets or sets environment of knowledgebase to be called.
/// </summary>
/// <value>
/// A value indicating whether to call test or prod environment of knowledge base.
Expand Down Expand Up @@ -172,6 +181,7 @@ protected async override Task<IQnAMakerClient> GetQnAMakerClientAsync(DialogCont
var (epKey, _) = this.EndpointKey.TryGetValue(dc.State);
var (hn, _) = this.HostName.TryGetValue(dc.State);
var (kbId, _) = this.KnowledgeBaseId.TryGetValue(dc.State);
var (logPersonalInformation, _) = this.LogPersonalInformation.TryGetValue(dc.State);

var endpoint = new QnAMakerEndpoint
{
Expand All @@ -180,7 +190,8 @@ protected async override Task<IQnAMakerClient> GetQnAMakerClientAsync(DialogCont
KnowledgeBaseId = kbId
};
var options = await GetQnAMakerOptionsAsync(dc).ConfigureAwait(false);
return new QnAMaker(endpoint, options, this.HttpClient);

return new QnAMaker(endpoint, options, this.HttpClient, this.TelemetryClient, (bool)logPersonalInformation);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ public QnAMakerRecognizer()
[JsonIgnore]
public HttpClient HttpClient { get; set; }

/// <summary>
/// Gets or sets the flag to determine if personal information should be logged in telemetry.
/// </summary>
/// <value>
/// The flag to indicate in personal information should be logged in telemetry.
/// </value>
[JsonProperty("logPersonalInformation")]
public BoolExpression LogPersonalInformation { get; set; } = "=settings.telemetry.logPersonalInformation";

public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialogContext, Activity activity, CancellationToken cancellationToken, Dictionary<string, string> telemetryProperties = null, Dictionary<string, double> telemetryMetrics = null)
{
// Identify matched intents
Expand Down Expand Up @@ -210,7 +219,7 @@ public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialog
recognizerResult.Intents.Add("None", new IntentScore() { Score = 1.0f });
}

this.TelemetryClient.TrackEvent("QnAMakerRecognizerResult", this.FillRecognizerResultTelemetryProperties(recognizerResult, telemetryProperties), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "QnAMakerRecognizerResult", this.FillRecognizerResultTelemetryProperties(recognizerResult, telemetryProperties), telemetryMetrics);

return recognizerResult;
}
Expand All @@ -227,6 +236,7 @@ protected virtual Task<IQnAMakerClient> GetQnAMakerClientAsync(DialogContext dc)
var (epKey, error) = this.EndpointKey.TryGetValue(dc.State);
var (hn, error2) = this.HostName.TryGetValue(dc.State);
var (kbId, error3) = this.KnowledgeBaseId.TryGetValue(dc.State);
var (logPersonalInfo, error4) = this.LogPersonalInformation.TryGetValue(dc.State);

var endpoint = new QnAMakerEndpoint
{
Expand All @@ -235,7 +245,7 @@ protected virtual Task<IQnAMakerClient> GetQnAMakerClientAsync(DialogContext dc)
KnowledgeBaseId = (string)kbId ?? throw new ArgumentNullException(nameof(KnowledgeBaseId), error3)
};

return Task.FromResult<IQnAMakerClient>(new QnAMaker(endpoint, httpClient: this.HttpClient));
return Task.FromResult<IQnAMakerClient>(new QnAMaker(endpoint, new QnAMakerOptions(), this.HttpClient, this.TelemetryClient, (bool)logPersonalInfo));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialog

var result = ProcessResults(results);

this.TelemetryClient.TrackEvent("CrossTrainedRecognizerSetResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "CrossTrainedRecognizerSetResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties), telemetryMetrics);

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialog
if (this.Recognizers.TryGetValue(option, out var recognizer))
{
var result = await recognizer.RecognizeAsync(dialogContext, activity, cancellationToken, telemetryProperties, telemetryMetrics).ConfigureAwait(false);
this.TelemetryClient.TrackEvent("MultiLanguagesRecognizerResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "MultiLanguagesRecognizerResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties), telemetryMetrics);
return result;
}
}

this.TelemetryClient.TrackEvent("MultiLanguagesRecognizerResult", this.FillRecognizerResultTelemetryProperties(new RecognizerResult() { }, telemetryProperties), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "MultiLanguagesRecognizerResult", this.FillRecognizerResultTelemetryProperties(new RecognizerResult() { }, telemetryProperties), telemetryMetrics);

// nothing recognized
return new RecognizerResult() { };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialog
// merge intents
var result = MergeResults(results);

this.TelemetryClient.TrackEvent("RecognizerSetResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "RecognizerSetResult", this.FillRecognizerResultTelemetryProperties(result, telemetryProperties), telemetryMetrics);

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialog
// Identify matched intents
var text = activity.Text ?? string.Empty;
var locale = activity.Locale ?? "en-us";

var recognizerResult = new RecognizerResult()
{
Text = text,
Expand Down Expand Up @@ -171,7 +171,7 @@ public override async Task<RecognizerResult> RecognizeAsync(DialogContext dialog

await dialogContext.Context.TraceActivityAsync(nameof(RegexRecognizer), JObject.FromObject(recognizerResult), "RecognizerResult", "Regex RecognizerResult", cancellationToken).ConfigureAwait(false);

this.TelemetryClient.TrackEvent("RegexRecognizerResult", this.FillRecognizerResultTelemetryProperties(recognizerResult, telemetryProperties), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "RegexRecognizerResult", this.FillRecognizerResultTelemetryProperties(recognizerResult, telemetryProperties), telemetryMetrics);

return recognizerResult;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public override Task<RecognizerResult> RecognizeAsync(DialogContext dialogContex
}
}

this.TelemetryClient.TrackEvent("ValueRecognizerResult", this.FillRecognizerResultTelemetryProperties(recognized, telemetryProperties), telemetryMetrics);
this.TrackRecognizerResult(dialogContext, "ValueRecognizerResult", this.FillRecognizerResultTelemetryProperties(recognized, telemetryProperties), telemetryMetrics);

return Task.FromResult(recognized);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Bot.Builder.Dialogs.Adaptive
{
public static class TelemetryExtensions
{
/// <summary>
/// Register IBotTelemetryClient as default langugage generator.
/// </summary>
/// <param name="dialogManager">botAdapter to add services to.</param>
/// <param name="telemetryClient">IBotTelemetryClient to use.</param>
/// <returns>botAdapter.</returns>
public static DialogManager UseTelemetry(this DialogManager dialogManager, IBotTelemetryClient telemetryClient)
{
dialogManager.TurnState.Set<IBotTelemetryClient>(telemetryClient);
dialogManager.Dialogs.TelemetryClient = telemetryClient;
return dialogManager;
}
}
}
55 changes: 42 additions & 13 deletions libraries/Microsoft.Bot.Builder.Dialogs/Recognizer.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Bot.Builder.Dialogs
{
Expand Down Expand Up @@ -70,22 +68,53 @@ public virtual async Task<T> RecognizeAsync<T>(DialogContext dialogContext, Acti
return result;
}

protected virtual Dictionary<string, string> FillRecognizerResultTelemetryProperties(RecognizerResult recognizerResult, Dictionary<string, string> telemetryProperties, ITurnContext turnContext = null)
/// <summary>
/// Uses the RecognizerResult to create a list of propeties to be included when tracking the result in telemetry.
/// </summary>
/// <param name="recognizerResult">Recognizer Result.</param>
/// <param name="telemetryProperties">A list of properties to append or override the properties created using the RecognizerResult.</param>
/// <param name="dialogContext">Dialog Context.</param>
/// <returns>A dictionary that can be included when calling the TrackEvent method on the TelemetryClient.</returns>
protected virtual Dictionary<string, string> FillRecognizerResultTelemetryProperties(RecognizerResult recognizerResult, Dictionary<string, string> telemetryProperties, DialogContext dialogContext = null)
{
Copy link
Contributor

@tomlm tomlm Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this shipped in 4.8? I would think this is a breaking change to change this signature.
I doubt you need the DC, you just sneed the ITurnContext.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was. I had misunderstood the way the expression value worked, which means that the current implementation doesn't get the right application ID, so I think I do need the dialog context unless there is another way to get the value ->

protected override Dictionary<string, string> FillRecognizerResultTelemetryProperties(RecognizerResult recognizerResult, Dictionary<string, string> telemetryProperties, DialogContext dc)
{
var (logPersonalInfo, error) = this.LogPersonalInformation.TryGetValue(dc.State);
var (applicationId, error2) = this.ApplicationId.TryGetValue(dc.State);
var topTwoIntents = (recognizerResult.Intents.Count > 0) ? recognizerResult.Intents.OrderByDescending(x => x.Value.Score).Take(2).ToArray() : null;
// Add the intent score and conversation id properties
var properties = new Dictionary<string, string>()
{
{ LuisTelemetryConstants.ApplicationIdProperty, applicationId },
{ LuisTelemetryConstants.IntentProperty, topTwoIntents?[0].Key ?? string.Empty },
{ LuisTelemetryConstants.IntentScoreProperty, topTwoIntents?[0].Value.Score?.ToString("N2") ?? "0.00" },
{ LuisTelemetryConstants.Intent2Property, (topTwoIntents?.Count() > 1) ? topTwoIntents?[1].Key ?? string.Empty : string.Empty },
{ LuisTelemetryConstants.IntentScore2Property, (topTwoIntents?.Count() > 1) ? topTwoIntents?[1].Value.Score?.ToString("N2") ?? "0.00" : "0.00" },
{ LuisTelemetryConstants.FromIdProperty, dc.Context.Activity.From.Id },
};

If I do need DialogContext, given that adaptive isn't GA yet and we have not documented any of the telemetry bits yet, I think this is an acceptable change for us to make here. Thoughts?

if (telemetryProperties == null)
var properties = new Dictionary<string, string>()
{
{ "Text", recognizerResult.Text },
{ "AlteredText", recognizerResult.AlteredText },
{ "TopIntent", recognizerResult.Intents.Any() ? recognizerResult.Intents.First().Key : null },
{ "TopIntentScore", recognizerResult.Intents.Any() ? recognizerResult.Intents.First().Value?.ToString() : null },
{ "Intents", recognizerResult.Intents.Any() ? JsonConvert.SerializeObject(recognizerResult.Intents) : null },
{ "Entities", recognizerResult.Entities != null ? recognizerResult.Entities.ToString() : null },
{ "AdditionalProperties", recognizerResult.Properties.Any() ? JsonConvert.SerializeObject(recognizerResult.Properties) : null },
};

// Additional Properties can override "stock" properties.
if (telemetryProperties != null)
{
telemetryProperties = new Dictionary<string, string>();
return telemetryProperties.Concat(properties)
.GroupBy(kv => kv.Key)
.ToDictionary(g => g.Key, g => g.First().Value);
}

telemetryProperties.Add("Text", recognizerResult.Text);
telemetryProperties.Add("AlteredText", recognizerResult.AlteredText);
telemetryProperties.Add("TopIntent", recognizerResult.Intents.Any() ? recognizerResult.Intents.First().Key : null);
telemetryProperties.Add("TopIntentScore", recognizerResult.Intents.Any() ? recognizerResult.Intents.First().Value?.ToString() : null);
telemetryProperties.Add("Intents", recognizerResult.Intents.Any() ? JsonConvert.SerializeObject(recognizerResult.Intents) : null);
telemetryProperties.Add("Entities", recognizerResult.Entities != null ? recognizerResult.Entities.ToString() : null);
telemetryProperties.Add("AdditionalProperties", recognizerResult.Properties.Any() ? JsonConvert.SerializeObject(recognizerResult.Properties) : null);
return properties;
}

/// <summary>
/// Tracks an event with the event name provided using the TelemetryClient attaching the properties / metrics.
/// </summary>
/// <param name="dialogContext">Dialog Context.</param>
/// <param name="eventName">The name of the event to track.</param>
/// <param name="telemetryProperties">The properties to be included as part of the event tracking.</param>
/// <param name="telemetryMetrics">The metrics to be included as part of the event tracking.</param>
protected void TrackRecognizerResult(DialogContext dialogContext, string eventName, Dictionary<string, string> telemetryProperties, Dictionary<string, double> telemetryMetrics)
{
if (this.TelemetryClient is NullBotTelemetryClient)
{
var turnStateTelemetryClient = dialogContext.Context.TurnState.Get<IBotTelemetryClient>();
this.TelemetryClient = turnStateTelemetryClient ?? this.TelemetryClient;
}

return telemetryProperties;
this.TelemetryClient.TrackEvent(eventName, telemetryProperties, telemetryMetrics);
}
}
}