This is an automated email from the ASF dual-hosted git repository.
sergeykamov pushed a commit to branch NLPCRAFT-491
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git
The following commit(s) were added to refs/heads/NLPCRAFT-491 by this push:
new 96ef59e WIP.
96ef59e is described below
commit 96ef59e88dbeefb0313b83fa1de56eea089e96f4
Author: Sergey Kamov <[email protected]>
AuthorDate: Sat Apr 2 12:21:21 2022 +0300
WIP.
---
.../org/apache/nlpcraft/examples/order/Order.scala | 44 ++++--
.../nlpcraft/examples/order/OrderModel.scala | 151 +++++++++++++--------
.../order/src/main/resources/order_model.yaml | 6 +
.../nlpcraft/examples/order/OrderModelCli.scala | 34 +++++
.../nlpcraft/examples/order/OrderModelSpec.scala | 2 +-
5 files changed, 172 insertions(+), 65 deletions(-)
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
index e9d4aa9..2611ea6 100644
---
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
@@ -23,8 +23,14 @@ private enum PizzaSize:
case SMALL, MEDIUM, LARGE
object Order:
- def getPizzaSizeKinds: String =
PizzaSize.values.map(_.toString.toLowerCase).mkString(", ")
- def pizza2Str(name: String, size: PizzaSize): String = s"$name
${size.toString.toLowerCase} size"
+ private val allPizzaSizeKinds: String =
withComma(PizzaSize.values.map(_.toString.toLowerCase))
+
+ private def withComma(iter: Iterable[String]): String = iter.mkString(", ")
+ private def pizza2Str(name: String, size: PizzaSize): String =
+ if size != null then s"$name ${size.toString.toLowerCase} size" else
name
+ private def seq2Str[T](name: String, seq: Seq[T], toStr: T => String = (t:
T) => t.toString): String =
+ if seq.nonEmpty then s"$name: ${withComma(seq.map(toStr))}." else ""
+ private def norm(s: String) = s.trim.replaceAll("(?m)^[ \t]*\r?\n", "")
import Order.*
@@ -59,19 +65,37 @@ class Order:
"Order is empty. Please order some pizza or drinks."
else
pizza.find { (_, size) => size == null } match
- case Some((name, _)) => s"Please specify $name size? It can be
$getPizzaSizeKinds"
+ case Some((name, _)) => s"Please specify $name size? It can be
$allPizzaSizeKinds"
case None => throw new AssertionError("Invalid state")
def ask2Confirm(): String =
require(isValid())
- s"Let me specify your order.\n${this.toString()}\nIs it correct?"
-
+ norm(
+ s"""
+ |Let me specify your order.
+ |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
+ |${seq2Str("Drinks", drinks.toSeq)}
+ |Is it correct?
+ """.stripMargin
+ )
+
+ def getState(): String =
+ norm(
+ s"""
+ |Current order state: '${if inProgress() then "in progress"
else "empty"}'.
+ |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
+ |${seq2Str("Drinks", drinks.toSeq)}
+ """.stripMargin
+ )
+
override def toString(): String =
- val ps = if pizza.nonEmpty then s"Pizza: ${pizza.map { (name, sz) =>
pizza2Str(name, sz) }.mkString(", ")}. " else ""
- val ds = if drinks.nonEmpty then s"Drinks: ${drinks.mkString(", ")}. "
else ""
-
- s"$ps$ds"
+ norm(
+ s"""
+ |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
+ |${seq2Str("Drinks", drinks.toSeq)}
+ """.stripMargin
+ ).replaceAll("\n", " ")
def clear(): Unit =
pizza.clear()
- drinks.clear()
\ No newline at end of file
+ drinks.clear()
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
index 48253a6..28acfad 100644
---
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
@@ -17,73 +17,118 @@
package org.apache.nlpcraft.examples.order
+import com.typesafe.scalalogging.LazyLogging
import org.apache.nlpcraft.*
import org.apache.nlpcraft.internal.util.NCResourceReader
import org.apache.nlpcraft.nlp.*
-import org.apache.nlpcraft.nlp.entity.parser.semantic.NCSemanticEntityParser
import org.apache.nlpcraft.nlp.entity.parser.*
-import org.apache.nlpcraft.nlp.token.enricher.NCEnStopWordsTokenEnricher
-import org.apache.nlpcraft.nlp.token.parser.NCOpenNLPTokenParser
+
import scala.collection.mutable
import org.apache.nlpcraft.NCResultType.*
+
import scala.jdk.CollectionConverters.*
+object OrderModel extends LazyLogging:
+ private def extractPizzaKind(e: NCEntity): String =
e.get[String]("ord:pizza:kind:value")
+ private def extractPizzaSize(e: NCEntity): PizzaSize =
PizzaSize.valueOf(e.get[String]("ord:pizza:size:value").toUpperCase)
+ private def extractDrink(e: NCEntity): String =
e.get[String]("ord:drink:value")
+ private def isStopWord(t: NCToken): Boolean = t.get[Boolean]("stopword")
+ private def confirmOrSpecify(ord: Order): NCResult =
+ NCResult(if ord.isValid() then ord.ask2Confirm() else
ord.ask2Specify(), ASK_DIALOG)
+ private def continue(ord: Order): NCResult =
+ NCResult(if ord.isValid() then s"OK, please continue your order: $ord"
else ord.ask2Specify(), ASK_DIALOG)
+
+ private def log(o: Order): Unit = logger.info(o.getState())
+ private def onStart(im: NCIntentMatch, o: Order): Unit =
+ logger.info(s"Initial state before request:
${im.getContext.getRequest.getText}")
+ private def onFinish(o: Order): Unit =
+ logger.info(s"Result state")
+ logger.info(o.getState())
+
+ private def withRelations(
+ ents: Seq[NCEntity], relations: Seq[NCEntity], skip: Seq[NCEntity],
allToks: Seq[NCToken]
+ ): Map[NCEntity, NCEntity] =
+ case class IdxHolder(from: Int, to: Int)
+
+ def getIndexes(e: NCEntity): IdxHolder =
+ val toks = e.getTokens.asScala
+ IdxHolder(toks.head.getIndex, toks.last.getIndex)
+
+ val skipIdxs = skip.flatMap(_.getTokens.asScala.map(_.getIndex)).toSet
+ val used = mutable.ArrayBuffer.empty[NCEntity]
+
+ def areNeighbours(i1: Int, i2: Int): Boolean =
+ if i2 == i1 + 1 then true else Range(i1 + 1, i2).forall(i =>
isStopWord(allToks(i)) || skipIdxs.contains(i))
+
+ ents.map(e => {
+ val eIdxs = getIndexes(e)
+ val r = relations.filter(r => !used.contains(r)).find(r => {
+ val rIdxs = getIndexes(r)
+ areNeighbours(rIdxs.to, eIdxs.from) || areNeighbours(eIdxs.to,
rIdxs.from)
+ }).orNull
+
+ if r != null then used += r
+ e -> r
+ }).toMap
+
+import org.apache.nlpcraft.examples.order.OrderModel.*
/**
*
*/
-class OrderModel extends NCModelAdapter(
+class OrderModel extends NCModelAdapter (
new NCModelConfig("nlpcraft.order.ex", "Order Example Model", "1.0"),
new NCPipelineBuilder().withSemantic("en", "order_model.yaml").build()
-):
+) with LazyLogging:
private val ords = mutable.HashMap.empty[String, Order]
private def getOrder(im: NCIntentMatch): Order =
ords.getOrElseUpdate(im.getContext.getRequest.getUserId, new Order)
- private def extractPizzaKind(e: NCEntity): String =
e.get[String]("ord:pizza:kind:value")
- private def extractPizzaSize(e: NCEntity): PizzaSize =
PizzaSize.valueOf(e.get[String]("ord:pizza:size:value").toUpperCase)
- private def extractDrink(e: NCEntity): String =
e.get[String]("ord:drink:value")
-
- private def confirmOrSpecify(ord: Order): NCResult =
- NCResult(if ord.isValid() then ord.ask2Confirm() else
ord.ask2Specify(), ASK_DIALOG)
-
- private def getAvgPosition(e: NCEntity): Double =
- val toks = e.getTokens.asScala
- (toks.head.getIndex + toks.last.getIndex) / 2.0
-
@NCIntent("intent=confirm term(confirm)={has(ent_groups, 'confirm')}")
def onConfirm(im: NCIntentMatch, @NCIntentTerm("confirm") confirm:
NCEntity): NCResult =
val ord = getOrder(im)
+ onStart(im, ord)
val dlg = im.getContext.getConversation.getDialogFlow
def cancelAll(): NCResult =
ord.clear()
NCResult("Order canceled. We are ready for new orders.",
ASK_RESULT)
- // 'stop' command.
- if !dlg.isEmpty && dlg.get(dlg .size() - 1).getIntentMatch.getIntentId
== "ord:stop" then
- confirm.getId match
- case "ord:confirm:yes" => cancelAll()
- case "ord:confirm:no" => confirmOrSpecify(ord)
- case _ => throw new AssertionError()
- // 'confirm' command.
- else
- if !ord.inProgress() then throw new NCRejection("No orders in
progress.")
-
- confirm.getId match
- case "ord:confirm:yes" =>
- if ord.isValid() then
- println(s"Done: $ord")
- ord.clear()
- NCResult("Congratulations. Your order executed. You
can start make new orders.", ASK_RESULT)
- else
- NCResult(ord.ask2Specify(), ASK_DIALOG)
- case "ord:confirm:no" => cancelAll()
- case _ => throw new AssertionError()
+ val res=
+ // 'stop' command.
+ if !dlg.isEmpty && dlg.get(dlg.size() -
1).getIntentMatch.getIntentId == "ord:stop" then
+ confirm.getId match
+ case "ord:confirm:yes" => cancelAll()
+ case "ord:confirm:no" => confirmOrSpecify(ord)
+ case "ord:confirm:continue" => continue(ord)
+ case _ => throw new AssertionError()
+ // 'confirm' command.
+ else
+ if !ord.inProgress() then throw new NCRejection("No orders in
progress.")
+
+ confirm.getId match
+ case "ord:confirm:yes" =>
+ if ord.isValid() then
+ logger.info(s"ORDER EXECUTED: $ord")
+ ord.clear()
+ NCResult("Congratulations. Your order executed.
You can start make new orders.", ASK_RESULT)
+ else
+ NCResult(ord.ask2Specify(), ASK_DIALOG)
+ case "ord:confirm:no" => cancelAll()
+ case "ord:confirm:continue" => continue(ord)
+ case _ => throw new AssertionError()
+
+ onFinish(ord)
+ res
@NCIntent("intent=stop term(stop)={# == 'ord:stop'}")
def onStop(im: NCIntentMatch, @NCIntentTerm("stop") stop: NCEntity):
NCResult =
- if getOrder(im).inProgress() then NCResult("Are you sure that you
want to cancel current order?", ASK_DIALOG)
- else NCResult("Nothing to cancel", ASK_RESULT)
+ val ord = getOrder(im)
+ onStart(im, ord)
+ val res =
+ if ord.inProgress() then NCResult("Are you sure that you want
to cancel current order?", ASK_DIALOG)
+ else NCResult("Nothing to cancel", ASK_RESULT)
+ onFinish(ord)
+ res
@NCIntent(
"intent=order " +
@@ -102,25 +147,19 @@ class OrderModel extends NCModelAdapter(
if pizzaKinds.isEmpty && drinks.isEmpty then throw new
NCRejection("Please order some pizza or drinks")
if pizzaSizes.size > pizzaKinds.size then throw new NCRejection("Pizza
and their sizes cannot be recognized")
- case class Holder(entity: NCEntity, position: Double)
-
val ord = getOrder(im)
- val hsSizes = mutable.ArrayBuffer.empty ++ pizzaSizes.map(p =>
Holder(p, getAvgPosition(p)))
-
- // Pizza. Each pizza can be specified by its size. Or size will be
asked additionally.
- pizzaKinds.foreach(p => {
- hsSizes.size match
- case 0 => ord.addPizza(extractPizzaKind(p))
- case _ =>
- val avgPos = getAvgPosition(p)
- val nextNeighbour = hsSizes.minBy(p => Math.abs(avgPos -
p.position))
- ord.addPizza(extractPizzaKind(p),
extractPizzaSize(nextNeighbour.entity))
- hsSizes -= nextNeighbour
- })
+ onStart(im, ord)
+ val m = withRelations(pizzaKinds, pizzaSizes, Seq.empty,
im.getContext.getTokens.asScala.toSeq)
+
+ for ((p, sz) <- m)
+ val pz = extractPizzaKind(p)
+ if sz != null then ord.addPizza(pz, extractPizzaSize(sz)) else
ord.addPizza(pz)
for (p <- drinks.map(extractDrink)) ord.addDrink(p)
- confirmOrSpecify(ord)
+ val res = confirmOrSpecify(ord)
+ onFinish(ord)
+ res
@NCIntent(
"intent=specifyPizzaSize " +
@@ -137,10 +176,14 @@ class OrderModel extends NCModelAdapter(
val ord = getOrder(im)
require(!ord.isValid())
+ onStart(im, ord)
+
val sz = extractPizzaSize(size)
pizzaOpt match
case Some(pizza) => ord.addPizza(extractPizzaKind(pizza), sz)
case None => if !ord.specifyPizzaSize(sz) then throw new
NCRejection("What specified?")
- confirmOrSpecify(ord)
\ No newline at end of file
+ val res = confirmOrSpecify(ord)
+ onFinish(ord)
+ res
\ No newline at end of file
diff --git a/nlpcraft-examples/order/src/main/resources/order_model.yaml
b/nlpcraft-examples/order/src/main/resources/order_model.yaml
index 660601a..b7c4c30 100644
--- a/nlpcraft-examples/order/src/main/resources/order_model.yaml
+++ b/nlpcraft-examples/order/src/main/resources/order_model.yaml
@@ -59,6 +59,12 @@ elements:
- "{no|nope|incorrect|wrong}"
- "{you are|*} {not|are not|aren't } {correct|right}"
+ - id: "ord:confirm:continue"
+ description: "Conformation (continue)."
+ groups: ["confirm"]
+ synonyms:
+ - "{continue}"
+
- id: "ord:stop"
description: "Stop and cancel all."
synonyms:
diff --git
a/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelCli.scala
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelCli.scala
new file mode 100644
index 0000000..344bc85
--- /dev/null
+++
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelCli.scala
@@ -0,0 +1,34 @@
+package org.apache.nlpcraft.examples.order
+
+import com.typesafe.scalalogging.LazyLogging
+import org.apache.nlpcraft.examples.order.OrderModelCli.logger
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.NCResultType.*
+
+import scala.util.Using
+
+object OrderModelCli extends App with LazyLogging :
+ main()
+
+ private def main(): Unit = Using.resource(new NCModelClient(new
OrderModel)) { client =>
+ logger.info("Application started.")
+
+ var resp: NCResult = null
+
+ while (true)
+ val prompt =
+ if resp == null || resp.getType == ASK_RESULT then "Ask your
question to the model: "
+ else "Your should answer on the model's question: "
+ logger.info(s">>>>>>>>>> $prompt")
+
+ try
+ resp = client.ask(scala.io.StdIn.readLine(), null, "userId")
+
+ logger.info(s">>>>>>>>>> Response type: ${resp.getType}")
+ logger.info(s">>>>>>>>>> Response body:\n${resp.getBody}")
+ catch
+ case e: NCRejection => logger.info(s"Request rejected:
${e.getMessage}")
+ case e: Throwable => logger.error(s"Unexpected error:
${e.getMessage}")
+ logger.error("Application exit.")
+ System.exit(-1)
+ }
diff --git
a/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
index e8bb51a..7f43742 100644
---
a/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
+++
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
@@ -38,7 +38,7 @@ class OrderModelSpec:
val resp = client.ask(txt, null, "userId")
buf += s">> $txt"
- buf += s">> ${resp.getBody} (${resp.getType})"
+ buf += s">> '${resp.getType}': ${resp.getBody}"
buf += ""
if expResType != resp.getType then