Scripts/Runtime/CallbackHandlers/WitResponseMatcher.cs (263 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. */ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Facebook.WitAi.Data; using Facebook.WitAi.Lib; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; namespace Facebook.WitAi.CallbackHandlers { [AddComponentMenu("Wit.ai/Response Matchers/Response Matcher")] public class WitResponseMatcher : WitResponseHandler { [Header("Intent")] [SerializeField] public string intent; [FormerlySerializedAs("confidence")] [Range(0, 1f), SerializeField] public float confidenceThreshold = .6f; [FormerlySerializedAs("valuePaths")] [Header("Value Matching")] [SerializeField] public ValuePathMatcher[] valueMatchers; [Header("Output")] [SerializeField] private FormattedValueEvents[] formattedValueEvents; [SerializeField] private MultiValueEvent onMultiValueEvent = new MultiValueEvent(); private static Regex valueRegex = new Regex(Regex.Escape("{value}"), RegexOptions.Compiled); protected override void OnHandleResponse(WitResponseNode response) { if (IntentMatches(response)) { if (ValueMatches(response)) { for (int j = 0; j < formattedValueEvents.Length; j++) { var formatEvent = formattedValueEvents[j]; var result = formatEvent.format; for (int i = 0; i < valueMatchers.Length; i++) { var reference = valueMatchers[i].Reference; var value = reference.GetStringValue(response); if (!string.IsNullOrEmpty(formatEvent.format)) { if (!string.IsNullOrEmpty(value)) { result = valueRegex.Replace(result, value, 1); result = result.Replace("{" + i + "}", value); } else if (result.Contains("{" + i + "}")) { result = ""; break; } } } if (!string.IsNullOrEmpty(result)) { formatEvent.onFormattedValueEvent?.Invoke(result); } } } List<string> values = new List<string>(); for (int i = 0; i < valueMatchers.Length; i++) { var value = valueMatchers[i].Reference.GetStringValue(response); values.Add(value); } onMultiValueEvent.Invoke(values.ToArray()); } } private bool ValueMatches(WitResponseNode response) { bool matches = true; for (int i = 0; i < valueMatchers.Length && matches; i++) { var matcher = valueMatchers[i]; var value = matcher.Reference.GetStringValue(response); matches &= !matcher.contentRequired || !string.IsNullOrEmpty(value); switch (matcher.matchMethod) { case MatchMethod.RegularExpression: matches &= Regex.Match(value, matcher.matchValue).Success; break; case MatchMethod.Text: matches &= value == matcher.matchValue; break; case MatchMethod.IntegerComparison: matches &= CompareInt(value, matcher); break; case MatchMethod.FloatComparison: matches &= CompareFloat(value, matcher); break; case MatchMethod.DoubleComparison: matches &= CompareDouble(value, matcher); break; } } return matches; } private bool CompareDouble(string value, ValuePathMatcher matcher) { // This one is freeform based on the input so we will retrun false if it is not parsable if (!double.TryParse(value, out double dValue)) return false; // We will throw an exception if match value is not a numeric value. This is a developer // error. double dMatchValue = double.Parse(matcher.matchValue); switch (matcher.comparisonMethod) { case ComparisonMethod.Equals: return Math.Abs(dValue - dMatchValue) < matcher.floatingPointComparisonTolerance; case ComparisonMethod.NotEquals: return Math.Abs(dValue - dMatchValue) > matcher.floatingPointComparisonTolerance; case ComparisonMethod.Greater: return dValue > dMatchValue; case ComparisonMethod.Less: return dValue < dMatchValue; case ComparisonMethod.GreaterThanOrEqualTo: return dValue >= dMatchValue; case ComparisonMethod.LessThanOrEqualTo: return dValue <= dMatchValue; } return false; } private bool CompareFloat(string value, ValuePathMatcher matcher) { // This one is freeform based on the input so we will retrun false if it is not parsable if (!float.TryParse(value, out float dValue)) return false; // We will throw an exception if match value is not a numeric value. This is a developer // error. float dMatchValue = float.Parse(matcher.matchValue); switch (matcher.comparisonMethod) { case ComparisonMethod.Equals: return Math.Abs(dValue - dMatchValue) < matcher.floatingPointComparisonTolerance; case ComparisonMethod.NotEquals: return Math.Abs(dValue - dMatchValue) > matcher.floatingPointComparisonTolerance; case ComparisonMethod.Greater: return dValue > dMatchValue; case ComparisonMethod.Less: return dValue < dMatchValue; case ComparisonMethod.GreaterThanOrEqualTo: return dValue >= dMatchValue; case ComparisonMethod.LessThanOrEqualTo: return dValue <= dMatchValue; } return false; } private bool CompareInt(string value, ValuePathMatcher matcher) { // This one is freeform based on the input so we will retrun false if it is not parsable if (!int.TryParse(value, out int dValue)) return false; // We will throw an exception if match value is not a numeric value. This is a developer // error. int dMatchValue = int.Parse(matcher.matchValue); switch (matcher.comparisonMethod) { case ComparisonMethod.Equals: return dValue == dMatchValue; case ComparisonMethod.NotEquals: return dValue != dMatchValue; case ComparisonMethod.Greater: return dValue > dMatchValue; case ComparisonMethod.Less: return dValue < dMatchValue; case ComparisonMethod.GreaterThanOrEqualTo: return dValue >= dMatchValue; case ComparisonMethod.LessThanOrEqualTo: return dValue <= dMatchValue; } return false; } private bool IntentMatches(WitResponseNode response) { var intentNode = response.GetFirstIntent(); if (string.IsNullOrEmpty(intent)) { return true; } if (intent == intentNode["name"].Value) { var actualConfidence = intentNode["confidence"].AsFloat; if (actualConfidence >= confidenceThreshold) { return true; } Debug.Log($"{intent} matched, but confidence ({actualConfidence.ToString("F")}) was below threshold ({confidenceThreshold.ToString("F")})"); } return false; } } [Serializable] public class MultiValueEvent : UnityEvent<string[]> { } [Serializable] public class ValueEvent : UnityEvent<string> { } [Serializable] public class FormattedValueEvents { [Tooltip("Modify the string output, values can be inserted with {value} or {0}, {1}, {2}")] public string format; public ValueEvent onFormattedValueEvent = new ValueEvent(); } [Serializable] public class ValuePathMatcher { [Tooltip("The path to a value within a WitResponseNode")] public string path; [Tooltip("A reference to a wit value object")] public WitValue witValueReference; [Tooltip("Does this path need to have text in the value to be considered a match")] public bool contentRequired = true; [Tooltip("If set the match value will be treated as a regular expression.")] public MatchMethod matchMethod; [Tooltip("The operator used to compare the value with the match value. Ex: response.value > matchValue")] public ComparisonMethod comparisonMethod; [Tooltip("Value used to compare with the result when Match Required is set")] public string matchValue; [Tooltip("The variance allowed when comparing two floating point values for equality")] public double floatingPointComparisonTolerance = .0001f; [Tooltip("The confidence levels to handle for this value.\nNOTE: The selected node must have a confidence sibling node.")] public ConfidenceRange[] confidenceRanges; private WitResponseReference pathReference; private WitResponseReference confidencePathReference; public WitResponseReference ConfidenceReference { get { if (null != confidencePathReference) return confidencePathReference; var confidencePath = Reference?.path; if (!string.IsNullOrEmpty(confidencePath)) { confidencePath = confidencePath.Substring(0, confidencePath.LastIndexOf(".")); confidencePath += ".confidence"; confidencePathReference = WitResultUtilities.GetWitResponseReference(confidencePath); } return confidencePathReference; } } public WitResponseReference Reference { get { if (witValueReference) return witValueReference.Reference; if (null == pathReference || pathReference.path != path) { pathReference = WitResultUtilities.GetWitResponseReference(path); } return pathReference; } } } public enum ComparisonMethod { Equals, NotEquals, Greater, GreaterThanOrEqualTo, Less, LessThanOrEqualTo } public enum MatchMethod { None, Text, RegularExpression, IntegerComparison, FloatComparison, DoubleComparison } }