testing: introduce ILuisService to make it possible to mock LUIS
This commit is contained in:
Родитель
1267f9df20
Коммит
200d76e7e6
|
@ -33,29 +33,14 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Bot.Connector;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class LuisModel : Attribute
|
||||
{
|
||||
public readonly string luisModelUrl;
|
||||
|
||||
public LuisModel(string luisModelUrl)
|
||||
{
|
||||
Field.SetNotNull(out this.luisModelUrl, nameof(luisModelUrl), luisModelUrl);
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
public class LuisIntent : Attribute
|
||||
{
|
||||
|
@ -67,55 +52,32 @@ namespace Microsoft.Bot.Builder
|
|||
}
|
||||
}
|
||||
|
||||
public class LuisResult
|
||||
{
|
||||
public Models.IntentRecommendation[] Intents { get; set; }
|
||||
|
||||
public Models.EntityRecommendation[] Entities { get; set; }
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static bool TryFindEntity(this LuisResult result, string type, out Models.EntityRecommendation entity)
|
||||
{
|
||||
entity = result.Entities?.FirstOrDefault(e => e.Type == type);
|
||||
return entity != null;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate Task IntentHandler(IDialogContext context, LuisResult luisResult);
|
||||
|
||||
[Serializable]
|
||||
public class LuisDialog : IDialog
|
||||
{
|
||||
public readonly string subscriptionKey;
|
||||
public readonly string modelID;
|
||||
public readonly string luisUrl;
|
||||
private readonly ILuisService service;
|
||||
|
||||
[NonSerialized]
|
||||
protected Dictionary<string, IntentHandler> handlerByIntent;
|
||||
|
||||
public LuisDialog()
|
||||
{
|
||||
var luisModel = ((LuisModel)this.GetType().GetCustomAttributes(typeof(LuisModel), true).FirstOrDefault())?.luisModelUrl;
|
||||
|
||||
if (!string.IsNullOrEmpty(luisModel))
|
||||
{
|
||||
this.luisUrl = luisModel;
|
||||
}
|
||||
else
|
||||
var type = this.GetType();
|
||||
var luisModel = type.GetCustomAttribute<LuisModel>(inherit: true);
|
||||
if (luisModel == null)
|
||||
{
|
||||
throw new Exception("Luis model attribute is not set for the class");
|
||||
}
|
||||
|
||||
this.AddAttributeBasedHandlers();
|
||||
this.service = new LuisService(luisModel);
|
||||
}
|
||||
|
||||
public LuisDialog(string subscriptionKey, string modelID)
|
||||
public LuisDialog(ILuisService service)
|
||||
{
|
||||
Field.SetNotNull(out this.subscriptionKey, nameof(subscriptionKey), subscriptionKey);
|
||||
Field.SetNotNull(out this.modelID, nameof(modelID), modelID);
|
||||
this.luisUrl = string.Format("https://api.projectoxford.ai/luis/v1/application?id={0}&subscription-key={1}&q=", this.modelID, this.subscriptionKey);
|
||||
Field.SetNotNull(out this.service, nameof(service), service);
|
||||
}
|
||||
|
||||
public virtual async Task StartAsync(IDialogContext context)
|
||||
|
@ -123,31 +85,19 @@ namespace Microsoft.Bot.Builder
|
|||
context.Wait(MessageReceived);
|
||||
}
|
||||
|
||||
protected virtual async Task<LuisResult> GetLuisResult(string luisUrl, string text)
|
||||
{
|
||||
var url = luisUrl + Uri.EscapeDataString(text);
|
||||
string json;
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
json = await client.GetStringAsync(url);
|
||||
}
|
||||
|
||||
Debug.WriteLine(json);
|
||||
var response = JsonConvert.DeserializeObject<LuisResult>(json);
|
||||
return response;
|
||||
}
|
||||
|
||||
protected async Task MessageReceived(IDialogContext context, IAwaitable<Message> item)
|
||||
{
|
||||
var message = await item;
|
||||
var luisRes = await GetLuisResult(this.luisUrl, message.Text);
|
||||
var intent = luisRes.Intents.FirstOrDefault(i => i.Score == luisRes.Intents.Select(t => t.Score).Max());
|
||||
|
||||
if (this.handlerByIntent == null)
|
||||
{
|
||||
this.AddAttributeBasedHandlers();
|
||||
}
|
||||
|
||||
var message = await item;
|
||||
var luisRes = await this.service.QueryAsync(message.Text);
|
||||
|
||||
var maximum = luisRes.Intents.Max(t => t.Score);
|
||||
var intent = luisRes.Intents.FirstOrDefault(i => i.Score == maximum);
|
||||
|
||||
IntentHandler handler;
|
||||
if (intent == null || !this.handlerByIntent.TryGetValue(intent.Intent, out handler))
|
||||
{
|
||||
|
@ -160,7 +110,7 @@ namespace Microsoft.Bot.Builder
|
|||
}
|
||||
else
|
||||
{
|
||||
var text = $"LuisModel[{this.modelID}] no default intent handler.";
|
||||
var text = $"No default intent handler found.";
|
||||
throw new Exception(text);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Bot.Builder
|
||||
{
|
||||
public class LuisResult
|
||||
{
|
||||
public Models.IntentRecommendation[] Intents { get; set; }
|
||||
|
||||
public Models.EntityRecommendation[] Entities { get; set; }
|
||||
}
|
||||
|
||||
public interface ILuisService
|
||||
{
|
||||
Uri BuildUri(string text);
|
||||
Task<LuisResult> QueryAsync(Uri uri);
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
[Serializable]
|
||||
public class LuisModel : Attribute
|
||||
{
|
||||
public readonly string ModelID;
|
||||
public readonly string SubscriptionKey;
|
||||
|
||||
public LuisModel(string modelID, string subscriptionKey)
|
||||
{
|
||||
Field.SetNotNull(out this.ModelID, nameof(modelID), modelID);
|
||||
Field.SetNotNull(out this.SubscriptionKey, nameof(subscriptionKey), subscriptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class LuisService : ILuisService
|
||||
{
|
||||
private readonly LuisModel model;
|
||||
|
||||
public LuisService(LuisModel model)
|
||||
{
|
||||
Field.SetNotNull(out this.model, nameof(model), model);
|
||||
}
|
||||
|
||||
public static readonly Uri UriBase = new Uri("https://api.projectoxford.ai/luis/v1/application");
|
||||
|
||||
Uri ILuisService.BuildUri(string text)
|
||||
{
|
||||
var queryString = HttpUtility.ParseQueryString(string.Empty);
|
||||
queryString["id"] = this.model.ModelID;
|
||||
queryString["subscription-key"] = this.model.SubscriptionKey;
|
||||
queryString["q"] = text;
|
||||
|
||||
var builder = new UriBuilder(UriBase);
|
||||
builder.Query = queryString.ToString();
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
async Task<LuisResult> ILuisService.QueryAsync(Uri uri)
|
||||
{
|
||||
string json;
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
json = await client.GetStringAsync(uri);
|
||||
}
|
||||
|
||||
var result = JsonConvert.DeserializeObject<LuisResult>(json);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class Extensions
|
||||
{
|
||||
public static async Task<LuisResult> QueryAsync(this ILuisService service, string text)
|
||||
{
|
||||
var uri = service.BuildUri(text);
|
||||
return await service.QueryAsync(uri);
|
||||
}
|
||||
|
||||
public static bool TryFindEntity(this LuisResult result, string type, out Models.EntityRecommendation entity)
|
||||
{
|
||||
entity = result.Entities?.FirstOrDefault(e => e.Type == type);
|
||||
return entity != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,6 +91,7 @@
|
|||
<Compile Include="Dialogs\IDialog.cs" />
|
||||
<Compile Include="Dialogs\IDialogContext.cs" />
|
||||
<Compile Include="Dialogs\LuisDialog.cs" />
|
||||
<Compile Include="Dialogs\LuisService.cs" />
|
||||
<Compile Include="Dialogs\PromptDialog.cs" />
|
||||
<Compile Include="Extensions\Extensions.cs" />
|
||||
<Compile Include="Fibers\Awaitable.cs" />
|
||||
|
@ -140,7 +141,7 @@
|
|||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Net.Compilers.1.1.1\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Net.Compilers.1.1.1\build\Microsoft.Net.Compilers.props'))" />
|
||||
</Target>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
|
|
|
@ -11,7 +11,7 @@ using System.Web;
|
|||
|
||||
namespace Microsoft.Bot.Sample.PizzaBot
|
||||
{
|
||||
[LuisModel("https://api.projectoxford.ai/luis/v1/application?id=a19f7eee-0280-4a9a-b5e5-73c16b32c43d&subscription-key=fe054e042fd14754a83f0a205f6552a5&q=")]
|
||||
[LuisModel("a19f7eee-0280-4a9a-b5e5-73c16b32c43d", "fe054e042fd14754a83f0a205f6552a5")]
|
||||
[Serializable]
|
||||
public class PizzaOrderDialog : LuisDialog
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
namespace Microsoft.Bot.Sample.SimpleAlarmBot
|
||||
{
|
||||
[LuisModel("https://api.projectoxford.ai/luis/v1/application?id=c413b2ef-382c-45bd-8ff0-f76d60e2a821&subscription-key=fe054e042fd14754a83f0a205f6552a5&q=")]
|
||||
[LuisModel("c413b2ef-382c-45bd-8ff0-f76d60e2a821", "fe054e042fd14754a83f0a205f6552a5")]
|
||||
[Serializable]
|
||||
public class SimpleAlarmBot : LuisDialog, ISerializable
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче