fix: add canonical value check

This commit is contained in:
Mohamed Marrouchi 2025-05-13 17:07:28 +01:00
parent 09666efec6
commit e85084cd41
8 changed files with 77 additions and 23 deletions

View File

@ -59,6 +59,7 @@ import {
blockProductListMock,
blocks,
mockNlpAffirmationPatterns,
mockNlpFirstNamePatterns,
mockNlpGreetingAnyNamePatterns,
mockNlpGreetingNamePatterns,
mockNlpGreetingPatterns,
@ -69,6 +70,7 @@ import {
subscriberContextBlankInstance,
} from '@/utils/test/mocks/conversation';
import {
mockNlpFirstNameEntities,
mockNlpGreetingFullNameEntities,
mockNlpGreetingNameEntities,
} from '@/utils/test/mocks/nlp';
@ -353,6 +355,25 @@ describe('BlockService', () => {
]);
});
it('should return match nlp patterns with synonyms match (canonical value)', () => {
const result = blockService.getMatchingNluPatterns(
mockNlpFirstNameEntities,
{
...blockGetStarted,
patterns: [...blockGetStarted.patterns, mockNlpFirstNamePatterns],
},
);
expect(result).toEqual([
[
{
entity: 'firstname',
match: 'value',
value: 'jhon',
},
],
]);
});
it('should return empty array when it does not match nlp patterns', () => {
const result = blockService.getMatchingNluPatterns(
mockNlpGreetingFullNameEntities,

View File

@ -294,11 +294,11 @@ export class BlockService extends BaseService<
* @returns The NLU patterns that matches the predicted entities
*/
getMatchingNluPatterns<E extends NLU.ParseEntities, B extends BlockStub>(
nlp: E,
{ entities }: E,
block: B,
): NlpPattern[][] {
// No nlp entities to check against
if (nlp.entities.length === 0) {
if (entities.length === 0) {
return [];
}
@ -312,14 +312,17 @@ export class BlockService extends BaseService<
}
// Filter NLP patterns match based on best guessed entities
return nlpPatterns.filter((entities: NlpPattern[]) => {
return entities.every((ev: NlpPattern) => {
return nlpPatterns.filter((patterns: NlpPattern[]) => {
return patterns.every((ev: NlpPattern) => {
if (ev.match === 'value') {
return nlp.entities.find((e) => {
return e.entity === ev.entity && e.value === ev.value;
return entities.find((e) => {
return (
e.entity === ev.entity &&
(e.value === ev.value || e.canonicalValue === ev.value)
);
});
} else if (ev.match === 'entity') {
return nlp.entities.find((e) => {
return entities.find((e) => {
return e.entity === ev.entity;
});
} else {
@ -429,12 +432,14 @@ export class BlockService extends BaseService<
* - Returns `true` if all conditions are met, otherwise `false`.
*/
private matchesNluEntity<E extends NLU.ParseEntity>(
{ entity, value }: E,
{ entity, value, canonicalValue }: E,
pattern: NlpPattern,
): boolean {
return (
entity === pattern.entity &&
(pattern.match !== 'value' || value === pattern.value)
(pattern.match !== 'value' ||
value === pattern.value ||
canonicalValue === pattern.value)
);
}

View File

@ -265,14 +265,14 @@ describe('BaseNlpHelper', () => {
describe('extractPatternBasedSlots', () => {
it('should match using a valid regex pattern', () => {
const entity: NlpEntityFull = {
name: 'number',
name: 'infos',
values: [
{
value: 'number',
metadata: { pattern: '\\d+', wordBoundary: true },
},
],
} as any;
} as NlpEntityFull;
const result = helper.extractPatternBasedSlots(
'Order 123 and 456 now!',
@ -280,14 +280,16 @@ describe('BaseNlpHelper', () => {
);
expect(result).toEqual([
{
entity: 'number',
entity: 'infos',
canonicalValue: 'number',
value: '123',
start: 6,
end: 9,
confidence: 1,
},
{
entity: 'number',
entity: 'infos',
canonicalValue: 'number',
value: '456',
start: 14,
end: 17,
@ -298,10 +300,10 @@ describe('BaseNlpHelper', () => {
it('should respect metadata like toLowerCase and removeSpaces', () => {
const entity: NlpEntityFull = {
name: 'code',
name: 'name',
values: [
{
value: 'Code',
value: 'brand',
metadata: {
pattern: 'HEX BOT',
toLowerCase: true,
@ -309,7 +311,7 @@ describe('BaseNlpHelper', () => {
},
},
],
} as any;
} as NlpEntityFull;
const result = helper.extractPatternBasedSlots(
'My CODE is HEX BOT!',
@ -317,7 +319,8 @@ describe('BaseNlpHelper', () => {
);
expect(result).toEqual([
{
entity: 'code',
entity: 'name',
canonicalValue: 'brand',
value: 'hexbot',
start: 11,
end: 18,
@ -349,6 +352,7 @@ describe('BaseNlpHelper', () => {
expect(result).toEqual([
{
entity: 'keyword',
canonicalValue: 'word',
value: '"ou"',
start: 9,
end: 13,

View File

@ -288,9 +288,9 @@ export default abstract class BaseNlpHelper<
}
return (entity.values
.flatMap((patternValue) => {
.flatMap((nlpValue) => {
const processedText = text;
const pattern = patternValue.metadata?.pattern;
const pattern = nlpValue.metadata?.pattern;
if (!pattern) {
this.logger.error('Missing NLP regex pattern');
@ -299,7 +299,7 @@ export default abstract class BaseNlpHelper<
let regex: RegExp;
try {
const shouldWrap = patternValue.metadata?.wordBoundary;
const shouldWrap = nlpValue.metadata?.wordBoundary;
regex = new RegExp(shouldWrap ? `\\b${pattern}\\b` : pattern, 'gi');
} catch {
this.logger.error('Invalid NLP regex pattern');
@ -312,21 +312,22 @@ export default abstract class BaseNlpHelper<
let value = match[0];
// Apply preprocessing if needed
if (patternValue.metadata?.removeSpaces) {
if (nlpValue.metadata?.removeSpaces) {
value = value.replace(/\s+/g, '');
}
if (patternValue.metadata?.toLowerCase) {
if (nlpValue.metadata?.toLowerCase) {
value = value.toLowerCase();
}
if (patternValue.metadata?.stripDiacritics) {
if (nlpValue.metadata?.stripDiacritics) {
value = value.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
return {
entity: entity.name,
value,
canonicalValue: nlpValue.value,
start: match.index!,
end: match.index! + match[0].length,
confidence: 1,

View File

@ -21,6 +21,9 @@ export namespace NLU {
confidence: number;
start?: number;
end?: number;
// When lookup strategy is either 'keywords' or 'pattern', the canonical value
// is the actual NlpValue.value, given the match is either a synonym (expression) or a pattern match
canonicalValue?: string;
}
export interface ParseEntities {

View File

@ -51,6 +51,7 @@ export class NlpService {
.filter(({ entity }) => nlpMap.has(entity))
.map((e) => {
const entity = nlpMap.get(e.entity)!;
return {
...e,
score: e.confidence * (entity.weight || 1),

View File

@ -294,6 +294,14 @@ export const mockNlpGreetingAnyNamePatterns: NlpPattern[] = [
},
];
export const mockNlpFirstNamePatterns: NlpPattern[] = [
{
entity: 'firstname',
match: 'value',
value: 'jhon',
},
];
export const mockModifiedNlpBlock: BlockFull = {
...baseBlockInstance,
name: 'Modified Mock Nlp',

View File

@ -44,3 +44,14 @@ export const mockNlpGreetingFullNameEntities: NLU.ParseEntities = {
},
],
};
export const mockNlpFirstNameEntities: NLU.ParseEntities = {
entities: [
{
entity: 'firstname',
value: 'jhonny',
canonicalValue: 'jhon',
confidence: 0.75,
},
],
};