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"]")