feat: add weight to nlp entity schema and readapt

This commit is contained in:
Mohamed Marrouchi 2025-04-02 11:57:57 +01:00 committed by MohamedAliBouhaouala
parent 95e07c84bc
commit 67ce8ebbfc
14 changed files with 61 additions and 20 deletions

View File

@ -83,12 +83,14 @@ const mockNlpEntityService = {
return Promise.resolve({ return Promise.resolve({
lookups: ['trait'], lookups: ['trait'],
id: '67e3e41eff551ca5be70559c', id: '67e3e41eff551ca5be70559c',
weight: 1,
}); });
} }
if (query.name === 'firstname') { if (query.name === 'firstname') {
return Promise.resolve({ return Promise.resolve({
lookups: ['trait'], lookups: ['trait'],
id: '67e3e41eff551ca5be70559d', id: '67e3e41eff551ca5be70559d',
weight: 1,
}); });
} }
return Promise.resolve(null); // Default response if the entity isn't found return Promise.resolve(null); // Default response if the entity isn't found
@ -444,7 +446,7 @@ describe('BlockService', () => {
expect(nlpEntityService.findOne).toHaveBeenCalledWith( expect(nlpEntityService.findOne).toHaveBeenCalledWith(
{ name: p.entity }, { name: p.entity },
undefined, undefined,
{ _id: 0, lookups: 1 }, { _id: 0, lookups: 1, weight: 1 },
); );
}); });
} }
@ -464,7 +466,7 @@ describe('BlockService', () => {
expect(nlpEntityService.findOne).toHaveBeenCalledWith( expect(nlpEntityService.findOne).toHaveBeenCalledWith(
{ name: p.entity }, { name: p.entity },
undefined, undefined,
{ _id: 0, lookups: 1 }, { _id: 0, lookups: 1, weight: 1 },
); );
}); });
} }

View File

@ -348,12 +348,6 @@ export class BlockService extends BaseService<
async matchBestNLP( async matchBestNLP(
blocks: Block[] | BlockFull[] | undefined, blocks: Block[] | BlockFull[] | undefined,
): Promise<Block | BlockFull | undefined> { ): Promise<Block | BlockFull | undefined> {
// @TODO make lookup scores configurable in hexabot settings
const lookupScores: { [key: string]: number } = {
trait: 2,
keywords: 1,
};
// No blocks to check against // No blocks to check against
if (blocks?.length === 0 || !blocks) { if (blocks?.length === 0 || !blocks) {
return undefined; return undefined;
@ -380,18 +374,14 @@ export class BlockService extends BaseService<
return await this.entityService.findOne( return await this.entityService.findOne(
{ name: entityName }, { name: entityName },
undefined, undefined,
{ lookups: 1, _id: 0 }, { lookups: 1, weight: 1, _id: 0 },
); );
}), }),
); );
nlpScore += entityLookups.reduce((score, entityLookup) => { nlpScore += entityLookups.reduce((score, entityLookup) => {
if ( if (entityLookup && entityLookup.lookups[0] && entityLookup.weight) {
entityLookup && return score + entityLookup.weight; // Add points based on the Nlp entity associated weight
entityLookup.lookups[0] &&
lookupScores[entityLookup.lookups[0]]
) {
return score + lookupScores[entityLookup.lookups[0]]; // Add points based on the lookup type
} }
return score; // Return the current score if no match return score; // Return the current score if no match
}, 0); }, 0);

View File

@ -41,7 +41,7 @@ export default [
{ {
group: WEB_CHANNEL_NAMESPACE, group: WEB_CHANNEL_NAMESPACE,
label: 'greeting_message', label: 'greeting_message',
value: 'Welcome! Ready to start a conversation with our chatbot?', value: '',
type: SettingType.textarea, type: SettingType.textarea,
translatable: true, translatable: true,
}, },

View File

@ -139,6 +139,7 @@ describe('BaseNlpHelper', () => {
updatedAt: new Date(), updatedAt: new Date(),
builtin: false, builtin: false,
lookups: [], lookups: [],
weight: 1,
}, },
entity2: { entity2: {
id: new ObjectId().toString(), id: new ObjectId().toString(),
@ -147,6 +148,7 @@ describe('BaseNlpHelper', () => {
updatedAt: new Date(), updatedAt: new Date(),
builtin: false, builtin: false,
lookups: [], lookups: [],
weight: 1,
}, },
}); });
jest.spyOn(NlpValue, 'getValueMap').mockReturnValue({ jest.spyOn(NlpValue, 'getValueMap').mockReturnValue({
@ -207,6 +209,7 @@ describe('BaseNlpHelper', () => {
updatedAt: new Date(), updatedAt: new Date(),
builtin: false, builtin: false,
lookups: [], lookups: [],
weight: 1,
}, },
}); });

View File

@ -109,6 +109,7 @@ describe('NlpEntityController', () => {
) as NlpEntityFull['values'], ) as NlpEntityFull['values'],
lookups: curr.lookups!, lookups: curr.lookups!,
builtin: curr.builtin!, builtin: curr.builtin!,
weight: curr.weight!,
}); });
return acc; return acc;
}, },
@ -163,6 +164,7 @@ describe('NlpEntityController', () => {
name: 'sentiment', name: 'sentiment',
lookups: ['trait'], lookups: ['trait'],
builtin: false, builtin: false,
weight: 1,
}; };
const result = await nlpEntityController.create(sentimentEntity); const result = await nlpEntityController.create(sentimentEntity);
expect(result).toEqualPayload(sentimentEntity); expect(result).toEqualPayload(sentimentEntity);
@ -214,6 +216,7 @@ describe('NlpEntityController', () => {
updatedAt: firstNameEntity!.updatedAt, updatedAt: firstNameEntity!.updatedAt,
lookups: firstNameEntity!.lookups, lookups: firstNameEntity!.lookups,
builtin: firstNameEntity!.builtin, builtin: firstNameEntity!.builtin,
weight: firstNameEntity!.weight,
}; };
const result = await nlpEntityController.findOne(firstNameEntity!.id, [ const result = await nlpEntityController.findOne(firstNameEntity!.id, [
'values', 'values',
@ -238,6 +241,7 @@ describe('NlpEntityController', () => {
doc: '', doc: '',
lookups: ['trait'], lookups: ['trait'],
builtin: false, builtin: false,
weight: 1,
}; };
const result = await nlpEntityController.updateOne( const result = await nlpEntityController.updateOne(
firstNameEntity!.id, firstNameEntity!.id,

View File

@ -372,6 +372,7 @@ describe('NlpSampleController', () => {
lookups: ['trait'], lookups: ['trait'],
doc: '', doc: '',
builtin: false, builtin: false,
weight: 1,
}; };
const priceValueEntity = await nlpEntityService.findOne({ const priceValueEntity = await nlpEntityService.findOne({
name: 'intent', name: 'intent',

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 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. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
@ -12,6 +12,7 @@ import {
IsBoolean, IsBoolean,
IsIn, IsIn,
IsNotEmpty, IsNotEmpty,
IsNumber,
IsOptional, IsOptional,
IsString, IsString,
Matches, Matches,
@ -47,6 +48,14 @@ export class NlpEntityCreateDto {
@IsBoolean() @IsBoolean()
@IsOptional() @IsOptional()
builtin?: boolean; builtin?: boolean;
@ApiPropertyOptional({
description: 'Nlp entity associated weight for next block triggering',
type: Number,
})
@IsNumber()
@IsOptional()
weight?: number;
} }
export type NlpEntityDto = DtoConfig<{ export type NlpEntityDto = DtoConfig<{

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 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. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
@ -58,6 +58,12 @@ export class NlpEntityStub extends BaseSchema {
@Prop({ type: Boolean, default: false }) @Prop({ type: Boolean, default: false })
builtin: boolean; builtin: boolean;
/**
* Entity's weight used to determine the next block to trigger in the conversational flow.
*/
@Prop({ type: Number, default: 1, unique: false })
weight: number;
/** /**
* Returns a map object for entities * Returns a map object for entities
* @param entities - Array of entities * @param entities - Array of entities

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 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. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
@ -17,18 +17,21 @@ export const nlpEntityFixtures: NlpEntityCreateDto[] = [
lookups: ['trait'], lookups: ['trait'],
doc: '', doc: '',
builtin: false, builtin: false,
weight: 1,
}, },
{ {
name: 'first_name', name: 'first_name',
lookups: ['keywords'], lookups: ['keywords'],
doc: '', doc: '',
builtin: false, builtin: false,
weight: 1,
}, },
{ {
name: 'built_in', name: 'built_in',
lookups: ['trait'], lookups: ['trait'],
doc: '', doc: '',
builtin: true, builtin: true,
weight: 1,
}, },
]; ];

View File

@ -348,6 +348,7 @@
"nlp_lookup_trait": "Trait", "nlp_lookup_trait": "Trait",
"doc": "Documentation", "doc": "Documentation",
"builtin": "Built-in?", "builtin": "Built-in?",
"weight": "Weight",
"dataset": "Dataset", "dataset": "Dataset",
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",

View File

@ -347,6 +347,7 @@
"nlp_lookup_trait": "Trait", "nlp_lookup_trait": "Trait",
"synonyms": "Synonymes", "synonyms": "Synonymes",
"doc": "Documentation", "doc": "Documentation",
"weight": "Poids",
"builtin": "Intégré?", "builtin": "Intégré?",
"dataset": "Données", "dataset": "Données",
"yes": "Oui", "yes": "Oui",

View File

@ -165,6 +165,16 @@ const NlpEntity = () => {
resizable: false, resizable: false,
renderHeader, renderHeader,
}, },
{
maxWidth: 210,
field: "weight",
headerName: t("label.weight"),
renderCell: (val) => <Chip label={val.value} variant="title" />,
sortable: true,
disableColumnMenu: true,
resizable: false,
renderHeader,
},
{ {
maxWidth: 90, maxWidth: 90,
field: "builtin", field: "builtin",

View File

@ -60,6 +60,7 @@ export const NlpEntityVarForm: FC<ComponentFormProps<INlpEntity>> = ({
name: data?.name || "", name: data?.name || "",
doc: data?.doc || "", doc: data?.doc || "",
lookups: data?.lookups || ["keywords"], lookups: data?.lookups || ["keywords"],
weight: data?.weight || 1,
}, },
}); });
const validationRules = { const validationRules = {
@ -82,6 +83,7 @@ export const NlpEntityVarForm: FC<ComponentFormProps<INlpEntity>> = ({
reset({ reset({
name: data.name, name: data.name,
doc: data.doc, doc: data.doc,
weight: data.weight,
}); });
} else { } else {
reset(); reset();
@ -130,6 +132,14 @@ export const NlpEntityVarForm: FC<ComponentFormProps<INlpEntity>> = ({
multiline={true} multiline={true}
/> />
</ContentItem> </ContentItem>
<ContentItem>
<Input
label={t("label.weight")}
{...register("weight", { valueAsNumber: true })}
type="number"
inputProps={{ min: 1, step: 1 }} // Restricts input to positive integers only
/>
</ContentItem>
</ContentContainer> </ContentContainer>
</form> </form>
</Wrapper> </Wrapper>

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2024 Hexastack. All rights reserved. * Copyright © 2025 Hexastack. All rights reserved.
* *
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 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. * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
@ -19,6 +19,7 @@ export interface INlpEntityAttributes {
lookups: Lookup[]; lookups: Lookup[];
doc?: string; doc?: string;
builtin?: boolean; builtin?: boolean;
weight: number;
} }
export enum NlpLookups { export enum NlpLookups {