Skip to content

feat: make nlu penalty factor setting configurable in UI #956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
95e07c8
feat: implement nlp based blocks prioritization strategy
Mar 26, 2025
67ce8eb
feat: add weight to nlp entity schema and readapt
Apr 2, 2025
1760e76
feat: remove commented obsolete code
Apr 2, 2025
46e0e2e
feat: restore settings
Apr 2, 2025
1db34e6
feat: apply feedback
Apr 2, 2025
5f08076
fix: re-adapt unit tests
Apr 4, 2025
896f01c
feat: priority scoring re-calculation & enabling weight modification …
Apr 7, 2025
58f92b9
fix: remove obsolete code
Apr 7, 2025
d27ee6d
feat: refine unit tests, apply mr coderabbit suggestions
Apr 8, 2025
01a97cc
fix: minor refactoring
Apr 8, 2025
eeb9142
feat: add nlp cache map type
Apr 8, 2025
3fc5762
feat: refine builtin nlp entities weight updates
Apr 14, 2025
f61e1eb
feat: add more test cases and refine edge case handling
Apr 14, 2025
8255f57
feat: add weight validation in UI
Apr 17, 2025
c2c0bd3
fix: apply feedback
Apr 22, 2025
ed959a1
feat: add a penalty factor & fix unit tests
Apr 23, 2025
1240138
feat: add documentation
Apr 23, 2025
a82fe04
fix: correct syntax
Apr 23, 2025
df9f192
fix: remove stale log statement
Apr 23, 2025
5a775a2
fix: enforce nlp entity weight restrictions
Apr 23, 2025
9bfac97
fix: correct typo in docs
Apr 23, 2025
e290a79
fix: typos in docs
Apr 23, 2025
07a2b9c
fix: fix formatting for function comment
Apr 23, 2025
3550529
fix: restore matchNLP function previous code
Apr 23, 2025
023efad
fix: remove blank line, make updateOne asynchronous
Apr 23, 2025
df3c552
fix: add AND operator in docs
Apr 24, 2025
8f6028a
fix: handle dependency injection in chat module
Apr 24, 2025
34f6baa
feat: refactor to use findAndPopulate in block score calculation
Apr 24, 2025
1941c54
feat: refine caching mechanisms
Apr 24, 2025
9318e3f
feat: add typing and enforce safety checks
Apr 24, 2025
5ead99c
fix: remove typo
Apr 24, 2025
aeee392
fix: remove async from block score calculation
Apr 25, 2025
999b634
fix: remove typo
Apr 25, 2025
89c46e9
fix: correct linting
Apr 25, 2025
a8666ce
fix: refine nlp pattern type check
Apr 28, 2025
c121ce7
feat: add weight to nlp entity schema and readapt
Apr 2, 2025
0894869
feat: implement nlp based blocks prioritization strategy
Mar 26, 2025
83ee5b9
fix: handle dependency injection in chat module
Apr 24, 2025
e9e3184
feat: make nlu penalty factor setting configurable in UI
Apr 24, 2025
0ce7894
fix: enforce typing
Apr 24, 2025
68835bd
fix: restore files
Apr 25, 2025
808f33b
fix: restore old files
Apr 25, 2025
4c9f7b4
fix: handle setting migrations in v2.2.6
Apr 25, 2025
384e777
fix: retailor log messages
Apr 28, 2025
40c1349
fix: remove extra penalty factor validation
Apr 28, 2025
e3ce245
fix: add a fallback nlu penalty factor, handle edge cases and fix typ…
Apr 28, 2025
a0b6f9b
fix: restore previous file
May 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions api/docs/nlp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# NLP Block Scoring
## Purpose

**NLP Block Scoring** is a mechanism used to select the most relevant response block based on:

- Matching patterns between user input and block definitions
- Configurable weights assigned to each entity type
- Confidence values provided by the NLU engine for detected entities

It enables more intelligent and context-aware block selection in conversational flows.

## Core Use Cases
### Standard Matching

A user input contains entities that directly match a block’s patterns.
```bash
Example: Input: intent = enquiry & subject = claim
Block A: Patterns: intent: enquiry & subject: claim
Block A will be selected.
```

### High Confidence, Partial Match

A block may match only some patterns but have high-confidence input on those matched ones, making it a better candidate than others with full matches but low-confidence entities.
**Note: Confidence is multiplied by a pre-defined weight for each entity type.**

```bash
Example:
Input: intent = issue (confidence: 0.92) & subject = claim (confidence: 0.65)
Block A: Pattern: intent: issue
Block B: Pattern: subject: claim
➤ Block A gets a high score based on confidence × weight (assuming both weights are equal to 1).
```

### Multiple Blocks with Similar Patterns

```bash
Input: intent = issue & subject = insurance
Block A: intent = enquiry & subject = insurance
Block B: subject = insurance
➤ Block B is selected — Block A mismatches on intent.
```

### Exclusion Due to Extra Patterns

If a block contains patterns that require entities not present in the user input, the block is excluded from scoring altogether. No penalties are applied — the block simply isn't considered a valid candidate.

```bash
Input: intent = issue & subject = insurance
Block A: intent = enquiry & subject = insurance & location = office
Block B: subject = insurance & time = morning
➤ Neither block is selected due to unmatched required patterns (`location`, `time`)
```

### Tie-Breaking with Penalty Factors

When multiple blocks receive similar scores, penalty factors can help break the tie — especially in cases where patterns are less specific (e.g., using `Any` as a value).

```bash
Input: intent = enquiry & subject = insurance

Block A: intent = enquiry & subject = Any
Block B: intent = enquiry & subject = insurance
Block C: subject = insurance

Scoring Summary:
- Block A matches both patterns, but subject = Any is considered less specific.
- Block B has a redundant but fully specific match.
- Block C matches only one pattern.

➤ Block A and Block B have similar raw scores.
➤ A penalty factor is applied to Block A due to its use of Any, reducing its final score.
➤ Block B is selected.
```

## How Scoring Works
### Matching and Confidence

For each entity in the block's pattern:
- If the entity `matches` an entity in the user input:
- the score is increased by: `confidence × weight`
- `Confidence` is a value between 0 and 1, returned by the NLU engine.
- `Weight` is a configured importance factor for that specific entity type.
- If the match is a wildcard (i.e., the block accepts any value):
- A **penalty factor** is applied to slightly reduce its contribution:
``confidence × weight × penaltyFactor``. This encourages more specific matches when available.

### Scoring Formula Summary

For each matched entity:

```bash
score += confidence × weight × [optional penalty factor if wildcard]
```

The total block score is the sum of all matched patterns in that block.

### Penalty Factor

The **penalty factor** is a global multiplier (typically less than `1`, e.g., `0.8`) applied when the match type is less specific — such as wildcard or loose entity type matches. It allows the system to:
- Break ties in favor of more precise blocks
- Discourage overly generic blocks from being selected when better matches are available
2 changes: 2 additions & 0 deletions api/src/chat/chat.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AttachmentModel } from '@/attachment/schemas/attachment.schema';
import { AttachmentService } from '@/attachment/services/attachment.service';
import { ChannelModule } from '@/channel/channel.module';
import { CmsModule } from '@/cms/cms.module';
import { NlpModule } from '@/nlp/nlp.module';
import { UserModule } from '@/user/user.module';

import { BlockController } from './controllers/block.controller';
Expand Down Expand Up @@ -68,6 +69,7 @@ import { SubscriberService } from './services/subscriber.service';
AttachmentModule,
EventEmitter2,
UserModule,
NlpModule,
],
controllers: [
CategoryController,
Expand Down
18 changes: 18 additions & 0 deletions api/src/chat/controllers/block.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ 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 { LoggerService } from '@/logger/logger.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 { PluginService } from '@/plugins/plugins.service';
import { SettingService } from '@/setting/services/setting.service';
import { InvitationRepository } from '@/user/repositories/invitation.repository';
Expand Down Expand Up @@ -93,6 +102,9 @@ describe('BlockController', () => {
RoleModel,
PermissionModel,
LanguageModel,
NlpEntityModel,
NlpSampleEntityModel,
NlpValueModel,
]),
],
providers: [
Expand All @@ -116,6 +128,12 @@ describe('BlockController', () => {
PermissionService,
LanguageService,
PluginService,
LoggerService,
NlpEntityService,
NlpEntityRepository,
NlpSampleEntityRepository,
NlpValueRepository,
NlpValueService,
{
provide: I18nService,
useValue: {
Expand Down
18 changes: 18 additions & 0 deletions api/src/chat/schemas/types/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import { z } from 'zod';

import { BlockFull } from '../block.schema';

import { PayloadType } from './button';

export const payloadPatternSchema = z.object({
Expand Down Expand Up @@ -57,3 +59,19 @@ export const patternSchema = z.union([
]);

export type Pattern = z.infer<typeof patternSchema>;

export type NlpPatternMatchResult = {
block: BlockFull;
matchedPattern: NlpPattern[];
};

export function isNlpPattern(pattern: NlpPattern) {
return (
(typeof pattern === 'object' &&
pattern !== null &&
'entity' in pattern &&
'match' in pattern &&
pattern.match === 'entity') ||
pattern.match === 'value'
);
}
Loading