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

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

commit 953e91d5eec3b8d0834cad08bd74a03f7cce73d6
Author: Sergey Kamov <[email protected]>
AuthorDate: Tue Jul 5 13:25:07 2022 +0300

    API refactored.
---
 .../org/apache/nlpcraft/NCDialogFlowItem.scala     |  4 +-
 .../scala/org/apache/nlpcraft/NCIntentMatch.scala  |  8 +--
 .../main/scala/org/apache/nlpcraft/NCModel.scala   | 10 ++--
 .../main/scala/org/apache/nlpcraft/NCResult.scala  |  6 +-
 .../internal/dialogflow/NCDialogFlowManager.scala  | 10 ++--
 .../nlpcraft/internal/impl/NCModelScanner.scala    | 67 ++++++++++++----------
 .../intent/matcher/NCIntentSolverManager.scala     | 32 +++++------
 .../internal/conversation/NCConversationSpec.scala |  8 +--
 .../conversation/NCConversationTimeoutSpec.scala   |  4 +-
 .../dialogflow/NCDialogFlowManagerSpec.scala       |  5 +-
 .../internal/impl/NCModelCallbacksSpec.scala       |  8 +--
 .../nlpcraft/internal/impl/NCModelClientSpec.scala |  5 +-
 .../internal/impl/NCModelClientSpec2.scala         |  2 +-
 .../internal/impl/NCModelClientSpec3.scala         |  2 +-
 .../internal/impl/NCModelPingPongSpec.scala        |  8 +--
 .../impl/scan/NCModelIntentsInvalidArgsSpec.scala  | 51 +++++++++++-----
 .../scan/NCModelIntentsInvalidIntentsSpec.scala    | 12 ++--
 .../impl/scan/NCModelIntentsNestedSpec.scala       | 20 ++++---
 .../internal/impl/scan/NCTestModelScala.scala      | 24 ++++++--
 .../apache/nlpcraft/nlp/NCEntityEnricherSpec.scala |  2 +-
 .../apache/nlpcraft/nlp/NCEntityMapperSpec.scala   |  2 +-
 .../apache/nlpcraft/nlp/NCTokenEnricherSpec.scala  |  2 +-
 .../apache/nlpcraft/nlp/NCVariantFilterSpec.scala  |  2 +-
 .../apache/nlpcraft/nlp/util/NCTestResult.scala}   | 16 ++----
 24 files changed, 171 insertions(+), 139 deletions(-)

diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCDialogFlowItem.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCDialogFlowItem.scala
index 294cc146..d82b13b9 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCDialogFlowItem.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCDialogFlowItem.scala
@@ -42,6 +42,6 @@ trait NCDialogFlowItem:
     /**
       * Gets the winning intent's result.
       *
-      * @return Winning intent's result.
+      * @return Winning intent's result. // TODO: None for debugAsk.
       */
-    def getResult: NCResult
+    def getResult: Option[NCResult]
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCIntentMatch.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCIntentMatch.scala
index b4b729ec..fe0f5893 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCIntentMatch.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCIntentMatch.scala
@@ -70,10 +70,4 @@ trait NCIntentMatch:
       *
       * @return Parsing variant that produced the matching for this intent.
       * @see #getIntentEntities() */
-    def getVariant: NCVariant
-
-    /**
-      * Gets context of the user input query.
-      *
-      * @return Original query context. */
-    def getContext: NCContext
+    def getVariant: NCVariant
\ No newline at end of file
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModel.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModel.scala
index e98c8c8e..c663e891 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModel.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCModel.scala
@@ -123,7 +123,7 @@ trait NCModel:
       * <p>
       * By default, this method returns {@code true}.
       *
-      * @param ctx Intent match context - the same instance that's passed to 
the matched intent callback.
+      * @param im Intent match context - the same instance that's passed to 
the matched intent callback.
       * @return If {@code true} is returned than the default workflow will 
continue and the matched intent's
       * callback will be called. However, if {@code false} is returned than 
the entire existing set of
       * parsing variants will be matched against all declared intents again. 
Returning false allows this
@@ -132,7 +132,7 @@ trait NCModel:
       * careful not to induce infinite loop in this behavior.
       * @throws NCRejection This callback can throw the rejection exception to 
abort user request processing. In this
       * case the {@link # onRejection ( NCIntentMatch, NCRejection)} callback 
will be called next. */
-    @throws[NCRejection] def onMatchedIntent(ctx: NCIntentMatch) = true
+    @throws[NCRejection] def onMatchedIntent(ctx: NCContext, im: 
NCIntentMatch) = true
 
     /**
       * A callback that is called when successful result is obtained from the 
intent callback and right before
@@ -143,14 +143,14 @@ trait NCModel:
       * <p>
       * Default implementation is a no-op returning {@code null}.
       *
-      * @param ctx Intent match context - the same instance that's passed to 
the matched intent callback
+      * @param im Intent match context - the same instance that's passed to 
the matched intent callback
       * that produced this result.
       * @param res Existing result.
       * @return Optional query result to return interrupting the default 
workflow. Specifically, if this
       * method returns a non-{@code null} result, it will be returned to the 
caller immediately overriding
       * default behavior and existing query result or error processing, if 
any. If the method returns {@code null} -
       * the default processing flow will continue. */
-    def onResult(ctx: NCIntentMatch, res: NCResult): NCResult = null
+    def onResult(ctx: NCContext, im: NCIntentMatch, res: NCResult): NCResult = 
null
 
     /**
       * A callback that is called when intent callback threw NCRejection 
exception. This callback is called
@@ -168,7 +168,7 @@ trait NCModel:
       * returns a non-{@code null} result, it will be returned to the caller 
immediately overriding default behavior
       * and existing query result or error processing, if any. If the method 
returns {@code null} - the default
       * processing flow will continue. */
-    def onRejection(ctx: NCIntentMatch, e: NCRejection): NCResult = null
+    def onRejection(ctx: NCContext, im: NCIntentMatch, e: NCRejection): 
NCResult = null
 
     /**
       * A callback that is called when intent callback failed with unexpected 
exception. Note that this callback may
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCResult.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCResult.scala
index 83826c4a..12d8c4e2 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCResult.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/NCResult.scala
@@ -23,9 +23,9 @@ import org.apache.nlpcraft.NCResultType.*
   *
   */
 object NCResult:
-    def apply(): NCResult = new NCResult(body = null, resultType = null, 
intentId = null)
-    def apply(body: Any, resultType: NCResultType): NCResult = new 
NCResult(body = body, resultType = resultType, intentId = null)
-    def apply(body: Any, resultType: NCResultType, intentId: String): NCResult 
= new NCResult(body = body, resultType =resultType, intentId)
+    def apply(body: Any, resultType: NCResultType, intentId: String): NCResult 
= new NCResult(body = body, resultType = resultType, intentId)
+    def apply(body: Any, resultType: NCResultType): NCResult = apply(body, 
resultType, intentId = null)
+
 
 /**
   *
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 f9342bd7..4202ad41 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
@@ -66,11 +66,11 @@ class NCDialogFlowManager(cfg: NCModelConfig) extends 
LazyLogging:
       * @param ctx
       * @return
       */
-    private def mkItem(intentMatch: NCIntentMatch, res: NCResult, ctx: 
NCContext): NCDialogFlowItem =
+    private def mkItem(intentMatch: NCIntentMatch, res: Option[NCResult], ctx: 
NCContext): NCDialogFlowItem =
         new NCDialogFlowItem:
             override val getIntentMatch: NCIntentMatch = intentMatch
             override val getRequest: NCRequest = ctx.getRequest
-            override val getResult: NCResult = res
+            override val getResult: Option[NCResult] = res
 
     /**
       *
@@ -105,10 +105,10 @@ class NCDialogFlowManager(cfg: NCModelConfig) extends 
LazyLogging:
       * Adds matched (winning) intent to the dialog flow.
       *
       * @param intentMatch
-      * @param res Intent callback result.
+      * @param res Intent callback result. // TODO: Option if debugAsk.
       * @param ctx Original query context.
       */
-    def addMatchedIntent(intentMatch: NCIntentMatch, res: NCResult, ctx: 
NCContext): Unit =
+    def addMatchedIntent(intentMatch: NCIntentMatch, res: Option[NCResult], 
ctx: NCContext): Unit =
         val item = mkItem(intentMatch, res, ctx)
 
         flow.synchronized {
@@ -123,7 +123,7 @@ class NCDialogFlowManager(cfg: NCModelConfig) extends 
LazyLogging:
       * @param ctx
       */
     def replaceLastItem(intentMatch: NCIntentMatch, res: NCResult, ctx: 
NCContext): Unit =
-        val item = mkItem(intentMatch, res, ctx)
+        val item = mkItem(intentMatch, Some(res), ctx)
 
         flow.synchronized {
             val buf = flow.getOrElseUpdate(ctx.getRequest.getUserId, 
mutable.ArrayBuffer.empty[NCDialogFlowItem])
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
index 1470bf63..ac306bce 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
@@ -31,18 +31,26 @@ import scala.collection.mutable
 import scala.jdk.CollectionConverters.*
 import scala.util.Using
 
+/**
+  *
+  * @param ctx
+  * @param im
+  */
+case class NCCallbackInput(ctx: NCContext, im: NCIntentMatch)
+
 /**
   *
   * @param intent
   * @param function
   * @param samples
   */
-case class NCModelIntent(intent: NCIDLIntent, function: NCIntentMatch => 
NCResult, samples: Seq[Seq[String]])
+case class NCModelIntent(intent: NCIDLIntent, function: NCCallbackInput => 
NCResult, samples: Seq[Seq[String]])
 
 object NCModelScanner extends LazyLogging:
     private final val CLS_INTENT = classOf[NCIntent]
     private final val CLS_INTENT_REF = classOf[NCIntentRef]
     private final val CLS_QRY_RES = classOf[NCResult]
+    private final val CLS_CTX = classOf[NCContext]
     private final val CLS_INTENT_MATCH = classOf[NCIntentMatch]
     private final val CLS_SAMPLE = classOf[NCIntentSample]
     private final val CLS_SAMPLE_REF = classOf[NCIntentSampleRef]
@@ -72,7 +80,7 @@ object NCModelScanner extends LazyLogging:
       * @param function
       * @param method
       */
-    private case class IntentHolder(intent: NCIDLIntent, function: 
NCIntentMatch => NCResult, method: Method)
+    private case class IntentHolder(intent: NCIDLIntent, function: 
NCCallbackInput => NCResult, method: Method)
 
     /**
       *
@@ -132,12 +140,11 @@ object NCModelScanner extends LazyLogging:
       * @param mtd
       * @param prmClss
       * @param argsList
-      * @param ctxFirstPrm
       * @return
       */
-    private def prepareParams(cfg: NCModelConfig, mtd: Method, prmClss: 
List[Class[_]], argsList: List[List[NCEntity]], ctxFirstPrm: Boolean): 
Seq[AnyRef] =
+    private def prepareParams(cfg: NCModelConfig, mtd: Method, prmClss: 
List[Class[_]], argsList: List[List[NCEntity]]): Seq[AnyRef] =
         prmClss.zip(argsList).zipWithIndex.map { case ((paramCls, argList), i) 
=>
-            def mkArg(): String = arg2Str(mtd, i, ctxFirstPrm)
+            def mkArg(): String = arg2Str(mtd, i)
 
             lazy val z = s"mdlId=${cfg.id}, type=$paramCls, arg=${mkArg()}"
             val entsCnt = argList.size
@@ -229,10 +236,8 @@ object NCModelScanner extends LazyLogging:
       *
       * @param mtd
       * @param argIdx
-      * @param cxtFirstParam
       */
-    private def arg2Str(mtd: Method, argIdx: Int, cxtFirstParam: Boolean): 
String =
-        s"#${argIdx + (if cxtFirstParam then 1 else 0)} of ${method2Str(mtd)}"
+    private def arg2Str(mtd: Method, argIdx: Int): String = s"#${argIdx + 2} 
of ${method2Str(mtd)}"
 
     /**
       * Gets its own methods including private and accessible from parents.
@@ -304,15 +309,14 @@ object NCModelScanner extends LazyLogging:
       * @param mtd
       * @param argClasses
       * @param paramGenTypes
-      * @param ctxFirstParam
       */
-    private def checkTypes(cfg: NCModelConfig, mtd: Method, argClasses: 
Seq[Class[_]], paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit =
+    private def checkTypes(cfg: NCModelConfig, mtd: Method, argClasses: 
Seq[Class[_]], paramGenTypes: Seq[Type]): Unit =
         require(argClasses.sizeIs == paramGenTypes.length)
 
         var warned = false
 
         argClasses.zip(paramGenTypes).zipWithIndex.foreach { case ((argClass, 
paramGenType), i) =>
-            def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
+            def mkArg(): String = arg2Str(mtd, i)
 
             lazy val z = s"mdlId=${cfg.id}, type=${class2Str(argClass)}, 
arg=${mkArg()}"
 
@@ -362,13 +366,12 @@ object NCModelScanner extends LazyLogging:
       * @param mtd
       * @param paramCls
       * @param limits
-      * @param ctxFirstParam
       */
-    private def checkMinMax(cfg: NCModelConfig, mtd: Method, paramCls: 
Seq[Class[_]], limits: Seq[(Int, Int)], ctxFirstParam: Boolean): Unit =
+    private def checkMinMax(cfg: NCModelConfig, mtd: Method, paramCls: 
Seq[Class[_]], limits: Seq[(Int, Int)]): Unit =
         require(paramCls.sizeIs == limits.length)
 
         paramCls.zip(limits).zipWithIndex.foreach { case ((cls, (min, max)), 
i) =>
-            def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
+            def mkArg(): String = arg2Str(mtd, i)
 
             val p1 = "its $IT annotated argument"
             val p2 = s"mdlId=${cfg.id}, arg=${mkArg()}"
@@ -395,23 +398,24 @@ object NCModelScanner extends LazyLogging:
       * @param intent
       * @return
       */
-    private def prepareCallback(cfg: NCModelConfig, method: Method, obj: 
AnyRef, intent: NCIDLIntent): NCIntentMatch => NCResult =
+    private def prepareCallback(cfg: NCModelConfig, method: Method, obj: 
AnyRef, intent: NCIDLIntent): NCCallbackInput => NCResult =
         lazy val z = s"mdlId=${cfg.id}, intentId=${intent.id}, 
type=${class2Str(method.getReturnType)}, callback=${method2Str(method)}"
 
         // Checks method result type.
-        if method.getReturnType != CLS_QRY_RES then E(s"Unexpected result type 
for @NCIntent annotated method [$z]")
+        if method.getReturnType != CLS_QRY_RES && 
!CLS_QRY_RES.isAssignableFrom(method.getReturnType) then
+            E(s"Unexpected result type for @NCIntent annotated method [$z]")
 
         val allParamTypes = method.getParameterTypes.toList
-        val ctxFirstParam = allParamTypes.nonEmpty && allParamTypes.head == 
CLS_INTENT_MATCH
 
-        def getList[T](data: List[T]): List[T] =
-            if data == null then List.empty
-            else if ctxFirstParam then data.drop(1)
-            else data
+        // TODO: texts
+        if allParamTypes.size < 2 then E(s"Unexpected parameters count for $I 
annotated method [count=${allParamTypes.size}, method=$z]")
+        // TODO: texts
+        if allParamTypes.head != CLS_CTX then E(s"First parameter for $I 
annotated method must be NCContext [method=$z]")
+        // TODO: texts
+        if allParamTypes(1) != CLS_INTENT_MATCH then E(s"Second parameter for 
$I annotated method must be NCIntentMatch [method=$z]")
 
-        val allAnns = method.getParameterAnnotations
-        val tokParamAnns = getList(allAnns.toList).filter(_ != null)
-        val tokParamTypes = getList(allParamTypes)
+        val tokParamAnns = 
method.getParameterAnnotations.toList.drop(2).filter(_ != null)
+        val tokParamTypes = allParamTypes.drop(2)
 
         // Checks entities parameters annotations count.
         if tokParamAnns.sizeIs != tokParamTypes.length then
@@ -420,7 +424,7 @@ object NCModelScanner extends LazyLogging:
         // Gets terms IDs.
         val termIds = tokParamAnns.zipWithIndex.map {
             case (annArr, idx) =>
-                def mkArg(): String = arg2Str(method, idx, ctxFirstParam)
+                def mkArg(): String = arg2Str(method, idx)
 
                 val termAnns = annArr.filter(_.isInstanceOf[NCIntentTerm])
 
@@ -448,17 +452,18 @@ object NCModelScanner extends LazyLogging:
             E(s"Unknown term ID in $IT annotation [termId=${invalidIds.head}, 
$z]")
 
         // Checks parameters.
-        val paramGenTypes = getList(method.getGenericParameterTypes.toList)
-        checkTypes(cfg, method, tokParamTypes, paramGenTypes, ctxFirstParam)
+        val paramGenTypes = method.getGenericParameterTypes.toList.drop(2)
+        checkTypes(cfg, method, tokParamTypes, paramGenTypes)
 
         // Checks limits.
         val allLimits = terms.map(t => t.id.orNull -> (t.min, t.max)).toMap
-        checkMinMax(cfg, method, tokParamTypes, termIds.map(allLimits), 
ctxFirstParam)
+        checkMinMax(cfg, method, tokParamTypes, termIds.map(allLimits))
 
-        (ctx: NCIntentMatch) =>
+        (cbData: NCCallbackInput) =>
             val args = mutable.Buffer.empty[AnyRef]
-            if ctxFirstParam then args += ctx
-            args ++= prepareParams(cfg, method, tokParamTypes, termIds.map(id 
=> ctx.getTermEntities(id)), ctxFirstParam)
+            args += cbData.ctx
+            args += cbData.im
+            args ++= prepareParams(cfg, method, tokParamTypes, termIds.map(id 
=> cbData.im.getTermEntities(id)))
             invoke(cfg, method, obj, args.toArray)
 
     /**
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 08688a48..b2dc4d31 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
@@ -22,6 +22,7 @@ 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.impl.NCCallbackInput
 import org.apache.nlpcraft.internal.intent.*
 
 import java.util.function.Function
@@ -98,7 +99,7 @@ object NCIntentSolverManager:
       * @param variant
       * @param variantIdx
       */
-    private case class IntentSolverResult(intentId: String, fn: NCIntentMatch 
=> NCResult, groups: List[IntentTermEntities], variant: IntentSolverVariant, 
variantIdx: Int)
+    private case class IntentSolverResult(intentId: String, fn: 
NCCallbackInput => NCResult, groups: List[IntentTermEntities], variant: 
IntentSolverVariant, variantIdx: Int)
 
     /**
       * NOTE: not thread-safe.
@@ -238,7 +239,7 @@ object NCIntentSolverManager:
       */
     private case class MatchHolder(
         intentMatch: IntentMatchHolder, // Match.
-        callback: NCIntentMatch => NCResult, // Callback function.
+        callback: NCCallbackInput => NCResult, // Callback function.
         variant: IntentSolverVariant, // Variant used for the match.
         variantIdx: Int // Variant index.
     )
@@ -258,7 +259,7 @@ import 
org.apache.nlpcraft.internal.intent.matcher.NCIntentSolverManager.*
 class NCIntentSolverManager(
     dialog: NCDialogFlowManager,
     conv: NCConversationManager,
-    intents: Map[NCIDLIntent, NCIntentMatch => NCResult]
+    intents: Map[NCIDLIntent, NCCallbackInput => NCResult]
 ) extends LazyLogging:
     private final val reqIds = mutable.HashMap.empty[UserModelKey, String]
 
@@ -270,7 +271,7 @@ class NCIntentSolverManager(
      * @param intents Intents to match for.
      * @return
      */
-    private def solveIntents(mdl: NCModel, ctx: NCContext, intents: 
Map[NCIDLIntent, NCIntentMatch => NCResult]): List[IntentSolverResult] =
+    private def solveIntents(mdl: NCModel, ctx: NCContext, intents: 
Map[NCIDLIntent, NCCallbackInput => NCResult]): List[IntentSolverResult] =
         dialog.ack(ctx.getRequest.getUserId)
 
         val matches = mutable.ArrayBuffer.empty[MatchHolder]
@@ -683,7 +684,6 @@ class NCIntentSolverManager(
         for (intentRes <- intentResults.filter(_ != null) if Loop.hasNext)
             def mkIntentMatch(arg: List[List[NCEntity]]): NCIntentMatch =
                 new NCIntentMatch:
-                    override val getContext: NCContext = ctx
                     override val getIntentId: String = intentRes.intentId
                     override val getIntentEntities: List[List[NCEntity]] = 
intentRes.groups.map(_.entities)
                     override def getTermEntities(idx: Int): List[NCEntity] = 
intentRes.groups(idx).entities
@@ -697,19 +697,19 @@ class NCIntentSolverManager(
 
             val im = mkIntentMatch(intentRes.groups.map(_.entities))
             try
-                if mdl.onMatchedIntent(im) then
+                if mdl.onMatchedIntent(ctx, im) then
                     // This can throw NCIntentSkip exception.
                     import NCIntentSolveType.*
 
-                    def saveHistory(res: NCResult, im: NCIntentMatch): Unit =
+                    def saveHistory(res: Option[NCResult], im: NCIntentMatch): 
Unit =
                         dialog.addMatchedIntent(im, res, ctx)
                         conv.getConversation(req.getUserId).addEntities(
                             req.getRequestId, 
im.getIntentEntities.flatten.distinct
                         )
                         logger.info(s"Intent '${intentRes.intentId}' for 
variant #${intentRes.variantIdx + 1} selected as the <|best match|>")
 
-                    def executeCallback(im: NCIntentMatch): NCResult =
-                        var cbRes = intentRes.fn(im)
+                    def executeCallback(in: NCCallbackInput): NCResult =
+                        var cbRes = intentRes.fn(in)
                         // Store winning intent match in the input.
                         if cbRes.intentId == null then cbRes = 
NCResult(cbRes.body, cbRes.resultType, intentRes.intentId)
                         cbRes
@@ -729,21 +729,21 @@ class NCIntentSolverManager(
                             typ match
                                 case SEARCH =>
                                     val imNew = mkIntentMatch(args)
-                                    val cbRes = executeCallback(imNew)
+                                    val cbRes = 
executeCallback(NCCallbackInput(ctx, imNew))
                                     dialog.replaceLastItem(imNew, cbRes, ctx)
                                     cbRes
-                                case SEARCH_NO_HISTORY => 
executeCallback(mkIntentMatch(args))
+                                case SEARCH_NO_HISTORY => 
executeCallback(NCCallbackInput(ctx, mkIntentMatch(args)))
                                 case _ => throw new 
AssertionError(s"Unexpected state: $typ")
 
                         
Loop.finish(IterationResult(Right(CallbackDataImpl(im.getIntentId, 
im.getIntentEntities, f)), im))
 
                     typ match
                         case REGULAR =>
-                            val cbRes = executeCallback(im)
-                            saveHistory(cbRes, im)
+                            val cbRes = executeCallback(NCCallbackInput(ctx, 
im))
+                            saveHistory(Some(cbRes), im)
                             Loop.finish(IterationResult(Left(cbRes), im))
                         case SEARCH =>
-                            saveHistory(NCResult(), im) // Added dummy result.
+                            saveHistory(None, im)
                             finishSearch()
                         case SEARCH_NO_HISTORY =>
                             finishSearch()
@@ -793,7 +793,7 @@ class NCIntentSolverManager(
 
                 typ match
                     case REGULAR =>
-                        mdl.onResult(loopRes.intentMatch, 
loopRes.result.swap.toOption.get) match
+                        mdl.onResult(ctx, loopRes.intentMatch, 
loopRes.result.swap.toOption.get) match
                             case null => loopRes.result
                             case mdlRes => Left(mdlRes)
                     case _ => loopRes.result
@@ -801,7 +801,7 @@ class NCIntentSolverManager(
                 case e: NCRejection =>
                     typ match
                         case REGULAR =>
-                            mdl.onRejection(if loopRes != null then 
loopRes.intentMatch else null, e) match
+                            mdl.onRejection(ctx, if loopRes != null then 
loopRes.intentMatch else null, e) match
                                 case null => throw e
                                 case mdlRejRes => Left(mdlRejRes)
                         case _ => throw e
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
index 40ab973e..b2ff04e5 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationSpec.scala
@@ -48,7 +48,7 @@ class NCConversationSpec:
                     pl
 
                 @NCIntent("intent=i1 term(t1)~{# == 'e1'} term(t2)~{# == 
'e2'}?")
-                def onMatch(@NCIntentTerm("t1") t1: NCEntity, 
@NCIntentTerm("t2") t2: Option[NCEntity]): NCResult = NCResult()
+                def onMatch(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("t1") t1: NCEntity, @NCIntentTerm("t2") t2: Option[NCEntity]): 
NCResult = NCTestResult()
 
         Using.resource(new NCModelClient(mdl)) { cli =>
             def execOk(txt: String): Unit = cli.ask(txt, null, usrId)
@@ -88,11 +88,11 @@ class NCConversationSpec:
                     pl
 
                 @NCIntent("intent=i1 term(t1)~{# == 'e1'}")
-                def onMatch(im: NCIntentMatch): NCResult =
-                    val conv = im.getContext.getConversation
+                def onMatch(ctx: NCContext, im: NCIntentMatch): NCResult =
+                    val conv = ctx.getConversation
                     conv.clearStm(_ => true)
                     conv.clearDialog(_ => true)
-                    NCResult()
+                    NCTestResult()
 
         Using.resource(new NCModelClient(mdl)) { client =>
             client.ask("e1", null, "userId")
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
index d9ecd85e..a91d3ac7 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationTimeoutSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/conversation/NCConversationTimeoutSpec.scala
@@ -53,8 +53,8 @@ class NCConversationTimeoutSpec:
                     pl
 
                 @NCIntent("intent=i term(e)~{# == 'test'}")
-                def onMatch(im: NCIntentMatch, @NCIntentTerm("e") e: 
NCEntity): NCResult =
-                    val conv = im.getContext.getConversation
+                def onMatch(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("e") e: NCEntity): NCResult =
+                    val conv = ctx.getConversation
                     val res = 
NCResult(conv.getData.getOpt("key").getOrElse(EMPTY), NCResultType.ASK_RESULT)
 
                     // For next calls.
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManagerSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManagerSpec.scala
index c305c545..2c2b7bba 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManagerSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/dialogflow/NCDialogFlowManagerSpec.scala
@@ -29,8 +29,7 @@ import java.util.function.Predicate
   *
   */
 class NCDialogFlowManagerSpec:
-    case class IntentMatchMock(intentId: String, ctx: NCContext) extends 
NCIntentMatch:
-        override val getContext: NCContext = ctx
+    case class IntentMatchMock(intentId: String) extends NCIntentMatch:
         override val getIntentId: String = intentId
         override val getIntentEntities: List[List[NCEntity]] = null
         override def getTermEntities(idx: Int): List[NCEntity] = null
@@ -70,7 +69,7 @@ class NCDialogFlowManagerSpec:
       * @param id
       * @param ctx
       */
-    private def addMatchedIntent(id: String, ctx: NCContext): Unit = 
mgr.addMatchedIntent(IntentMatchMock(id, ctx), null, ctx)
+    private def addMatchedIntent(id: String, ctx: NCContext): Unit = 
mgr.addMatchedIntent(IntentMatchMock(id), null, ctx)
 
     /**
       *
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 0e475e02..bb947844 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
@@ -51,16 +51,16 @@ class NCModelCallbacksSpec:
     private val MDL: NCTestModelAdapter =
         new NCTestModelAdapter():
             @NCIntent("intent=x term(x)={# == 'x'}")
-            def intent(im: NCIntentMatch, @NCIntentTerm("x") x: NCEntity): 
NCResult =
+            def intent(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("x") 
x: NCEntity): NCResult =
                 if has(IntentError) then throw new RuntimeException("Error")
                 else if has(IntentReject) then throw new 
NCRejection("Rejection")
                 else RESULT_INTENT
 
-            override def onMatchedIntent(ctx: NCIntentMatch): Boolean = 
getOrElse(MatchFalse, false, true)
+            override def onMatchedIntent(ctx: NCContext, im: NCIntentMatch): 
Boolean = getOrElse(MatchFalse, false, true)
             override def onVariant(vrn: NCVariant): Boolean = 
getOrElse(VariantFalse, false, true)
             override def onContext(ctx: NCContext): NCResult = 
getOrElse(ContextNotNull, RESULT_CONTEXT, null)
-            override def onResult(ctx: NCIntentMatch, res: NCResult): NCResult 
= getOrElse(ResultNotNull, RESULT_RESULT, null)
-            override def onRejection(ctx: NCIntentMatch, e: NCRejection): 
NCResult = getOrElse(RejectionNotNull, RESULT_REJECTION, null)
+            override def onResult(ctx: NCContext, im: NCIntentMatch, res: 
NCResult): NCResult = getOrElse(ResultNotNull, RESULT_RESULT, null)
+            override def onRejection(ctx: NCContext, im: NCIntentMatch, e: 
NCRejection): NCResult = getOrElse(RejectionNotNull, RESULT_REJECTION, null)
             override def onError(ctx: NCContext, e: Throwable): NCResult = 
getOrElse(ErrorNotNull, RESULT_ERROR, null)
 
     MDL.pipeline.entParsers += 
NCTestUtils.mkEnSemanticParser(List(NCSemanticTestElement("x")))
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 0cdaa76a..fc8426c3 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
@@ -68,9 +68,8 @@ class NCModelClientSpec:
             new NCTestModelAdapter():
                 @NCIntentSample(Array("Lights on at second floor kitchen"))
                 @NCIntent("intent=ls term(act)={# == 'ls:on'} term(loc)={# == 
'ls:loc'}*")
-                def onMatch(@NCIntentTerm("act") act: NCEntity, 
@NCIntentTerm("loc") locs: List[NCEntity]): NCResult = NCResult()
+                def onMatch(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("act") act: NCEntity, @NCIntentTerm("loc") locs: List[NCEntity]): 
NCResult = NCResult("test", NCResultType.ASK_RESULT)
         )
-
     /**
       * 
       */
@@ -80,6 +79,6 @@ class NCModelClientSpec:
             new NCTestModelAdapter():
                 @NCIntent("intent=ls term(act)={has(ent_groups, 'act')} 
term(loc)={# == 'ls:loc'}*")
                 @NCIntentSample(Array("Lights on at second floor kitchen"))
-                def onMatch(@NCIntentTerm("act") act: NCEntity, 
@NCIntentTerm("loc") locs: List[NCEntity]): NCResult = NCResult()
+                def onMatch(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("act") act: NCEntity, @NCIntentTerm("loc") locs: List[NCEntity]): 
NCResult = NCResult("test", NCResultType.ASK_RESULT)
         )
 
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
index 8a1f120c..1df8fae1 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec2.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec2.scala
@@ -44,7 +44,7 @@ class NCModelClientSpec2:
                 pl
 
             @NCIntent("intent=i1 term(t1)={# == 'e1'} term(t2List)={# == 
'e2'}*")
-            def onMatch(@NCIntentTerm("t1") act: NCEntity, 
@NCIntentTerm("t2List") locs: List[NCEntity]): NCResult =
+            def onMatch(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("t1") 
act: NCEntity, @NCIntentTerm("t2List") locs: List[NCEntity]): NCResult =
                 E("Shouldn't be called.")
 
         Using.resource(new NCModelClient(mdl)) { client =>
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
index feececf3..54331930 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec3.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/NCModelClientSpec3.scala
@@ -44,7 +44,7 @@ class NCModelClientSpec3:
                 pl
 
             @NCIntent("intent=i1 term(t1)={# == 'e1'}")
-            def onMatch(@NCIntentTerm("t1") t1: NCEntity): NCResult = 
NCResult("Data", NCResultType.ASK_RESULT)
+            def onMatch(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("t1") 
t1: NCEntity): NCResult = NCResult("Data", NCResultType.ASK_RESULT)
 
         Using.resource(new NCModelClient(mdl)) { client =>
             def ask(): NCCallbackData = client.debugAsk("e1", null, "userId", 
true)
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 f33150f8..66553c86 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
@@ -40,13 +40,13 @@ class NCModelPingPongSpec:
     private val MDL: NCTestModelAdapter =
         new NCTestModelAdapter():
             @NCIntent("intent=command term(command)={# == 'command'}")
-            def onCommand(im: NCIntentMatch, @NCIntentTerm("command") command: 
NCEntity): NCResult =
+            def onCommand(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("command") command: NCEntity): NCResult =
                 R(ASK_DIALOG, s"Confirm your request 'command'")
 
             @NCIntent("intent=confirmCommand term(confirm)={# == 'confirm'}")
-            def onConfirmCommand(im: NCIntentMatch, @NCIntentTerm("confirm") 
confirm: NCEntity): NCResult =
+            def onConfirmCommand(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("confirm") confirm: NCEntity): NCResult =
                 val lastIntentId =
-                    im.getContext.
+                    ctx.
                         getConversation.
                         getDialogFlow.lastOption.
                         flatMap(p => 
Option(p.getIntentMatch.getIntentId)).orNull
@@ -59,7 +59,7 @@ class NCModelPingPongSpec:
                 R(ASK_RESULT, s"'dialog' confirmed.")
 
             @NCIntent("intent=other term(other)={# == 'other'}")
-            def onOther(im: NCIntentMatch, @NCIntentTerm("other") other: 
NCEntity): NCResult =
+            def onOther(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("other") other: NCEntity): NCResult =
                 R(ASK_RESULT, s"Some request by: ${other.mkText}")
 
     MDL.pipeline.entParsers += 
NCTestUtils.mkEnSemanticParser(List(STE("command"), STE("confirm"), 
STE("other")))
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
index 453e865a..9b093af4 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
@@ -19,7 +19,7 @@ package org.apache.nlpcraft.internal.impl.scan
 
 import org.apache.nlpcraft.*
 import org.apache.nlpcraft.annotations.*
-import org.apache.nlpcraft.internal.impl.NCModelScanner
+import org.apache.nlpcraft.internal.impl.{NCCallbackInput, NCModelScanner}
 import org.apache.nlpcraft.nlp.util.*
 import org.junit.jupiter.api.Test
 
@@ -32,43 +32,59 @@ import java.util
 class NCModelIntentsInvalidArgsSpec:
     class DefinedClassModelValid extends NCTestModelAdapter:
         @NCIntent("intent=validList term(list)~{# == 'x'}[0,10]")
-        def validList(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
processListEntity(list)
+        def validList(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("list") 
list: List[NCEntity]): NCResult = processListEntity(list)
 
         @NCIntent("intent=validOpt term(opt)~{# == 'x'}?")
-        def validOpt(@NCIntentTerm("opt") opt: Option[NCEntity]): NCResult = 
processOptEntity(opt)
+        def validOpt(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("opt") 
opt: Option[NCEntity]): NCResult = processOptEntity(opt)
 
     class DefinedClassModelInvalidLst extends NCTestModelAdapter:
         @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]")
-        def invalidList(@NCIntentTerm("list") list: List[Int]): NCResult = 
processListInt(list)
+        def invalidList(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("list") list: List[Int]): NCResult = processListInt(list)
+
+    class DefinedClassModelInvalidArg1 extends NCTestModelAdapter:
+        @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]")
+        def invalidArg(ctx: NCContext, @NCIntentTerm("list") list: List[Int]): 
NCResult = processListInt(list)
+
+    class DefinedClassModelInvalidArg2 extends NCTestModelAdapter:
+        @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]")
+        def invalidArg(im: NCIntentMatch, @NCIntentTerm("list") list: 
List[Int]): NCResult = processListInt(list)
+
+    class DefinedClassModelInvalidArg3 extends NCTestModelAdapter:
+        @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]")
+        def invalidList(im: NCIntentMatch, ctx: NCContext, 
@NCIntentTerm("list") list: List[Int]): NCResult = processListInt(list)
 
     class DefinedClassModelInvalidOpt extends NCTestModelAdapter:
         @NCIntent("intent=invalidOpt term(opt)~{# == 'x'}?")
-        def invalidOpt(@NCIntentTerm("opt") opt: Option[Int]): NCResult = 
processOptInt(opt)
+        def invalidOpt(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("opt") 
opt: Option[Int]): NCResult = processOptInt(opt)
 
     private val CHECKED_MDL_VALID: NCModel = new DefinedClassModelValid
     private val CHECKED_MDL_INVALID_LST: NCModel = new 
DefinedClassModelInvalidLst
     private val CHECKED_MDL_INVALID_OPT: NCModel = new 
DefinedClassModelInvalidOpt
+
+    private val CHECKED_MDL_INVALID_ARG1: NCModel = new 
DefinedClassModelInvalidArg1
+    private val CHECKED_MDL_INVALID_ARG2: NCModel = new 
DefinedClassModelInvalidArg2
+    private val CHECKED_MDL_INVALID_ARG3: NCModel = new 
DefinedClassModelInvalidArg3
+
     private val UNCHECKED_MDL: NCModel =
         new NCTestModelAdapter:
             @NCIntent("intent=validList term(list)~{# == 'x'}[0,10]")
-            def validList(@NCIntentTerm("list") list: List[NCEntity]): 
NCResult = processListEntity(list)
+            def validList(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("list") list: List[NCEntity]): NCResult = processListEntity(list)
 
             @NCIntent("intent=validOpt term(opt)~{# == 'x'}?")
-            def validOpt(@NCIntentTerm("opt") opt: Option[NCEntity]): NCResult 
= processOptEntity(opt)
+            def validOpt(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("opt") opt: Option[NCEntity]): NCResult = processOptEntity(opt)
 
             @NCIntent("intent=invalidList term(list)~{# == 'x'}[0,10]")
-            def invalidList(@NCIntentTerm("list") list: List[Int]): NCResult = 
processListInt(list)
+            def invalidList(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("list") list: List[Int]): NCResult = processListInt(list)
 
             @NCIntent("intent=invalidOpt term(opt)~{# == 'x'}?")
-            def invalidOpt(@NCIntentTerm("opt") opt: Option[Int]): NCResult = 
processOptInt(opt)
+            def invalidOpt(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("opt") opt: Option[Int]): NCResult = processOptInt(opt)
 
-    private val INTENT_MATCH =
+    private val INPUT =
         val e = NCTestEntity("id", "reqId", tokens = NCTestToken())
 
         def col[T](t: T): List[T] = List(t)
 
-        new NCIntentMatch:
-            override val getContext: NCContext = null
+        val im = new NCIntentMatch:
             override val getIntentId: String = "intentId"
             override val getIntentEntities: List[List[NCEntity]] = col(col(e))
             override def getTermEntities(idx: Int): List[NCEntity] = col(e)
@@ -76,6 +92,8 @@ class NCModelIntentsInvalidArgsSpec:
             override val getVariant: NCVariant = new NCVariant:
                 override val getEntities: List[NCEntity] = col(e)
 
+        NCCallbackInput(null, im)
+
     private def mkResult0(obj: Any): NCResult =
         println(s"Result body: $obj, class=${obj.getClass}")
         NCResult(obj, NCResultType.ASK_RESULT)
@@ -103,13 +121,13 @@ class NCModelIntentsInvalidArgsSpec:
     private def testOk(mdl: NCModel, intentId: String): Unit =
         val i = NCModelScanner.scan(mdl).find(_.intent.id == intentId).get
 
-        println(s"Test finished [modelClass=${mdl.getClass}, intent=$intentId, 
result=${i.function(INTENT_MATCH)}")
+        println(s"Test finished [modelClass=${mdl.getClass}, intent=$intentId, 
result=${i.function(INPUT)}")
 
     private def testRuntimeClassCast(mdl: NCModel, intentId: String): Unit =
         val i = NCModelScanner.scan(mdl).find(_.intent.id == intentId).get
 
         try
-            i.function(INTENT_MATCH)
+            i.function(INPUT)
 
             require(false)
         catch
@@ -139,6 +157,11 @@ class NCModelIntentsInvalidArgsSpec:
         testScanValidation(CHECKED_MDL_INVALID_LST)
         testScanValidation(CHECKED_MDL_INVALID_OPT)
 
+        // Missed or wrong 2 mandatory callback arguments.
+        testScanValidation(CHECKED_MDL_INVALID_ARG1)
+        testScanValidation(CHECKED_MDL_INVALID_ARG2)
+        testScanValidation(CHECKED_MDL_INVALID_ARG3)
+
         testOk(UNCHECKED_MDL, "validList")
         testOk(UNCHECKED_MDL, "validOpt")
         testRuntimeClassCast(UNCHECKED_MDL, "invalidList")
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
index 1b629e3c..0a0c2236 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
@@ -48,7 +48,7 @@ class NCModelIntentsInvalidIntentsSpec:
             new NCTestModelAdapter():
                 @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
                 @NCIntent("intent=validList2 term(list)~{# == 'x'}[0,10]")
-                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+                def x(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("list") 
list: List[NCEntity]): NCResult = null
         )
 
     /**
@@ -59,7 +59,7 @@ class NCModelIntentsInvalidIntentsSpec:
         testError(
             new NCTestModelAdapter():
                 @NCIntentRef("missed")
-                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+                def x(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("list") 
list: List[NCEntity]): NCResult = null
         )
 
     /**
@@ -70,7 +70,7 @@ class NCModelIntentsInvalidIntentsSpec:
         testError(
             new NCTestModelAdapter():
                 @NCIntentSample(Array("sample"))
-                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+                def x(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("list") 
list: List[NCEntity]): NCResult = null
         )
 
     /**
@@ -97,7 +97,7 @@ class NCModelIntentsInvalidIntentsSpec:
         testError(
             new NCTestModelAdapter():
                 @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
-                def x(@NCIntentTerm("list") e: NCEntity): NCResult = null
+                def x(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("list") 
e: NCEntity): NCResult = null
         )
 
     /**
@@ -109,7 +109,7 @@ class NCModelIntentsInvalidIntentsSpec:
             new NCTestModelAdapter():
                 @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
                 @NCIntentSample(Array("x", "x"))
-                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+                def x(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("list") 
list: List[NCEntity]): NCResult = null
         )
 
     /**
@@ -120,5 +120,5 @@ class NCModelIntentsInvalidIntentsSpec:
         NCModelScanner.scan(
             new NCTestModelAdapter():
                 @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
-                def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult = 
null
+                def x(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("list") 
list: List[NCEntity]): NCResult = null
         )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
index 3fe98a5e..f23136db 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
@@ -32,20 +32,22 @@ class NCModelIntentsNestedSpec:
             @NCIntentObject
             val nested2: Object = new Object():
                 @NCIntent("intent=intent3 term(x)~{true}")
-                def intent1(@NCIntentTerm("x") x: NCEntity) = NCResult()
+                def intent1(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("x") x: NCEntity) = NCTestResult()
 
             @NCIntent("intent=intent2 term(x)~{true}")
-            def intent1(@NCIntentTerm("x") x: NCEntity) = NCResult()
+            def intent1(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("x") 
x: NCEntity) = NCTestResult()
 
         @NCIntent("intent=intent1 term(x)~{true}")
-        def intent1(@NCIntentTerm("x") x: NCEntity) = NCResult()
+        def intent1(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("x") x: 
NCEntity) = NCTestResult()
 
         @NCIntent("import('scan/idl.idl')")
         def intent4(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: List[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = NCResult()
+        ): NCResult = NCTestResult()
 
     private val MDL_VALID2: NCModel = new NCTestModelAdapter:
         @NCIntent("import('scan/idl.idl')")
@@ -56,20 +58,22 @@ class NCModelIntentsNestedSpec:
             @NCIntentObject
             val nested2 = new RefClass():
                 @NCIntent("intent=intent3 term(x)~{true}")
-                def intent1(@NCIntentTerm("x") x: NCEntity) = NCResult()
+                def intent1(ctx: NCContext, im: NCIntentMatch, 
@NCIntentTerm("x") x: NCEntity) = NCTestResult()
 
             @NCIntent("intent=intent2 term(x)~{true}")
-            def intent1(@NCIntentTerm("x") x: NCEntity) = NCResult()
+            def intent1(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("x") 
x: NCEntity) = NCTestResult()
 
         @NCIntent("intent=intent1 term(x)~{true}")
-        def intent1(@NCIntentTerm("x") x: NCEntity) = NCResult()
+        def intent1(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("x") x: 
NCEntity) = NCTestResult()
 
         @NCIntentRef("impIntId") // Reference via nested2 (RefClass)
         def intent4(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: List[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = NCResult()
+        ): NCResult = NCTestResult()
 
     private val MDL_INVALID: NCModel = new NCTestModelAdapter :
         @NCIntentObject
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
index 485b8942..b2f487f4 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCTestModelScala.scala
@@ -28,36 +28,44 @@ object NCTestModelScala:
         @NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
         @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
         def intent(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: Seq[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = NCResult()
+        ): NCResult = NCTestResult()
 
         @NCIntent("import('scan/idl.idl')")
         @NCIntentSampleRef("scan/samples.txt")
         def intentImport(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: List[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = NCResult()
+        ): NCResult = NCTestResult()
 
     @NCIntent("import('scan/idl.idl')")
     class NCTestModelScalaClass extends NCTestModelAdapter :
         @NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
         @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
         def intent(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: Seq[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ) = NCResult()
+        ) = NCTestResult()
 
         @NCIntentRef("impIntId")
         @NCIntentSampleRef("scan/samples.txt")
         def intentImport(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: List[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ) = NCResult()
+        ) = NCTestResult()
 
     /**
       *
@@ -67,15 +75,19 @@ object NCTestModelScala:
         @NCIntent("intent=locInt term(single)~{# == 'id1'} term(list)~{# == 
'id2'}[0,10] term(opt)~{# == 'id3'}?")
         @NCIntentSample(Array("What are the least performing categories for 
the last quarter?"))
         def intent(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: Seq[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = NCResult()
+        ): NCResult = NCTestResult()
 
         @NCIntent("import('scan/idl.idl')")
         @NCIntentSampleRef("scan/samples.txt")
         def intentImport(
+            ctx: NCContext,
+            im: NCIntentMatch,
             @NCIntentTerm("single") single: NCEntity,
             @NCIntentTerm("list") list: List[NCEntity],
             @NCIntentTerm("opt") opt: Option[NCEntity]
-        ): NCResult = NCResult()
+        ): NCResult = NCTestResult()
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityEnricherSpec.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityEnricherSpec.scala
index ed26c3e7..f9333c93 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityEnricherSpec.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityEnricherSpec.scala
@@ -34,7 +34,7 @@ class NCEntityEnricherSpec:
     private def test0(pipeline: NCPipeline, ok: Boolean): Unit =
         val mdl: NCModel = new NCModelAdapter(new NCModelConfig("test.id", 
"Test model", "1.0"), pipeline):
             @NCIntent("intent=i term(any)={meta_ent('k1') == 'v1'}")
-            def onMatch(): NCResult = NCResult("OK", NCResultType.ASK_RESULT)
+            def onMatch(ctx: NCContext, im: NCIntentMatch): NCResult = 
NCResult("OK", NCResultType.ASK_RESULT)
 
         NCTestUtils.askSomething(mdl, ok)
 
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityMapperSpec.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityMapperSpec.scala
index 85a5d465..018ed8f1 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityMapperSpec.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCEntityMapperSpec.scala
@@ -68,7 +68,7 @@ class NCEntityMapperSpec:
             pl
 
         @NCIntent("intent=abcd term(abcd)={# == 'abcd'}")
-        def onMatch(@NCIntentTerm("abcd") abcd: NCEntity): NCResult = 
NCResult("OK", NCResultType.ASK_RESULT)
+        def onMatch(ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("abcd") 
abcd: NCEntity): NCResult = NCResult("OK", NCResultType.ASK_RESULT)
 
     @Test
     def test(): Unit = Using.resource(new NCModelClient(mdl)) { client =>
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCTokenEnricherSpec.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCTokenEnricherSpec.scala
index 04f652a6..b5facb78 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCTokenEnricherSpec.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCTokenEnricherSpec.scala
@@ -34,7 +34,7 @@ class NCTokenEnricherSpec:
     private def test0(pipeline: NCPipeline, ok: Boolean): Unit =
         val mdl: NCModel = new NCModelAdapter(new NCModelConfig("test.id", 
"Test model", "1.0"), pipeline):
             @NCIntent("intent=i term(any)={meta_ent('nlp:token:k1') == 'v1'}")
-            def onMatch(): NCResult = NCResult("OK", NCResultType.ASK_RESULT)
+            def onMatch(ctx: NCContext, im: NCIntentMatch): NCResult = 
NCResult("OK", NCResultType.ASK_RESULT)
 
         NCTestUtils.askSomething(mdl, ok)
 
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCVariantFilterSpec.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCVariantFilterSpec.scala
index 586cb09b..cdd3e994 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCVariantFilterSpec.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/NCVariantFilterSpec.scala
@@ -35,7 +35,7 @@ class NCVariantFilterSpec:
     private def test0(pipeline: NCPipeline, ok: Boolean): Unit =
         val mdl: NCModel = new NCModelAdapter(new NCModelConfig("test.id", 
"Test model", "1.0"), pipeline):
             @NCIntent("intent=i term(any)={true}")
-            def onMatch(): NCResult = NCResult("OK", NCResultType.ASK_RESULT)
+            def onMatch(ctx: NCContext, im: NCIntentMatch): NCResult = 
NCResult("OK", NCResultType.ASK_RESULT)
 
         NCTestUtils.askSomething(mdl, ok)
 
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCResult.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestResult.scala
similarity index 58%
copy from nlpcraft/src/main/scala/org/apache/nlpcraft/NCResult.scala
copy to nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestResult.scala
index 83826c4a..7ff8e907 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/NCResult.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/nlp/util/NCTestResult.scala
@@ -15,22 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.nlpcraft
+package org.apache.nlpcraft.nlp.util
 
-import org.apache.nlpcraft.NCResultType.*
+import org.apache.nlpcraft.{NCResult, NCResultType}
 
 /**
   *
   */
-object NCResult:
-    def apply(): NCResult = new NCResult(body = null, resultType = null, 
intentId = null)
-    def apply(body: Any, resultType: NCResultType): NCResult = new 
NCResult(body = body, resultType = resultType, intentId = null)
-    def apply(body: Any, resultType: NCResultType, intentId: String): NCResult 
= new NCResult(body = body, resultType =resultType, intentId)
+object  NCTestResult {
+    def apply(): NCTestResult = new NCTestResult()
+}
 
 /**
   *
-  * @param body
-  * @param resultType
-  * @param intentId
   */
-case class NCResult(body: Any, resultType: NCResultType, intentId: String)
+class NCTestResult extends NCResult("test", NCResultType.ASK_RESULT, null)

Reply via email to