mirror of
https://github.com/leon-ai/leon.git
synced 2024-12-03 02:45:21 +03:00
refactor(server): default NLU result
This commit is contained in:
parent
a8e3fb8b87
commit
a215fc8770
@ -1,17 +1,3 @@
|
|||||||
/**
|
|
||||||
* TODO next:
|
|
||||||
* 1. [OK] Fix brain.ts TS errors
|
|
||||||
* 2. [OK] Refactor brain.ts; split "execute" into smaller functions:
|
|
||||||
* // [OK] handle this scope into its own method:
|
|
||||||
// - handleLogicActionSkillProcessOutput
|
|
||||||
// - handleLogicActionSkillProcessError
|
|
||||||
// - handleLogicActionSkillProcessClose
|
|
||||||
* 3. Fix nlu.ts TS errors
|
|
||||||
* 4. Refactor nlu.ts; split into smaller functions
|
|
||||||
* 5. Restore multi client support on HTTP server / socket server
|
|
||||||
* 6. Publish to "develop" (or just fix TS errors only and publish first, then refactor)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'
|
import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'
|
||||||
|
@ -5,6 +5,7 @@ import { spawn } from 'node:child_process'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import kill from 'tree-kill'
|
import kill from 'tree-kill'
|
||||||
|
|
||||||
|
import type { NLUResult } from '@/core/nlp/types'
|
||||||
import { langs } from '@@/core/langs.json'
|
import { langs } from '@@/core/langs.json'
|
||||||
import { version } from '@@/package.json'
|
import { version } from '@@/package.json'
|
||||||
import { HAS_LOGGER, IS_TESTING_ENV, TCP_SERVER_BIN_PATH } from '@/constants'
|
import { HAS_LOGGER, IS_TESTING_ENV, TCP_SERVER_BIN_PATH } from '@/constants'
|
||||||
@ -14,35 +15,30 @@ import { StringHelper } from '@/helpers/string-helper'
|
|||||||
import { LangHelper } from '@/helpers/lang-helper'
|
import { LangHelper } from '@/helpers/lang-helper'
|
||||||
import Conversation from '@/core/conversation'
|
import Conversation from '@/core/conversation'
|
||||||
|
|
||||||
const defaultNluResultObj = {
|
const DEFAULT_NLU_RESULT = {
|
||||||
utterance: null,
|
utterance: '',
|
||||||
currentEntities: [],
|
currentEntities: [],
|
||||||
entities: [],
|
entities: [],
|
||||||
currentResolvers: [],
|
currentResolvers: [],
|
||||||
resolvers: [],
|
resolvers: [],
|
||||||
slots: null,
|
slots: {},
|
||||||
configDataFilePath: null,
|
configDataFilePath: '',
|
||||||
answers: [], // For dialog action type
|
answers: [], // For dialog action type
|
||||||
classification: {
|
classification: {
|
||||||
domain: null,
|
domain: '',
|
||||||
skill: null,
|
skill: '',
|
||||||
action: null,
|
action: '',
|
||||||
confidence: 0
|
confidence: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NLU {
|
export default class NLU {
|
||||||
private static instance: NLU
|
private static instance: NLU
|
||||||
|
private conversation = new Conversation('conv0')
|
||||||
|
private nluResult: NLUResult = DEFAULT_NLU_RESULT
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!NLU.instance) {
|
if (!NLU.instance) {
|
||||||
this.globalResolversNlp = {}
|
|
||||||
this.skillsResolversNlp = {}
|
|
||||||
this.mainNlp = {}
|
|
||||||
this.ner = {}
|
|
||||||
this.conv = new Conversation('conv0')
|
|
||||||
this.nluResult = defaultNluResultObj // TODO
|
|
||||||
|
|
||||||
LogHelper.title('NLU')
|
LogHelper.title('NLU')
|
||||||
LogHelper.success('New instance')
|
LogHelper.success('New instance')
|
||||||
|
|
||||||
@ -118,7 +114,7 @@ export default class NLU {
|
|||||||
* Handle in action loop logic before NLU processing
|
* Handle in action loop logic before NLU processing
|
||||||
*/
|
*/
|
||||||
async handleActionLoop(utterance) {
|
async handleActionLoop(utterance) {
|
||||||
const { domain, intent } = this.conv.activeContext
|
const { domain, intent } = this.conversation.activeContext
|
||||||
const [skillName, actionName] = intent.split('.')
|
const [skillName, actionName] = intent.split('.')
|
||||||
const configDataFilePath = join(
|
const configDataFilePath = join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
@ -128,8 +124,8 @@ export default class NLU {
|
|||||||
`config/${BRAIN.lang}.json`
|
`config/${BRAIN.lang}.json`
|
||||||
)
|
)
|
||||||
this.nluResult = {
|
this.nluResult = {
|
||||||
...defaultNluResultObj, // Reset entities, slots, etc.
|
...DEFAULT_NLU_RESULT, // Reset entities, slots, etc.
|
||||||
slots: this.conv.activeContext.slots,
|
slots: this.conversation.activeContext.slots,
|
||||||
utterance,
|
utterance,
|
||||||
configDataFilePath,
|
configDataFilePath,
|
||||||
classification: {
|
classification: {
|
||||||
@ -209,7 +205,7 @@ export default class NLU {
|
|||||||
// Ensure expected items are in the utterance, otherwise clean context and reprocess
|
// Ensure expected items are in the utterance, otherwise clean context and reprocess
|
||||||
if (!hasMatchingEntity && !hasMatchingResolver) {
|
if (!hasMatchingEntity && !hasMatchingResolver) {
|
||||||
BRAIN.talk(`${BRAIN.wernicke('random_context_out_of_topic')}.`)
|
BRAIN.talk(`${BRAIN.wernicke('random_context_out_of_topic')}.`)
|
||||||
this.conv.cleanActiveContext()
|
this.conversation.cleanActiveContext()
|
||||||
await this.process(utterance)
|
await this.process(utterance)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -218,9 +214,9 @@ export default class NLU {
|
|||||||
const processedData = await BRAIN.execute(this.nluResult)
|
const processedData = await BRAIN.execute(this.nluResult)
|
||||||
// Reprocess with the original utterance that triggered the context at first
|
// Reprocess with the original utterance that triggered the context at first
|
||||||
if (processedData.core?.restart === true) {
|
if (processedData.core?.restart === true) {
|
||||||
const { originalUtterance } = this.conv.activeContext
|
const { originalUtterance } = this.conversation.activeContext
|
||||||
|
|
||||||
this.conv.cleanActiveContext()
|
this.conversation.cleanActiveContext()
|
||||||
await this.process(originalUtterance)
|
await this.process(originalUtterance)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -233,15 +229,15 @@ export default class NLU {
|
|||||||
!processedData.action.next_action &&
|
!processedData.action.next_action &&
|
||||||
processedData.core?.isInActionLoop === false
|
processedData.core?.isInActionLoop === false
|
||||||
) {
|
) {
|
||||||
this.conv.cleanActiveContext()
|
this.conversation.cleanActiveContext()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Break the action loop and prepare for the next action if necessary
|
// Break the action loop and prepare for the next action if necessary
|
||||||
if (processedData.core?.isInActionLoop === false) {
|
if (processedData.core?.isInActionLoop === false) {
|
||||||
this.conv.activeContext.isInActionLoop = !!processedData.action.loop
|
this.conversation.activeContext.isInActionLoop = !!processedData.action.loop
|
||||||
this.conv.activeContext.actionName = processedData.action.next_action
|
this.conversation.activeContext.actionName = processedData.action.next_action
|
||||||
this.conv.activeContext.intent = `${processedData.classification.skill}.${processedData.action.next_action}`
|
this.conversation.activeContext.intent = `${processedData.classification.skill}.${processedData.action.next_action}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return processedData
|
return processedData
|
||||||
@ -268,7 +264,7 @@ export default class NLU {
|
|||||||
if (processedData && Object.keys(processedData).length > 0) {
|
if (processedData && Object.keys(processedData).length > 0) {
|
||||||
// Set new context with the next action if there is one
|
// Set new context with the next action if there is one
|
||||||
if (processedData.action.next_action) {
|
if (processedData.action.next_action) {
|
||||||
this.conv.activeContext = {
|
this.conversation.activeContext = {
|
||||||
lang: BRAIN.lang,
|
lang: BRAIN.lang,
|
||||||
slots: processedData.slots,
|
slots: processedData.slots,
|
||||||
isInActionLoop: !!processedData.nextAction.loop,
|
isInActionLoop: !!processedData.nextAction.loop,
|
||||||
@ -313,14 +309,14 @@ export default class NLU {
|
|||||||
await this.mergeSpacyEntities(utterance)
|
await this.mergeSpacyEntities(utterance)
|
||||||
|
|
||||||
// Pre NLU processing according to the active context if there is one
|
// Pre NLU processing according to the active context if there is one
|
||||||
if (this.conv.hasActiveContext()) {
|
if (this.conversation.hasActiveContext()) {
|
||||||
// When the active context is in an action loop, then directly trigger the action
|
// When the active context is in an action loop, then directly trigger the action
|
||||||
if (this.conv.activeContext.isInActionLoop) {
|
if (this.conversation.activeContext.isInActionLoop) {
|
||||||
return resolve(await this.handleActionLoop(utterance))
|
return resolve(await this.handleActionLoop(utterance))
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the active context has slots filled
|
// When the active context has slots filled
|
||||||
if (Object.keys(this.conv.activeContext.slots).length > 0) {
|
if (Object.keys(this.conversation.activeContext.slots).length > 0) {
|
||||||
try {
|
try {
|
||||||
return resolve(await this.handleSlotFilling(utterance))
|
return resolve(await this.handleSlotFilling(utterance))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -339,13 +335,13 @@ export default class NLU {
|
|||||||
* If there are several "delete it" across skills, Leon needs to make use of
|
* If there are several "delete it" across skills, Leon needs to make use of
|
||||||
* the current context ({domain}.{skill}) to define the most accurate classification
|
* the current context ({domain}.{skill}) to define the most accurate classification
|
||||||
*/
|
*/
|
||||||
if (this.conv.hasActiveContext()) {
|
if (this.conversation.hasActiveContext()) {
|
||||||
classifications.forEach(({ intent: newIntent, score: newScore }) => {
|
classifications.forEach(({ intent: newIntent, score: newScore }) => {
|
||||||
if (newScore > 0.6) {
|
if (newScore > 0.6) {
|
||||||
const [skillName] = newIntent.split('.')
|
const [skillName] = newIntent.split('.')
|
||||||
const newDomain = MODEL_LOADER.mainNLPContainer.getIntentDomain(locale, newIntent)
|
const newDomain = MODEL_LOADER.mainNLPContainer.getIntentDomain(locale, newIntent)
|
||||||
const contextName = `${newDomain}.${skillName}`
|
const contextName = `${newDomain}.${skillName}`
|
||||||
if (this.conv.activeContext.name === contextName) {
|
if (this.conversation.activeContext.name === contextName) {
|
||||||
score = newScore
|
score = newScore
|
||||||
intent = newIntent
|
intent = newIntent
|
||||||
domain = newDomain
|
domain = newDomain
|
||||||
@ -356,7 +352,7 @@ export default class NLU {
|
|||||||
|
|
||||||
const [skillName, actionName] = intent.split('.')
|
const [skillName, actionName] = intent.split('.')
|
||||||
this.nluResult = {
|
this.nluResult = {
|
||||||
...defaultNluResultObj, // Reset entities, slots, etc.
|
...DEFAULT_NLU_RESULT, // Reset entities, slots, etc.
|
||||||
utterance,
|
utterance,
|
||||||
answers, // For dialog action type
|
answers, // For dialog action type
|
||||||
classification: {
|
classification: {
|
||||||
@ -452,8 +448,8 @@ export default class NLU {
|
|||||||
|
|
||||||
// In case all slots have been filled in the first utterance
|
// In case all slots have been filled in the first utterance
|
||||||
if (
|
if (
|
||||||
this.conv.hasActiveContext() &&
|
this.conversation.hasActiveContext() &&
|
||||||
Object.keys(this.conv.activeContext.slots).length > 0
|
Object.keys(this.conversation.activeContext.slots).length > 0
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return resolve(await this.handleSlotFilling(utterance))
|
return resolve(await this.handleSlotFilling(utterance))
|
||||||
@ -463,10 +459,10 @@ export default class NLU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newContextName = `${this.nluResult.classification.domain}.${skillName}`
|
const newContextName = `${this.nluResult.classification.domain}.${skillName}`
|
||||||
if (this.conv.activeContext.name !== newContextName) {
|
if (this.conversation.activeContext.name !== newContextName) {
|
||||||
this.conv.cleanActiveContext()
|
this.conversation.cleanActiveContext()
|
||||||
}
|
}
|
||||||
this.conv.activeContext = {
|
this.conversation.activeContext = {
|
||||||
lang: BRAIN.lang,
|
lang: BRAIN.lang,
|
||||||
slots: {},
|
slots: {},
|
||||||
isInActionLoop: false,
|
isInActionLoop: false,
|
||||||
@ -479,17 +475,17 @@ export default class NLU {
|
|||||||
}
|
}
|
||||||
// Pass current utterance entities to the NLU result object
|
// Pass current utterance entities to the NLU result object
|
||||||
this.nluResult.currentEntities =
|
this.nluResult.currentEntities =
|
||||||
this.conv.activeContext.currentEntities
|
this.conversation.activeContext.currentEntities
|
||||||
// Pass context entities to the NLU result object
|
// Pass context entities to the NLU result object
|
||||||
this.nluResult.entities = this.conv.activeContext.entities
|
this.nluResult.entities = this.conversation.activeContext.entities
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const processedData = await BRAIN.execute(this.nluResult)
|
const processedData = await BRAIN.execute(this.nluResult)
|
||||||
|
|
||||||
// Prepare next action if there is one queuing
|
// Prepare next action if there is one queuing
|
||||||
if (processedData.nextAction) {
|
if (processedData.nextAction) {
|
||||||
this.conv.cleanActiveContext()
|
this.conversation.cleanActiveContext()
|
||||||
this.conv.activeContext = {
|
this.conversation.activeContext = {
|
||||||
lang: BRAIN.lang,
|
lang: BRAIN.lang,
|
||||||
slots: {},
|
slots: {},
|
||||||
isInActionLoop: !!processedData.nextAction.loop,
|
isInActionLoop: !!processedData.nextAction.loop,
|
||||||
@ -528,11 +524,11 @@ export default class NLU {
|
|||||||
* and ask for more entities if necessary
|
* and ask for more entities if necessary
|
||||||
*/
|
*/
|
||||||
async slotFill(utterance) {
|
async slotFill(utterance) {
|
||||||
if (!this.conv.activeContext.nextAction) {
|
if (!this.conversation.activeContext.nextAction) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { domain, intent } = this.conv.activeContext
|
const { domain, intent } = this.conversation.activeContext
|
||||||
const [skillName, actionName] = intent.split('.')
|
const [skillName, actionName] = intent.split('.')
|
||||||
const configDataFilePath = join(
|
const configDataFilePath = join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
@ -543,7 +539,7 @@ export default class NLU {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.nluResult = {
|
this.nluResult = {
|
||||||
...defaultNluResultObj, // Reset entities, slots, etc.
|
...DEFAULT_NLU_RESULT, // Reset entities, slots, etc.
|
||||||
utterance,
|
utterance,
|
||||||
classification: {
|
classification: {
|
||||||
domain,
|
domain,
|
||||||
@ -559,16 +555,16 @@ export default class NLU {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Continue to loop for questions if a slot has been filled correctly
|
// Continue to loop for questions if a slot has been filled correctly
|
||||||
let notFilledSlot = this.conv.getNotFilledSlot()
|
let notFilledSlot = this.conversation.getNotFilledSlot()
|
||||||
if (notFilledSlot && entities.length > 0) {
|
if (notFilledSlot && entities.length > 0) {
|
||||||
const hasMatch = entities.some(
|
const hasMatch = entities.some(
|
||||||
({ entity }) => entity === notFilledSlot.expectedEntity
|
({ entity }) => entity === notFilledSlot.expectedEntity
|
||||||
)
|
)
|
||||||
|
|
||||||
if (hasMatch) {
|
if (hasMatch) {
|
||||||
this.conv.setSlots(BRAIN.lang, entities)
|
this.conversation.setSlots(BRAIN.lang, entities)
|
||||||
|
|
||||||
notFilledSlot = this.conv.getNotFilledSlot()
|
notFilledSlot = this.conversation.getNotFilledSlot()
|
||||||
if (notFilledSlot) {
|
if (notFilledSlot) {
|
||||||
BRAIN.talk(notFilledSlot.pickedQuestion)
|
BRAIN.talk(notFilledSlot.pickedQuestion)
|
||||||
SOCKET_SERVER.socket.emit('is-typing', false)
|
SOCKET_SERVER.socket.emit('is-typing', false)
|
||||||
@ -578,31 +574,31 @@ export default class NLU {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.conv.areSlotsAllFilled()) {
|
if (!this.conversation.areSlotsAllFilled()) {
|
||||||
BRAIN.talk(`${BRAIN.wernicke('random_context_out_of_topic')}.`)
|
BRAIN.talk(`${BRAIN.wernicke('random_context_out_of_topic')}.`)
|
||||||
} else {
|
} else {
|
||||||
this.nluResult = {
|
this.nluResult = {
|
||||||
...defaultNluResultObj, // Reset entities, slots, etc.
|
...DEFAULT_NLU_RESULT, // Reset entities, slots, etc.
|
||||||
// Assign slots only if there is a next action
|
// Assign slots only if there is a next action
|
||||||
slots: this.conv.activeContext.nextAction
|
slots: this.conversation.activeContext.nextAction
|
||||||
? this.conv.activeContext.slots
|
? this.conversation.activeContext.slots
|
||||||
: {},
|
: {},
|
||||||
utterance: this.conv.activeContext.originalUtterance,
|
utterance: this.conversation.activeContext.originalUtterance,
|
||||||
configDataFilePath,
|
configDataFilePath,
|
||||||
classification: {
|
classification: {
|
||||||
domain,
|
domain,
|
||||||
skill: skillName,
|
skill: skillName,
|
||||||
action: this.conv.activeContext.nextAction,
|
action: this.conversation.activeContext.nextAction,
|
||||||
confidence: 1
|
confidence: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.conv.cleanActiveContext()
|
this.conversation.cleanActiveContext()
|
||||||
|
|
||||||
return BRAIN.execute(this.nluResult)
|
return BRAIN.execute(this.nluResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.conv.cleanActiveContext()
|
this.conversation.cleanActiveContext()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -617,7 +613,7 @@ export default class NLU {
|
|||||||
const hasMandatorySlots = Object.keys(slots)?.length > 0
|
const hasMandatorySlots = Object.keys(slots)?.length > 0
|
||||||
|
|
||||||
if (hasMandatorySlots) {
|
if (hasMandatorySlots) {
|
||||||
this.conv.activeContext = {
|
this.conversation.activeContext = {
|
||||||
lang: BRAIN.lang,
|
lang: BRAIN.lang,
|
||||||
slots,
|
slots,
|
||||||
isInActionLoop: false,
|
isInActionLoop: false,
|
||||||
@ -629,7 +625,7 @@ export default class NLU {
|
|||||||
entities: this.nluResult.entities
|
entities: this.nluResult.entities
|
||||||
}
|
}
|
||||||
|
|
||||||
const notFilledSlot = this.conv.getNotFilledSlot()
|
const notFilledSlot = this.conversation.getNotFilledSlot()
|
||||||
// Loop for questions if a slot hasn't been filled
|
// Loop for questions if a slot hasn't been filled
|
||||||
if (notFilledSlot) {
|
if (notFilledSlot) {
|
||||||
const { actions } = JSON.parse(
|
const { actions } = JSON.parse(
|
||||||
|
Loading…
Reference in New Issue
Block a user