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

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


The following commit(s) were added to refs/heads/master by this push:
     new 1556a3a  Api extended. Bugfixes.
1556a3a is described below

commit 1556a3ad6919419f57e67f6609f2cc1446e9bc1a
Author: Sergey Kamov <[email protected]>
AuthorDate: Sun Apr 3 11:38:38 2022 +0300

    Api extended. Bugfixes.
---
 .../scala/org/apache/nlpcraft/NCCallbackData.java  |  44 +++++
 .../scala/org/apache/nlpcraft/NCModelClient.java   |  25 +++
 .../internal/conversation/NCConversationData.scala |  25 ++-
 .../conversation/NCConversationManager.scala       |   2 +-
 .../internal/dialogflow/NCDialogFlowManager.scala  |  38 ++++-
 .../nlpcraft/internal/impl/NCModelClientImpl.scala |  34 +++-
 .../intent/matcher/NCIntentSolverManager.scala     | 186 ++++++++++++++++-----
 .../conversation/NCConversationManagerSpec.scala   |   8 +-
 .../internal/conversation/NCConversationSpec.scala |  77 +++++++++
 .../conversation/NCConversationTimeoutSpec.scala   |  76 +++++++++
 .../internal/impl/NCModelCallbacksSpec.scala       |   2 +-
 .../nlpcraft/internal/impl/NCModelClientSpec.scala |  25 ++-
 .../internal/impl/NCModelClientSpec2.scala         | 102 +++++++++++
 .../internal/impl/NCModelClientSpec3.scala         |  69 ++++++++
 .../internal/impl/NCModelPingPongSpec.scala        |   2 +-
 .../internal/impl/NCPipelineManagerSpec.scala      |   2 +-
 .../semantic/NCSemanticEntityParserJsonSpec.scala  |   2 +-
 .../semantic/NCSemanticEntityParserSpec.scala      |   2 +-
 .../semantic/NCSemanticEntityParserYamlSpec.scala  |   2 +-
 .../org/apache/nlpcraft/nlp/util/NCTestUtils.scala |  12 +-
 20 files changed, 663 insertions(+), 72 deletions(-)

diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCCallbackData.java 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCCallbackData.java
new file mode 100644
index 0000000..d6a3db2
--- /dev/null
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCCallbackData.java
@@ -0,0 +1,44 @@
+/*
+ * 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
+ *
+ *      https://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;
+
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ *
+ */
+public interface NCCallbackData {
+    /**
+     *
+     * @return
+     */
+    String getIntentId();
+
+    /**
+     *
+     * @return
+     */
+    List<List<NCEntity>> getCallbackArguments();
+
+    /**
+     *
+     * @return
+     */
+    Function<List<List<NCEntity>>, NCResult> getCallback();
+}
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModelClient.java 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModelClient.java
index 54f023b..f625233 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModelClient.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModelClient.java
@@ -98,4 +98,29 @@ public class NCModelClient implements AutoCloseable {
     public void validateSamples() {
         impl.validateSamples();
     }
+
+    /**
+     * TODO:
+     * Gets callback information which contains intent ID and callback 
arguments entities.
+     * Note that
+     *  - Callback is not called in this case.
+     *  - if model `onContext` method overrided - error thrown because we 
don't find intents in this case.
+     *
+     *  Callback.
+     *   - You can call callback only one time.
+     *   - You can't call callback if it is not last request.
+     *   - if you call callback and 'saveHistory' flag was true - dialog 
overriden by callback result instead of saved before empty result.
+     *   - if you call callback and 'saveHistory' flag was false - history 
data is still ignored.
+     *   - No matter of callback execution time - history data based on 
request timestamp.
+     *
+     * @param txt
+     * @param data
+     * @param usrId
+     * @param saveHistory if true that found intent data added to dialog flow 
(with empty NCResult, bacause callback wasn't called) and STM.
+     *                    if false that found intent is not saved in STM and 
dialog flow.
+     * @return
+     */
+    public NCCallbackData debugAsk(String txt, Map<String, Object> data, 
String usrId, boolean saveHistory) {
+        return impl.debugAsk(txt, data, usrId, saveHistory);
+    }
 }
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationData.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationData.scala
index 472dc6a..3092964 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationData.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationData.scala
@@ -36,7 +36,7 @@ case class NCConversationData(
     mdlId: String,
     timeoutMs: Long,
     maxDepth: Int
-) extends LazyLogging {
+) extends LazyLogging:
     private final val data = new NCPropertyMapAdapter()
 
     case class EntityHolder(entity: NCEntity, var entityTypeUsageTime: Long = 
0)
@@ -47,6 +47,12 @@ case class NCConversationData(
     private val lastEnts = mutable.ArrayBuffer.empty[Iterable[NCEntity]]
     private val ctx = mutable.ArrayBuffer.empty[NCEntity]
 
+    /**
+      *
+      */
+    val getUserData: NCPropertyMap = data
+
+
     @volatile private var lastUpdateTstamp = NCUtils.nowUtcMs()
     @volatile private var depth = 0
 
@@ -195,14 +201,17 @@ case class NCConversationData(
       *
       * @return
       */
-    def getEntities: Seq[NCEntity] =
-        stm.synchronized {
-            val reqIds = ctx.map(_.getRequestId).distinct.zipWithIndex.toMap
-            ctx.groupBy(_.getRequestId).toSeq.sortBy(p => 
reqIds(p._1)).reverse.flatMap(_._2)
-        }
+    def getEntities: Seq[NCEntity] = stm.synchronized {
+        val reqIds = ctx.map(_.getRequestId).distinct.zipWithIndex.toMap
+        ctx.groupBy(_.getRequestId).toSeq.sortBy(p => 
reqIds(p._1)).reverse.flatMap(_._2)
+    }
 
     /**
       *
       */
-    val getUserData: NCPropertyMap = data
-}
\ No newline at end of file
+    def clear(): Unit = stm.synchronized {
+        ctx.clear()
+        stm.clear()
+        lastEnts.clear()
+        data.clear()
+    }
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationManager.scala
index bc7c557..ce9d66c 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/conversation/NCConversationManager.scala
@@ -61,7 +61,7 @@ class NCConversationManager(cfg: NCModelConfig) extends 
LazyLogging:
 
         for ((key, value) <- convs)
             if value.tstamp < now - cfg.getConversationTimeout then
-                value.conv.getUserData.clear()
+                value.conv.clear()
                 delKeys += key
 
         convs --= delKeys
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManager.scala
index 4c76db8..20b3f9e 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManager.scala
@@ -63,6 +63,19 @@ class NCDialogFlowManager(cfg: NCModelConfig) extends 
LazyLogging:
 
     /**
       *
+      * @param intentMatch
+      * @param res
+      * @param ctx
+      * @return
+      */
+    private def mkItem(intentMatch: NCIntentMatch, res: NCResult, ctx: 
NCContext): NCDialogFlowItem =
+        new NCDialogFlowItem:
+            override val getIntentMatch: NCIntentMatch = intentMatch
+            override val getRequest: NCRequest = ctx.getRequest
+            override val getResult: NCResult = res
+
+    /**
+      *
       * @return
       */
     def start(): Unit =
@@ -98,10 +111,7 @@ class NCDialogFlowManager(cfg: NCModelConfig) extends 
LazyLogging:
       * @param ctx Original query context.
       */
     def addMatchedIntent(intentMatch: NCIntentMatch, res: NCResult, ctx: 
NCContext): Unit =
-        val item: NCDialogFlowItem = new NCDialogFlowItem:
-            override val getIntentMatch: NCIntentMatch = intentMatch
-            override val getRequest: NCRequest = ctx.getRequest
-            override val getResult: NCResult = res
+        val item = mkItem(intentMatch, res, ctx)
 
         flow.synchronized {
             flow.getOrElseUpdate(ctx.getRequest.getUserId, 
mutable.ArrayBuffer.empty[NCDialogFlowItem]).append(item)
@@ -109,6 +119,26 @@ class NCDialogFlowManager(cfg: NCModelConfig) extends 
LazyLogging:
         }
 
     /**
+      *
+      * @param intentMatch
+      * @param res
+      * @param ctx
+      */
+    def replaceLastItem(intentMatch: NCIntentMatch, res: NCResult, ctx: 
NCContext): Unit =
+        val item = mkItem(intentMatch, res, ctx)
+
+        flow.synchronized {
+            val buf = flow.getOrElseUpdate(ctx.getRequest.getUserId, 
mutable.ArrayBuffer.empty[NCDialogFlowItem])
+
+            // If buf is empty - it cleared by timer, so there is nothing to 
replace.     
+            if buf.nonEmpty then
+                buf.remove(buf.size - 1)
+                buf.append(item)
+
+            flow.notifyAll()
+        }
+
+    /**
       * Gets sequence of dialog flow items sorted from oldest to newest (i.e. 
dialog flow) for given user ID.
       *
       * @param usrId User ID.
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelClientImpl.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelClientImpl.scala
index 30913c0..4615c0c 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelClientImpl.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelClientImpl.scala
@@ -48,7 +48,7 @@ class NCModelClientImpl(mdl: NCModel) extends LazyLogging:
     private val convMgr = NCConversationManager(mdl.getConfig)
     private val dlgMgr = NCDialogFlowManager(mdl.getConfig)
     private val plMgr = NCModelPipelineManager(mdl.getConfig, mdl.getPipeline)
-    private val intentsMgr = NCIntentSolverManager(dlgMgr, intents.map(p => 
p.intent -> p.function).toMap)
+    private val intentsMgr = NCIntentSolverManager(dlgMgr, convMgr, 
intents.map(p => p.intent -> p.function).toMap)
 
     init()
 
@@ -75,19 +75,23 @@ class NCModelClientImpl(mdl: NCModel) extends LazyLogging:
         dlgMgr.start()
         plMgr.start()
 
-     /*
+    /**
+      *
       * @param txt
       * @param data
       * @param usrId
+      * @param typ
       * @return
       */
-    def ask(txt: String, data: JMap[String, AnyRef], usrId: String): NCResult =
+    private def ask0(txt: String, data: JMap[String, AnyRef], usrId: String, 
typ: NCIntentSolveType): Either[NCResult, NCCallbackData] =
         val plData = plMgr.prepare(txt, data, usrId)
 
         val userId = plData.request.getUserId
         val convHldr = convMgr.getConversation(userId)
         val allEnts = plData.variants.flatMap(_.getEntities.asScala)
 
+        convHldr.updateEntities()
+
         val conv: NCConversation =
             new NCConversation:
                 override val getData: NCPropertyMap = convHldr.getUserData
@@ -105,7 +109,16 @@ class NCModelClientImpl(mdl: NCModel) extends LazyLogging:
                 override val getVariants: util.Collection[NCVariant] = 
plData.variants.asJava
                 override val getTokens: JList[NCToken] = plData.tokens
 
-        intentsMgr.solve(mdl, ctx)
+        intentsMgr.solve(mdl, ctx, typ)
+
+     /*
+      * @param txt
+      * @param data
+      * @param usrId
+      * @return
+      */
+    def ask(txt: String, data: JMap[String, AnyRef], usrId: String): NCResult =
+        ask0(txt, data, usrId, NCIntentSolveType.REGULAR).swap.toOption.get
 
     /**
       *
@@ -187,3 +200,16 @@ class NCModelClientImpl(mdl: NCModel) extends LazyLogging:
         plMgr.close()
         dlgMgr.close()
         convMgr.close()
+        intentsMgr.close()
+
+    /**
+      *
+      * @param txt
+      * @param data
+      * @param usrId
+      * @param saveHist
+      * @return
+      */
+    def debugAsk(txt: String, data: JMap[String, AnyRef], usrId: String, 
saveHist: Boolean): NCCallbackData =
+        import NCIntentSolveType.*
+        ask0(txt, data, usrId, if saveHist then SEARCH else 
SEARCH_NO_HISTORY).toOption.get
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/matcher/NCIntentSolverManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/matcher/NCIntentSolverManager.scala
index afe36a9..c2c3f06 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/matcher/NCIntentSolverManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/matcher/NCIntentSolverManager.scala
@@ -20,6 +20,7 @@ package org.apache.nlpcraft.internal.intent.matcher
 import com.typesafe.scalalogging.LazyLogging
 import org.apache.nlpcraft.*
 import org.apache.nlpcraft.internal.ascii.NCAsciiTable
+import org.apache.nlpcraft.internal.conversation.NCConversationManager
 import org.apache.nlpcraft.internal.dialogflow.NCDialogFlowManager
 import org.apache.nlpcraft.internal.intent.*
 
@@ -31,6 +32,15 @@ import scala.collection.mutable.ArrayBuffer
 import scala.jdk.CollectionConverters.*
 import scala.language.postfixOps
 
+/**
+  *
+  */
+enum NCIntentSolveType:
+    case REGULAR, SEARCH, SEARCH_NO_HISTORY
+
+/**
+  *
+  */
 object NCIntentSolverManager:
     /**
       * Sentence variant & its weight.
@@ -72,6 +82,18 @@ object NCIntentSolverManager:
 
     /**
       *
+      * @param getIntentId
+      * @param getCallbackArguments
+      * @param getCallback
+      */
+    private case class CallbackDataImpl(
+        getIntentId: String,
+        getCallbackArguments: JList[JList[NCEntity]],
+        getCallback: Function[JList[JList[NCEntity]], NCResult]
+    ) extends NCCallbackData
+
+    /**
+      *
       * @param intentId
       * @param fn
       * @param groups
@@ -162,12 +184,14 @@ object NCIntentSolverManager:
       */
     private case class IntentEntity(var used: Boolean, var conv: Boolean, 
entity: NCEntity)
 
+    type ResultData = Either[NCResult, NCCallbackData]
+
     /**
       *
       * @param result
       * @param intentMatch
       */
-    private case class IterationResult(result: NCResult, intentMatch: 
NCIntentMatch)
+    private case class IterationResult(result: ResultData, intentMatch: 
NCIntentMatch)
 
     /**
       * @param termId
@@ -221,12 +245,25 @@ object NCIntentSolverManager:
         variantIdx: Int // Variant index.
     )
 
+    /**
+      *
+      * @param userId
+      * @param mldId
+      */
+    private case class UserModelKey(userId: String, mldId: String)
+
 import org.apache.nlpcraft.internal.intent.matcher.NCIntentSolverManager.*
 
 /**
  * Intent solver that finds the best matching intent given user sentence.
  */
-class NCIntentSolverManager(dialog: NCDialogFlowManager, intents: 
Map[NCIDLIntent, NCIntentMatch => NCResult]) extends LazyLogging:
+class NCIntentSolverManager(
+    dialog: NCDialogFlowManager,
+    conv: NCConversationManager,
+    intents: Map[NCIDLIntent, NCIntentMatch => NCResult]
+) extends LazyLogging:
+    private final val reqIds = mutable.HashMap.empty[UserModelKey, String]
+
     /**
      * Main entry point for intent engine.
      *
@@ -244,7 +281,7 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
         for (
             (vrn, vrnIdx) <- ctx.getVariants.asScala.zipWithIndex if 
mdl.onVariant(vrn);
             ents = vrn.getEntities.asScala;
-            varEntsGroups = ents.map(t => if t.getGroups != null then 
t.getGroups.asScala else Set.empty[String]);
+            varEntsGroups = ents.filter(t => t.getGroups != null && 
!t.getGroups.isEmpty).map(_.getGroups.asScala);
             (intent, callback) <- intents
         )
             val convEnts: Seq[IntentEntity] =
@@ -388,7 +425,6 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
         val opts = intent.options
         val flow = dialog.getDialogFlow(ctx.getRequest.getUserId)
         val varStr = s"(variant #${varIdx + 1})"
-        val flowRegex = intent.flowRegex
 
         // Check dialog flow regex first, if any.
         val flowMatched: Boolean =
@@ -450,7 +486,6 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
                 None
             else
                 val usedSenEnts = senEnts.filter(_.used)
-                val unusedSenEnts = senEnts.filter(!_.used)
                 val usedConvEnts = convEnts.filter(_.used)
                 val usedToks = usedSenEnts.flatMap(_.entity.getTokens.asScala)
                 val unusedToks = ctx.getTokens.asScala.filter(p => 
!usedToks.contains(p))
@@ -553,7 +588,7 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
                     )
                 // Term not found at all.
                 case None => None
-        catch case e: Exception => throw new NCException(s"Runtime error 
processing IDL term: $term", e)
+        catch case e: Exception => E(s"Runtime error processing IDL term: 
$term", e)
 
     /**
      * Solves term's predicate.
@@ -612,7 +647,7 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
                 Option.when(depth >= 0)(depth + 1)
 
             val convDepthsSum = -usedEnts.flatMap(getConversationDepth).sum
-            
+
             // Mark found entities as used.
             for (e <- usedEnts) e.used = true
 
@@ -622,9 +657,11 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
       *
       * @param mdl
       * @param ctx
+      * @param typ
+      * @param key
       * @return
       */
-    private def solveIteration(mdl: NCModel, ctx: NCContext): 
Option[IterationResult] =
+    private def solveIteration(mdl: NCModel, ctx: NCContext, typ: 
NCIntentSolveType, key: UserModelKey): Option[IterationResult] =
         require(intents.nonEmpty)
 
         val req = ctx.getRequest
@@ -636,17 +673,17 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
         if intentResults.isEmpty then throw new NCRejection("No matching 
intent found.")
 
         object Loop:
-            private var data: Option[Option[IterationResult]] = None
-            private var stopped: Boolean = false
+            private var data: Option[IterationResult] = _
 
-            def hasNext: Boolean = !stopped
-            def finish(data: Option[IterationResult] = None): Unit =
-                Loop.data = Option(data)
-                Loop.stopped = true
-            def result: Option[IterationResult] = data.getOrElse(throw new 
NCRejection("No matching intent found - all intents were skipped."))
+            def hasNext: Boolean = data == null
+            def finish(data: IterationResult): Unit = Loop.data = Option(data)
+            def finish(): Unit = Loop.data = None
+            def result(): Option[IterationResult] =
+                if data == null then throw new NCRejection("No matching intent 
found - all intents were skipped.")
+                data
 
         for (intentRes <- intentResults.filter(_ != null) if Loop.hasNext)
-            val intentMatch: NCIntentMatch =
+            def mkIntentMatch(arg: JList[JList[NCEntity]]): NCIntentMatch =
                 new NCIntentMatch:
                     override val getContext: NCContext = ctx
                     override val getIntentId: String = intentRes.intentId
@@ -659,16 +696,59 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
                     override val getVariant: NCVariant =
                         new NCVariant:
                             override def getEntities: JList[NCEntity] = 
intentRes.variant.entities.asJava
+
+            val im = 
mkIntentMatch(intentRes.groups.map(_.entities).map(_.asJava).asJava)
             try
-                if mdl.onMatchedIntent(intentMatch) then
+                if mdl.onMatchedIntent(im) then
                     // This can throw NCIntentSkip exception.
-                    val cbRes = intentRes.fn(intentMatch)
-                    // Store won intent match in the input.
-                    if cbRes.getIntentId == null then
-                        cbRes.setIntentId(intentRes.intentId)
-                    logger.info(s"Intent '${intentRes.intentId}' for variant 
#${intentRes.variantIdx + 1} selected as the <|best match|>")
-                    dialog.addMatchedIntent(intentMatch, cbRes, ctx)
-                    Loop.finish(Option(IterationResult(cbRes, intentMatch)))
+                    import NCIntentSolveType.*
+
+                    def saveHistory(res: NCResult, im: NCIntentMatch): Unit =
+                        dialog.addMatchedIntent(im, res, ctx)
+                        conv.getConversation(req.getUserId).addEntities(
+                            req.getRequestId, 
im.getIntentEntities.asScala.flatMap(_.asScala).toSeq.distinct
+                        )
+                        logger.info(s"Intent '${intentRes.intentId}' for 
variant #${intentRes.variantIdx + 1} selected as the <|best match|>")
+
+                    def executeCallback(im: NCIntentMatch): NCResult =
+                        val cbRes = intentRes.fn(im)
+                        // Store winning intent match in the input.
+                        if cbRes.getIntentId == null then 
cbRes.setIntentId(intentRes.intentId)
+                        cbRes
+
+                    def finishSearch(): Unit =
+                        val cb = new Function[JList[JList[NCEntity]], 
NCResult]:
+                            @volatile private var called = false
+                            override def apply(args: JList[JList[NCEntity]]): 
NCResult =
+                                if called then E("Callback was already 
called.")
+                                called = true
+
+                                val reqId = reqIds.synchronized { 
reqIds.getOrElse(key, null) }
+
+                                // TODO: text.
+                                if reqId != ctx.getRequest.getRequestId then 
E("Callback is out of date.")
+
+                                typ match
+                                    case SEARCH =>
+                                        val imNew = mkIntentMatch(args)
+                                        val cbRes = executeCallback(imNew)
+                                        dialog.replaceLastItem(imNew, cbRes, 
ctx)
+                                        cbRes
+                                    case SEARCH_NO_HISTORY => 
executeCallback(mkIntentMatch(args))
+                                    case _ => throw new 
AssertionError(s"Unexpected state: $typ")
+
+                        
Loop.finish(IterationResult(Right(CallbackDataImpl(im.getIntentId, 
im.getIntentEntities, cb)), im))
+
+                    typ match
+                        case REGULAR =>
+                            val cbRes = executeCallback(im)
+                            saveHistory(cbRes, im)
+                            Loop.finish(IterationResult(Left(cbRes), im))
+                        case SEARCH =>
+                            saveHistory(new NCResult(), im) // Added dummy 
result.
+                            finishSearch()
+                        case SEARCH_NO_HISTORY =>
+                            finishSearch()
                 else
                     logger.info(s"Model '${ctx.getModelConfig.getId}' 
triggered rematching of intents by intent '${intentRes.intentId}' on variant 
#${intentRes.variantIdx + 1}.")
                     Loop.finish()
@@ -679,42 +759,66 @@ class NCIntentSolverManager(dialog: NCDialogFlowManager, 
intents: Map[NCIDLInten
                             case s if s != null => logger.info(s"Selected 
intent '${intentRes.intentId}' skipped: $s")
                             case _ => logger.info(s"Selected intent 
'${intentRes.intentId}' skipped.")
 
-        Loop.result
+        Loop.result()
 
     /**
       *
       * @param mdl
       * @param ctx
+      * @param typ
       * @return
       */
-    def solve(mdl: NCModel, ctx: NCContext): NCResult =
-        var res: NCResult = mdl.onContext(ctx)
+    def solve(mdl: NCModel, ctx: NCContext, typ: NCIntentSolveType): 
ResultData =
+        import NCIntentSolveType.REGULAR
+
+        val key = UserModelKey(ctx.getRequest.getUserId, mdl.getConfig.getId)
+        reqIds.synchronized { reqIds.put(key, ctx.getRequest.getRequestId)}
+
+        val mdlCtxRes = mdl.onContext(ctx)
 
-        if res != null then
-            // TODO: text.
-            if intents.nonEmpty then logger.warn("`onContext` method overrides 
existed intents. They are ignored.")
+        if mdlCtxRes != null then
+            if typ != REGULAR then E("'onContext()' method is overridden, 
intents cannot be found.")
+            if intents.nonEmpty then logger.warn("'onContext()' method 
overrides existing intents - they are ignored.")
 
-            res
+            Left(mdlCtxRes)
         else
             if intents.isEmpty then
-                // TODO: text.
-                throw NCRejection("Intent solver has no registered intents and 
model's `onContext` method returns null result.")
+                throw NCRejection("There are no registered intents and model's 
'onContext()' method returns 'null' result.")
 
             var loopRes: IterationResult = null
 
             try
                 while (loopRes == null)
-                    solveIteration(mdl, ctx) match
+                    solveIteration(mdl, ctx, typ, key) match
                         case Some(iterRes) => loopRes = iterRes
                         case None => // No-op.
 
-                res = mdl.onResult(loopRes.intentMatch, loopRes.result)
-
-                if res != null then res else loopRes.result
+                typ match
+                    case REGULAR =>
+                        mdl.onResult(loopRes.intentMatch, 
loopRes.result.swap.toOption.get) match
+                            case null => loopRes.result
+                            case mdlRes => Left(mdlRes)
+                    case _ => loopRes.result
             catch
                 case e: NCRejection =>
-                    val res = mdl.onRejection(if loopRes != null then 
loopRes.intentMatch else null, e)
-                    if res != null then res else throw e
+                    typ match
+                        case REGULAR =>
+                            mdl.onRejection(if loopRes != null then 
loopRes.intentMatch else null, e) match
+                                case null => throw e
+                                case mdlRejRes => Left(mdlRejRes)
+                        case _ => throw e
+
                 case e: Throwable =>
-                    val res = mdl.onError(ctx, e)
-                    if res != null then res else throw e
\ No newline at end of file
+                    typ match
+                        case REGULAR =>
+                            mdl.onError(ctx, e) match
+                                case null => throw e
+                                case mdlErrRes =>
+                                    logger.warn("Error during execution.", e)
+                                    Left(mdlErrRes)
+                        case _ => throw e
+
+    /**
+      *
+      */
+    def close(): Unit = reqIds.clear()
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationManagerSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationManagerSpec.scala
index 113b538..1b6d6dc 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationManagerSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationManagerSpec.scala
@@ -65,22 +65,20 @@ class NCConversationManagerSpec:
         val t = NCTestToken()
         val reqId = "req1"
 
-        // TODO: important (error in code) - drop method and use saved 
conversation instead - error is thrown.
-        def getConversation: NCConversationData = mgr.getConversation("user1")
+        val conv = mgr.getConversation("user1")
 
         def checkSize(size: Int): Unit =
-            val conv = getConversation
             require(conv.getEntities.sizeIs == size, s"Unexpected entities 
size: ${conv.getEntities.size}, expected: $size")
 
         // Initial empty.
         checkSize(0)
 
         // Added. Still empty.
-        getConversation.addEntities(reqId, Seq(NCTestEntity("e1", reqId, 
tokens = t), NCTestEntity("e2", reqId, tokens = t)))
+        conv.addEntities(reqId, Seq(NCTestEntity("e1", reqId, tokens = t), 
NCTestEntity("e2", reqId, tokens = t)))
         checkSize(0)
 
         // Updated. Not empty.
-        getConversation.updateEntities()
+        conv.updateEntities()
         checkSize(2)
 
         // Cleared by timeout.
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationSpec.scala
new file mode 100644
index 0000000..112c01a
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationSpec.scala
@@ -0,0 +1,77 @@
+/*
+ * 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
+ *
+ *      https://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.internal.conversation
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.nlp.entity.parser.semantic.NCSemanticTestElement
+import org.junit.jupiter.api.Test
+import org.apache.nlpcraft.nlp.util.*
+import org.junit.jupiter.api.Assertions.{assertFalse, assertTrue}
+
+import scala.jdk.CollectionConverters.*
+import scala.util.Using
+
+/**
+  *
+  */
+class NCConversationSpec:
+    private val usrId = "userId"
+
+    /**
+      *
+      */
+    @Test
+    def test(): Unit =
+        val mdl: NCModel =
+            new NCTestModelAdapter:
+                import NCSemanticTestElement as TE
+                override val getPipeline: NCPipeline =
+                    val pl = mkEnPipeline
+                    
pl.getEntityParsers.add(NCTestUtils.mkEnSemanticParser(TE("e1"), TE("e2")))
+                    pl
+
+                @NCIntent("intent=i1 term(t1)~{# == 'e1'} term(t2)~{# == 
'e2'}?")
+                def onMatch(@NCIntentTerm("t1") t1: NCEntity, 
@NCIntentTerm("t2") t2: Option[NCEntity]): NCResult = new NCResult()
+
+        Using.resource(new NCModelClient(mdl)) { cli =>
+            def execOk(txt: String): Unit = cli.ask(txt, null, usrId)
+            def execReject(txt: String): Unit =
+                try
+                    cli.ask(txt, null, usrId)
+                    require(false)
+                catch
+                    case e: NCRejection => // OK.
+                    case e: Throwable => throw e
+
+            // missed 'e1'
+            execReject("e2")
+            execOk("e1 e2")
+
+            // 'e1' received from conversation.
+            execOk("e2")
+
+            cli.clearStm(usrId)
+            cli.clearDialog(usrId)
+
+            // missed 'e1' again.
+            execReject("e2")
+            execOk("e1 e2")
+
+            // 'e1' received from conversation.
+            execOk("e2")
+        }
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationTimeoutSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationTimeoutSpec.scala
new file mode 100644
index 0000000..99a4499
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationTimeoutSpec.scala
@@ -0,0 +1,76 @@
+/*
+ * 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
+ *
+ *      https://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.internal.conversation
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.nlp.entity.parser.semantic.NCSemanticTestElement
+import org.apache.nlpcraft.nlp.util.*
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+
+import scala.jdk.CollectionConverters.*
+import scala.util.Using
+
+/**
+  *
+  */
+class NCConversationTimeoutSpec:
+    private val TIMEOUT = 200
+    private val VALUE = "value"
+    private val EMPTY = "empty"
+
+    /**
+      *
+      */
+    @Test
+    def test(): Unit =
+        val mdl: NCModel =
+            new NCTestModelAdapter:
+                override val getConfig: NCModelConfig =
+                    val cfg = new NCModelConfig("testId", "test", "1.0", "Test 
description", "Test origin")
+                    cfg.setConversationTimeout(TIMEOUT)
+                    cfg
+
+                override val getPipeline: NCPipeline =
+                    val pl = mkEnPipeline
+                    import NCSemanticTestElement as TE
+                    
pl.getEntityParsers.add(NCTestUtils.mkEnSemanticParser(TE("test")))
+                    pl
+
+                @NCIntent("intent=i term(e)~{# == 'test'}")
+                def onMatch(im: NCIntentMatch, @NCIntentTerm("e") e: 
NCEntity): NCResult =
+                    val conv = im.getContext.getConversation
+                    val res = new 
NCResult(conv.getData.getOpt("key").orElse(EMPTY), NCResultType.ASK_RESULT)
+
+                    // For next calls.
+                    conv.getData.put("key", VALUE)
+
+                    res
+
+        Using.resource(new NCModelClient(mdl)) { cli =>
+            def check(hasValue: Boolean): Unit =
+                require(cli.ask("test", null, "userId").getBody.toString == 
(if hasValue then VALUE else EMPTY))
+
+            check(false)
+            check(true)
+
+            Thread.sleep(TIMEOUT * 2)
+
+            check(false)
+            check(true)
+        }
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelCallbacksSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelCallbacksSpec.scala
index 6da1b1d..6fea886 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelCallbacksSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelCallbacksSpec.scala
@@ -62,7 +62,7 @@ class NCModelCallbacksSpec:
             override def onRejection(ctx: NCIntentMatch, e: NCRejection): 
NCResult = getOrElse(RejectionNotNull, RESULT_REJECTION, null)
             override def onError(ctx: NCContext, e: Throwable): NCResult = 
getOrElse(ErrorNotNull, RESULT_ERROR, null)
 
-    
MDL.getPipeline.getEntityParsers.add(NCTestUtils.mkENSemanticParser(Seq(NCSemanticTestElement("x")).asJava))
+    
MDL.getPipeline.getEntityParsers.add(NCTestUtils.mkEnSemanticParser(Seq(NCSemanticTestElement("x")).asJava))
 
     /**
       *
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec.scala
index 5123c84..5140d7b 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec.scala
@@ -26,9 +26,24 @@ import org.junit.jupiter.api.Test
 import scala.jdk.CollectionConverters.*
 import scala.util.Using
 
+/**
+  *
+  */
 class NCModelClientSpec:
+    /**
+      *
+      * @param e
+      * @return
+      */
+    private def s(e: NCEntity): String =
+        s"Entity [id=${e.getId}, text=${e.mkText()}, 
properties={${e.keysSet().asScala.map(k => s"$k=${e.get(k)}")}}]"
+
+    /**
+      *
+      * @param mdl
+      */
     private def test0(mdl: NCTestModelAdapter): Unit =
-        
mdl.getPipeline.getEntityParsers.add(NCTestUtils.mkENSemanticParser("models/lightswitch_model.yaml"))
+        
mdl.getPipeline.getEntityParsers.add(NCTestUtils.mkEnSemanticParser("models/lightswitch_model.yaml"))
 
         Using.resource(new NCModelClient(mdl)) { client =>
             val res = client.ask("Lights on at second floor kitchen", null, 
"userId")
@@ -37,7 +52,12 @@ class NCModelClientSpec:
             println(s"Body: ${res.getBody}")
 
             client.validateSamples()
+
+            val winner = client.debugAsk("Lights on at second floor kitchen", 
null, "userId", true)
+            println(s"Winner intent: ${winner.getIntentId}")
+            println("Entities: \n" + winner.getCallbackArguments.asScala.map(p 
=> p.asScala.map(s).mkString(", ")).mkString("\n"))
         }
+
     /**
       *
       */
@@ -50,6 +70,9 @@ class NCModelClientSpec:
                 def onMatch(@NCIntentTerm("act") act: NCEntity, 
@NCIntentTerm("loc") locs: List[NCEntity]): NCResult = new NCResult()
         )
 
+    /**
+      * 
+      */
     @Test
     def test2(): Unit =
         test0(
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec2.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec2.scala
new file mode 100644
index 0000000..01194a2
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec2.scala
@@ -0,0 +1,102 @@
+/*
+ * 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
+ *
+ *      https://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.internal.impl
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.nlp.entity.parser.*
+import org.apache.nlpcraft.nlp.entity.parser.semantic.*
+import org.apache.nlpcraft.nlp.util.*
+import org.junit.jupiter.api.Test
+
+import java.util
+import java.util.List as JList
+import scala.collection.mutable
+import scala.jdk.CollectionConverters.*
+import scala.util.Using
+
+/**
+  * 
+  */
+class NCModelClientSpec2:
+    @Test
+    def test(): Unit =
+        import NCSemanticTestElement as TE
+
+        val mdl = new NCTestModelAdapter:
+            override val getPipeline: NCPipeline =
+                val pl = mkEnPipeline
+                
pl.getEntityParsers.add(NCTestUtils.mkEnSemanticParser(TE("e1"), TE("e2")))
+                pl.getTokenEnrichers.add(EN_TOK_LEMMA_POS_ENRICHER)
+                pl
+
+            @NCIntent("intent=i1 term(t1)={# == 'e1'} term(t2List)={# == 
'e2'}*")
+            def onMatch(@NCIntentTerm("t1") act: NCEntity, 
@NCIntentTerm("t2List") locs: List[NCEntity]): NCResult =
+                E("Shouldn't be called.")
+
+        Using.resource(new NCModelClient(mdl)) { client =>
+            case class Result(txt: String):
+                private val wi = client.debugAsk(txt, null, "userId", true)
+                private val allArgs: JList[JList[NCEntity]] = 
wi.getCallbackArguments
+
+                val intentId: String = wi.getIntentId
+                val size: Int = allArgs.size()
+
+                lazy val first: Seq[NCEntity] = 
allArgs.asScala.head.asScala.toSeq
+                lazy val second: Seq[NCEntity] = 
allArgs.asScala.last.asScala.toSeq
+
+                // 1. One argument.
+            var res = Result("e1")
+
+            require(res.intentId == "i1")
+            require(res.size == 2)
+
+            def check(e: NCEntity, txt: String): Unit =
+                require(e.mkText() == txt)
+                // All data aren't lost.
+                require(e.getTokens.get(0).keysSet().contains("lemma"))
+
+            require(res.first.size == 1)
+            check(res.first.head, "e1")
+
+            require(res.second.isEmpty)
+
+            // 2. One argument.
+            res = Result("e1 e2 e2")
+
+            require(res.intentId == "i1")
+            require(res.size == 2)
+
+            require(res.first.size == 1)
+            check(res.first.head, "e1")
+
+            require(res.second.size == 2)
+            check(res.second.head, "e2")
+            check(res.second.last, "e2")
+
+            // 3. No winners.
+            try
+                client.debugAsk("x", null, "userId", false)
+
+                require(false)
+            catch
+                case e: NCRejection => println(s"Expected rejection: 
${e.getMessage}")
+                case e: Throwable => throw e
+        }
+
+
+
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec3.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec3.scala
new file mode 100644
index 0000000..962399f
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec3.scala
@@ -0,0 +1,69 @@
+/*
+ * 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
+ *
+ *      https://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.internal.impl
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.nlp.entity.parser.*
+import org.apache.nlpcraft.nlp.entity.parser.semantic.*
+import org.apache.nlpcraft.nlp.util.*
+import org.junit.jupiter.api.Test
+
+import java.util
+import java.util.List as JList
+import scala.collection.mutable
+import scala.jdk.CollectionConverters.*
+import scala.util.Using
+
+/**
+  * 
+  */
+class NCModelClientSpec3:
+    @Test
+    def test(): Unit =
+        import NCSemanticTestElement as TE
+        val mdl: NCTestModelAdapter = new NCTestModelAdapter:
+            override val getPipeline: NCPipeline =
+                val pl = mkEnPipeline
+                
pl.getEntityParsers.add(NCTestUtils.mkEnSemanticParser(TE("e1")))
+                pl
+
+            @NCIntent("intent=i1 term(t1)={# == 'e1'}")
+            def onMatch(@NCIntentTerm("t1") t1: NCEntity): NCResult = new 
NCResult("Data", NCResultType.ASK_RESULT)
+
+        Using.resource(new NCModelClient(mdl)) { client =>
+            def ask(): NCCallbackData = client.debugAsk("e1", null, "userId", 
true)
+            def execCallback(cb: NCCallbackData): NCResult = 
cb.getCallback.apply(cb.getCallbackArguments)
+            def execCallbackOk(cb: NCCallbackData): Unit = println(s"Result: 
${execCallback(cb).getBody}")
+            def execCallbackFail(cb: NCCallbackData): Unit =
+                try execCallback(cb)
+                catch case e: NCException => println(s"Expected error: 
${e.getMessage}")
+
+            var cbData = ask()
+            execCallbackOk(cbData)
+            execCallbackFail(cbData) // It cannot be called again (Error is 
'Callback was already called.')
+
+            cbData = ask()
+            execCallbackOk(cbData)
+
+            cbData = ask()
+            ask()
+            execCallbackFail(cbData) // Cannot be called, because there are 
new requests  (Error is 'Callback is out of date.')
+        }
+
+
+
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelPingPongSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelPingPongSpec.scala
index 3428ab6..7428b3f 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelPingPongSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelPingPongSpec.scala
@@ -61,7 +61,7 @@ class NCModelPingPongSpec:
             def onOther(im: NCIntentMatch, @NCIntentTerm("other") other: 
NCEntity): NCResult =
                 R(ASK_RESULT, s"Some request by: ${other.mkText()}")
 
-    
MDL.getPipeline.getEntityParsers.add(NCTestUtils.mkENSemanticParser(Seq(STE("command"),
 STE("confirm"), STE("other")).asJava))
+    
MDL.getPipeline.getEntityParsers.add(NCTestUtils.mkEnSemanticParser(Seq(STE("command"),
 STE("confirm"), STE("other")).asJava))
 
     @BeforeEach
     def setUp(): Unit = client = new NCModelClient(MDL)
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCPipelineManagerSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCPipelineManagerSpec.scala
index 5c50dd2..85c3e85 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCPipelineManagerSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCPipelineManagerSpec.scala
@@ -43,7 +43,7 @@ class NCPipelineManagerSpec:
         def test(txt: String, variantCnt: Int, elements: NCSemanticElement*): 
Unit =
             val pipeline = mkEnPipeline
 
-            
pipeline.getEntityParsers.add(NCTestUtils.mkENSemanticParser(elements.asJava))
+            
pipeline.getEntityParsers.add(NCTestUtils.mkEnSemanticParser(elements.asJava))
 
             val res = new NCModelPipelineManager(CFG, pipeline).prepare(txt, 
null, "userId")
 
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserJsonSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserJsonSpec.scala
index b11bcf5..00de5ab 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserJsonSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserJsonSpec.scala
@@ -34,7 +34,7 @@ import scala.jdk.OptionConverters.RichOptional
   *
   */
 class NCSemanticEntityParserJsonSpec:
-    private val semParser = 
NCTestUtils.mkENSemanticParser("models/alarm_model.json")
+    private val semParser = 
NCTestUtils.mkEnSemanticParser("models/alarm_model.json")
 
     /**
       * 
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserSpec.scala
index 71935ea..6e5ed77 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserSpec.scala
@@ -36,7 +36,7 @@ import scala.jdk.OptionConverters.RichOptional
 class NCSemanticEntityParserSpec:
     import NCSemanticTestElement as E
     private val semParser: NCSemanticEntityParser =
-        NCTestUtils.mkENSemanticParser(
+        NCTestUtils.mkEnSemanticParser(
             Seq(
                 // Standard.
                 E("t1", synonyms = Set("t1")),
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserYamlSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserYamlSpec.scala
index 541c4e5..89f3ddd 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserYamlSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/entity/parser/semantic/NCSemanticEntityParserYamlSpec.scala
@@ -32,7 +32,7 @@ import scala.jdk.OptionConverters.RichOptional
   *
   */
 class NCSemanticEntityParserYamlSpec:
-    private val semParser: NCSemanticEntityParser = 
NCTestUtils.mkENSemanticParser("models/lightswitch_model.yaml")
+    private val semParser: NCSemanticEntityParser = 
NCTestUtils.mkEnSemanticParser("models/lightswitch_model.yaml")
 
     /**
       * 
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestUtils.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestUtils.scala
index eb727d0..b444cf9 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestUtils.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestUtils.scala
@@ -138,13 +138,21 @@ object NCTestUtils:
       * @param macros
       * @return
       */
-    def mkENSemanticParser(elms: JList[NCSemanticElement], macros: 
JMap[String, String] = null): NCSemanticEntityParser =
+    def mkEnSemanticParser(elms: JList[NCSemanticElement], macros: 
JMap[String, String] = null): NCSemanticEntityParser =
         new NCSemanticEntityParser(mkSemanticStemmer, EN_TOK_PARSER, macros, 
elms)
 
     /**
       *
+      * @param elms
+      * @return
+      */
+    def mkEnSemanticParser(elms: NCSemanticElement*): NCSemanticEntityParser =
+        new NCSemanticEntityParser(mkSemanticStemmer, EN_TOK_PARSER, null, 
elms.asJava)
+
+    /**
+      *
       * @param src
       * @return
       */
-    def mkENSemanticParser(src: String): NCSemanticEntityParser =
+    def mkEnSemanticParser(src: String): NCSemanticEntityParser =
         new NCSemanticEntityParser(mkSemanticStemmer, EN_TOK_PARSER, src)
\ No newline at end of file

Reply via email to