This is an automated email from the ASF dual-hosted git repository.

aradzinski pushed a commit to branch NLPCRAFT-206
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git


The following commit(s) were added to refs/heads/NLPCRAFT-206 by this push:
     new f9c1cbe  WIP.
f9c1cbe is described below

commit f9c1cbefb4ba46a5d2deac5599a05d43b505e042
Author: Aaron Radzinzski <[email protected]>
AuthorDate: Wed Mar 3 15:15:53 2021 -0800

    WIP.
---
 .../apache/nlpcraft/model/NCDialogFlowItem.java    | 168 +++++++++++++++++++++
 .../model/intent/impl/NCIntentSolver.scala         |   7 +-
 .../mgrs/dialogflow/NCDialogFlowManager.scala      |  96 +++++++++---
 3 files changed, 246 insertions(+), 25 deletions(-)

diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCDialogFlowItem.java 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCDialogFlowItem.java
new file mode 100644
index 0000000..a32ba76
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCDialogFlowItem.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.model;
+
+import java.util.*;
+
+/**
+ * An item of the dialog flow. Dialog flow is a chronologically ordered list 
of dialog flow
+ * items. Each item represents a snapshot of winning intent's match data. An 
instance of this interface
+ * is passed into user-defined dialog flow match method.
+ * <p>
+ * Read full documentation in <a target=_ 
href="https://nlpcraft.apache.org/intent-matching.html";>Intent Matching</a> 
section and review
+ * <a target=_ 
href="https://github.com/apache/incubator-nlpcraft/tree/master/nlpcraft/src/main/scala/org/apache/nlpcraft/examples/";>examples</a>.
+ */
+public interface NCDialogFlowItem {
+    /**
+     * Gets ID of the matched intent.
+     *
+     * @return ID of the matched intent.
+     */
+    String getIntentId();
+
+    /**
+     * Gets a subset of tokens representing matched intent. This subset is 
grouped by the matched terms
+     * where a {@code null} sub-list defines an optional term. Order and index 
of sub-lists corresponds
+     * to the order and index of terms in the matching intent. Number of 
sub-lists will always be the same
+     * as the number of terms in the matched intent.
+     * <p>
+     * Note that unlike {@link #getVariant()} method
+     * this method returns only subset of the tokens that were part of the 
matched intent. Specifically, it will
+     * not return tokens for free words, stopwords or unmatched ("dangling") 
tokens.
+     * <p>
+     * Consider using {@link NCIntentTerm} annotation instead for simpler 
access to intent tokens.
+     *
+     * @return List of list of tokens representing matched intent.
+     * @see #getVariant()
+     * @see NCIntentTerm
+     */
+    List<List<NCToken>> getIntentTokens();
+
+    /**
+     * Gets tokens for given term. This is a companion method for {@link 
#getIntentTokens()}.
+     * <p>
+     * Consider using {@link NCIntentTerm} annotation instead for simpler 
access to intent tokens.
+     *
+     * @param idx Index of the term (starting from <code>0</code>).
+     * @return List of tokens, potentially {@code null}, for given term.
+     * @see NCIntentTerm
+     * @see #getTermTokens(String)
+     */
+    List<NCToken> getTermTokens(int idx);
+
+    /**
+     * Gets tokens for given term. This is a companion method for {@link 
#getIntentTokens()}.
+     * <p>
+     * Consider using {@link NCIntentTerm} annotation instead for simpler 
access to intent tokens.
+     *
+     * @param termId ID of the term for which to get tokens.
+     * @return List of tokens, potentially {@code null}, for given term.
+     * @see NCIntentTerm
+     * @see #getTermTokens(int)
+     */
+    List<NCToken> getTermTokens(String termId);
+
+    /**
+     * Gets sentence parsing variant that produced the matching for this 
intent. Returned variant is one of the
+     * variants provided by {@link NCContext#getVariants()} methods. Note that 
tokens returned by this method are
+     * a superset of the tokens returned by {@link #getIntentTokens()} method, 
i.e. not all tokens
+     * from this variant may have been used in matching of the winning intent.
+     *
+     * @return Sentence parsing variant that produced the matching for this 
intent.
+     * @see #getIntentTokens()
+     */
+    NCVariant getVariant();
+
+    /**
+     * Indicates whether or not the intent match was ambiguous (not exact).
+     * <p>
+     * An exact match means that for the intent to match it has to use all 
non-free word tokens
+     * in the user input, i.e. only free word tokens can be left after the 
match. An ambiguous match
+     * doesn't have this restriction. Note that an ambiguous match should be 
used with a great care.
+     * An ambiguous match completely ignores extra found user or system tokens 
(which are not part
+     * of the intent template) which could have altered the matching outcome 
had they been considered.
+     * <p>
+     * Intent callbacks that check this property should always provide custom 
rejection message.
+     *
+     * @return {@code True} if the intent match was exact, {@code false} 
otherwise.
+     */
+    boolean isAmbiguous();
+
+    /**
+     * Gets descriptor of the user on behalf of which the input request was 
submitted.
+     *
+     * @return User descriptor.
+     */
+    NCUser getUser();
+
+    /**
+     * Gets descriptor of the user's company on behalf of which the input 
request was submitted.
+     *
+     * @return User company descriptor.
+     */
+    NCCompany getCompany();
+
+    /**
+     * Gets globally unique server ID of the input request.
+     * <p>
+     * Server request is defined as a processing of a one user input request 
(a session).
+     * Note that the model can be accessed multiple times during processing of 
a single user request
+     * and therefore multiple instances of this interface can return the same 
server
+     * request ID. In fact, users of this interfaces can use this fact by 
using this ID,
+     * for example, as a map key for a session scoped storage.
+     *
+     * @return Server request ID.
+     */
+    String getServerRequestId();
+
+    /**
+     * Gets normalized text of the user input.
+     *
+     * @return Normalized text of the user input.
+     */
+    String getNormalizedText();
+
+    /**
+     * Gets UTC/GMT timestamp in ms when user input was received.
+     *
+     * @return UTC/GMT timestamp in ms when user input was received.
+     */
+    long getReceiveTimestamp();
+
+    /**
+     * Gets optional address of the remote client.
+     *
+     * @return Optional address of the remote client.
+     */
+    Optional<String> getRemoteAddress();
+
+    /**
+     * Gets string representation of the user client agent that made the call 
with
+     * this request.
+     *
+     * @return User agent string from user client (web browser, REST client, 
etc.).
+     */
+    Optional<String> getClientAgent();
+
+    /**
+     * Gets optional JSON data passed in with the user request.
+     *
+     * @return Optional JSON data.
+     */
+    Optional<String> getJsonData();
+}
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/NCIntentSolver.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/NCIntentSolver.scala
index 9a68efc..f7dbfeb 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/NCIntentSolver.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/NCIntentSolver.scala
@@ -137,7 +137,12 @@ class NCIntentSolver(intents: List[(NCDslIntent/*Intent*/, 
NCIntentMatch ⇒ NCR
                     
                 logger.info(s"Intent '${res.intentId}' for variant 
#${res.variantIdx + 1} selected as the ${g(bo("'best match'"))}.")
 
-                NCDialogFlowManager.addMatchedIntent(res.intentId, 
req.getUser.getId, ctx.getModel.getId, span)
+                NCDialogFlowManager.addMatchedIntent(
+                    intentMatch,
+                    res,
+                    ctx,
+                    span
+                )
                 
                 if (logHldr != null)
                     logHldr.setMatchedIntentIndex(i)
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/dialogflow/NCDialogFlowManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/dialogflow/NCDialogFlowManager.scala
index 17d7eba..825c556 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/dialogflow/NCDialogFlowManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/dialogflow/NCDialogFlowManager.scala
@@ -19,6 +19,8 @@ package org.apache.nlpcraft.probe.mgrs.dialogflow
 
 import io.opencensus.trace.Span
 import org.apache.nlpcraft.common.{NCService, _}
+import org.apache.nlpcraft.model.{NCContext, NCDialogFlowItem, NCIntentMatch}
+import org.apache.nlpcraft.model.intent.impl.NCIntentSolverResult
 import org.apache.nlpcraft.probe.mgrs.model.NCModelManager
 
 import scala.collection._
@@ -27,10 +29,17 @@ import scala.collection._
  * Dialog flow manager.
  */
 object NCDialogFlowManager extends NCService {
-    case class Key(usrId: Long, mdlId: String)
-    case class Value(intent: String, tstamp: Long)
+    /**
+      *
+      * @param usrId
+      * @param mdlId
+      */
+    case class Key(
+        usrId: Long,
+        mdlId: String
+    )
 
-    private final val flow = mutable.HashMap.empty[Key, 
mutable.ArrayBuffer[Value]]
+    private final val flow = mutable.HashMap.empty[Key, 
mutable.ArrayBuffer[NCDialogFlowItem]]
 
     @volatile private var gc: Thread = _
 
@@ -79,23 +88,48 @@ object NCDialogFlowManager extends NCService {
     }
 
     /**
-     * Adds matched (winning) intent to the dialog flow for the given user and 
model IDs.
-     *
-     * @param intId Intent ID.
-     * @param usrId User ID.
-     * @param mdlId Model ID.
-     */
-    def addMatchedIntent(intId: String, usrId: Long, mdlId: String, parent: 
Span = null): Unit = {
-        startScopedSpan("addMatchedIntent", parent, "usrId" → usrId, "mdlId" → 
mdlId, "intId" → intId) { _ ⇒
+      * Adds matched (winning) intent to the dialog flow.
+      *
+      * @param intentMatch
+      * @param res Intent match result.
+      * @param ctx Original query context.
+      */
+    def addMatchedIntent(intentMatch: NCIntentMatch, res: 
NCIntentSolverResult, ctx: NCContext, parent: Span = null): Unit = {
+        val usrId = ctx.getRequest.getUser.getId
+        val mdlId = ctx.getModel.getId
+        val intentId = res.intentId
+        
+        startScopedSpan("addMatchedIntent", parent, "usrId" → usrId, "mdlId" → 
mdlId, "intentId" → intentId) { _ ⇒
             flow.synchronized {
-                flow.getOrElseUpdate(Key(usrId, mdlId), 
mutable.ArrayBuffer.empty[Value]).append(
-                    Value(intId, System.currentTimeMillis())
-                )
-
+                val req = ctx.getRequest
+                
+                val key = Key(usrId, mdlId)
+                val item: NCDialogFlowItem = new NCDialogFlowItem {
+                    override val getIntentId = intentId
+                    override val getIntentTokens = intentMatch.getIntentTokens
+                    override def getTermTokens(idx: Int) = 
intentMatch.getTermTokens(idx)
+                    override def getTermTokens(termId: String) = 
intentMatch.getTermTokens(termId)
+                    override val getVariant = intentMatch.getVariant
+                    override val isAmbiguous = !res.isExactMatch // TODO: 
rename for consistency?
+                    override val getUser = req.getUser
+                    override val getCompany = req.getCompany
+                    override val getServerRequestId = req.getServerRequestId
+                    override val getNormalizedText = req.getNormalizedText
+                    override val getReceiveTimestamp = req.getReceiveTimestamp
+                    override val getRemoteAddress = req.getRemoteAddress
+                    override val getClientAgent = req.getClientAgent
+                    override val getJsonData = req.getJsonData
+                }
+                
+                flow.getOrElseUpdate(key, 
mutable.ArrayBuffer.empty[NCDialogFlowItem]).append(item)
                 flow.notifyAll()
             }
 
-            logger.trace(s"Added to dialog flow [mdlId=$mdlId, intId=$intId, 
userId=$usrId]")
+            logger.trace(s"Added matched intent to dialog flow [" +
+                s"mdlId=$mdlId, " +
+                s"intentId=$intentId, " +
+                s"userId=$usrId" +
+            s"]")
         }
     }
 
@@ -106,10 +140,24 @@ object NCDialogFlowManager extends NCService {
      * @param mdlId Model ID.
      * @return Dialog flow.
      */
-    def getDialogFlow(usrId: Long, mdlId: String, parent: Span = null): 
Seq[String] =
-        startScopedSpan("getDialogFlow", parent, "usrId" → usrId, "mdlId" → 
mdlId) { _ ⇒
+    def getStringFlow(usrId: Long, mdlId: String, parent: Span = null): 
Seq[String] =
+        startScopedSpan("getStringFlow", parent, "usrId" → usrId, "mdlId" → 
mdlId) { _ ⇒
+            flow.synchronized {
+                flow.getOrElseUpdate(Key(usrId, mdlId), 
mutable.ArrayBuffer.empty[NCDialogFlowItem]).map(_.getIntentId)
+            }
+        }
+    
+    /**
+      * Gets sequence of dialog flow items sorted from oldest to newest (i.e. 
dialog flow) for given user and model IDs.
+      *
+      * @param usrId User ID.
+      * @param mdlId Model ID.
+      * @return Dialog flow.
+      */
+    def getItemFlow(usrId: Long, mdlId: String, parent: Span = null): 
Seq[NCDialogFlowItem] =
+        startScopedSpan("getItemFlow", parent, "usrId" → usrId, "mdlId" → 
mdlId) { _ ⇒
             flow.synchronized {
-                flow.getOrElseUpdate(Key(usrId, mdlId), 
mutable.ArrayBuffer.empty[Value]).map(_.intent)
+                flow.getOrElseUpdate(Key(usrId, mdlId), 
mutable.ArrayBuffer.empty[NCDialogFlowItem])
             }
         }
 
@@ -129,7 +177,7 @@ object NCDialogFlowManager extends NCService {
                     case Some(mdl) ⇒
                         val ms = now - mdl.model.getConversationTimeout
 
-                        values --= values.filter(_.tstamp < ms)
+                        values --= values.filter(_.getReceiveTimestamp < ms)
 
                         timeouts += mdl.model.getId → 
mdl.model.getConversationTimeout
 
@@ -144,7 +192,7 @@ object NCDialogFlowManager extends NCService {
 
             flow --= delKeys
 
-            val times = (for ((key, values) ← flow) yield values.map(v ⇒ 
v.tstamp + timeouts(key.mdlId))).flatten
+            val times = (for ((key, values) ← flow) yield values.map(v ⇒ 
v.getReceiveTimestamp + timeouts(key.mdlId))).flatten
 
             if (times.nonEmpty)
                 times.min
@@ -167,7 +215,7 @@ object NCDialogFlowManager extends NCService {
                 flow.notifyAll()
             }
 
-            logger.trace(s"Dialog history is cleared [" +
+            logger.trace(s"Dialog flow history is cleared [" +
                 s"usrId=$usrId, " +
                 s"mdlId=$mdlId" +
             s"]")
@@ -186,12 +234,12 @@ object NCDialogFlowManager extends NCService {
             val key = Key(usrId, mdlId)
 
             flow.synchronized {
-                flow(key) = flow(key).filterNot(v ⇒ pred(v.intent))
+                flow(key) = flow(key).filterNot(v ⇒ pred(v.getIntentId))
 
                 flow.notifyAll()
             }
 
-            logger.trace(s"Dialog history is cleared [" +
+            logger.trace(s"Dialog flow history is cleared [" +
                 s"usrId=$usrId, " +
                 s"mdlId=$mdlId" +
             s"]")

Reply via email to