LuisIntentScorable.cs
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license.
4 //
5 // Microsoft Bot Framework: http://botframework.com
6 //
7 // Bot Builder SDK GitHub:
8 // https://github.com/Microsoft/BotBuilder
9 //
10 // Copyright (c) Microsoft Corporation
11 // All rights reserved.
12 //
13 // MIT License:
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 
38 using Microsoft.Bot.Connector;
39 using System;
40 using System.Collections.Generic;
41 using System.Linq;
42 using System.Reflection;
43 using System.Runtime.CompilerServices;
44 using System.Text;
45 using System.Threading;
46 using System.Threading.Tasks;
47 
48 namespace Microsoft.Bot.Builder.Scorables.Internals
49 {
50  public sealed class LuisIntentScorableFactory : IScorableFactory<IResolver, IntentRecommendation>
51  {
52  private readonly Func<ILuisModel, ILuisService> make;
53  public LuisIntentScorableFactory(Func<ILuisModel, ILuisService> make)
54  {
55  SetField.NotNull(out this.make, nameof(make), make);
56  }
57 
59  {
60  var scorableByMethod = methods.ToDictionary(m => m, m => new MethodScorable(m));
61 
62  var specs =
63  from method in methods
64  from model in InheritedAttributes.For<LuisModelAttribute>(method)
65  from intent in InheritedAttributes.For<LuisIntentAttribute>(method)
66  select new { method, intent, model };
67 
68  // for a given LUIS model and intent, fold the corresponding method scorables together to enable overload resolution
69  var scorables =
70  from spec in specs
71  group spec by new { spec.model, spec.intent } into modelIntents
72  let method = modelIntents.Select(m => scorableByMethod[m.method]).ToArray().Fold(BindingComparer.Instance)
73  let service = this.make(modelIntents.Key.model)
74  select new LuisIntentScorable<IBinding, IBinding>(service, modelIntents.Key.model, modelIntents.Key.intent, method);
75 
76  var all = scorables.ToArray().Fold(IntentComparer.Instance);
77 
78  return all;
79  }
80  }
81 
85  [Serializable]
86  public sealed class LuisIntentScorable<InnerState, InnerScore> : ResolverScorable<LuisIntentScorable<InnerState, InnerScore>.Scope, IntentRecommendation, InnerState, InnerScore>
87  {
88  private readonly ILuisService service;
89  private readonly ILuisModel model;
90  private readonly LuisIntentAttribute intent;
91 
92  public sealed class Scope : ResolverScope<InnerScore>
93  {
94  public readonly ILuisModel Model;
95  public readonly LuisResult Result;
96  public readonly IntentRecommendation Intent;
97  public Scope(ILuisModel model, LuisResult result, IntentRecommendation intent, IResolver inner)
98  : base(inner)
99  {
100  SetField.NotNull(out this.Model, nameof(model), model);
101  SetField.NotNull(out this.Result, nameof(result), result);
102  SetField.NotNull(out this.Intent, nameof(intent), intent);
103  }
104  public override bool TryResolve(Type type, object tag, out object value)
105  {
106  if (type.IsAssignableFrom(typeof(ILuisModel)))
107  {
108  value = this.Model;
109  return true;
110  }
111  if (type.IsAssignableFrom(typeof(LuisResult)))
112  {
113  value = this.Result;
114  return true;
115  }
116  if (type.IsAssignableFrom(typeof(IntentRecommendation)))
117  {
118  value = this.Intent;
119  return true;
120  }
121 
122  var name = tag as string;
123  if (name != null)
124  {
125  var typeE = type.IsAssignableFrom(typeof(EntityRecommendation));
126  var typeS = type.IsAssignableFrom(typeof(string));
127  var typeIE = type.IsAssignableFrom(typeof(IReadOnlyList<EntityRecommendation>));
128  var typeIS = type.IsAssignableFrom(typeof(IReadOnlyList<string>));
129  if (typeE || typeS || typeIE || typeIS)
130  {
131  var entities = this.Result.Entities.Where(e => e.Type == name).ToArray();
132  if (entities.Length > 0)
133  {
134  if (entities.Length == 1)
135  {
136  if (typeE)
137  {
138  value = entities[0];
139  return true;
140  }
141  if (typeS)
142  {
143  value = entities[0].Entity;
144  return true;
145  }
146  }
147 
148  if (typeIE)
149  {
150  value = entities;
151  return true;
152  }
153  if (typeIS)
154  {
155  value = entities.Select(e => e.Entity).ToArray();
156  return true;
157  }
158  }
159  // TODO: parsing and interpretation of LUIS entity resolutions
160  }
161  }
162 
163  // i.e. for IActivity
164  return base.TryResolve(type, tag, out value);
165  }
166  }
167 
169  : base(inner)
170  {
171  SetField.NotNull(out this.service, nameof(service), service);
172  SetField.NotNull(out this.model, nameof(model), model);
173  SetField.NotNull(out this.intent, nameof(intent), intent);
174  }
175 
176  public override string ToString()
177  {
178  return $"{this.GetType().Name}({this.intent}, {this.inner})";
179  }
180 
181  // assumes that LuisResult is cacheable with Uri as complete key (i.e. ILuisService is not required)
182  private static readonly ConditionalWeakTable<IResolver, Dictionary<Uri, Task<LuisResult>>> Cache
183  = new ConditionalWeakTable<IResolver, Dictionary<Uri, Task<LuisResult>>>();
184 
185  protected override async Task<Scope> PrepareAsync(IResolver resolver, CancellationToken token)
186  {
187  IMessageActivity message;
188  if (!resolver.TryResolve(null, out message))
189  {
190  return null;
191  }
192 
193  var text = message.Text;
194  if (text == null)
195  {
196  return null;
197  }
198 
199  var taskByUri = Cache.GetOrCreateValue(resolver);
200 
201  var uri = this.service.BuildUri(text);
202  Task<LuisResult> task;
203  lock (taskByUri)
204  {
205  if (! taskByUri.TryGetValue(uri, out task))
206  {
207  task = this.service.QueryAsync(uri, token);
208  taskByUri.Add(uri, task);
209  }
210  }
211 
212  var result = await task;
213  var intents = result.Intents;
214  if (intents == null)
215  {
216  return null;
217  }
218 
219  var intent = intents.SingleOrDefault(i => i.Intent.Equals(this.intent.IntentName, StringComparison.OrdinalIgnoreCase));
220  if (intent == null)
221  {
222  return null;
223  }
224 
225  // "builtin.intent.none" seems to have a null score
226 
227  var scope = new Scope(this.model, result, intent, resolver);
228  scope.Item = resolver;
229  scope.Scorable = this.inner;
230  scope.State = await this.inner.PrepareAsync(scope, token);
231  return scope;
232  }
233 
234  protected override IntentRecommendation GetScore(IResolver resolver, Scope state)
235  {
236  return state.Intent;
237  }
238 
239  protected override Task DoneAsync(IResolver item, Scope state, CancellationToken token)
240  {
241  try
242  {
243  Cache.Remove(item);
244  return base.DoneAsync(item, state, token);
245  }
246  catch (OperationCanceledException error)
247  {
248  return Task.FromCanceled(error.CancellationToken);
249  }
250  catch (Exception error)
251  {
252  return Task.FromException(error);
253  }
254  }
255  }
256 
257  public sealed class IntentComparer : IComparer<IntentRecommendation>
258  {
259  public static readonly IComparer<IntentRecommendation> Instance = new IntentComparer();
260  private IntentComparer()
261  {
262  }
263 
264  int IComparer<IntentRecommendation>.Compare(IntentRecommendation one, IntentRecommendation two)
265  {
266  var scoreOne = one.Score.GetValueOrDefault();
267  var scoreTwo = two.Score.GetValueOrDefault();
268  return scoreOne.CompareTo(scoreTwo);
269  }
270  }
271 }
A mockable interface for the LUIS service.
Definition: LuisService.cs:169
override IntentRecommendation GetScore(IResolver resolver, Scope state)
override bool TryResolve(Type type, object tag, out object value)
Namespace for the Microsoft Bot Connector SDK.
Namespace for models generated from the http://luis.ai REST API.
Definition: FormDialog.cs:837
string Text
Content for the message
IScorable< Item, Score > ScorableFor(IEnumerable< MethodInfo > methods)
Allow the scoring of items, with external comparison of scores, and enable the winner to take some ac...
Definition: Scorable.cs:49
Allow the resolution of values based on type and optionally tag.
override async Task< Scope > PrepareAsync(IResolver resolver, CancellationToken token)
LuisIntentScorable(ILuisService service, ILuisModel model, LuisIntentAttribute intent, IScorable< IResolver, InnerScore > inner)
Namespace for internal machinery that is not useful for most developers and may change in the future...
static readonly IComparer< IntentRecommendation > Instance
Scorable to represent binding arguments to a method&#39;s parameters.
LUIS intent recommendation. Look at https://www.luis.ai/Help for more information.
Definition: FormDialog.cs:845
The LUIS model information.
Definition: LuisModel.cs:81
Scorable to represent a specific LUIS intent recommendation.
override Task DoneAsync(IResolver item, Scope state, CancellationToken token)
A mockable interface for the LUIS model.
Definition: LuisModel.cs:53
Luis entity recommendation. Look at https://www.luis.ai/Help for more information.
Definition: FormDialog.cs:840
Associate a LUIS intent with a dialog method.
Definition: LuisDialog.cs:56
bool TryResolve(Type type, object tag, out object value)
double Score
The score for the detected intent.
static readonly IComparer< IBinding > Instance
Namespace for the internal fibers machinery that is not useful for most developers and may change in ...
Core namespace for Dialogs and associated infrastructure.
Namespace for the machinery needed to talk to http://luis.ai.
Helper methods to enumerate inherited attributes for a method.
Root namespace for the Microsoft Bot Builder SDK.
Scope(ILuisModel model, LuisResult result, IntentRecommendation intent, IResolver inner)