@memberjunction/ai-vector-dupe 6.2.0-edge.0 → 6.2.0-edge.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/duplicateEntryCheckTypes.d.ts +80 -0
- package/dist/duplicateEntryCheckTypes.d.ts.map +1 -0
- package/dist/duplicateEntryCheckTypes.js +29 -0
- package/dist/duplicateEntryCheckTypes.js.map +1 -0
- package/dist/duplicateRecordDetector.d.ts +153 -13
- package/dist/duplicateRecordDetector.d.ts.map +1 -1
- package/dist/duplicateRecordDetector.js +436 -50
- package/dist/duplicateRecordDetector.js.map +1 -1
- package/dist/entryCheckDeadline.d.ts +73 -0
- package/dist/entryCheckDeadline.d.ts.map +1 -0
- package/dist/entryCheckDeadline.js +118 -0
- package/dist/entryCheckDeadline.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/reasoning/DecisionReasoningProvider.d.ts +177 -0
- package/dist/reasoning/DecisionReasoningProvider.d.ts.map +1 -0
- package/dist/reasoning/DecisionReasoningProvider.js +306 -0
- package/dist/reasoning/DecisionReasoningProvider.js.map +1 -0
- package/dist/reasoning/DecisionThenPromptReasoningProvider.d.ts +88 -0
- package/dist/reasoning/DecisionThenPromptReasoningProvider.d.ts.map +1 -0
- package/dist/reasoning/DecisionThenPromptReasoningProvider.js +159 -0
- package/dist/reasoning/DecisionThenPromptReasoningProvider.js.map +1 -0
- package/dist/reasoning/DuplicateReasoningProvider.d.ts +12 -6
- package/dist/reasoning/DuplicateReasoningProvider.d.ts.map +1 -1
- package/dist/reasoning/DuplicateReasoningProvider.js +12 -6
- package/dist/reasoning/DuplicateReasoningProvider.js.map +1 -1
- package/dist/reasoning/DuplicateReasoningTypes.d.ts +21 -2
- package/dist/reasoning/DuplicateReasoningTypes.d.ts.map +1 -1
- package/dist/reasoning/MatchedSetDeltaBuilder.d.ts +17 -3
- package/dist/reasoning/MatchedSetDeltaBuilder.d.ts.map +1 -1
- package/dist/reasoning/MatchedSetDeltaBuilder.js +49 -2
- package/dist/reasoning/MatchedSetDeltaBuilder.js.map +1 -1
- package/package.json +14 -14
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 'Decision' mode reasoning provider.
|
|
3
|
+
*
|
|
4
|
+
* Asks a typed decision model one Likelihood per candidate ("the candidate record is the same
|
|
5
|
+
* real-world entity as the new record") in a single `AIDecisionRunner` call per matched set. The
|
|
6
|
+
* state is the same fields the prompt provider renders, from the same helper, serialized compactly.
|
|
7
|
+
*
|
|
8
|
+
* It only **recommends**: a candidate whose probability is at or above
|
|
9
|
+
* {@link DecisionReasoningProvider.UncertainAbove} is `Uncertain` and flagged for a person; one below
|
|
10
|
+
* it is `NotDuplicate`. It never emits `Merge` and never proposes a survivor or a field map, so a
|
|
11
|
+
* `Decision`-mode set is never auto-merge eligible (typed-decision plan §3.6, rule 8). Survivor and
|
|
12
|
+
* field choices remain LLM work: see `DecisionThenPromptReasoningProvider`.
|
|
13
|
+
*
|
|
14
|
+
* A failed decision is a failed reasoning call, handled as the `Prompt` mode handles one: the
|
|
15
|
+
* detector saves the set's match rows with empty LLM columns, so every candidate stays `Pending`
|
|
16
|
+
* for review and none is auto-merge eligible.
|
|
17
|
+
*
|
|
18
|
+
* @module @memberjunction/ai-vector-dupe
|
|
19
|
+
*/
|
|
20
|
+
import { AIDecisionParams } from '@memberjunction/ai-prompts';
|
|
21
|
+
import { type DecisionAnsweringModel, type DecisionModelCalibration, type MJAIPromptEntityExtended } from '@memberjunction/ai-core-plus';
|
|
22
|
+
import { type DecisionQuestion, type PlattCalibration } from '@memberjunction/ai';
|
|
23
|
+
import { DuplicateReasoningProvider } from './DuplicateReasoningProvider.js';
|
|
24
|
+
import { DuplicateReasoningInput, DuplicateReasoningOutput, DuplicateReasoningContext, DuplicateReasoningCandidateVerdict, ReasoningCandidate, ReasoningSourceRecord } from './DuplicateReasoningTypes.js';
|
|
25
|
+
/** One candidate's probability of being the same real-world entity as the source record. */
|
|
26
|
+
export interface DuplicateCandidateProbability {
|
|
27
|
+
/** The candidate record id (matches the input candidate's RecordID). */
|
|
28
|
+
RecordID: string;
|
|
29
|
+
/**
|
|
30
|
+
* The Likelihood's probability in [0, 1], **calibrated** for the model that answered (see
|
|
31
|
+
* {@link DUPLICATE_DECISION_CALIBRATION}). Null when the decision returned no answer for this
|
|
32
|
+
* candidate, or when the answering model has no calibration.
|
|
33
|
+
*/
|
|
34
|
+
Probability: number | null;
|
|
35
|
+
/** The model's own probability, before calibration; null when there was no answer. */
|
|
36
|
+
RawProbability?: number | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Platt calibration of the duplicate Likelihood ("the candidate is the same real-world entity as
|
|
40
|
+
* the new record"), per decision model, each tied to the exact model it was fitted on (see
|
|
41
|
+
* `FindDecisionCalibration` in `@memberjunction/ai-core-plus`): the MJ decision model that answered
|
|
42
|
+
* (`ModelName`) and the model the driver reports behind it (`ResolvedModel`). A model's raw
|
|
43
|
+
* probabilities are not calibrated: Jev ranks candidates almost perfectly (AUC 0.99) but its raw
|
|
44
|
+
* probabilities run high for similar records that are not duplicates.
|
|
45
|
+
*
|
|
46
|
+
* Any other model, including Jev at another version or `LLM Decision` answered by another chat
|
|
47
|
+
* model, gives no calibrated probability, since its raw one can't be banded. The entry check then
|
|
48
|
+
* flags nothing, as it does for a failed decision; batch `Decision` mode bands its candidates
|
|
49
|
+
* `Uncertain` for review; and `DecisionThenPrompt` sends them all to the prompt. The provider logs
|
|
50
|
+
* the missing calibration once per model. An app that binds another decision model supplies its
|
|
51
|
+
* calibration by overriding {@link DecisionReasoningProvider.CalibrationFor}.
|
|
52
|
+
*
|
|
53
|
+
* Fitted on the duplicate-check measurement (plan Task 3.8, 2026-09-29), on every candidate: 160
|
|
54
|
+
* labelled new records for MJ: Actions (80 rewrites of an existing action, 80 similar actions that
|
|
55
|
+
* don't exist), five vector candidates each.
|
|
56
|
+
* - **Jev** at its pinned `APIName`, `typesafe/jev-1.13-20260917`, which OpenRouter reports back as
|
|
57
|
+
* the resolved model.
|
|
58
|
+
* - **LLM Decision** when its chat model is GPT-OSS-120B, its `LLM Decision` prompt's first choice.
|
|
59
|
+
* The stored runs don't record the resolved model; this is the prompt's binding on that day.
|
|
60
|
+
*
|
|
61
|
+
* Refit whenever a model's pinned version, its delegated chat model or the question changes.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DUPLICATE_DECISION_CALIBRATION: readonly DecisionModelCalibration<PlattCalibration>[];
|
|
64
|
+
/** The shipped calibration for the exact model that answered, or null when it has none. */
|
|
65
|
+
export declare function DuplicateDecisionCalibrationFor(answeredBy: DecisionAnsweringModel): PlattCalibration | null;
|
|
66
|
+
/**
|
|
67
|
+
* The duplicate Likelihood calibrated for the exact model that answered, or null when that model
|
|
68
|
+
* has no calibration.
|
|
69
|
+
*/
|
|
70
|
+
export declare function CalibratedDuplicateProbability(probability: number, answeredBy: DecisionAnsweringModel): number | null;
|
|
71
|
+
/** The outcome of one decision call over a matched set. */
|
|
72
|
+
export interface DuplicateDecisionResult {
|
|
73
|
+
/** Whether the decision call succeeded. */
|
|
74
|
+
Success: boolean;
|
|
75
|
+
/** Why the call failed, when {@link Success} is false. */
|
|
76
|
+
ErrorMessage?: string;
|
|
77
|
+
/** One entry per input candidate, in input order. Empty when the call failed. */
|
|
78
|
+
Candidates: DuplicateCandidateProbability[];
|
|
79
|
+
/** The decision's `MJ: AI Prompt Runs` row id, when the runner wrote one. */
|
|
80
|
+
AIPromptRunID: string | null;
|
|
81
|
+
/**
|
|
82
|
+
* Set when the exact model that answered has no calibration: the model, as
|
|
83
|
+
* `DescribeAnsweringModel` names it (`Jev (typesafe/jev-1.14-...)`). Every candidate's
|
|
84
|
+
* `Probability` is then null and its `RawProbability` holds the model's own answer. The entry
|
|
85
|
+
* check treats such a result like a failed decision and flags nothing.
|
|
86
|
+
*/
|
|
87
|
+
UncalibratedModel?: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Typed-decision reasoning provider. Registered under the 'Decision' `ReasoningMode`.
|
|
91
|
+
*/
|
|
92
|
+
export declare class DecisionReasoningProvider extends DuplicateReasoningProvider {
|
|
93
|
+
/**
|
|
94
|
+
* The flagging threshold used when none is passed, as it is when the class factory builds the
|
|
95
|
+
* provider, on **calibrated** probabilities. A flag asks a person to look, so it favours precision:
|
|
96
|
+
* a missed duplicate is only today's behaviour, and needless flags teach people to ignore them.
|
|
97
|
+
* At a calibrated 0.7, scored out of fold, Jev flagged the true source at 96.6% precision, caught
|
|
98
|
+
* 70% of duplicates, and flagged 2.5% of new records; LLM Decision scored 74%, 36% and 12.5%. The
|
|
99
|
+
* vector threshold alone flagged every candidate. (Duplicate-check measurement, plan Task 3.8,
|
|
100
|
+
* 2026-09-29, re-scored 2026-09-30.)
|
|
101
|
+
*/
|
|
102
|
+
static readonly DEFAULT_UNCERTAIN_ABOVE = 0.7;
|
|
103
|
+
/**
|
|
104
|
+
* The threshold for the decision stage of `DecisionThenPrompt`, on calibrated probabilities. There
|
|
105
|
+
* the decision only drops implausible candidates before the prompt reasons over the rest, so it
|
|
106
|
+
* keeps recall: at a calibrated 0.3, scored out of fold, Jev kept 98.8% of true duplicates and passed
|
|
107
|
+
* 14.8% of candidates.
|
|
108
|
+
*/
|
|
109
|
+
static readonly PRE_FILTER_UNCERTAIN_ABOVE = 0.3;
|
|
110
|
+
/** The seeded decision prompt whose model bindings choose the decision model. */
|
|
111
|
+
static readonly DEFAULT_PROMPT_NAME = "Default Decision";
|
|
112
|
+
/** A candidate at or above this probability is `Uncertain` (flagged for a person); below it, `NotDuplicate`. */
|
|
113
|
+
readonly UncertainAbove: number;
|
|
114
|
+
/**
|
|
115
|
+
* @param uncertainAbove the flagging threshold in [0, 1]. Defaults to
|
|
116
|
+
* {@link DecisionReasoningProvider.DEFAULT_UNCERTAIN_ABOVE}.
|
|
117
|
+
*/
|
|
118
|
+
constructor(uncertainAbove?: number);
|
|
119
|
+
/**
|
|
120
|
+
* Reason over a matched set with one decision call. A failed call returns `Success: false` with
|
|
121
|
+
* the error and the decision's run id, and no verdicts: the detector writes no LLM columns for a
|
|
122
|
+
* failed set, so no candidate is marked `NotDuplicate` and none is merged.
|
|
123
|
+
*/
|
|
124
|
+
Reason(input: DuplicateReasoningInput, context: DuplicateReasoningContext): Promise<DuplicateReasoningOutput>;
|
|
125
|
+
/**
|
|
126
|
+
* Run the decision for a matched set: one `AIDecisionRunner` call, with one Likelihood per
|
|
127
|
+
* candidate. The context's `CancellationToken` and `TimeoutMS`, when set, bound the model call.
|
|
128
|
+
* Never throws. A set with no candidates succeeds without a call.
|
|
129
|
+
*/
|
|
130
|
+
DecideCandidates(input: DuplicateReasoningInput, context: DuplicateReasoningContext): Promise<DuplicateDecisionResult>;
|
|
131
|
+
/**
|
|
132
|
+
* Whether a candidate stays in play: its probability is at or above {@link UncertainAbove}, or
|
|
133
|
+
* unknown. An unknown probability fails toward inclusion.
|
|
134
|
+
*/
|
|
135
|
+
IsPlausible(probability: number | null): boolean;
|
|
136
|
+
/** Band one candidate's probability into `Uncertain` or `NotDuplicate`, carrying the probability as its confidence. */
|
|
137
|
+
BandCandidate(candidate: DuplicateCandidateProbability): DuplicateReasoningCandidateVerdict;
|
|
138
|
+
/**
|
|
139
|
+
* Turn a successful decision into the reasoning contract. Recommends only: no `Merge`, no
|
|
140
|
+
* survivor and no field choices.
|
|
141
|
+
*/
|
|
142
|
+
RecommendFromDecision(decision: DuplicateDecisionResult): DuplicateReasoningOutput;
|
|
143
|
+
/** Resolve the decision prompt by name from the AIEngine cache (no DB query). */
|
|
144
|
+
protected ResolveDecisionPrompt(): MJAIPromptEntityExtended | null;
|
|
145
|
+
/**
|
|
146
|
+
* The decision's state: the fields the prompt provider renders, from the same helper
|
|
147
|
+
* ({@link DuplicateReasoningProvider.buildPromptData}), serialized as compact JSON.
|
|
148
|
+
*/
|
|
149
|
+
protected BuildDecisionState(input: DuplicateReasoningInput): string;
|
|
150
|
+
/** One Likelihood per candidate, keyed by {@link QuestionKey}. */
|
|
151
|
+
protected BuildQuestions(input: DuplicateReasoningInput): Record<string, DecisionQuestion>;
|
|
152
|
+
/** The question key for the candidate at `index`. A label for code only: the model never reads it. */
|
|
153
|
+
protected QuestionKey(index: number): string;
|
|
154
|
+
/**
|
|
155
|
+
* The statement the model judges for one candidate. It names both records by label and record id,
|
|
156
|
+
* because the model reads only the instructions and the state, and duplicates often share a label.
|
|
157
|
+
*/
|
|
158
|
+
protected BuildQuestionInstructions(source: ReasoningSourceRecord, candidate: ReasoningCandidate): string;
|
|
159
|
+
protected BuildDecisionParams(prompt: MJAIPromptEntityExtended, input: DuplicateReasoningInput, context: DuplicateReasoningContext): AIDecisionParams;
|
|
160
|
+
/**
|
|
161
|
+
* The calibration for the exact model that answered (the MJ decision model and the model the
|
|
162
|
+
* driver reports behind it), or null when it has none. Defaults to
|
|
163
|
+
* {@link DUPLICATE_DECISION_CALIBRATION}; override to calibrate another model.
|
|
164
|
+
*/
|
|
165
|
+
protected CalibrationFor(answeredBy: DecisionAnsweringModel): PlattCalibration | null;
|
|
166
|
+
/** Read each candidate's probability from the run, in input order, calibrated for the model that answered. */
|
|
167
|
+
private readProbabilities;
|
|
168
|
+
/** Log a model with no calibration the first time it answers, not on every decision. */
|
|
169
|
+
private logUncalibratedOnce;
|
|
170
|
+
private likelihoodOf;
|
|
171
|
+
private failedDecision;
|
|
172
|
+
/** A failed decision: `Success: false`, with the error and the decision's run id. */
|
|
173
|
+
private failedDecisionOutput;
|
|
174
|
+
private describeBand;
|
|
175
|
+
private summarize;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=DecisionReasoningProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DecisionReasoningProvider.d.ts","sourceRoot":"","sources":["../../src/reasoning/DecisionReasoningProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 'Decision' mode reasoning provider.
|
|
3
|
+
*
|
|
4
|
+
* Asks a typed decision model one Likelihood per candidate ("the candidate record is the same
|
|
5
|
+
* real-world entity as the new record") in a single `AIDecisionRunner` call per matched set. The
|
|
6
|
+
* state is the same fields the prompt provider renders, from the same helper, serialized compactly.
|
|
7
|
+
*
|
|
8
|
+
* It only **recommends**: a candidate whose probability is at or above
|
|
9
|
+
* {@link DecisionReasoningProvider.UncertainAbove} is `Uncertain` and flagged for a person; one below
|
|
10
|
+
* it is `NotDuplicate`. It never emits `Merge` and never proposes a survivor or a field map, so a
|
|
11
|
+
* `Decision`-mode set is never auto-merge eligible (typed-decision plan §3.6, rule 8). Survivor and
|
|
12
|
+
* field choices remain LLM work: see `DecisionThenPromptReasoningProvider`.
|
|
13
|
+
*
|
|
14
|
+
* A failed decision is a failed reasoning call, handled as the `Prompt` mode handles one: the
|
|
15
|
+
* detector saves the set's match rows with empty LLM columns, so every candidate stays `Pending`
|
|
16
|
+
* for review and none is auto-merge eligible.
|
|
17
|
+
*
|
|
18
|
+
* @module @memberjunction/ai-vector-dupe
|
|
19
|
+
*/
|
|
20
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
21
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
22
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
23
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
24
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
25
|
+
};
|
|
26
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
27
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
28
|
+
};
|
|
29
|
+
var DecisionReasoningProvider_1;
|
|
30
|
+
import { LogError } from '@memberjunction/core';
|
|
31
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
32
|
+
import { AIEngine } from '@memberjunction/aiengine';
|
|
33
|
+
import { AIDecisionRunner, AIDecisionParams } from '@memberjunction/ai-prompts';
|
|
34
|
+
import { DescribeAnsweringModel, FindDecisionCalibration } from '@memberjunction/ai-core-plus';
|
|
35
|
+
import { ApplyPlattCalibration } from '@memberjunction/ai';
|
|
36
|
+
import { DuplicateReasoningProvider, DECISION_REASONING_PROVIDER_KEY } from './DuplicateReasoningProvider.js';
|
|
37
|
+
/**
|
|
38
|
+
* Platt calibration of the duplicate Likelihood ("the candidate is the same real-world entity as
|
|
39
|
+
* the new record"), per decision model, each tied to the exact model it was fitted on (see
|
|
40
|
+
* `FindDecisionCalibration` in `@memberjunction/ai-core-plus`): the MJ decision model that answered
|
|
41
|
+
* (`ModelName`) and the model the driver reports behind it (`ResolvedModel`). A model's raw
|
|
42
|
+
* probabilities are not calibrated: Jev ranks candidates almost perfectly (AUC 0.99) but its raw
|
|
43
|
+
* probabilities run high for similar records that are not duplicates.
|
|
44
|
+
*
|
|
45
|
+
* Any other model, including Jev at another version or `LLM Decision` answered by another chat
|
|
46
|
+
* model, gives no calibrated probability, since its raw one can't be banded. The entry check then
|
|
47
|
+
* flags nothing, as it does for a failed decision; batch `Decision` mode bands its candidates
|
|
48
|
+
* `Uncertain` for review; and `DecisionThenPrompt` sends them all to the prompt. The provider logs
|
|
49
|
+
* the missing calibration once per model. An app that binds another decision model supplies its
|
|
50
|
+
* calibration by overriding {@link DecisionReasoningProvider.CalibrationFor}.
|
|
51
|
+
*
|
|
52
|
+
* Fitted on the duplicate-check measurement (plan Task 3.8, 2026-09-29), on every candidate: 160
|
|
53
|
+
* labelled new records for MJ: Actions (80 rewrites of an existing action, 80 similar actions that
|
|
54
|
+
* don't exist), five vector candidates each.
|
|
55
|
+
* - **Jev** at its pinned `APIName`, `typesafe/jev-1.13-20260917`, which OpenRouter reports back as
|
|
56
|
+
* the resolved model.
|
|
57
|
+
* - **LLM Decision** when its chat model is GPT-OSS-120B, its `LLM Decision` prompt's first choice.
|
|
58
|
+
* The stored runs don't record the resolved model; this is the prompt's binding on that day.
|
|
59
|
+
*
|
|
60
|
+
* Refit whenever a model's pinned version, its delegated chat model or the question changes.
|
|
61
|
+
*/
|
|
62
|
+
export const DUPLICATE_DECISION_CALIBRATION = Object.freeze([
|
|
63
|
+
Object.freeze({ ModelName: 'Jev', ResolvedModel: 'typesafe/jev-1.13-20260917', Calibration: Object.freeze({ A: 2.5855, B: -4.4485 }) }),
|
|
64
|
+
Object.freeze({ ModelName: 'LLM Decision', ResolvedModel: 'GPT-OSS-120B', Calibration: Object.freeze({ A: 0.9918, B: -1.4843 }) })
|
|
65
|
+
]);
|
|
66
|
+
/** The shipped calibration for the exact model that answered, or null when it has none. */
|
|
67
|
+
export function DuplicateDecisionCalibrationFor(answeredBy) {
|
|
68
|
+
return FindDecisionCalibration(DUPLICATE_DECISION_CALIBRATION, answeredBy);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The duplicate Likelihood calibrated for the exact model that answered, or null when that model
|
|
72
|
+
* has no calibration.
|
|
73
|
+
*/
|
|
74
|
+
export function CalibratedDuplicateProbability(probability, answeredBy) {
|
|
75
|
+
const calibration = DuplicateDecisionCalibrationFor(answeredBy);
|
|
76
|
+
return calibration ? ApplyPlattCalibration(probability, calibration) : null;
|
|
77
|
+
}
|
|
78
|
+
/** Models already logged as having no calibration, so each is logged once per process. */
|
|
79
|
+
const loggedUncalibratedModels = new Set();
|
|
80
|
+
/**
|
|
81
|
+
* Typed-decision reasoning provider. Registered under the 'Decision' `ReasoningMode`.
|
|
82
|
+
*/
|
|
83
|
+
let DecisionReasoningProvider = class DecisionReasoningProvider extends DuplicateReasoningProvider {
|
|
84
|
+
static { DecisionReasoningProvider_1 = this; }
|
|
85
|
+
/**
|
|
86
|
+
* The flagging threshold used when none is passed, as it is when the class factory builds the
|
|
87
|
+
* provider, on **calibrated** probabilities. A flag asks a person to look, so it favours precision:
|
|
88
|
+
* a missed duplicate is only today's behaviour, and needless flags teach people to ignore them.
|
|
89
|
+
* At a calibrated 0.7, scored out of fold, Jev flagged the true source at 96.6% precision, caught
|
|
90
|
+
* 70% of duplicates, and flagged 2.5% of new records; LLM Decision scored 74%, 36% and 12.5%. The
|
|
91
|
+
* vector threshold alone flagged every candidate. (Duplicate-check measurement, plan Task 3.8,
|
|
92
|
+
* 2026-09-29, re-scored 2026-09-30.)
|
|
93
|
+
*/
|
|
94
|
+
static { this.DEFAULT_UNCERTAIN_ABOVE = 0.7; }
|
|
95
|
+
/**
|
|
96
|
+
* The threshold for the decision stage of `DecisionThenPrompt`, on calibrated probabilities. There
|
|
97
|
+
* the decision only drops implausible candidates before the prompt reasons over the rest, so it
|
|
98
|
+
* keeps recall: at a calibrated 0.3, scored out of fold, Jev kept 98.8% of true duplicates and passed
|
|
99
|
+
* 14.8% of candidates.
|
|
100
|
+
*/
|
|
101
|
+
static { this.PRE_FILTER_UNCERTAIN_ABOVE = 0.3; }
|
|
102
|
+
/** The seeded decision prompt whose model bindings choose the decision model. */
|
|
103
|
+
static { this.DEFAULT_PROMPT_NAME = 'Default Decision'; }
|
|
104
|
+
/**
|
|
105
|
+
* @param uncertainAbove the flagging threshold in [0, 1]. Defaults to
|
|
106
|
+
* {@link DecisionReasoningProvider.DEFAULT_UNCERTAIN_ABOVE}.
|
|
107
|
+
*/
|
|
108
|
+
constructor(uncertainAbove = DecisionReasoningProvider_1.DEFAULT_UNCERTAIN_ABOVE) {
|
|
109
|
+
super();
|
|
110
|
+
this.UncertainAbove = uncertainAbove;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Reason over a matched set with one decision call. A failed call returns `Success: false` with
|
|
114
|
+
* the error and the decision's run id, and no verdicts: the detector writes no LLM columns for a
|
|
115
|
+
* failed set, so no candidate is marked `NotDuplicate` and none is merged.
|
|
116
|
+
*/
|
|
117
|
+
async Reason(input, context) {
|
|
118
|
+
const decision = await this.DecideCandidates(input, context);
|
|
119
|
+
return decision.Success
|
|
120
|
+
? this.RecommendFromDecision(decision)
|
|
121
|
+
: this.failedDecisionOutput(decision);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Run the decision for a matched set: one `AIDecisionRunner` call, with one Likelihood per
|
|
125
|
+
* candidate. The context's `CancellationToken` and `TimeoutMS`, when set, bound the model call.
|
|
126
|
+
* Never throws. A set with no candidates succeeds without a call.
|
|
127
|
+
*/
|
|
128
|
+
async DecideCandidates(input, context) {
|
|
129
|
+
if (input.Candidates.length === 0) {
|
|
130
|
+
return { Success: true, Candidates: [], AIPromptRunID: null };
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await AIEngine.Instance.Config(false, context.ContextUser, context.Provider);
|
|
134
|
+
const prompt = this.ResolveDecisionPrompt();
|
|
135
|
+
if (!prompt) {
|
|
136
|
+
return this.failedDecision(`Decision prompt "${DecisionReasoningProvider_1.DEFAULT_PROMPT_NAME}" not found`, null);
|
|
137
|
+
}
|
|
138
|
+
const run = await new AIDecisionRunner().ExecuteDecision(this.BuildDecisionParams(prompt, input, context));
|
|
139
|
+
return this.readProbabilities(input, run);
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
LogError(e);
|
|
143
|
+
return this.failedDecision(e instanceof Error ? e.message : String(e), null);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Whether a candidate stays in play: its probability is at or above {@link UncertainAbove}, or
|
|
148
|
+
* unknown. An unknown probability fails toward inclusion.
|
|
149
|
+
*/
|
|
150
|
+
IsPlausible(probability) {
|
|
151
|
+
return probability == null || probability >= this.UncertainAbove;
|
|
152
|
+
}
|
|
153
|
+
/** Band one candidate's probability into `Uncertain` or `NotDuplicate`, carrying the probability as its confidence. */
|
|
154
|
+
BandCandidate(candidate) {
|
|
155
|
+
const flagged = this.IsPlausible(candidate.Probability);
|
|
156
|
+
return {
|
|
157
|
+
RecordID: candidate.RecordID,
|
|
158
|
+
Recommendation: flagged ? 'Uncertain' : 'NotDuplicate',
|
|
159
|
+
Confidence: candidate.Probability,
|
|
160
|
+
Reasoning: this.describeBand(candidate.Probability, flagged, candidate.RawProbability)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Turn a successful decision into the reasoning contract. Recommends only: no `Merge`, no
|
|
165
|
+
* survivor and no field choices.
|
|
166
|
+
*/
|
|
167
|
+
RecommendFromDecision(decision) {
|
|
168
|
+
const verdicts = decision.Candidates.map(c => this.BandCandidate(c));
|
|
169
|
+
const overall = this.deriveOverall(verdicts);
|
|
170
|
+
return {
|
|
171
|
+
Success: true,
|
|
172
|
+
Recommendation: overall.Recommendation,
|
|
173
|
+
Confidence: overall.Confidence,
|
|
174
|
+
Reasoning: this.summarize(verdicts),
|
|
175
|
+
CandidateVerdicts: verdicts,
|
|
176
|
+
SurvivorRecordID: null,
|
|
177
|
+
FieldChoices: [],
|
|
178
|
+
AIPromptRunID: decision.AIPromptRunID
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Resolve the decision prompt by name from the AIEngine cache (no DB query). */
|
|
182
|
+
ResolveDecisionPrompt() {
|
|
183
|
+
const target = DecisionReasoningProvider_1.DEFAULT_PROMPT_NAME.toLowerCase();
|
|
184
|
+
return AIEngine.Instance.Prompts.find(p => (p.Name ?? '').trim().toLowerCase() === target) ?? null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The decision's state: the fields the prompt provider renders, from the same helper
|
|
188
|
+
* ({@link DuplicateReasoningProvider.buildPromptData}), serialized as compact JSON.
|
|
189
|
+
*/
|
|
190
|
+
BuildDecisionState(input) {
|
|
191
|
+
return JSON.stringify(this.buildPromptData(input));
|
|
192
|
+
}
|
|
193
|
+
/** One Likelihood per candidate, keyed by {@link QuestionKey}. */
|
|
194
|
+
BuildQuestions(input) {
|
|
195
|
+
const questions = {};
|
|
196
|
+
input.Candidates.forEach((candidate, index) => {
|
|
197
|
+
questions[this.QuestionKey(index)] = {
|
|
198
|
+
Kind: 'Likelihood',
|
|
199
|
+
Instructions: this.BuildQuestionInstructions(input.SourceRecord, candidate)
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
return questions;
|
|
203
|
+
}
|
|
204
|
+
/** The question key for the candidate at `index`. A label for code only: the model never reads it. */
|
|
205
|
+
QuestionKey(index) {
|
|
206
|
+
return `candidate_${index + 1}`;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The statement the model judges for one candidate. It names both records by label and record id,
|
|
210
|
+
* because the model reads only the instructions and the state, and duplicates often share a label.
|
|
211
|
+
*/
|
|
212
|
+
BuildQuestionInstructions(source, candidate) {
|
|
213
|
+
return `The candidate record "${candidate.Label}" (recordId ${candidate.RecordID}) is the same real-world entity ` +
|
|
214
|
+
`as the new record, the sourceRecord "${source.Label}" (recordId ${source.RecordID}).`;
|
|
215
|
+
}
|
|
216
|
+
BuildDecisionParams(prompt, input, context) {
|
|
217
|
+
const params = new AIDecisionParams();
|
|
218
|
+
params.prompt = prompt;
|
|
219
|
+
params.contextUser = context.ContextUser;
|
|
220
|
+
params.cancellationToken = context.CancellationToken;
|
|
221
|
+
params.timeoutMS = context.TimeoutMS;
|
|
222
|
+
params.State = this.BuildDecisionState(input);
|
|
223
|
+
params.Questions = this.BuildQuestions(input);
|
|
224
|
+
return params;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The calibration for the exact model that answered (the MJ decision model and the model the
|
|
228
|
+
* driver reports behind it), or null when it has none. Defaults to
|
|
229
|
+
* {@link DUPLICATE_DECISION_CALIBRATION}; override to calibrate another model.
|
|
230
|
+
*/
|
|
231
|
+
CalibrationFor(answeredBy) {
|
|
232
|
+
return DuplicateDecisionCalibrationFor(answeredBy);
|
|
233
|
+
}
|
|
234
|
+
/** Read each candidate's probability from the run, in input order, calibrated for the model that answered. */
|
|
235
|
+
readProbabilities(input, run) {
|
|
236
|
+
const runID = run.promptRun?.ID ?? null;
|
|
237
|
+
if (!run.success) {
|
|
238
|
+
return this.failedDecision(run.errorMessage ?? 'Decision execution failed', runID);
|
|
239
|
+
}
|
|
240
|
+
const answeredBy = {
|
|
241
|
+
ModelName: run.modelInfo?.modelName,
|
|
242
|
+
ResolvedModel: run.DecisionResult?.ResolvedModel
|
|
243
|
+
};
|
|
244
|
+
const calibration = this.CalibrationFor(answeredBy);
|
|
245
|
+
const candidates = input.Candidates.map((candidate, index) => {
|
|
246
|
+
const raw = this.likelihoodOf(run.Answers[this.QuestionKey(index)]);
|
|
247
|
+
return {
|
|
248
|
+
RecordID: candidate.RecordID,
|
|
249
|
+
Probability: raw == null || !calibration ? null : ApplyPlattCalibration(raw, calibration),
|
|
250
|
+
RawProbability: raw
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
const result = { Success: true, Candidates: candidates, AIPromptRunID: runID };
|
|
254
|
+
if (!calibration) {
|
|
255
|
+
result.UncalibratedModel = DescribeAnsweringModel(answeredBy);
|
|
256
|
+
this.logUncalibratedOnce(result.UncalibratedModel);
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
/** Log a model with no calibration the first time it answers, not on every decision. */
|
|
261
|
+
logUncalibratedOnce(model) {
|
|
262
|
+
if (loggedUncalibratedModels.has(model)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
loggedUncalibratedModels.add(model);
|
|
266
|
+
LogError(`Duplicate decision: the model "${model}" has no calibration, so its probabilities can't be banded. ` +
|
|
267
|
+
'The entry check flags nothing for it, and Decision mode flags its candidates for review. ' +
|
|
268
|
+
'Add its calibration (DUPLICATE_DECISION_CALIBRATION, or override CalibrationFor).');
|
|
269
|
+
}
|
|
270
|
+
likelihoodOf(answer) {
|
|
271
|
+
return answer?.Kind === 'Likelihood' ? answer.Probability : null;
|
|
272
|
+
}
|
|
273
|
+
failedDecision(message, runID) {
|
|
274
|
+
return { Success: false, ErrorMessage: message, Candidates: [], AIPromptRunID: runID };
|
|
275
|
+
}
|
|
276
|
+
/** A failed decision: `Success: false`, with the error and the decision's run id. */
|
|
277
|
+
failedDecisionOutput(decision) {
|
|
278
|
+
const output = this.failedOutput(`Decision failed: ${decision.ErrorMessage ?? 'unknown error'}`);
|
|
279
|
+
output.AIPromptRunID = decision.AIPromptRunID;
|
|
280
|
+
return output;
|
|
281
|
+
}
|
|
282
|
+
describeBand(probability, flagged, rawProbability) {
|
|
283
|
+
if (probability == null && rawProbability != null) {
|
|
284
|
+
return `The decision model has no calibration (raw probability ${rawProbability.toFixed(2)}). Flagged for review.`;
|
|
285
|
+
}
|
|
286
|
+
if (probability == null) {
|
|
287
|
+
return 'The decision model returned no answer for this candidate. Flagged for review.';
|
|
288
|
+
}
|
|
289
|
+
const p = probability.toFixed(2);
|
|
290
|
+
const threshold = this.UncertainAbove.toFixed(2);
|
|
291
|
+
return flagged
|
|
292
|
+
? `Decision model: ${p} probability this is the same entity, at or above ${threshold}. Flagged for review.`
|
|
293
|
+
: `Decision model: ${p} probability this is the same entity, below ${threshold}.`;
|
|
294
|
+
}
|
|
295
|
+
summarize(verdicts) {
|
|
296
|
+
const flagged = verdicts.filter(v => v.Recommendation === 'Uncertain').length;
|
|
297
|
+
return `Decision model flagged ${flagged} of ${verdicts.length} candidates for review ` +
|
|
298
|
+
`(probability at or above ${this.UncertainAbove.toFixed(2)}). Decision mode recommends only; it never merges.`;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
DecisionReasoningProvider = DecisionReasoningProvider_1 = __decorate([
|
|
302
|
+
RegisterClass(DuplicateReasoningProvider, DECISION_REASONING_PROVIDER_KEY),
|
|
303
|
+
__metadata("design:paramtypes", [Number])
|
|
304
|
+
], DecisionReasoningProvider);
|
|
305
|
+
export { DecisionReasoningProvider };
|
|
306
|
+
//# sourceMappingURL=DecisionReasoningProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DecisionReasoningProvider.js","sourceRoot":"","sources":["../../src/reasoning/DecisionReasoningProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 'DecisionThenPrompt' mode reasoning provider: an explicit chain.
|
|
3
|
+
*
|
|
4
|
+
* The detector resolves exactly one provider per `ReasoningMode`, so a decision cannot feed the
|
|
5
|
+
* prompt provider through configuration alone. This provider chains them:
|
|
6
|
+
*
|
|
7
|
+
* 1. {@link DecisionReasoningProvider} runs one decision over the set and drops every candidate
|
|
8
|
+
* whose probability is below its threshold.
|
|
9
|
+
* 2. `PromptReasoningProvider` reasons over **only the survivors**, so auto-merge still works
|
|
10
|
+
* wherever the prompt provider allows it.
|
|
11
|
+
*
|
|
12
|
+
* Both stages resolve through the class factory under the 'Decision' and 'Prompt' modes, so an
|
|
13
|
+
* application's override of either mode applies inside the chain as well.
|
|
14
|
+
*
|
|
15
|
+
* With no survivors the set is `NotDuplicate` and the prompt never runs. If the decision fails,
|
|
16
|
+
* every candidate goes to the prompt provider, which is the `Prompt` mode's behaviour: narrowing is
|
|
17
|
+
* an optimisation, so its failure must never hide a candidate.
|
|
18
|
+
*
|
|
19
|
+
* Each match row points at the run that produced its verdict: a survivor's at the prompt run, a
|
|
20
|
+
* dropped candidate's at the decision run. A failed decision's run is named in the error log, since
|
|
21
|
+
* a match row holds only one prompt run id.
|
|
22
|
+
*
|
|
23
|
+
* @module @memberjunction/ai-vector-dupe
|
|
24
|
+
*/
|
|
25
|
+
import { DuplicateReasoningProvider } from './DuplicateReasoningProvider.js';
|
|
26
|
+
import { DecisionReasoningProvider, DuplicateCandidateProbability } from './DecisionReasoningProvider.js';
|
|
27
|
+
import { DuplicateReasoningInput, DuplicateReasoningOutput, DuplicateReasoningContext } from './DuplicateReasoningTypes.js';
|
|
28
|
+
/**
|
|
29
|
+
* Chained reasoning provider: the decision filters, then the prompt reasons over the survivors.
|
|
30
|
+
* Registered under the 'DecisionThenPrompt' `ReasoningMode`.
|
|
31
|
+
*/
|
|
32
|
+
export declare class DecisionThenPromptReasoningProvider extends DuplicateReasoningProvider {
|
|
33
|
+
/** The filter stage. Its `UncertainAbove` is the survival threshold. */
|
|
34
|
+
protected readonly DecisionStage: DecisionReasoningProvider;
|
|
35
|
+
/** The reasoning stage, which sees only the survivors. */
|
|
36
|
+
protected readonly PromptStage: DuplicateReasoningProvider;
|
|
37
|
+
/**
|
|
38
|
+
* The class factory passes no arguments, so both stages resolve through the class factory
|
|
39
|
+
* under the 'Decision' and 'Prompt' modes: an application's override of either mode applies
|
|
40
|
+
* inside the chain too.
|
|
41
|
+
*
|
|
42
|
+
* @param decisionStage the filter stage; defaults to {@link ResolveDecisionStage}, at the pre-filter
|
|
43
|
+
* threshold ({@link DecisionReasoningProvider.PRE_FILTER_UNCERTAIN_ABOVE}), which keeps recall
|
|
44
|
+
* @param promptStage the reasoning stage; defaults to {@link ResolvePromptStage}
|
|
45
|
+
*/
|
|
46
|
+
constructor(decisionStage?: DecisionReasoningProvider, promptStage?: DuplicateReasoningProvider);
|
|
47
|
+
/**
|
|
48
|
+
* Filter the set with the decision, then reason over the survivors with the prompt.
|
|
49
|
+
*/
|
|
50
|
+
Reason(input: DuplicateReasoningInput, context: DuplicateReasoningContext): Promise<DuplicateReasoningOutput>;
|
|
51
|
+
/**
|
|
52
|
+
* The input with the dropped candidates removed, from the candidate list and from the field
|
|
53
|
+
* deltas. A field whose remaining values no longer differ is dropped too, since the deltas carry
|
|
54
|
+
* differing fields only.
|
|
55
|
+
*/
|
|
56
|
+
protected NarrowToSurvivors(input: DuplicateReasoningInput, dropped: DuplicateCandidateProbability[]): DuplicateReasoningInput;
|
|
57
|
+
/**
|
|
58
|
+
* The filter stage: the provider registered for the 'Decision' mode, built with the pre-filter
|
|
59
|
+
* threshold ({@link DecisionReasoningProvider.PRE_FILTER_UNCERTAIN_ABOVE}) as its `uncertainAbove`
|
|
60
|
+
* argument, since here the decision only drops implausible candidates and keeps recall. The chain
|
|
61
|
+
* needs its probability API, so a registration that is not a `DecisionReasoningProvider` falls
|
|
62
|
+
* back to the shipped one, at the same threshold.
|
|
63
|
+
*/
|
|
64
|
+
protected ResolveDecisionStage(): DecisionReasoningProvider;
|
|
65
|
+
/** The reasoning stage: the provider registered for the 'Prompt' mode, else the shipped one. */
|
|
66
|
+
protected ResolvePromptStage(): DuplicateReasoningProvider;
|
|
67
|
+
/**
|
|
68
|
+
* The provider the class factory registers for a mode, built with `params` as its constructor
|
|
69
|
+
* arguments, or null when none is registered.
|
|
70
|
+
*/
|
|
71
|
+
private resolveRegisteredProvider;
|
|
72
|
+
/** No candidate survived: `NotDuplicate` for the set, from the decision alone, with no prompt call. */
|
|
73
|
+
private noSurvivorsOutput;
|
|
74
|
+
/**
|
|
75
|
+
* Give each dropped candidate its own `NotDuplicate` verdict from the decision, carrying the
|
|
76
|
+
* decision's run id, so its match row neither falls back to the set-level recommendation nor
|
|
77
|
+
* points at a prompt run that never saw it. The prompt's verdicts and set-level fields are
|
|
78
|
+
* otherwise left as the prompt returned them.
|
|
79
|
+
*/
|
|
80
|
+
private withDroppedVerdicts;
|
|
81
|
+
/** The failure for the log, naming the decision's run when the runner wrote one. */
|
|
82
|
+
private describeFailure;
|
|
83
|
+
private stillDiffers;
|
|
84
|
+
private recordIDSet;
|
|
85
|
+
/** Record ids compare case-insensitively (SQL Server and PostgreSQL case UUIDs differently). */
|
|
86
|
+
private normalizeRecordID;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=DecisionThenPromptReasoningProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DecisionThenPromptReasoningProvider.d.ts","sourceRoot":"","sources":["../../src/reasoning/DecisionThenPromptReasoningProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EACH,0BAA0B,EAI7B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACH,yBAAyB,EACzB,6BAA6B,EAEhC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACH,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EAE5B,MAAM,2BAA2B,CAAC;AAEnC;;;GAGG;AACH,qBACa,mCAAoC,SAAQ,0BAA0B;IAC/E,wEAAwE;IACxE,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,yBAAyB,CAAC;IAC5D,0DAA0D;IAC1D,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;IAE3D;;;;;;;;OAQG;gBACS,aAAa,CAAC,EAAE,yBAAyB,EAAE,WAAW,CAAC,EAAE,0BAA0B;IAM/F;;OAEG;IACU,MAAM,CACf,KAAK,EAAE,uBAAuB,EAC9B,OAAO,EAAE,yBAAyB,GACnC,OAAO,CAAC,wBAAwB,CAAC;IAcpC;;;;OAIG;IACH,SAAS,CAAC,iBAAiB,CACvB,KAAK,EAAE,uBAAuB,EAC9B,OAAO,EAAE,6BAA6B,EAAE,GACzC,uBAAuB;IAY1B;;;;;;OAMG;IACH,SAAS,CAAC,oBAAoB,IAAI,yBAAyB;IAM3D,gGAAgG;IAChG,SAAS,CAAC,kBAAkB,IAAI,0BAA0B;IAI1D;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAKjC,uGAAuG;IACvG,OAAO,CAAC,iBAAiB;IAOzB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAgB3B,oFAAoF;IACpF,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,WAAW;IAInB,gGAAgG;IAChG,OAAO,CAAC,iBAAiB;CAG5B"}
|