blob: 084da88a049b7f6081f47226c0c74446008734cf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nlpcraft.model.intent.solver
import com.typesafe.scalalogging.LazyLogging
import io.opencensus.trace.Span
import org.apache.nlpcraft.common._
import org.apache.nlpcraft.common.debug.NCLogHolder
import org.apache.nlpcraft.common.opencensus.NCOpenCensusTrace
import org.apache.nlpcraft.common.util.NCUtils
import org.apache.nlpcraft.model.impl.NCVariantImpl
import org.apache.nlpcraft.model.intent.NCIdlIntent
import org.apache.nlpcraft.model.{NCContext, NCIntentMatch, NCIntentSkip, NCModel, NCRejection, NCResult, NCToken, NCVariant}
import org.apache.nlpcraft.probe.mgrs.dialogflow.NCDialogFlowManager
import scala.collection.JavaConverters._
/**
* Front-end for intent solver.
*/
class NCIntentSolver(intents: List[(NCIdlIntent/*Intent*/, NCIntentMatchNCResult/*Callback*/)])
extends LazyLogging with NCOpenCensusTrace {
class RedoSolver extends RuntimeException
/**
*
* @param in
* @param span
* @return
*/
def solve(in: NCIntentSolverInput, span: Span): NCResult = {
var doIt = true
var res: NCResult = null
while (doIt)
try {
res = solve0(in, span)
doIt = false
}
catch {
case _: RedoSolver()
}
res
}
/**
*
* @param in Intent solver input.
* @param span Parent span.
* @return
* @throws NCRejection
*/
def solve0(in: NCIntentSolverInput, span: Span): NCResult = {
if (intents.isEmpty)
// Should it be an assertion?
throw new NCRejection("Intent solver has no registered intents.")
val ctx = in.context
val req = ctx.getRequest
val meta = ctx.getModel.getMetadata
val logHldr = meta synchronized {
meta.get(NCUtils.mkLogHolderKey(req.getServerRequestId)).asInstanceOf[NCLogHolder]
}
val results = try NCIntentSolverEngine.solve(ctx, intents, logHldr) catch {
case e: Exceptionthrow new NCRejection("Processing failed due to unexpected error.", e)
}
if (results.isEmpty)
throw new NCRejection("No matching intent found.")
var i = -1
for (res ← results if res != null) {
try {
i += 1
val intentMatch: NCIntentMatch = new NCIntentMatch() {
override val getContext: NCContext =
ctx
override val getIntentTokens: java.util.List[java.util.List[NCToken]] =
res.groups.map(_.tokens.asJava).asJava
override val getVariant: NCVariant =
new NCVariantImpl(res.variant.tokens)
override val isAmbiguous: Boolean =
!res.isExactMatch
override val getIntentId: String =
res.intentId
override def getTermTokens(idx: Int): java.util.List[NCToken] =
res.groups(idx).tokens.asJava
override def getTermTokens(termId: String): java.util.List[NCToken] =
res.groups.find(_.termId === termId).flatMap(grp ⇒ Some(grp.tokens)).getOrElse(Nil).asJava
}
if (!in.context.getModel.asInstanceOf[NCModel].onMatchedIntent(intentMatch)) {
logger.info(
s"Model '${ctx.getModel.getId}' triggered rematching of intents " +
s"by intent '${res.intentId}' on variant #${res.variantIdx + 1}."
)
throw new RedoSolver
}
var cbRes: NCResult = null
startScopedSpan("intentCallback", span) { _ ⇒
cbRes = res.fn.apply(intentMatch)
}
// Store winning intent match in the input.
in.intentMatch = intentMatch
// Don't override if user already set it.
if (cbRes.getTokens == null)
cbRes.setTokens(res.groups.flatMap(_.tokens).asJava)
if (cbRes.getIntentId == null)
cbRes.setIntentId(res.intentId)
logger.info(s"Intent '${res.intentId}' for variant #${res.variantIdx + 1} selected as the ${g(bo("'best match'"))}.")
NCDialogFlowManager.addMatchedIntent(
intentMatch,
res,
ctx,
span
)
if (logHldr != null)
logHldr.setMatchedIntentIndex(i)
return cbRes
}
catch {
case e: NCIntentSkip
// No-op - just skipping this result.
e.getMessage match {
case s if s != null ⇒ logger.info(s"Selected intent '${res.intentId}' skipped: $s")
case _ ⇒ logger.info(s"Selected intent '${res.intentId}' skipped.")
}
}
}
throw new NCRejection("No matching intent found - all intents were skipped.")
}
}