WitResponseMatcher.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /*
  2. * Copyright (c) Meta Platforms, Inc. and affiliates.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the license found in the
  6. * LICENSE file in the root directory of this source tree.
  7. */
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Text.RegularExpressions;
  11. using Meta.WitAi.Attributes;
  12. using Meta.WitAi.Data;
  13. using Meta.WitAi.Json;
  14. using Meta.WitAi.Utilities;
  15. using UnityEngine;
  16. using UnityEngine.Events;
  17. using UnityEngine.Serialization;
  18. namespace Meta.WitAi.CallbackHandlers
  19. {
  20. [AddComponentMenu("Wit.ai/Response Matchers/Response Matcher")]
  21. public class WitResponseMatcher : WitIntentMatcher
  22. {
  23. [FormerlySerializedAs("valuePaths")]
  24. [Header("Value Matching")]
  25. #if UNITY_2021_3_2 || UNITY_2021_3_3 || UNITY_2021_3_4 || UNITY_2021_3_5
  26. [NonReorderable]
  27. #endif
  28. [SerializeField] public ValuePathMatcher[] valueMatchers;
  29. [Header("Output")]
  30. #if UNITY_2021_3_2 || UNITY_2021_3_3 || UNITY_2021_3_4 || UNITY_2021_3_5
  31. [NonReorderable]
  32. #endif
  33. [SerializeField] private FormattedValueEvents[] formattedValueEvents;
  34. [SerializeField] private MultiValueEvent onMultiValueEvent = new MultiValueEvent();
  35. [TooltipBox("Triggered if the matching conditions did not match. The parameter will be the transcription that was received. This will only trigger if there were values for intents or entities, but those values didn't match this matcher.")]
  36. [SerializeField] private StringEvent onDidNotMatch = new StringEvent();
  37. [TooltipBox("Triggered if a request was checked and no intents were found. This will still trigger if entities match and only applies to intents. The parameter will be the transcription.")]
  38. [SerializeField] private StringEvent onOutOfDomain = new StringEvent();
  39. private static Regex valueRegex = new Regex(Regex.Escape("{value}"), RegexOptions.Compiled);
  40. // Handle validation
  41. protected override string OnValidateResponse(WitResponseNode response, bool isEarlyResponse)
  42. {
  43. // Return base
  44. string result = base.OnValidateResponse(response, isEarlyResponse);
  45. if (!string.IsNullOrEmpty(result))
  46. {
  47. return result;
  48. }
  49. // Only check value matches on early
  50. if (isEarlyResponse && !ValueMatches(response))
  51. {
  52. return "No value matches";
  53. }
  54. // Success
  55. return string.Empty;
  56. }
  57. // Ignore for mismatched intent
  58. protected override void OnResponseInvalid(WitResponseNode response, string error)
  59. {
  60. if (response.GetIntents().Length > 0 || response.EntityCount() > 0)
  61. {
  62. onDidNotMatch?.Invoke(response.GetTranscription());
  63. }
  64. if (response.GetIntents().Length == 0)
  65. {
  66. onOutOfDomain?.Invoke(response.GetTranscription());
  67. }
  68. }
  69. // Handle valid callback
  70. protected override void OnResponseSuccess(WitResponseNode response)
  71. {
  72. // Check value matches
  73. if (ValueMatches(response))
  74. {
  75. for (int j = 0; j < formattedValueEvents.Length; j++)
  76. {
  77. var formatEvent = formattedValueEvents[j];
  78. var result = formatEvent.format;
  79. for (int i = 0; i < valueMatchers.Length; i++)
  80. {
  81. var reference = valueMatchers[i].Reference;
  82. var value = reference.GetStringValue(response);
  83. if (!string.IsNullOrEmpty(formatEvent.format))
  84. {
  85. if (!string.IsNullOrEmpty(value))
  86. {
  87. result = valueRegex.Replace(result, value, 1);
  88. result = result.Replace("{" + i + "}", value);
  89. }
  90. else if (result.Contains("{" + i + "}"))
  91. {
  92. result = "";
  93. break;
  94. }
  95. }
  96. }
  97. if (!string.IsNullOrEmpty(result))
  98. {
  99. formatEvent.onFormattedValueEvent?.Invoke(result);
  100. }
  101. }
  102. }
  103. else
  104. {
  105. onDidNotMatch?.Invoke(response.GetTranscription());
  106. }
  107. // Get all values & perform multi value event
  108. List<string> values = new List<string>();
  109. foreach (var matcher in valueMatchers)
  110. {
  111. // Add value
  112. var value = matcher.Reference.GetStringValue(response);
  113. values.Add(value);
  114. // Refresh confidence
  115. if (matcher.ConfidenceReference != null)
  116. {
  117. float confidenceValue = ValueMatches(response, matcher)
  118. ? matcher.ConfidenceReference.GetFloatValue(response)
  119. : 0f;
  120. RefreshConfidenceRange(confidenceValue, matcher.confidenceRanges, matcher.allowConfidenceOverlap);
  121. }
  122. }
  123. onMultiValueEvent.Invoke(values.ToArray());
  124. }
  125. private bool ValueMatches(WitResponseNode response)
  126. {
  127. bool matches = true;
  128. for (int i = 0; i < valueMatchers.Length && matches; i++)
  129. {
  130. matches &= ValueMatches(response, valueMatchers[i]);
  131. }
  132. return matches;
  133. }
  134. private bool ValueMatches(WitResponseNode response, ValuePathMatcher matcher)
  135. {
  136. var value = matcher.Reference.GetStringValue(response);
  137. bool result = !matcher.contentRequired || !string.IsNullOrEmpty(value);
  138. switch (matcher.matchMethod)
  139. {
  140. case MatchMethod.RegularExpression:
  141. result &= Regex.Match(value, matcher.matchValue).Success;
  142. break;
  143. case MatchMethod.Text:
  144. result &= value == matcher.matchValue;
  145. break;
  146. case MatchMethod.IntegerComparison:
  147. result &= CompareInt(value, matcher);
  148. break;
  149. case MatchMethod.FloatComparison:
  150. result &= CompareFloat(value, matcher);
  151. break;
  152. case MatchMethod.DoubleComparison:
  153. result &= CompareDouble(value, matcher);
  154. break;
  155. }
  156. return result;
  157. }
  158. private bool CompareDouble(string value, ValuePathMatcher matcher)
  159. {
  160. // This one is freeform based on the input so we will retrun false if it is not parsable
  161. if (!double.TryParse(value, out double dValue)) return false;
  162. // We will throw an exception if match value is not a numeric value. This is a developer
  163. // error.
  164. double dMatchValue = double.Parse(matcher.matchValue);
  165. switch (matcher.comparisonMethod)
  166. {
  167. case ComparisonMethod.Equals:
  168. return Math.Abs(dValue - dMatchValue) < matcher.floatingPointComparisonTolerance;
  169. case ComparisonMethod.NotEquals:
  170. return Math.Abs(dValue - dMatchValue) > matcher.floatingPointComparisonTolerance;
  171. case ComparisonMethod.Greater:
  172. return dValue > dMatchValue;
  173. case ComparisonMethod.Less:
  174. return dValue < dMatchValue;
  175. case ComparisonMethod.GreaterThanOrEqualTo:
  176. return dValue >= dMatchValue;
  177. case ComparisonMethod.LessThanOrEqualTo:
  178. return dValue <= dMatchValue;
  179. }
  180. return false;
  181. }
  182. private bool CompareFloat(string value, ValuePathMatcher matcher)
  183. {
  184. // This one is freeform based on the input so we will retrun false if it is not parsable
  185. if (!float.TryParse(value, out float dValue)) return false;
  186. // We will throw an exception if match value is not a numeric value. This is a developer
  187. // error.
  188. float dMatchValue = float.Parse(matcher.matchValue);
  189. switch (matcher.comparisonMethod)
  190. {
  191. case ComparisonMethod.Equals:
  192. return Math.Abs(dValue - dMatchValue) <
  193. matcher.floatingPointComparisonTolerance;
  194. case ComparisonMethod.NotEquals:
  195. return Math.Abs(dValue - dMatchValue) >
  196. matcher.floatingPointComparisonTolerance;
  197. case ComparisonMethod.Greater:
  198. return dValue > dMatchValue;
  199. case ComparisonMethod.Less:
  200. return dValue < dMatchValue;
  201. case ComparisonMethod.GreaterThanOrEqualTo:
  202. return dValue >= dMatchValue;
  203. case ComparisonMethod.LessThanOrEqualTo:
  204. return dValue <= dMatchValue;
  205. }
  206. return false;
  207. }
  208. private bool CompareInt(string value, ValuePathMatcher matcher)
  209. {
  210. // This one is freeform based on the input so we will retrun false if it is not parsable
  211. if (!int.TryParse(value, out int dValue)) return false;
  212. // We will throw an exception if match value is not a numeric value. This is a developer
  213. // error.
  214. int dMatchValue = int.Parse(matcher.matchValue);
  215. switch (matcher.comparisonMethod)
  216. {
  217. case ComparisonMethod.Equals:
  218. return dValue == dMatchValue;
  219. case ComparisonMethod.NotEquals:
  220. return dValue != dMatchValue;
  221. case ComparisonMethod.Greater:
  222. return dValue > dMatchValue;
  223. case ComparisonMethod.Less:
  224. return dValue < dMatchValue;
  225. case ComparisonMethod.GreaterThanOrEqualTo:
  226. return dValue >= dMatchValue;
  227. case ComparisonMethod.LessThanOrEqualTo:
  228. return dValue <= dMatchValue;
  229. }
  230. return false;
  231. }
  232. }
  233. [Serializable]
  234. public class MultiValueEvent : UnityEvent<string[]>
  235. {
  236. }
  237. [Serializable]
  238. public class ValueEvent : UnityEvent<string>
  239. { }
  240. [Serializable]
  241. public class FormattedValueEvents
  242. {
  243. [Tooltip("Modify the string output, values can be inserted with {value} or {0}, {1}, {2}")]
  244. public string format;
  245. public ValueEvent onFormattedValueEvent = new ValueEvent();
  246. }
  247. [Serializable]
  248. public class ValuePathMatcher
  249. {
  250. [Tooltip("The path to a value within a WitResponseNode")]
  251. public string path;
  252. [Tooltip("A reference to a wit value object")]
  253. public WitValue witValueReference;
  254. [Tooltip("Does this path need to have text in the value to be considered a match")]
  255. public bool contentRequired = true;
  256. [Tooltip("If set the match value will be treated as a regular expression.")]
  257. public MatchMethod matchMethod;
  258. [Tooltip("The operator used to compare the value with the match value. Ex: response.value > matchValue")]
  259. public ComparisonMethod comparisonMethod;
  260. [Tooltip("Value used to compare with the result when Match Required is set")]
  261. public string matchValue;
  262. [Tooltip("The variance allowed when comparing two floating point values for equality")]
  263. public double floatingPointComparisonTolerance = .0001f;
  264. [Tooltip("Confidence ranges are executed in order. If checked, all confidence values will be checked instead of stopping on the first one that matches.")]
  265. [SerializeField] public bool allowConfidenceOverlap;
  266. [Tooltip("The confidence levels to handle for this value.\nNOTE: The selected node must have a confidence sibling node.")]
  267. public ConfidenceRange[] confidenceRanges;
  268. private WitResponseReference pathReference;
  269. private WitResponseReference confidencePathReference;
  270. public WitResponseReference ConfidenceReference
  271. {
  272. get
  273. {
  274. if (null != confidencePathReference) return confidencePathReference;
  275. var confidencePath = Reference?.path;
  276. if (!string.IsNullOrEmpty(confidencePath))
  277. {
  278. confidencePath = confidencePath.Substring(0, confidencePath.LastIndexOf("."));
  279. confidencePath += ".confidence";
  280. confidencePathReference = WitResultUtilities.GetWitResponseReference(confidencePath);
  281. }
  282. return confidencePathReference;
  283. }
  284. }
  285. public WitResponseReference Reference
  286. {
  287. get
  288. {
  289. if (witValueReference) return witValueReference.Reference;
  290. if (null == pathReference || pathReference.path != path)
  291. {
  292. pathReference = WitResultUtilities.GetWitResponseReference(path);
  293. }
  294. return pathReference;
  295. }
  296. }
  297. }
  298. public enum ComparisonMethod
  299. {
  300. Equals,
  301. NotEquals,
  302. Greater,
  303. GreaterThanOrEqualTo,
  304. Less,
  305. LessThanOrEqualTo
  306. }
  307. public enum MatchMethod
  308. {
  309. None,
  310. Text,
  311. RegularExpression,
  312. IntegerComparison,
  313. FloatComparison,
  314. DoubleComparison
  315. }
  316. }