allow LuisRecognizer to recognize without turnContext (#5362)

Co-authored-by: Michael Richardson <v-micric@microsoft.com>
This commit is contained in:
Michael Richardson 2021-03-26 09:35:05 -07:00 коммит произвёл GitHub
Родитель 46e623f1b3
Коммит 7db214b603
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 199 добавлений и 65 удалений

Просмотреть файл

@ -492,6 +492,21 @@ namespace Microsoft.Bot.Builder.AI.Luis
return result;
}
/// <summary>
/// Return results of the analysis (Suggested actions and intents).
/// </summary>
/// <remarks>No telemetry is provided when using this method.</remarks>
/// <param name="utterance">utterance to recognize.</param>
/// <param name="recognizerOptions">A <see cref="LuisRecognizerOptions"/> instance to be used by the call.
/// This parameter overrides the default <see cref="LuisRecognizerOptions"/> passed in the constructor.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>The LUIS results of the analysis of the current message text in the current turn's context activity.</returns>
public virtual async Task<RecognizerResult> RecognizeAsync(string utterance, LuisRecognizerOptions recognizerOptions = null, CancellationToken cancellationToken = default(CancellationToken))
{
recognizerOptions ??= _luisRecognizerOptions;
return await RecognizeInternalAsync(utterance, recognizerOptions, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Invoked prior to a LuisResult being logged.
/// </summary>
@ -637,6 +652,20 @@ namespace Microsoft.Bot.Builder.AI.Luis
return result;
}
/// <summary>
/// Returns a RecognizerResult object.
/// </summary>
/// <param name="utterance">utterance to recognize.</param>
/// <param name="predictionOptions">LuisRecognizerOptions implementation to override current properties.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>RecognizerResult object.</returns>
private async Task<RecognizerResult> RecognizeInternalAsync(string utterance, LuisRecognizerOptions predictionOptions, CancellationToken cancellationToken)
{
var recognizer = predictionOptions ?? _luisRecognizerOptions;
var result = await recognizer.RecognizeInternalAsync(utterance, HttpClient, cancellationToken).ConfigureAwait(false);
return result;
}
/// <summary>
/// Returns a LuisRecognizerOptionsV2.
/// This exists to maintain backwards compatibility with existing constructors.

Просмотреть файл

@ -65,5 +65,8 @@ namespace Microsoft.Bot.Builder.AI.Luis
// Support DialogContext
internal abstract Task<RecognizerResult> RecognizeInternalAsync(DialogContext context, Activity activity, HttpClient httpClient, CancellationToken cancellationToken);
// Support string utterance
internal abstract Task<RecognizerResult> RecognizeInternalAsync(string utterance, HttpClient httpClient, CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -65,33 +65,9 @@ namespace Microsoft.Bot.Builder.AI.Luis
}
else
{
var credentials = new ApiKeyServiceClientCredentials(Application.EndpointKey);
using (var runtime = new LUISRuntimeClient(credentials, httpClient, false) { Endpoint = Application.Endpoint })
{
luisResult = await runtime.Prediction.ResolveAsync(
Application.ApplicationId,
utterance,
timezoneOffset: PredictionOptions.TimezoneOffset,
verbose: PredictionOptions.IncludeAllIntents,
staging: PredictionOptions.Staging,
spellCheck: PredictionOptions.SpellCheck,
bingSpellCheckSubscriptionKey: PredictionOptions.BingSpellCheckSubscriptionKey,
log: PredictionOptions.Log ?? true,
cancellationToken: cancellationToken).ConfigureAwait(false);
}
luisResult = await GetLuisResultAsync(utterance, httpClient, cancellationToken).ConfigureAwait(false);
recognizerResult = new RecognizerResult
{
Text = utterance,
AlteredText = luisResult.AlteredQuery,
Intents = LuisUtil.GetIntents(luisResult),
Entities = LuisUtil.ExtractEntitiesAndMetadata(luisResult.Entities, luisResult.CompositeEntities, PredictionOptions.IncludeInstanceData ?? true, utterance),
};
LuisUtil.AddProperties(luisResult, recognizerResult);
if (IncludeAPIResults)
{
recognizerResult.Properties.Add("luisResult", luisResult);
}
recognizerResult = BuildRecognizerResultFromLuisResult(luisResult, utterance);
}
var traceInfo = JObject.FromObject(
@ -109,5 +85,46 @@ namespace Microsoft.Bot.Builder.AI.Luis
await turnContext.TraceActivityAsync("LuisRecognizer", traceInfo, LuisTraceType, LuisTraceLabel, cancellationToken).ConfigureAwait(false);
return recognizerResult;
}
internal override async Task<RecognizerResult> RecognizeInternalAsync(string utterance, HttpClient httpClient, CancellationToken cancellationToken)
{
var luisResult = await GetLuisResultAsync(utterance, httpClient, cancellationToken).ConfigureAwait(false);
return BuildRecognizerResultFromLuisResult(luisResult, utterance);
}
private async Task<LuisResult> GetLuisResultAsync(string utterance, HttpClient httpClient, CancellationToken cancellationToken)
{
var credentials = new ApiKeyServiceClientCredentials(Application.EndpointKey);
using var runtime = new LUISRuntimeClient(credentials, httpClient, false) { Endpoint = Application.Endpoint };
return await runtime.Prediction.ResolveAsync(
Application.ApplicationId,
utterance,
timezoneOffset: PredictionOptions.TimezoneOffset,
verbose: PredictionOptions.IncludeAllIntents,
staging: PredictionOptions.Staging,
spellCheck: PredictionOptions.SpellCheck,
bingSpellCheckSubscriptionKey: PredictionOptions.BingSpellCheckSubscriptionKey,
log: PredictionOptions.Log ?? true,
cancellationToken: cancellationToken).ConfigureAwait(false);
}
private RecognizerResult BuildRecognizerResultFromLuisResult(LuisResult luisResult, string utterance)
{
var recognizerResult = new RecognizerResult
{
Text = utterance,
AlteredText = luisResult.AlteredQuery,
Intents = LuisUtil.GetIntents(luisResult),
Entities = LuisUtil.ExtractEntitiesAndMetadata(luisResult.Entities, luisResult.CompositeEntities, PredictionOptions.IncludeInstanceData ?? true, utterance),
};
LuisUtil.AddProperties(luisResult, recognizerResult);
if (IncludeAPIResults)
{
recognizerResult.Properties.Add("luisResult", luisResult);
}
return recognizerResult;
}
}
}

Просмотреть файл

@ -106,6 +106,11 @@ namespace Microsoft.Bot.Builder.AI.Luis
return await RecognizeAsync(turnContext, turnContext?.Activity?.AsMessageActivity()?.Text, PredictionOptions, httpClient, cancellationToken).ConfigureAwait(false);
}
internal override async Task<RecognizerResult> RecognizeInternalAsync(string utterance, HttpClient httpClient, CancellationToken cancellationToken)
{
return await RecognizeAsync(utterance, PredictionOptions, httpClient, cancellationToken).ConfigureAwait(false);
}
private static JObject BuildRequestBody(string utterance, LuisV3.LuisPredictionOptions options)
{
var content = new JObject
@ -162,45 +167,8 @@ namespace Microsoft.Bot.Builder.AI.Luis
}
else
{
var uri = BuildUri(options);
var content = BuildRequestBody(utterance, options);
using (var request = new HttpRequestMessage(HttpMethod.Post, uri.Uri))
{
using (var stringContent = new StringContent(content.ToString(), Encoding.UTF8, "application/json"))
{
request.Content = stringContent;
request.Headers.Add("Ocp-Apim-Subscription-Key", Application.EndpointKey);
var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
luisResponse = (JObject)JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
}
var prediction = (JObject)luisResponse["prediction"];
recognizerResult = new RecognizerResult();
recognizerResult.Text = utterance;
recognizerResult.AlteredText = prediction["alteredQuery"]?.Value<string>();
recognizerResult.Intents = LuisV3.LuisUtil.GetIntents(prediction);
recognizerResult.Entities = LuisV3.LuisUtil.ExtractEntitiesAndMetadata(prediction);
LuisV3.LuisUtil.AddProperties(prediction, recognizerResult);
if (IncludeAPIResults)
{
recognizerResult.Properties.Add("luisResult", luisResponse);
}
if (PredictionOptions.IncludeInstanceData)
{
var instanceObject = recognizerResult.Entities["$instance"];
if (instanceObject == null)
{
recognizerResult.Entities.Add("$instance", new JObject());
}
}
luisResponse = await GetLuisResponseAsync(utterance, options, httpClient, cancellationToken).ConfigureAwait(false);
recognizerResult = BuildRecognizerResultFromLuisResponse(luisResponse, utterance);
}
var traceInfo = JObject.FromObject(
@ -219,6 +187,67 @@ namespace Microsoft.Bot.Builder.AI.Luis
return recognizerResult;
}
private async Task<RecognizerResult> RecognizeAsync(string utterance, LuisV3.LuisPredictionOptions options, HttpClient httpClient, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(utterance))
{
return new RecognizerResult
{
Text = utterance
};
}
else
{
var luisResponse = await GetLuisResponseAsync(utterance, options, httpClient, cancellationToken).ConfigureAwait(false);
return BuildRecognizerResultFromLuisResponse(luisResponse, utterance);
}
}
private async Task<JObject> GetLuisResponseAsync(string utterance, LuisV3.LuisPredictionOptions options, HttpClient httpClient, CancellationToken cancellationToken)
{
var uri = BuildUri(options);
var content = BuildRequestBody(utterance, options);
using var request = new HttpRequestMessage(HttpMethod.Post, uri.Uri);
using var stringContent = new StringContent(content.ToString(), Encoding.UTF8, "application/json");
request.Content = stringContent;
request.Headers.Add("Ocp-Apim-Subscription-Key", Application.EndpointKey);
var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return (JObject)JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
}
private RecognizerResult BuildRecognizerResultFromLuisResponse(JObject luisResponse, string utterance)
{
var prediction = (JObject)luisResponse["prediction"];
var recognizerResult = new RecognizerResult
{
Text = utterance,
AlteredText = prediction["alteredQuery"]?.Value<string>(),
Intents = LuisV3.LuisUtil.GetIntents(prediction),
Entities = LuisV3.LuisUtil.ExtractEntitiesAndMetadata(prediction)
};
LuisV3.LuisUtil.AddProperties(prediction, recognizerResult);
if (IncludeAPIResults)
{
recognizerResult.Properties.Add("luisResult", luisResponse);
}
if (PredictionOptions.IncludeInstanceData)
{
var instanceObject = recognizerResult.Entities["$instance"];
if (instanceObject == null)
{
recognizerResult.Entities.Add("$instance", new JObject());
}
}
return recognizerResult;
}
private UriBuilder BuildUri(LuisV3.LuisPredictionOptions options)
{
var path = new StringBuilder(Application.Endpoint);

Просмотреть файл

@ -138,6 +138,34 @@ namespace Microsoft.Bot.Builder.AI.Luis.Tests
Assert.Empty(result.Entities);
}
[Fact]
public async Task UtteranceWithoutTurnContext()
{
const string utterance = "email about something wicked this way comes from bart simpson and also kb435";
const string responsePath = "Patterns.json";
var expectedPath = GetFilePath(responsePath);
JObject oracle;
using (var expectedJsonReader = new JsonTextReader(new StreamReader(expectedPath)))
{
oracle = (JObject)await JToken.ReadFromAsync(expectedJsonReader);
}
var mockResponse = oracle["v2"]["response"];
GetEnvironmentVars();
var mockHttp = GetMockHttpClientHandlerObject(utterance, new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(mockResponse))));
var options = new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true };
var luisRecognizer = GetLuisRecognizer(mockHttp, false, options) as LuisRecognizer;
var result = await luisRecognizer.RecognizeAsync(utterance);
Assert.NotNull(result);
Assert.Null(result.AlteredText);
Assert.Equal(utterance, result.Text);
Assert.NotNull(result.Intents);
Assert.NotNull(result.Entities);
}
[Fact]
public async Task V1DatetimeResolution()
{

Просмотреть файл

@ -109,6 +109,34 @@ namespace Microsoft.Bot.Builder.AI.Luis.Tests
Assert.Empty(result.Entities);
}
[Fact]
public async Task UtteranceWithoutTurnContext()
{
const string utterance = "email about something wicked this way comes from bart simpson and also kb435";
const string responsePath = "Patterns.json";
var expectedPath = GetFilePath(responsePath);
JObject oracle;
using (var expectedJsonReader = new JsonTextReader(new StreamReader(expectedPath)))
{
oracle = (JObject)await JToken.ReadFromAsync(expectedJsonReader);
}
var mockResponse = oracle["v3"]["response"];
GetEnvironmentVars();
var mockHttp = GetMockHttpClientHandlerObject(utterance, new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(mockResponse))));
var options = new LuisV3.LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true };
var luisRecognizer = GetLuisRecognizer(mockHttp, options) as LuisRecognizer;
var result = await luisRecognizer.RecognizeAsync(utterance);
Assert.NotNull(result);
Assert.Null(result.AlteredText);
Assert.Equal(utterance, result.Text);
Assert.NotNull(result.Intents);
Assert.NotNull(result.Entities);
}
[Fact]
public async Task TraceActivity()
{