in nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/matcher/NCIntentSolverManager.scala [345:498]
private def logMatches(matches: ArrayBuffer[MatchHolder]): Unit =
if matches.nonEmpty then
val tbl = NCAsciiTable("Variant", "Intent", "Term Entities", "Intent Match Weight")
for (m <- matches)
val im = m.intentMatch
val w = im.weight
val ents = mutable.ListBuffer.empty[String]
ents += s"intent=${im.intent.id}"
var grpIdx = 0
for (grp <- im.entityGroups)
ents += s" ${grp.term.toString}"
grpIdx += 1
if grp.usedEntities.nonEmpty then
var entIdx = 0
for (e <- grp.usedEntities)
val conv = if e.conv then "(conv) " else ""
ents += s" #$entIdx: $conv${e.entity.getType}(${e.entity.mkText})"
entIdx += 1
else
ents += " <empty>"
if m == matches.head then
tbl += (
Seq(s"#${m.variantIdx + 1}", "<|best match|>"), Seq(im.intent.id, "<|best match|>"), ents, w
)
else
tbl += (
s"#${m.variantIdx + 1}", im.intent.id, ents, w
)
tbl.info(
logger,
s"Found ${matches.size} matching ${if matches.size > 1 then "intents"else "intent"} (sorted best to worst):".?
)
else
logger.info(s"No matching intent found:")
logger.info(s" +-- Turn on DEBUG log level to see more details.")
/**
*
* @param m1
* @param m2
*/
private def logEqualMatches(m1: MatchHolder, m2: MatchHolder): Unit =
val mw1 = m1.intentMatch.weight
val mw2 = m2.intentMatch.weight
val v1 = m1.variant
val v2 = m2.variant
val tbl = new NCAsciiTable()
tbl += (s"${"Intent ID"}", m1.intentMatch.intent.id, m2.intentMatch.intent.id)
tbl += (s"${"Variant #"}", m1.variantIdx + 1, m2.variantIdx + 1)
tbl += (s"${"Intent Match Weight"}", mw1.toString, mw2.toString)
tbl += (s"${"Variant Weight"}", v1.toString, v2.toString)
tbl.warn(logger, "Two matching intents have the same weight for their matches (variants weight will be used further):".?)
/**
*
* @param intent
* @param senEnts
* @param convEnts
*/
private def solveIntent(
ctx: NCContext, intent: NCIDLIntent, senEnts: Seq[IntentEntity], convEnts: Seq[IntentEntity], varIdx: Int
): Option[IntentMatchHolder] =
val intentId = intent.id
val opts = intent.options
val flow = dialog.getDialogFlow(ctx.getRequest.getUserId)
val varStr = s"(variant #${varIdx + 1})"
// Check dialog flow regex first, if any.
val flowMatched: Boolean =
intent.flowRegex match
case Some(regex) =>
val flowStr = flow.map(_.getIntentMatch.getIntentId).mkString(" ")
def process(matched: Boolean): Boolean =
val s = if matched then "matched" else "did not match"
logger.info(s"Intent '$intentId' $s regex dialog flow $varStr:")
logger.info(s" |-- ${"Intent IDs :"} $flowStr")
logger.info(s" +-- ${"Match regex :"} ${regex.toString}")
matched
process(regex.matcher(flowStr).find(0))
case None => true
if flowMatched then
val intentW = new Weight()
val intentGrps = mutable.ArrayBuffer.empty[TermEntitiesGroup]
var abort = false
var lastTermMatch: TermMatch = null
val sess = ctx.getConversation.getData // Conversation metadata (shared across all terms).
val convMeta = sess.keysSet.map(k => k -> sess(k).asInstanceOf[Object]).toMap
val ents = senEnts.map(_.entity)
// Check terms.
for (term <- intent.terms if !abort)
// Fresh context for each term.
val idlCtx = NCIDLContext(
ctx.getModelConfig,
ents,
intentMeta = intent.meta,
convMeta = convMeta,
req = ctx.getRequest,
vars = mutable.HashMap.empty[String, NCIDLFunction] ++ term.decls
)
solveTerm(term, idlCtx, senEnts, if term.conv then convEnts else Seq.empty) match
case Some(termMatch) =>
if opts.ordered && lastTermMatch != null && !termMatch.after(lastTermMatch) then
abort = true
else
// Term is found.
// Add its weight and grab its entities.
intentW += termMatch.weight
intentGrps += TermEntitiesGroup(term, termMatch.usedEntities)
lastTermMatch = termMatch
logMatch(intent, term, termMatch)
case None =>
// Term is missing. Stop further processing for this intent. This intent cannot be matched.
logger.debug(s"Intent '$intentId' did not match because of unmatched term '$term' $varStr.")
abort = true
if abort then
None
else
val usedSenEnts = senEnts.filter(_.used)
val usedConvEnts = convEnts.filter(_.used)
val usedToks = usedSenEnts.flatMap(_.entity.getTokens)
val unusedToks = ctx.getTokens.filter(p => !usedToks.contains(p))
if !opts.allowStmEntityOnly && usedSenEnts.isEmpty && usedConvEnts.nonEmpty then
logger.info(
s"""
|Intent '$intentId' did not match because all its matched tokens came from STM $varStr.
|See intent 'allowStmEntityOnly' option.
|""".stripMargin
)
None
else if !opts.ignoreUnusedFreeWords && unusedToks.nonEmpty then
logger.info(
s"""
|Intent '$intentId' did not match because of unused free words $varStr.
|See intent 'ignoreUnusedFreeWords' option.
|Unused free words indexes: ${unusedToks.map(_.getIndex).mkString("{", ",", "}")}