feat: add the flow escape new type of helpers

This commit is contained in:
Mohamed Marrouchi 2025-06-12 11:23:40 +01:00
parent d77a02032a
commit c5e7bbcd1d
11 changed files with 138 additions and 49 deletions

View File

@ -161,8 +161,13 @@ export class HelperService {
}
const settings = await this.settingService.getSettings();
const defaultHelperKey = `default_${type}_helper`;
if (!(defaultHelperKey in settings.chatbot_settings)) {
throw new Error(`Default ${type.toUpperCase()} helper setting not found`);
}
const defaultHelperName = settings.chatbot_settings[
`default_${type}_helper` as any
defaultHelperKey
] as HelperName;
const defaultHelper = this.get<T>(type, defaultHelperName);

View File

@ -0,0 +1,52 @@
/*
* 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 EventWrapper from '@/channel/lib/EventWrapper';
import { BlockStub } from '@/chat/schemas/block.schema';
import { LoggerService } from '@/logger/logger.service';
import { SettingService } from '@/setting/services/setting.service';
import { HelperService } from '../helper.service';
import { FlowEscape, HelperName, HelperType } from '../types';
import BaseHelper from './base-helper';
export default abstract class BaseFlowEscapeHelper<
N extends HelperName = HelperName,
> extends BaseHelper<N> {
protected readonly type: HelperType = HelperType.FLOW_ESCAPE;
constructor(
name: N,
settingService: SettingService,
helperService: HelperService,
logger: LoggerService,
) {
super(name, settingService, helperService, logger);
}
/**
* Checks if the helper can handle the flow escape for the given block message.
*
* @param _blockMessage - The block message to check.
* @returns - Whether the helper can handle the flow escape for the given block message.
*/
abstract canHandleFlowEscape<T extends BlockStub>(_blockMessage: T): boolean;
/**
* Adjudicates the flow escape event.
*
* @param _event - The event wrapper containing the event data.
* @param _block - The block associated with the event.
* @returns - A promise that resolves to a FlowEscape.AdjudicationResult.
*/
abstract adjudicate<T extends BlockStub>(
_event: EventWrapper<any, any>,
_block: T,
): Promise<FlowEscape.AdjudicationResult>;
}

View File

@ -9,6 +9,7 @@
import { ExtensionSetting } from '@/setting/schemas/types';
import { HyphenToUnderscore } from '@/utils/types/extension';
import BaseFlowEscapeHelper from './lib/base-flow-escape-helper';
import BaseHelper from './lib/base-helper';
import BaseLlmHelper from './lib/base-llm-helper';
import BaseNlpHelper from './lib/base-nlp-helper';
@ -93,9 +94,31 @@ export namespace LLM {
}
}
export namespace FlowEscape {
export enum Action {
REPROMPT = 're_prompt',
COERCE = 'coerce_to_option',
NEW_CTX = 'new_context',
}
export type AdjudicationResult =
| {
action: Action.COERCE;
coercedOption: string;
}
| {
action: Action.REPROMPT;
repromptMessage?: string;
}
| {
action: Action.NEW_CTX;
};
}
export enum HelperType {
NLU = 'nlu',
LLM = 'llm',
FLOW_ESCAPE = 'flow_escape',
STORAGE = 'storage',
UTIL = 'util',
}
@ -105,6 +128,7 @@ export type HelperName = `${string}-helper`;
interface HelperTypeMap {
[HelperType.NLU]: BaseNlpHelper<HelperName>;
[HelperType.LLM]: BaseLlmHelper<HelperName>;
[HelperType.FLOW_ESCAPE]: BaseFlowEscapeHelper<HelperName>;
[HelperType.STORAGE]: BaseStorageHelper<HelperName>;
[HelperType.UTIL]: BaseHelper;
}

View File

@ -50,6 +50,20 @@ export const DEFAULT_SETTINGS = [
},
weight: 3,
},
{
group: 'chatbot_settings',
label: 'default_flow_escape_helper',
value: '',
type: SettingType.select,
config: {
multiple: false,
allowCreate: false,
entity: 'Helper',
idKey: 'name',
labelKey: 'name',
},
weight: 3,
},
{
group: 'chatbot_settings',
label: 'default_storage_helper',

View File

@ -9,7 +9,8 @@
"default_nlu_helper": "Default NLU Helper",
"default_llm_helper": "Default LLM Helper",
"default_storage_helper": "Default Storage Helper",
"default_nlu_penalty_factor": "NLU Penalty Factor"
"default_nlu_penalty_factor": "NLU Penalty Factor",
"default_flow_escape_helper": "Default Flow Escape Helper"
},
"help": {
"global_fallback": "Global fallback allows you to send custom messages when user entry does not match any of the block messages.",
@ -17,6 +18,7 @@
"default_nlu_helper": "The NLU helper is responsible for processing and understanding user inputs, including tasks like intent prediction, language detection, and entity recognition.",
"default_llm_helper": "The LLM helper leverages advanced generative AI to perform tasks such as text generation, chat completion, and complex query responses.",
"default_storage_helper": "The storage helper defines where to store attachment files. By default, the default local storage helper stores them locally, but you can choose to use Minio or any other storage solution.",
"default_nlu_penalty_factor": "The NLU penalty factor is a coefficient (between 0 and 1) applied exclusively to NLU-based entity matching. It reduces the score contribution of patterns that match broadly (e.g. using wildcard values like Any) rather than specific entity values. This helps the engine prioritize blocks triggered by more precise NLU matches, without affecting other matching strategies such as text, regex, or interaction triggers."
"default_nlu_penalty_factor": "The NLU penalty factor is a coefficient (between 0 and 1) applied exclusively to NLU-based entity matching. It reduces the score contribution of patterns that match broadly (e.g. using wildcard values like Any) rather than specific entity values. This helps the engine prioritize blocks triggered by more precise NLU matches, without affecting other matching strategies such as text, regex, or interaction triggers.",
"default_flow_escape_helper": "The Flow Escape helper is used when the users message does not match any option in a flow. It assists the chatbot in deciding whether to re-prompt, provide an explanation, or end the conversation."
}
}

View File

@ -9,7 +9,8 @@
"default_nlu_helper": "Utilitaire NLU par défaut",
"default_llm_helper": "Utilitaire LLM par défaut",
"default_storage_helper": "Utilitaire de stockage par défaut",
"default_nlu_penalty_factor": "Facteur de pénalité NLU"
"default_nlu_penalty_factor": "Facteur de pénalité NLU",
"default_flow_escape_helper": "Utilitaire de secours de flux par défaut"
},
"help": {
"global_fallback": "La réponse de secours globale vous permet d'envoyer des messages personnalisés lorsque l'entrée de l'utilisateur ne correspond à aucun des messages des blocs.",
@ -17,6 +18,7 @@
"default_nlu_helper": "Utilitaire du traitement et de la compréhension des entrées des utilisateurs, incluant des tâches telles que la prédiction d'intention, la détection de langue et la reconnaissance d'entités.",
"default_llm_helper": "Utilitaire responsable de l'intelligence artificielle générative avancée pour effectuer des tâches telles que la génération de texte, la complétion de chat et les réponses à des requêtes complexes.",
"default_storage_helper": "Utilitaire de stockage définit l'emplacement où stocker les fichiers joints. Par défaut, le stockage local les conserve localement, mais vous pouvez choisir d'utiliser Minio ou toute autre solution de stockage.",
"default_nlu_penalty_factor": "Le facteur de pénalité NLU est un coefficient (entre 0 et 1) appliqué exclusivement aux correspondances d'entités basées sur NLU. Il réduit la contribution au score des motifs qui correspondent de manière générale (par exemple, en utilisant des valeurs génériques comme Any) plutôt que des valeurs d'entité spécifiques. Cela permet au chatbot de donner la priorité aux blocs déclenchés par des correspondances NLU plus précises, sans affecter d'autres stratégies de correspondance telles que le texte, les expressions regex ou les déclencheurs d'interaction."
"default_nlu_penalty_factor": "Le facteur de pénalité NLU est un coefficient (entre 0 et 1) appliqué exclusivement aux correspondances d'entités basées sur NLU. Il réduit la contribution au score des motifs qui correspondent de manière générale (par exemple, en utilisant des valeurs génériques comme Any) plutôt que des valeurs d'entité spécifiques. Cela permet au chatbot de donner la priorité aux blocs déclenchés par des correspondances NLU plus précises, sans affecter d'autres stratégies de correspondance telles que le texte, les expressions regex ou les déclencheurs d'interaction.",
"default_flow_escape_helper": "Lutilitaire de secours de flux est utilisé lorsque le message de lutilisateur ne correspond à aucune option dans un scénario. Il aide le chatbot à décider sil faut reformuler la question, fournir une explication ou mettre fin à la conversation."
}
}

View File

@ -20,8 +20,8 @@ import { PasswordInput } from "@/app-components/inputs/PasswordInput";
import { useTranslate } from "@/hooks/useTranslate";
import { EntityType, Format } from "@/services/types";
import { AttachmentResourceRef } from "@/types/attachment.types";
import { IEntityMapTypes } from "@/types/base.types";
import { IBlock } from "@/types/block.types";
import { IHelper } from "@/types/helper.types";
import { ISetting, SettingType } from "@/types/setting.types";
import { MIME_TYPES } from "@/utils/attachment";
@ -32,6 +32,12 @@ interface RenderSettingInputProps {
isDisabled?: (setting: ISetting) => boolean;
}
const DEFAULT_HELPER_ENTITIES: Record<string, keyof IEntityMapTypes> = {
["default_nlu_helper"]: EntityType.NLU_HELPER,
["default_llm_helper"]: EntityType.LLM_HELPER,
["default_flow_escape_helper"]: EntityType.FLOW_ESCAPE_HELPER,
["default_storage_helper"]: EntityType.STORAGE_HELPER,
};
const SettingInput: React.FC<RenderSettingInputProps> = ({
setting,
field,
@ -125,54 +131,25 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
{...rest}
/>
);
} else if (setting.label === "default_nlu_helper") {
} else if (
setting.label.startsWith("default_") &&
setting.label.endsWith("_helper")
) {
const { onChange, ...rest } = field;
return (
<AutoCompleteEntitySelect<IHelper, "name", false>
<AutoCompleteEntitySelect<any, string, boolean>
searchFields={["name"]}
entity={EntityType.NLU_HELPER}
entity={DEFAULT_HELPER_ENTITIES[setting.label]}
format={Format.BASIC}
labelKey="name"
idKey="name"
label={t("label.default_nlu_helper")}
helperText={t("help.default_nlu_helper")}
multiple={false}
onChange={(_e, selected, ..._) => onChange(selected?.name)}
{...rest}
/>
);
} else if (setting.label === "default_llm_helper") {
const { onChange, ...rest } = field;
return (
<AutoCompleteEntitySelect<IHelper, "name", false>
searchFields={["name"]}
entity={EntityType.LLM_HELPER}
format={Format.BASIC}
labelKey="name"
idKey="name"
label={t("label.default_llm_helper")}
helperText={t("help.default_llm_helper")}
multiple={false}
onChange={(_e, selected, ..._) => onChange(selected?.name)}
{...rest}
/>
);
} else if (setting.label === "default_storage_helper") {
const { onChange, ...rest } = field;
return (
<AutoCompleteEntitySelect<IHelper, "name", false>
searchFields={["name"]}
entity={EntityType.STORAGE_HELPER}
format={Format.BASIC}
labelKey="name"
idKey="name"
label={t("label.default_storage_helper")}
helperText={t("help.default_storage_helper")}
multiple={false}
onChange={(_e, selected, ..._) => onChange(selected?.name)}
labelKey={setting.config?.labelKey || "name"}
idKey={setting.config?.idKey || "name"}
label={label}
helperText={helperText}
multiple={!!setting.config?.multiple}
onChange={(_e, selected, ..._) =>
onChange(selected?.[setting.config?.idKey || "name"])
}
{...rest}
/>
);

View File

@ -74,6 +74,7 @@ export const ROUTES = {
[EntityType.HELPER]: "/helper",
[EntityType.NLU_HELPER]: "/helper/nlu",
[EntityType.LLM_HELPER]: "/helper/llm",
[EntityType.FLOW_ESCAPE_HELPER]: "helper/flow_escape",
[EntityType.STORAGE_HELPER]: "/helper/storage",
} as const;

View File

@ -304,6 +304,14 @@ export const LlmHelperEntity = new schema.Entity(
},
);
export const FlowEscapeHelperEntity = new schema.Entity(
EntityType.FLOW_ESCAPE_HELPER,
undefined,
{
idAttribute: ({ name }) => name,
},
);
export const StorageHelperEntity = new schema.Entity(
EntityType.STORAGE_HELPER,
undefined,
@ -341,5 +349,6 @@ export const ENTITY_MAP = {
[EntityType.HELPER]: HelperEntity,
[EntityType.NLU_HELPER]: NluHelperEntity,
[EntityType.LLM_HELPER]: LlmHelperEntity,
[EntityType.FLOW_ESCAPE_HELPER]: FlowEscapeHelperEntity,
[EntityType.STORAGE_HELPER]: StorageHelperEntity,
} as const;

View File

@ -38,6 +38,7 @@ export enum EntityType {
HELPER = "Helper",
NLU_HELPER = "NluHelper",
LLM_HELPER = "LlmHelper",
FLOW_ESCAPE_HELPER = "FlowEscapeHelper",
STORAGE_HELPER = "StorageHelper",
}

View File

@ -116,6 +116,7 @@ export const POPULATE_BY_TYPE = {
[EntityType.HELPER]: [],
[EntityType.NLU_HELPER]: [],
[EntityType.LLM_HELPER]: [],
[EntityType.FLOW_ESCAPE_HELPER]: [],
[EntityType.STORAGE_HELPER]: [],
} as const;
@ -208,6 +209,7 @@ export interface IEntityMapTypes {
[EntityType.HELPER]: IEntityTypes<IHelperAttributes, IHelper>;
[EntityType.NLU_HELPER]: IEntityTypes<IHelperAttributes, IHelper>;
[EntityType.LLM_HELPER]: IEntityTypes<IHelperAttributes, IHelper>;
[EntityType.FLOW_ESCAPE_HELPER]: IEntityTypes<IHelperAttributes, IHelper>;
[EntityType.STORAGE_HELPER]: IEntityTypes<IHelperAttributes, IHelper>;
}