fix: enhance implementation

This commit is contained in:
Mohamed Marrouchi
2025-05-12 14:13:09 +01:00
parent 9642e823d0
commit 41de535a7c
18 changed files with 623 additions and 341 deletions

View File

@@ -20,14 +20,7 @@ import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { NlpEntityRepository } from '@/nlp/repositories/nlp-entity.repository';
import { NlpSampleEntityRepository } from '@/nlp/repositories/nlp-sample-entity.repository';
import { NlpValueRepository } from '@/nlp/repositories/nlp-value.repository';
import { NlpEntityModel } from '@/nlp/schemas/nlp-entity.schema';
import { NlpSampleEntityModel } from '@/nlp/schemas/nlp-sample-entity.schema';
import { NlpValueModel } from '@/nlp/schemas/nlp-value.schema';
import { NlpEntityService } from '@/nlp/services/nlp-entity.service';
import { NlpValueService } from '@/nlp/services/nlp-value.service';
import { NlpService } from '@/nlp/services/nlp.service';
import { PluginService } from '@/plugins/plugins.service';
import { SettingService } from '@/setting/services/setting.service';
import { InvitationRepository } from '@/user/repositories/invitation.repository';
@@ -101,9 +94,6 @@ describe('BlockController', () => {
RoleModel,
PermissionModel,
LanguageModel,
NlpEntityModel,
NlpSampleEntityModel,
NlpValueModel,
]),
],
providers: [
@@ -127,11 +117,6 @@ describe('BlockController', () => {
PermissionService,
LanguageService,
PluginService,
NlpEntityService,
NlpEntityRepository,
NlpSampleEntityRepository,
NlpValueRepository,
NlpValueService,
{
provide: I18nService,
useValue: {
@@ -155,6 +140,10 @@ describe('BlockController', () => {
set: jest.fn(),
},
},
{
provide: NlpService,
useValue: {},
},
],
});
[blockController, blockService, categoryService] = await getMocks([

View File

@@ -27,18 +27,24 @@ import WebChannelHandler from '@/extensions/channels/web/index.channel';
import { WEB_CHANNEL_NAME } from '@/extensions/channels/web/settings';
import { Web } from '@/extensions/channels/web/types';
import WebEventWrapper from '@/extensions/channels/web/wrapper';
import { HelperService } from '@/helper/helper.service';
import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { NlpEntityRepository } from '@/nlp/repositories/nlp-entity.repository';
import { NlpSampleEntityRepository } from '@/nlp/repositories/nlp-sample-entity.repository';
import { NlpSampleRepository } from '@/nlp/repositories/nlp-sample.repository';
import { NlpValueRepository } from '@/nlp/repositories/nlp-value.repository';
import { NlpEntityModel } from '@/nlp/schemas/nlp-entity.schema';
import { NlpSampleEntityModel } from '@/nlp/schemas/nlp-sample-entity.schema';
import { NlpSampleModel } from '@/nlp/schemas/nlp-sample.schema';
import { NlpValueModel } from '@/nlp/schemas/nlp-value.schema';
import { NlpEntityService } from '@/nlp/services/nlp-entity.service';
import { NlpSampleEntityService } from '@/nlp/services/nlp-sample-entity.service';
import { NlpSampleService } from '@/nlp/services/nlp-sample.service';
import { NlpValueService } from '@/nlp/services/nlp-value.service';
import { NlpService } from '@/nlp/services/nlp.service';
import { PluginService } from '@/plugins/plugins.service';
import { SettingService } from '@/setting/services/setting.service';
import {
@@ -52,21 +58,19 @@ import {
blockGetStarted,
blockProductListMock,
blocks,
mockModifiedNlpBlock,
mockModifiedNlpBlockOne,
mockModifiedNlpBlockTwo,
mockNlpBlock,
mockNlpPatternsSetOne,
mockNlpPatternsSetThree,
mockNlpPatternsSetTwo,
mockNlpAffirmationPatterns,
mockNlpGreetingAnyNamePatterns,
mockNlpGreetingNamePatterns,
mockNlpGreetingPatterns,
mockNlpGreetingWrongNamePatterns,
} from '@/utils/test/mocks/block';
import {
contextBlankInstance,
subscriberContextBlankInstance,
} from '@/utils/test/mocks/conversation';
import {
mockNlpEntitiesSetOne,
nlpEntitiesGreeting,
mockNlpGreetingFullNameEntities,
mockNlpGreetingNameEntities,
} from '@/utils/test/mocks/nlp';
import {
closeInMongodConnection,
@@ -94,7 +98,6 @@ describe('BlockService', () => {
let hasPreviousBlocks: Block;
let contentService: ContentService;
let contentTypeService: ContentTypeService;
let nlpEntityService: NlpEntityService;
beforeAll(async () => {
const { getMocks } = await buildTestingMocks({
@@ -115,6 +118,7 @@ describe('BlockService', () => {
NlpEntityModel,
NlpSampleEntityModel,
NlpValueModel,
NlpSampleModel,
]),
],
providers: [
@@ -132,12 +136,14 @@ describe('BlockService', () => {
LanguageService,
NlpEntityRepository,
NlpValueRepository,
NlpSampleRepository,
NlpSampleEntityRepository,
NlpEntityService,
{
provide: NlpValueService,
useValue: {},
},
NlpValueService,
NlpSampleService,
NlpSampleEntityService,
NlpService,
HelperService,
{
provide: PluginService,
useValue: {},
@@ -177,14 +183,12 @@ describe('BlockService', () => {
contentTypeService,
categoryRepository,
blockRepository,
nlpEntityService,
] = await getMocks([
BlockService,
ContentService,
ContentTypeService,
CategoryRepository,
BlockRepository,
NlpEntityService,
]);
category = (await categoryRepository.findOne({ label: 'default' }))!;
hasPreviousBlocks = (await blockRepository.findOne({
@@ -302,7 +306,7 @@ describe('BlockService', () => {
it('should match block with nlp', async () => {
webEventGreeting.setSender(subscriberWithLabels);
webEventGreeting.setNLP(nlpEntitiesGreeting);
webEventGreeting.setNLP(mockNlpGreetingFullNameEntities);
const result = await blockService.match(blocks, webEventGreeting);
expect(result).toEqual(blockGetStarted);
});
@@ -310,19 +314,28 @@ describe('BlockService', () => {
describe('matchNLP', () => {
it('should return undefined for match nlp against a block with no patterns', () => {
const result = blockService.matchNLP(nlpEntitiesGreeting, blockEmpty);
expect(result).toEqual(undefined);
const result = blockService.getMatchingNluPatterns(
mockNlpGreetingFullNameEntities,
blockEmpty,
);
expect(result).toEqual([]);
});
it('should return undefined for match nlp when no nlp entities are provided', () => {
const result = blockService.matchNLP({ entities: [] }, blockGetStarted);
expect(result).toEqual(undefined);
const result = blockService.getMatchingNluPatterns(
{ entities: [] },
blockGetStarted,
);
expect(result).toEqual([]);
});
it('should return match nlp patterns', () => {
const result = blockService.matchNLP(
nlpEntitiesGreeting,
blockGetStarted,
const result = blockService.getMatchingNluPatterns(
mockNlpGreetingFullNameEntities,
{
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpGreetingNamePatterns],
},
);
expect(result).toEqual([
[
@@ -333,146 +346,168 @@ describe('BlockService', () => {
},
{
entity: 'firstname',
match: 'entity',
match: 'value',
value: 'jhon',
},
],
]);
});
it('should return empty array when it does not match nlp patterns', () => {
const result = blockService.matchNLP(nlpEntitiesGreeting, {
...blockGetStarted,
patterns: [[{ entity: 'lastname', match: 'value', value: 'Belakhel' }]],
});
const result = blockService.getMatchingNluPatterns(
mockNlpGreetingFullNameEntities,
{
...blockGetStarted,
patterns: [
[{ entity: 'lastname', match: 'value', value: 'Belakhel' }],
],
},
);
expect(result).toEqual([]);
});
it('should return empty array when unknown nlp patterns', () => {
const result = blockService.matchNLP(nlpEntitiesGreeting, {
...blockGetStarted,
patterns: [[{ entity: 'product', match: 'value', value: 'pizza' }]],
});
const result = blockService.getMatchingNluPatterns(
mockNlpGreetingFullNameEntities,
{
...blockGetStarted,
patterns: [[{ entity: 'product', match: 'value', value: 'pizza' }]],
},
);
expect(result).toEqual([]);
});
});
describe('matchBestNLP', () => {
it('should return the block with the highest NLP score', async () => {
const blocks = [mockNlpBlock, blockGetStarted];
const nlp = mockNlpEntitiesSetOne;
const mockExpectedBlock: BlockFull = {
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpGreetingNamePatterns],
};
const blocks: BlockFull[] = [
// no match
blockGetStarted,
// match
mockExpectedBlock,
// match
{
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpGreetingPatterns],
},
// no match
{
...blockGetStarted,
patterns: [
...blockGetStarted.patterns,
mockNlpGreetingWrongNamePatterns,
],
},
// no match
{
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpAffirmationPatterns],
},
// no match
blockGetStarted,
];
// Spy on calculateBlockScore to check if it's called
const calculateBlockScoreSpy = jest.spyOn(
blockService,
'calculateBlockScore',
'calculateNluPatternMatchScore',
);
const bestBlock = await blockService.matchBestNLP(
blocks,
mockNlpGreetingNameEntities,
);
const bestBlock = await blockService.matchBestNLP(blocks, nlp);
// Ensure calculateBlockScore was called at least once for each block
expect(calculateBlockScoreSpy).toHaveBeenCalledTimes(2); // Called for each block
// Restore the spy after the test
calculateBlockScoreSpy.mockRestore();
// Assert that the block with the highest NLP score is selected
expect(bestBlock).toEqual(mockNlpBlock);
expect(bestBlock).toEqual(mockExpectedBlock);
});
it('should return the block with the highest NLP score applying penalties', async () => {
const blocks = [mockNlpBlock, mockModifiedNlpBlock];
const nlp = mockNlpEntitiesSetOne;
const mockExpectedBlock: BlockFull = {
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpGreetingNamePatterns],
};
const blocks: BlockFull[] = [
// no match
blockGetStarted,
// match
mockExpectedBlock,
// match
{
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpGreetingPatterns],
},
// match
{
...blockGetStarted,
patterns: [
...blockGetStarted.patterns,
mockNlpGreetingAnyNamePatterns,
],
},
];
const nlp = mockNlpGreetingNameEntities;
// Spy on calculateBlockScore to check if it's called
const calculateBlockScoreSpy = jest.spyOn(
blockService,
'calculateBlockScore',
);
const bestBlock = await blockService.matchBestNLP(blocks, nlp);
// Ensure calculateBlockScore was called at least once for each block
expect(calculateBlockScoreSpy).toHaveBeenCalledTimes(2); // Called for each block
// Restore the spy after the test
calculateBlockScoreSpy.mockRestore();
// Assert that the block with the highest NLP score is selected
expect(bestBlock).toEqual(mockNlpBlock);
});
it('another case where it should return the block with the highest NLP score applying penalties', async () => {
const blocks = [mockModifiedNlpBlockOne, mockModifiedNlpBlockTwo]; // You can add more blocks with different patterns and scores
const nlp = mockNlpEntitiesSetOne;
// Spy on calculateBlockScore to check if it's called
const calculateBlockScoreSpy = jest.spyOn(
blockService,
'calculateBlockScore',
'calculateNluPatternMatchScore',
);
const bestBlock = await blockService.matchBestNLP(blocks, nlp);
// Ensure calculateBlockScore was called at least once for each block
expect(calculateBlockScoreSpy).toHaveBeenCalledTimes(3); // Called for each block
// Restore the spy after the test
calculateBlockScoreSpy.mockRestore();
// Assert that the block with the highest NLP score is selected
expect(bestBlock).toEqual(mockModifiedNlpBlockTwo);
expect(bestBlock).toEqual(mockExpectedBlock);
});
it('should return undefined if no blocks match or the list is empty', async () => {
const blocks: BlockFull[] = [];
const nlp = mockNlpEntitiesSetOne;
const blocks: BlockFull[] = [
{
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpAffirmationPatterns],
},
blockGetStarted,
];
const bestBlock = await blockService.matchBestNLP(blocks, nlp);
const bestBlock = await blockService.matchBestNLP(
blocks,
mockNlpGreetingNameEntities,
);
// Assert that undefined is returned when no blocks are available
expect(bestBlock).toBeUndefined();
});
});
describe('calculateBlockScore', () => {
describe('calculateNluPatternMatchScore', () => {
it('should calculate the correct NLP score for a block', async () => {
const getNlpCacheMapSpy = jest.spyOn(nlpEntityService, 'getNlpMap');
const score = await blockService.calculateBlockScore(
mockNlpPatternsSetOne,
mockNlpEntitiesSetOne,
const matchingScore = blockService.calculateNluPatternMatchScore(
mockNlpGreetingNamePatterns,
mockNlpGreetingNameEntities,
);
const score2 = await blockService.calculateBlockScore(
mockNlpPatternsSetTwo,
mockNlpEntitiesSetOne,
);
expect(getNlpCacheMapSpy).toHaveBeenCalledTimes(2);
getNlpCacheMapSpy.mockRestore();
expect(score).toBeGreaterThan(0);
expect(score2).toBe(0);
expect(score).toBeGreaterThan(score2);
expect(matchingScore).toBeGreaterThan(0);
});
it('should calculate the correct NLP score for a block and apply penalties ', async () => {
const getNlpCacheMapSpy = jest.spyOn(nlpEntityService, 'getNlpMap');
const score = await blockService.calculateBlockScore(
mockNlpPatternsSetOne,
mockNlpEntitiesSetOne,
);
const score2 = await blockService.calculateBlockScore(
mockNlpPatternsSetThree,
mockNlpEntitiesSetOne,
const scoreWithoutPenalty = blockService.calculateNluPatternMatchScore(
mockNlpGreetingNamePatterns,
mockNlpGreetingNameEntities,
);
expect(getNlpCacheMapSpy).toHaveBeenCalledTimes(2);
getNlpCacheMapSpy.mockRestore();
expect(score).toBeGreaterThan(0);
expect(score2).toBeGreaterThan(0);
expect(score).toBeGreaterThan(score2);
});
it('should return 0 if no matching entities are found', async () => {
const getNlpCacheMapSpy = jest.spyOn(nlpEntityService, 'getNlpMap');
const score = await blockService.calculateBlockScore(
mockNlpPatternsSetTwo,
mockNlpEntitiesSetOne,
const scoreWithPenalty = blockService.calculateNluPatternMatchScore(
mockNlpGreetingAnyNamePatterns,
mockNlpGreetingNameEntities,
);
expect(getNlpCacheMapSpy).toHaveBeenCalledTimes(1);
getNlpCacheMapSpy.mockRestore();
expect(score).toBe(0); // No matching entity
expect(scoreWithoutPenalty).toBeGreaterThan(scoreWithPenalty);
});
});

View File

@@ -16,8 +16,7 @@ import { CONSOLE_CHANNEL_NAME } from '@/extensions/channels/console/settings';
import { NLU } from '@/helper/types';
import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { NlpEntityFull } from '@/nlp/schemas/nlp-entity.schema';
import { NlpEntityService } from '@/nlp/services/nlp-entity.service';
import { NlpService } from '@/nlp/services/nlp.service';
import { PluginService } from '@/plugins/plugins.service';
import { PluginType } from '@/plugins/types';
import { SettingService } from '@/setting/services/setting.service';
@@ -27,7 +26,12 @@ import { getRandomElement } from '@/utils/helpers/safeRandom';
import { BlockDto } from '../dto/block.dto';
import { EnvelopeFactory } from '../helpers/envelope-factory';
import { BlockRepository } from '../repositories/block.repository';
import { Block, BlockFull, BlockPopulate } from '../schemas/block.schema';
import {
Block,
BlockFull,
BlockPopulate,
BlockStub,
} from '../schemas/block.schema';
import { Label } from '../schemas/label.schema';
import { Subscriber } from '../schemas/subscriber.schema';
import { Context } from '../schemas/types/context';
@@ -55,7 +59,7 @@ export class BlockService extends BaseService<
private readonly pluginService: PluginService,
protected readonly i18n: I18nService,
protected readonly languageService: LanguageService,
protected readonly entityService: NlpEntityService,
protected readonly nlpService: NlpService,
) {
super(repository);
}
@@ -164,18 +168,6 @@ export class BlockService extends BaseService<
// Perform a text match (Text or Quick reply)
const text = event.getText().trim();
// Check & catch user language through NLP
const nlp = event.getNLP();
if (nlp) {
const languages = await this.languageService.getLanguages();
const lang = nlp.entities.find((e) => e.entity === 'language');
if (lang && Object.keys(languages).indexOf(lang.value) !== -1) {
const profile = event.getSender();
profile.language = lang.value;
event.setSender(profile);
}
}
// Perform a text pattern match
block = filteredBlocks
.filter((b) => {
@@ -184,9 +176,12 @@ export class BlockService extends BaseService<
.shift();
// Perform an NLP Match
const nlp = event.getNLP();
if (!block && nlp) {
block = await this.matchBestNLP(filteredBlocks, nlp);
const scoredEntities =
await this.nlpService.computePredictionScore(nlp);
block = await this.matchBestNLP(filteredBlocks, scoredEntities);
}
}
@@ -289,28 +284,29 @@ export class BlockService extends BaseService<
}
/**
* Performs an NLP pattern match based on the best guessed entities and/or values
* Performs an NLU pattern match based on the predicted entities and/or values
*
* @param nlp - Parsed NLP entities
* @param block - The block to test
*
* @returns The NLP patterns that matches
* @returns The NLU patterns that matches the predicted entities
*/
matchNLP(
nlp: NLU.ParseEntities,
block: Block | BlockFull,
): NlpPattern[][] | undefined {
getMatchingNluPatterns<E extends NLU.ParseEntities, B extends BlockStub>(
nlp: E,
block: B,
): NlpPattern[][] {
// No nlp entities to check against
if (nlp.entities.length === 0) {
return undefined;
return [];
}
const nlpPatterns = block.patterns.filter((p) => {
return Array.isArray(p);
}) as NlpPattern[][];
// No nlp patterns found
if (nlpPatterns.length === 0) {
return undefined;
return [];
}
// Filter NLP patterns match based on best guessed entities
@@ -333,86 +329,96 @@ export class BlockService extends BaseService<
}
/**
* Matches the provided NLU parsed entities with patterns in a set of blocks and returns
* the block with the highest matching score.
* Finds and returns the block that best matches the given scored NLU entities.
*
* For each block, it checks the patterns against the NLU parsed entities, calculates
* a score for each match, and selects the block with the highest score.
* This function evaluates each block by matching its NLP patterns against the provided
* `scoredEntities`, using `matchNLP` and `calculateNluPatternMatchScore` to compute
* a confidence score for each match. The block with the highest total pattern match score
* is returned.
*
* @param {BlockFull[]} blocks - An array of BlockFull objects representing potential matches.
* @param {NLU.ParseEntities} nlp - The NLU parsed entities used for pattern matching.
* If no block yields a positive score, the function returns `undefined`.
*
* @returns {Promise<BlockFull | undefined>} - A promise that resolves to the BlockFull
* with the highest match score, or undefined if no matches are found.
* @param blocks - A list of blocks to evaluate, each potentially containing NLP patterns.
* @param scoredEntities - The scored NLU entities to use for pattern matching.
*
* @returns A promise that resolves to the block with the highest NLP match score,
* or `undefined` if no suitable match is found.
*/
async matchBestNLP(
blocks: BlockFull[],
nlp: NLU.ParseEntities,
): Promise<BlockFull | undefined> {
const scoredBlocks = await Promise.all(
blocks.map(async (block) => {
const matchedPatterns = this.matchNLP(nlp, block) || [];
const scores = await Promise.all(
matchedPatterns.map((pattern) =>
this.calculateBlockScore(pattern, nlp),
),
async matchBestNLP<B extends BlockStub>(
blocks: B[],
scoredEntities: NLU.ScoredEntities,
): Promise<B | undefined> {
const bestMatch = blocks.reduce(
(bestMatch, block) => {
const matchedPatterns = this.getMatchingNluPatterns(
scoredEntities,
block,
);
const maxScore = scores.length > 0 ? Math.max(...scores) : 0;
// Compute the score (Weighted sum = weight * confidence)
// for each of block NLU patterns
const score = matchedPatterns.reduce((maxScore, patterns) => {
const score = this.calculateNluPatternMatchScore(
patterns,
scoredEntities,
);
return Math.max(maxScore, score);
}, 0);
return { block, score: maxScore };
}),
);
const best = scoredBlocks.reduce(
(acc, curr) => (curr.score > acc.score ? curr : acc),
return score > bestMatch.score ? { block, score } : bestMatch;
},
{ block: undefined, score: 0 },
);
return best.block;
return bestMatch.block;
}
/**
* Computes the NLP score for a given block using its matched NLP patterns and parsed NLP entities.
* Calculates the total NLU pattern match score by summing the individual pattern scores
* for each pattern that matches a scored entity.
*
* Each pattern is evaluated against the parsed NLP entities to determine matches based on entity name,
* value, and confidence. A score is computed using the entity's weight and the confidence level of the match.
* A penalty factor is optionally applied for entity-level matches to adjust the scoring.
* For each pattern in the list, the function attempts to find a matching entity in the
* NLU prediction. If a match is found, the score is computed using `computePatternScore`,
* potentially applying a penalty if the match is generic (entity-only).
*
* The function uses a cache (`nlpCacheMap`) to avoid redundant database lookups for entity metadata.
* This scoring mechanism allows the system to prioritize more precise matches and
* quantify the overall alignment between predicted NLU entities and predefined patterns.
*
* @param patterns - The NLP patterns associated with the block.
* @param nlp - The parsed NLP entities from the user input.
* @returns A numeric score representing how well the block matches the given NLP context.
* @param patterns - A list of patterns to evaluate against the NLU prediction.
* @param prediction - The scored entities resulting from NLU inference.
* @param [penaltyFactor=0.95] - Optional penalty factor to apply for generic matches (default is 0.95).
*
* @returns The total aggregated match score based on matched patterns and their computed scores.
*/
async calculateBlockScore(
calculateNluPatternMatchScore(
patterns: NlpPattern[],
nlp: NLU.ParseEntities,
): Promise<number> {
if (!patterns.length) return 0;
prediction: NLU.ScoredEntities,
penaltyFactor = 0.95,
): number {
if (!patterns.length) {
throw new Error(
'Unable to compute the NLU match score : patterns are missing',
);
}
const nlpCacheMap = await this.entityService.getNlpMap();
// @TODO Make nluPenaltyFactor configurable in UI settings
const nluPenaltyFactor = 0.95;
const patternScores: number[] = patterns
.filter(({ entity }) => nlpCacheMap.has(entity))
.map((pattern) => {
const entityData = nlpCacheMap.get(pattern.entity);
return patterns.reduce((score, pattern) => {
const matchedEntity: NLU.ScoredEntity | undefined =
prediction.entities.find((e) => this.matchesNluEntity(e, pattern));
const matchedEntity: NLU.ParseEntity | undefined = nlp.entities.find(
(e) => this.matchesEntityData(e, pattern, entityData!),
if (!matchedEntity) {
throw new Error(
'Unable to compute the NLU match score : pattern / entity mismatch',
);
return this.computePatternScore(
matchedEntity,
pattern,
entityData!,
nluPenaltyFactor,
);
});
}
// Sum the scores
return patternScores.reduce((sum, score) => sum + score, 0);
const patternScore = this.computePatternScore(
matchedEntity,
pattern,
penaltyFactor,
);
return score + patternScore;
}, 0);
}
/**
@@ -430,36 +436,44 @@ export class BlockService extends BaseService<
* - If the pattern's match type is `'value'`, it further ensures that the entity's value matches the specified value in the pattern.
* - Returns `true` if all conditions are met, otherwise `false`.
*/
private matchesEntityData(
e: NLU.ParseEntity,
private matchesNluEntity<E extends NLU.ParseEntity>(
{ entity, value }: E,
pattern: NlpPattern,
entityData: NlpEntityFull,
): boolean {
return (
e.entity === pattern.entity &&
entityData.values?.some((v) => v.value === e.value) &&
(pattern.match !== 'value' || e.value === pattern.value)
entity === pattern.entity &&
(pattern.match !== 'value' || value === pattern.value)
);
}
/**
* Computes the score for a given entity based on its confidence, weight, and penalty factor.
* Computes a pattern score by applying a penalty factor based on the matching rule of the pattern.
*
* @param entity - The `ParseEntity` to check, which may be `undefined` if no match is found.
* @param pattern - The `NlpPattern` object that specifies how to match the entity and its value.
* @param entityData - The cached data for the given entity, including `weight` and `values`.
* @param nlpPenaltyFactor - The penalty factor applied when the pattern's match type is 'entity'.
* @returns The computed score based on the entity's confidence, the cached weight, and the penalty factor.
* This scoring mechanism allows prioritization of more specific patterns (entity + value) over
* more generic ones (entity only).
*
* @param entity - The scored entity object containing the base score.
* @param pattern - The pattern definition to match against the entity.
* @param [penaltyFactor=0.95] - Optional penalty factor applied when the pattern only matches the entity (default is 0.95).
*
* @returns The final pattern score after applying any applicable penalty.
*/
private computePatternScore(
entity: NLU.ParseEntity | undefined,
entity: NLU.ScoredEntity,
pattern: NlpPattern,
entityData: NlpEntityFull,
nlpPenaltyFactor: number,
penaltyFactor: number = 0.95,
): number {
if (!entity || !entity.confidence) return 0;
const penalty = pattern.match === 'entity' ? nlpPenaltyFactor : 1;
return entity.confidence * entityData.weight * penalty;
if (!entity || !pattern) {
throw new Error(
'Unable to compute pattern score : missing entity/pattern',
);
}
// In case the pattern matches the entity regardless of the value (any)
// we apply a penalty so that we prioritize other patterns where both entity and value matches
const penalty = pattern.match === 'entity' ? penaltyFactor : 1;
return entity.score * penalty;
}
/**

View File

@@ -35,12 +35,17 @@ import { I18nService } from '@/i18n/services/i18n.service';
import { LanguageService } from '@/i18n/services/language.service';
import { NlpEntityRepository } from '@/nlp/repositories/nlp-entity.repository';
import { NlpSampleEntityRepository } from '@/nlp/repositories/nlp-sample-entity.repository';
import { NlpSampleRepository } from '@/nlp/repositories/nlp-sample.repository';
import { NlpValueRepository } from '@/nlp/repositories/nlp-value.repository';
import { NlpEntityModel } from '@/nlp/schemas/nlp-entity.schema';
import { NlpSampleEntityModel } from '@/nlp/schemas/nlp-sample-entity.schema';
import { NlpSampleModel } from '@/nlp/schemas/nlp-sample.schema';
import { NlpValueModel } from '@/nlp/schemas/nlp-value.schema';
import { NlpEntityService } from '@/nlp/services/nlp-entity.service';
import { NlpSampleEntityService } from '@/nlp/services/nlp-sample-entity.service';
import { NlpSampleService } from '@/nlp/services/nlp-sample.service';
import { NlpValueService } from '@/nlp/services/nlp-value.service';
import { NlpService } from '@/nlp/services/nlp.service';
import { PluginService } from '@/plugins/plugins.service';
import { SettingService } from '@/setting/services/setting.service';
import { installBlockFixtures } from '@/utils/test/fixtures/block';
@@ -111,6 +116,7 @@ describe('BlockService', () => {
NlpEntityModel,
NlpSampleEntityModel,
NlpValueModel,
NlpSampleModel,
]),
JwtModule,
],
@@ -127,6 +133,11 @@ describe('BlockService', () => {
MessageRepository,
MenuRepository,
LanguageRepository,
ContextVarRepository,
NlpEntityRepository,
NlpSampleEntityRepository,
NlpValueRepository,
NlpSampleRepository,
BlockService,
CategoryService,
ContentTypeService,
@@ -140,13 +151,12 @@ describe('BlockService', () => {
MenuService,
WebChannelHandler,
ContextVarService,
ContextVarRepository,
LanguageService,
NlpEntityService,
NlpEntityRepository,
NlpSampleEntityRepository,
NlpValueRepository,
NlpValueService,
NlpSampleService,
NlpSampleEntityService,
NlpService,
{
provide: HelperService,
useValue: {},

View File

@@ -21,6 +21,8 @@ import {
import EventWrapper from '@/channel/lib/EventWrapper';
import { config } from '@/config';
import { HelperService } from '@/helper/helper.service';
import { HelperType } from '@/helper/types';
import { LanguageService } from '@/i18n/services/language.service';
import { LoggerService } from '@/logger/logger.service';
import { WebsocketGateway } from '@/websocket/websocket.gateway';
@@ -46,6 +48,7 @@ export class ChatService {
private readonly websocketGateway: WebsocketGateway,
private readonly helperService: HelperService,
private readonly attachmentService: AttachmentService,
private readonly languageService: LanguageService,
) {}
/**
@@ -330,15 +333,7 @@ export class ChatService {
return;
}
if (event.getText() && !event.getNLP()) {
try {
const helper = await this.helperService.getDefaultNluHelper();
const nlp = await helper.predict(event.getText(), true);
event.setNLP(nlp);
} catch (err) {
this.logger.error('Unable to perform NLP parse', err);
}
}
await this.enrichEventWithNLU(event);
this.botService.handleMessageEvent(event);
} catch (err) {
@@ -346,6 +341,40 @@ export class ChatService {
}
}
/**
* Enriches an incoming event by performing NLP inference and updating the sender's language profile if detected.
*
* @param event - The incoming event object containing user input and metadata.
* @returns Resolves when preprocessing is complete. Any errors are logged without throwing.
*/
async enrichEventWithNLU(event: EventWrapper<any, any>) {
if (!event.getText() || event.getNLP()) {
return;
}
try {
const helper = await this.helperService.getDefaultHelper(HelperType.NLU);
const nlp = await helper.predict(event.getText(), true);
// Check & catch user language through NLP
if (nlp) {
const languages = await this.languageService.getLanguages();
const spokenLanguage = nlp.entities.find(
(e) => e.entity === 'language',
);
if (spokenLanguage && spokenLanguage.value in languages) {
const profile = event.getSender();
profile.language = spokenLanguage.value;
event.setSender(profile);
}
}
event.setNLP(nlp);
} catch (err) {
this.logger.error('Unable to perform NLP parse', err);
}
}
/**
* Handle new subscriber and send notification the websocket
*

View File

@@ -26,6 +26,14 @@ export namespace NLU {
export interface ParseEntities {
entities: ParseEntity[];
}
export interface ScoredEntity extends ParseEntity {
score: number; // Computed as confidence * weight
}
export interface ScoredEntities extends ParseEntities {
entities: ScoredEntity[];
}
}
export namespace LLM {

View File

@@ -47,7 +47,7 @@ export class LanguageService extends BaseService<
* and the corresponding value is the `Language` object.
*/
@Cacheable(LANGUAGES_CACHE_KEY)
async getLanguages() {
async getLanguages(): Promise<Record<string, Language>> {
const languages = await this.findAll();
return languages.reduce((acc, curr) => {
return {

View File

@@ -91,7 +91,7 @@ describe('NlpEntityRepository', () => {
});
});
describe('findPageAndPopulate', () => {
describe('findAndPopulate', () => {
it('should return all nlp entities with populate', async () => {
const pageQuery = getPageQuery<NlpEntity>({
sort: ['name', 'desc'],
@@ -99,7 +99,7 @@ describe('NlpEntityRepository', () => {
const firstNameValues = await nlpValueRepository.find({
entity: firstNameNlpEntity!.id,
});
const result = await nlpEntityRepository.findPageAndPopulate(
const result = await nlpEntityRepository.findAndPopulate(
{ _id: firstNameNlpEntity!.id },
pageQuery,
);

View File

@@ -71,18 +71,15 @@ describe('NlpValueRepository', () => {
});
});
describe('findPageAndPopulate', () => {
it('should return all nlp entities with populate', async () => {
describe('findAndPopulate', () => {
it('should return all nlp values with populate', async () => {
const pageQuery = getPageQuery<NlpValue>({
sort: ['value', 'desc'],
sort: ['createdAt', 'asc'],
});
const result = await nlpValueRepository.findPageAndPopulate(
{},
pageQuery,
);
const result = await nlpValueRepository.findAndPopulate({}, pageQuery);
const nlpValueFixturesWithEntities = nlpValueFixtures.reduce(
(acc, curr) => {
const ValueWithEntities = {
const fullValue: NlpValueFull = {
...curr,
entity: nlpEntityFixtures[
parseInt(curr.entity!)
@@ -90,13 +87,21 @@ describe('NlpValueRepository', () => {
builtin: curr.builtin!,
expressions: curr.expressions!,
metadata: curr.metadata!,
id: '',
createdAt: new Date(),
updatedAt: new Date(),
};
acc.push(ValueWithEntities);
acc.push(fullValue);
return acc;
},
[] as TFixtures<NlpValueFull>[],
);
expect(result).toEqualPayload(nlpValueFixturesWithEntities);
expect(result).toEqualPayload(nlpValueFixturesWithEntities, [
'id',
'createdAt',
'updatedAt',
'metadata',
]);
});
});

View File

@@ -29,7 +29,7 @@ import { NlpValueModel } from '../schemas/nlp-value.schema';
import { NlpEntityService } from './nlp-entity.service';
import { NlpValueService } from './nlp-value.service';
describe('nlpEntityService', () => {
describe('NlpEntityService', () => {
let nlpEntityService: NlpEntityService;
let nlpEntityRepository: NlpEntityRepository;
let nlpValueRepository: NlpValueRepository;
@@ -221,32 +221,47 @@ describe('nlpEntityService', () => {
const result = await nlpEntityService.getNlpMap();
expect(result).toBeInstanceOf(Map);
expect(result.get('firstname')).toEqual(
expect.objectContaining({
expect(result.get('firstname')).toEqualPayload(
{
name: 'firstname',
lookups: ['keywords'],
doc: '',
builtin: false,
weight: 1,
weight: 0.85,
values: [
expect.objectContaining({
{
value: 'jhon',
expressions: ['john', 'joohn', 'jhonny'],
builtin: true,
doc: '',
}),
},
],
}),
},
['id', 'createdAt', 'updatedAt', 'metadata', 'entity'],
);
expect(result.get('subject')).toEqual(
expect.objectContaining({
expect(result.get('subject')).toEqualPayload(
{
name: 'subject',
lookups: ['trait'],
doc: '',
builtin: false,
weight: 1,
values: [],
}),
weight: 0.95,
values: [
{
value: 'product',
expressions: [],
builtin: false,
doc: '',
},
{
value: 'claim',
expressions: [],
builtin: false,
doc: '',
},
],
},
['id', 'createdAt', 'updatedAt', 'metadata', 'entity'],
);
});
});

View File

@@ -217,7 +217,10 @@ describe('NlpSampleService', () => {
.mockResolvedValue([{ name: 'intent' } as NlpEntity]);
jest
.spyOn(languageService, 'getLanguages')
.mockResolvedValue({ en: { id: '1' } });
.mockResolvedValue({ en: { id: '1' } } as unknown as Record<
string,
Language
>);
jest
.spyOn(languageService, 'getDefaultLanguage')
.mockResolvedValue({ code: 'en' } as Language);
@@ -240,7 +243,10 @@ describe('NlpSampleService', () => {
.mockResolvedValue([{ name: 'intent' } as NlpEntity]);
jest
.spyOn(languageService, 'getLanguages')
.mockResolvedValue({ en: { id: '1' } });
.mockResolvedValue({ en: { id: '1' } } as unknown as Record<
string,
Language
>);
jest
.spyOn(languageService, 'getDefaultLanguage')
.mockResolvedValue({ code: 'en' } as Language);
@@ -258,7 +264,10 @@ describe('NlpSampleService', () => {
it('should successfully process and save valid dataset rows', async () => {
const mockData = 'text,intent,language\nHi,greet,en\nBye,bye,en';
const mockLanguages = { en: { id: '1' } };
const mockLanguages = { en: { id: '1' } } as unknown as Record<
string,
Language
>;
jest
.spyOn(languageService, 'getLanguages')

View File

@@ -98,25 +98,33 @@ describe('NlpValueService', () => {
});
});
describe('findPageAndPopulate', () => {
it('should return all nlp entities with populate', async () => {
const pageQuery = getPageQuery<NlpValue>({ sort: ['value', 'desc'] });
const result = await nlpValueService.findPageAndPopulate({}, pageQuery);
describe('findAndPopulate', () => {
it('should return all nlp values with populate', async () => {
const pageQuery = getPageQuery<NlpValue>({ sort: ['createdAt', 'asc'] });
const result = await nlpValueService.findAndPopulate({}, pageQuery);
const nlpValueFixturesWithEntities = nlpValueFixtures.reduce(
(acc, curr) => {
const ValueWithEntities = {
const fullValue: NlpValueFull = {
...curr,
entity: nlpEntityFixtures[parseInt(curr.entity!)] as NlpEntity,
expressions: curr.expressions!,
metadata: curr.metadata!,
builtin: curr.builtin!,
metadata: {},
id: '',
createdAt: new Date(),
updatedAt: new Date(),
};
acc.push(ValueWithEntities);
acc.push(fullValue);
return acc;
},
[] as Omit<NlpValueFull, keyof BaseSchema>[],
);
expect(result).toEqualPayload(nlpValueFixturesWithEntities);
expect(result).toEqualPayload(nlpValueFixturesWithEntities, [
'id',
'createdAt',
'updatedAt',
'metadata',
]);
});
});

View File

@@ -0,0 +1,134 @@
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { MongooseModule } from '@nestjs/mongoose';
import { HelperService } from '@/helper/helper.service';
import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { LanguageService } from '@/i18n/services/language.service';
import { SettingRepository } from '@/setting/repositories/setting.repository';
import { SettingModel } from '@/setting/schemas/setting.schema';
import { SettingSeeder } from '@/setting/seeds/setting.seed';
import { SettingService } from '@/setting/services/setting.service';
import { installNlpValueFixtures } from '@/utils/test/fixtures/nlpvalue';
import {
closeInMongodConnection,
rootMongooseTestModule,
} from '@/utils/test/test';
import { buildTestingMocks } from '@/utils/test/utils';
import { NlpEntityRepository } from '../repositories/nlp-entity.repository';
import { NlpSampleEntityRepository } from '../repositories/nlp-sample-entity.repository';
import { NlpSampleRepository } from '../repositories/nlp-sample.repository';
import { NlpValueRepository } from '../repositories/nlp-value.repository';
import { NlpEntityModel } from '../schemas/nlp-entity.schema';
import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema';
import { NlpSampleModel } from '../schemas/nlp-sample.schema';
import { NlpValueModel } from '../schemas/nlp-value.schema';
import { NlpEntityService } from './nlp-entity.service';
import { NlpSampleEntityService } from './nlp-sample-entity.service';
import { NlpSampleService } from './nlp-sample.service';
import { NlpValueService } from './nlp-value.service';
import { NlpService } from './nlp.service';
describe('NlpService', () => {
let nlpService: NlpService;
beforeAll(async () => {
const { getMocks } = await buildTestingMocks({
imports: [
rootMongooseTestModule(installNlpValueFixtures),
MongooseModule.forFeature([
NlpEntityModel,
NlpValueModel,
NlpSampleEntityModel,
NlpSampleModel,
LanguageModel,
SettingModel,
]),
],
providers: [
NlpService,
NlpEntityService,
NlpEntityRepository,
NlpValueService,
NlpSampleService,
NlpSampleEntityService,
HelperService,
LanguageService,
SettingService,
NlpValueRepository,
NlpSampleEntityRepository,
NlpSampleRepository,
SettingRepository,
SettingSeeder,
LanguageRepository,
{
provide: CACHE_MANAGER,
useValue: {
del: jest.fn(),
set: jest.fn(),
get: jest.fn(),
},
},
],
});
[nlpService] = await getMocks([NlpService]);
});
afterAll(closeInMongodConnection);
afterEach(jest.clearAllMocks);
describe('computePredictionScore()', () => {
it('should compute score as confidence * weight for matched entities', async () => {
const result = await nlpService.computePredictionScore({
entities: [
{ entity: 'intent', value: 'greeting', confidence: 0.98 },
{ entity: 'subject', value: 'product', confidence: 0.9 },
{ entity: 'firstname', value: 'Jhon', confidence: 0.78 },
{ entity: 'irrelevant', value: 'test', confidence: 1 },
],
});
expect(result).toEqual({
entities: [
{
entity: 'intent',
value: 'greeting',
confidence: 0.98,
score: 0.98,
},
{
entity: 'subject',
value: 'product',
confidence: 0.9,
score: 0.855,
},
{
entity: 'firstname',
value: 'Jhon',
confidence: 0.78,
score: 0.663,
},
],
});
});
it('should return empty array if no entity matches', async () => {
const result = await nlpService.computePredictionScore({
entities: [{ entity: 'unknown', value: 'x', confidence: 1 }],
});
expect(result).toEqual({ entities: [] });
});
});
});

View File

@@ -10,6 +10,7 @@ import { Injectable, NotFoundException } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { HelperService } from '@/helper/helper.service';
import { NLU } from '@/helper/types';
import { LoggerService } from '@/logger/logger.service';
import { NlpEntity, NlpEntityDocument } from '../schemas/nlp-entity.schema';
@@ -29,6 +30,36 @@ export class NlpService {
protected readonly helperService: HelperService,
) {}
/**
* Computes a prediction score for each parsed NLU entity based on its confidence and a predefined weight.
*
* `score = confidence * weight`
*
* If a weight is not defined for a given entity, a default of 1 is used.
*
* @param input - The input object containing parsed entities.
* @param input.entities - The list of entities returned from NLU inference.
*
* @returns A promise that resolves to a list of scored entities.
*/
async computePredictionScore({
entities,
}: NLU.ParseEntities): Promise<NLU.ScoredEntities> {
const nlpMap = await this.nlpEntityService.getNlpMap();
const scoredEntities = entities
.filter(({ entity }) => nlpMap.has(entity))
.map((e) => {
const entity = nlpMap.get(e.entity)!;
return {
...e,
score: e.confidence * (entity.weight || 1),
};
});
return { entities: scoredEntities };
}
/**
* Handles the event triggered when a new NLP entity is created. Synchronizes the entity with the external NLP provider.
*

View File

@@ -24,7 +24,7 @@ export const nlpEntityFixtures: NlpEntityCreateDto[] = [
lookups: ['keywords'],
doc: '',
builtin: false,
weight: 1,
weight: 0.85,
},
{
name: 'built_in',
@@ -38,7 +38,7 @@ export const nlpEntityFixtures: NlpEntityCreateDto[] = [
lookups: ['trait'],
doc: '',
builtin: false,
weight: 1,
weight: 0.95,
},
];

View File

@@ -11,7 +11,7 @@ import mongoose from 'mongoose';
import { NlpValueCreateDto } from '@/nlp/dto/nlp-value.dto';
import { NlpValueModel } from '@/nlp/schemas/nlp-value.schema';
import { installNlpEntityFixtures } from './nlpentity';
import { installNlpEntityFixtures, nlpEntityFixtures } from './nlpentity';
export const nlpValueFixtures: NlpValueCreateDto[] = [
{
@@ -56,16 +56,36 @@ export const nlpValueFixtures: NlpValueCreateDto[] = [
builtin: false,
doc: '',
},
{
entity: '3',
value: 'product',
expressions: [],
builtin: false,
doc: '',
},
{
entity: '3',
value: 'claim',
expressions: [],
builtin: false,
doc: '',
},
];
export const installNlpValueFixtures = async () => {
const nlpEntities = await installNlpEntityFixtures();
const NlpValue = mongoose.model(NlpValueModel.name, NlpValueModel.schema);
const nlpValues = await NlpValue.insertMany(
nlpValueFixtures.map((v) => ({
...v,
entity: v?.entity ? nlpEntities[parseInt(v.entity)].id : null,
entity: v?.entity
? nlpEntities.find(
(e) =>
e.name === nlpEntityFixtures[parseInt(v.entity as string)].name,
).id
: null,
})),
);
return { nlpEntities, nlpValues };

View File

@@ -230,23 +230,20 @@ export const blockGetStarted = {
value: 'Livre',
type: PayloadType.attachments,
},
[
{
entity: 'intent',
match: 'value',
value: 'greeting',
},
{
entity: 'firstname',
match: 'entity',
},
],
],
trigger_labels: customerLabelsMock,
message: ['Welcome! How are you ? '],
} as unknown as BlockFull;
export const mockNlpPatternsSetOne: NlpPattern[] = [
export const mockNlpGreetingPatterns: NlpPattern[] = [
{
entity: 'intent',
match: 'value',
value: 'greeting',
},
];
export const mockNlpGreetingNamePatterns: NlpPattern[] = [
{
entity: 'intent',
match: 'value',
@@ -259,7 +256,20 @@ export const mockNlpPatternsSetOne: NlpPattern[] = [
},
];
export const mockNlpPatternsSetTwo: NlpPattern[] = [
export const mockNlpGreetingWrongNamePatterns: NlpPattern[] = [
{
entity: 'intent',
match: 'value',
value: 'greeting',
},
{
entity: 'firstname',
match: 'value',
value: 'doe',
},
];
export const mockNlpAffirmationPatterns: NlpPattern[] = [
{
entity: 'intent',
match: 'value',
@@ -272,7 +282,7 @@ export const mockNlpPatternsSetTwo: NlpPattern[] = [
},
];
export const mockNlpPatternsSetThree: NlpPattern[] = [
export const mockNlpGreetingAnyNamePatterns: NlpPattern[] = [
{
entity: 'intent',
match: 'value',
@@ -284,33 +294,6 @@ export const mockNlpPatternsSetThree: NlpPattern[] = [
},
];
export const mockNlpBlock: BlockFull = {
...baseBlockInstance,
name: 'Mock Nlp',
patterns: [
'Hello',
'/we*lcome/',
{ label: 'Mock Nlp', value: 'MOCK_NLP' },
mockNlpPatternsSetOne,
[
{
entity: 'intent',
match: 'value',
value: 'greeting',
},
{
entity: 'firstname',
match: 'value',
value: 'doe',
},
],
],
trigger_labels: customerLabelsMock,
message: ['Good to see you again '],
} as unknown as BlockFull;
export const mockModifiedNlpBlock: BlockFull = {
...baseBlockInstance,
name: 'Modified Mock Nlp',
@@ -318,7 +301,7 @@ export const mockModifiedNlpBlock: BlockFull = {
'Hello',
'/we*lcome/',
{ label: 'Modified Mock Nlp', value: 'MODIFIED_MOCK_NLP' },
mockNlpPatternsSetThree,
mockNlpGreetingAnyNamePatterns,
],
trigger_labels: customerLabelsMock,
message: ['Hello there'],
@@ -331,7 +314,7 @@ export const mockModifiedNlpBlockOne: BlockFull = {
'Hello',
'/we*lcome/',
{ label: 'Modified Mock Nlp One', value: 'MODIFIED_MOCK_NLP_ONE' },
mockNlpPatternsSetTwo,
mockNlpAffirmationPatterns,
[
{
entity: 'firstname',
@@ -356,7 +339,7 @@ export const mockModifiedNlpBlockTwo: BlockFull = {
match: 'entity',
},
],
mockNlpPatternsSetThree,
mockNlpGreetingAnyNamePatterns,
],
trigger_labels: customerLabelsMock,
message: ['Hello Madam'],
@@ -400,5 +383,3 @@ export const blockCarouselMock = {
} as unknown as BlockFull;
export const blocks: BlockFull[] = [blockGetStarted, blockEmpty];
export const nlpBlocks: BlockFull[] = [blockGetStarted, mockNlpBlock];

View File

@@ -8,42 +8,36 @@
import { NLU } from '@/helper/types';
export const nlpEntitiesGreeting: NLU.ParseEntities = {
export const mockNlpGreetingNameEntities: NLU.ScoredEntities = {
entities: [
{
entity: 'intent',
value: 'greeting',
confidence: 0.999,
score: 0.999,
},
{
entity: 'firstname',
value: 'jhon',
confidence: 0.5,
score: 0.425,
},
],
};
export const mockNlpGreetingFullNameEntities: NLU.ParseEntities = {
entities: [
...mockNlpGreetingNameEntities.entities,
{
entity: 'lastname',
value: 'doe',
confidence: 0.5,
score: 0.425,
},
],
};
export const mockNlpEntitiesSetOne: NLU.ParseEntities = {
entities: [
{
entity: 'intent',
value: 'greeting',
confidence: 0.999,
},
{
entity: 'firstname',
value: 'jhon',
confidence: 0.5,
},
],
};
export const mockNlpEntitiesSetTwo: NLU.ParseEntities = {
export const mockNlpGreetingWrongNameEntities: NLU.ParseEntities = {
entities: [
{
entity: 'intent',