@memberjunction/ai-recommendations 5.0.0 → 5.2.0
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 +376 -0
- package/package.json +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# @memberjunction/ai-recommendations
|
|
2
|
+
|
|
3
|
+
A provider-based recommendation engine for MemberJunction. Manages recommendation runs, delegates to pluggable providers via the class factory, and tracks results through Recommendation, Recommendation Run, and Recommendation Item entities.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TD
|
|
9
|
+
subgraph Engine["@memberjunction/ai-recommendations"]
|
|
10
|
+
REB["RecommendationEngineBase<br/>(singleton BaseEngine)"]
|
|
11
|
+
RPB["RecommendationProviderBase<br/>(abstract)"]
|
|
12
|
+
RR["RecommendationRequest<T>"]
|
|
13
|
+
RRES["RecommendationResult"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph Providers["Registered Providers"]
|
|
17
|
+
P1["Provider A"]
|
|
18
|
+
P2["Provider B"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph MJEntities["MemberJunction Entities"]
|
|
22
|
+
RP["Recommendation Providers"]
|
|
23
|
+
RUN["Recommendation Runs"]
|
|
24
|
+
REC["Recommendations"]
|
|
25
|
+
RI["Recommendation Items"]
|
|
26
|
+
LIST["Lists / List Details"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph MJCore["MemberJunction Core"]
|
|
30
|
+
BE["BaseEngine"]
|
|
31
|
+
CF["ClassFactory"]
|
|
32
|
+
MD["Metadata"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
REB -->|extends| BE
|
|
36
|
+
REB -->|discovers| CF
|
|
37
|
+
CF -->|creates| P1
|
|
38
|
+
CF -->|creates| P2
|
|
39
|
+
P1 -->|extends| RPB
|
|
40
|
+
P2 -->|extends| RPB
|
|
41
|
+
REB --> RP
|
|
42
|
+
REB --> RUN
|
|
43
|
+
RPB --> REC
|
|
44
|
+
RPB --> RI
|
|
45
|
+
REB --> LIST
|
|
46
|
+
|
|
47
|
+
style Engine fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
48
|
+
style Providers fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
49
|
+
style MJEntities fill:#b8762f,stroke:#8a5722,color:#fff
|
|
50
|
+
style MJCore fill:#7c5295,stroke:#563a6b,color:#fff
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @memberjunction/ai-recommendations
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Overview
|
|
60
|
+
|
|
61
|
+
This package provides the framework for running recommendations in MemberJunction. It follows the engine/provider pattern used throughout the platform:
|
|
62
|
+
|
|
63
|
+
1. **RecommendationEngineBase** -- a singleton engine (extending `BaseEngine`) that loads provider metadata, selects a provider, creates Recommendation Run tracking records, and delegates the actual recommendation logic
|
|
64
|
+
2. **RecommendationProviderBase** -- an abstract class that concrete providers implement to generate recommendations for each source record
|
|
65
|
+
3. **RecommendationRequest/RecommendationResult** -- typed request and response objects that flow through the pipeline
|
|
66
|
+
|
|
67
|
+
Providers are discovered at runtime through MemberJunction's `ClassFactory` using `@RegisterClass(RecommendationProviderBase, 'ProviderName')`.
|
|
68
|
+
|
|
69
|
+
## Recommendation Flow
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
sequenceDiagram
|
|
73
|
+
participant Caller
|
|
74
|
+
participant Engine as RecommendationEngineBase
|
|
75
|
+
participant CF as ClassFactory
|
|
76
|
+
participant Provider as RecommendationProvider
|
|
77
|
+
participant DB as MJ Database
|
|
78
|
+
|
|
79
|
+
Caller->>Engine: Recommend(request)
|
|
80
|
+
Engine->>Engine: TryThrowIfNotLoaded()
|
|
81
|
+
|
|
82
|
+
alt Provider specified
|
|
83
|
+
Engine->>Engine: Use request.Provider
|
|
84
|
+
else No provider
|
|
85
|
+
Engine->>Engine: Use first available
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Engine->>Engine: GetRecommendationEntities(request)
|
|
89
|
+
|
|
90
|
+
alt From List
|
|
91
|
+
Engine->>DB: Load List + List Details
|
|
92
|
+
Engine->>DB: Load entity records by IDs
|
|
93
|
+
else From EntityAndRecordsInfo
|
|
94
|
+
Engine->>DB: Load records by entity name + IDs
|
|
95
|
+
else Pre-built
|
|
96
|
+
Engine->>Engine: Validate Recommendations array
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Engine->>DB: Create Recommendation Run (Status: In Progress)
|
|
100
|
+
|
|
101
|
+
opt CreateErrorList = true
|
|
102
|
+
Engine->>DB: Create error tracking List
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Engine->>CF: CreateInstance(provider.Name)
|
|
106
|
+
CF-->>Engine: Provider instance
|
|
107
|
+
|
|
108
|
+
Engine->>Provider: Recommend(request)
|
|
109
|
+
|
|
110
|
+
loop For each recommendation
|
|
111
|
+
Provider->>Provider: Call external API
|
|
112
|
+
Provider->>DB: SaveRecommendation + Items
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
Provider-->>Engine: RecommendationResult
|
|
116
|
+
|
|
117
|
+
Engine->>DB: Update Run (Completed/Error)
|
|
118
|
+
Engine-->>Caller: RecommendationResult
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Core Components
|
|
122
|
+
|
|
123
|
+
### RecommendationEngineBase
|
|
124
|
+
|
|
125
|
+
A singleton engine that manages the recommendation lifecycle.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
|
|
129
|
+
|
|
130
|
+
// Access the singleton
|
|
131
|
+
const engine = RecommendationEngineBase.Instance;
|
|
132
|
+
|
|
133
|
+
// Initialize (loads Recommendation Providers metadata)
|
|
134
|
+
await engine.Config(false, contextUser);
|
|
135
|
+
|
|
136
|
+
// Run recommendations
|
|
137
|
+
const result = await engine.Recommend(request);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Key properties and methods:**
|
|
141
|
+
|
|
142
|
+
| Member | Description |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `Instance` | Static getter for the singleton instance |
|
|
145
|
+
| `RecommendationProviders` | Array of `RecommendationProviderEntity` loaded from metadata |
|
|
146
|
+
| `Config(forceRefresh?, contextUser?, provider?)` | Loads provider metadata into cache |
|
|
147
|
+
| `Recommend<T>(request)` | Runs the full recommendation pipeline |
|
|
148
|
+
|
|
149
|
+
### RecommendationProviderBase
|
|
150
|
+
|
|
151
|
+
Abstract base class for implementing recommendation providers.
|
|
152
|
+
|
|
153
|
+
```mermaid
|
|
154
|
+
classDiagram
|
|
155
|
+
class RecommendationProviderBase {
|
|
156
|
+
<<abstract>>
|
|
157
|
+
-_md : Metadata
|
|
158
|
+
-_ContextUser : UserInfo
|
|
159
|
+
+ContextUser : UserInfo
|
|
160
|
+
+Recommend(request)* RecommendationResult
|
|
161
|
+
#SaveRecommendation(rec, runID, items) boolean
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class ConcreteProvider {
|
|
165
|
+
+Recommend(request) RecommendationResult
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
RecommendationProviderBase <|-- ConcreteProvider
|
|
169
|
+
|
|
170
|
+
style RecommendationProviderBase fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
171
|
+
style ConcreteProvider fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The `SaveRecommendation` helper method handles:
|
|
175
|
+
1. Setting the `RecommendationRunID` on the recommendation entity
|
|
176
|
+
2. Saving the recommendation record
|
|
177
|
+
3. Linking and saving all `RecommendationItemEntity` records
|
|
178
|
+
|
|
179
|
+
### RecommendationRequest\<T\>
|
|
180
|
+
|
|
181
|
+
The request object supports three ways to specify source records:
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
graph TD
|
|
185
|
+
RR["RecommendationRequest"]
|
|
186
|
+
OPT1["Recommendations[]<br/>Pre-built entities"]
|
|
187
|
+
OPT2["EntityAndRecordsInfo<br/>Entity name + Record IDs"]
|
|
188
|
+
OPT3["ListID<br/>MJ List reference"]
|
|
189
|
+
|
|
190
|
+
RR --> OPT1
|
|
191
|
+
RR --> OPT2
|
|
192
|
+
RR --> OPT3
|
|
193
|
+
|
|
194
|
+
style RR fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
195
|
+
style OPT1 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
196
|
+
style OPT2 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
197
|
+
style OPT3 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
| Field | Type | Description |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `Recommendations` | `RecommendationEntity[]` | Pre-built unsaved recommendation entities |
|
|
203
|
+
| `EntityAndRecordsInfo` | `{ EntityName, RecordIDs }` | Entity name and array of record IDs to process |
|
|
204
|
+
| `ListID` | `string` | ID of a MJ List whose details become the source records |
|
|
205
|
+
| `Provider` | `RecommendationProviderEntity` | Specific provider to use (defaults to first available) |
|
|
206
|
+
| `CurrentUser` | `UserInfo` | User context |
|
|
207
|
+
| `Options` | `T` | Generic additional options passed to the provider |
|
|
208
|
+
| `CreateErrorList` | `boolean` | Whether to create an error tracking list |
|
|
209
|
+
| `RunID` | `string` | Set automatically by the engine |
|
|
210
|
+
| `ErrorListID` | `string` | Set automatically if error list is created |
|
|
211
|
+
|
|
212
|
+
### RecommendationResult
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
class RecommendationResult {
|
|
216
|
+
Request: RecommendationRequest;
|
|
217
|
+
RecommendationRun?: RecommendationRunEntity;
|
|
218
|
+
RecommendationItems?: RecommendationItemEntity[];
|
|
219
|
+
Success: boolean;
|
|
220
|
+
ErrorMessage: string;
|
|
221
|
+
|
|
222
|
+
AppendWarning(message: string): void; // Adds warning without setting Success=false
|
|
223
|
+
AppendError(message: string): void; // Adds error and sets Success=false
|
|
224
|
+
GetErrorMessages(): string[]; // Splits ErrorMessage into array
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Usage
|
|
229
|
+
|
|
230
|
+
### Running Recommendations from a List
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
|
|
234
|
+
import { RecommendationRequest } from '@memberjunction/ai-recommendations';
|
|
235
|
+
|
|
236
|
+
const engine = RecommendationEngineBase.Instance;
|
|
237
|
+
await engine.Config(false, contextUser);
|
|
238
|
+
|
|
239
|
+
const request = new RecommendationRequest();
|
|
240
|
+
request.ListID = 'list-uuid';
|
|
241
|
+
request.CurrentUser = contextUser;
|
|
242
|
+
request.CreateErrorList = true;
|
|
243
|
+
|
|
244
|
+
const result = await engine.Recommend(request);
|
|
245
|
+
|
|
246
|
+
if (result.Success) {
|
|
247
|
+
console.log(`Generated ${result.RecommendationItems?.length ?? 0} items`);
|
|
248
|
+
} else {
|
|
249
|
+
console.error(result.ErrorMessage);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Running Recommendations by Entity and Record IDs
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const request = new RecommendationRequest();
|
|
257
|
+
request.EntityAndRecordsInfo = {
|
|
258
|
+
EntityName: 'Products',
|
|
259
|
+
RecordIDs: ['id-1', 'id-2', 'id-3']
|
|
260
|
+
};
|
|
261
|
+
request.CurrentUser = contextUser;
|
|
262
|
+
|
|
263
|
+
const result = await engine.Recommend(request);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Implementing a Provider
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { RecommendationProviderBase } from '@memberjunction/ai-recommendations';
|
|
270
|
+
import { RecommendationRequest, RecommendationResult } from '@memberjunction/ai-recommendations';
|
|
271
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
272
|
+
import { Metadata } from '@memberjunction/core';
|
|
273
|
+
import { RecommendationItemEntity } from '@memberjunction/core-entities';
|
|
274
|
+
|
|
275
|
+
@RegisterClass(RecommendationProviderBase, 'My Recommendation Provider')
|
|
276
|
+
export class MyProvider extends RecommendationProviderBase {
|
|
277
|
+
async Recommend(request: RecommendationRequest): Promise<RecommendationResult> {
|
|
278
|
+
const result = new RecommendationResult(request);
|
|
279
|
+
const md = new Metadata();
|
|
280
|
+
|
|
281
|
+
for (const rec of request.Recommendations) {
|
|
282
|
+
// Call your recommendation API/algorithm
|
|
283
|
+
const suggestions = await this.getSuggestions(rec.SourceEntityRecordID);
|
|
284
|
+
|
|
285
|
+
const items: RecommendationItemEntity[] = [];
|
|
286
|
+
for (const suggestion of suggestions) {
|
|
287
|
+
const item = await md.GetEntityObject<RecommendationItemEntity>(
|
|
288
|
+
'Recommendation Items', request.CurrentUser
|
|
289
|
+
);
|
|
290
|
+
item.NewRecord();
|
|
291
|
+
item.DestinationEntityID = suggestion.entityID;
|
|
292
|
+
item.DestinationEntityRecordID = suggestion.recordID;
|
|
293
|
+
item.MatchProbability = suggestion.score;
|
|
294
|
+
items.push(item);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
await this.SaveRecommendation(rec, request.RunID, items);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private async getSuggestions(recordID: string): Promise<Suggestion[]> {
|
|
304
|
+
// Your recommendation logic here
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Database Entities
|
|
311
|
+
|
|
312
|
+
```mermaid
|
|
313
|
+
erDiagram
|
|
314
|
+
RECOMMENDATION_PROVIDERS {
|
|
315
|
+
string ID PK
|
|
316
|
+
string Name
|
|
317
|
+
string Description
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
RECOMMENDATION_RUNS {
|
|
321
|
+
string ID PK
|
|
322
|
+
string RecommendationProviderID FK
|
|
323
|
+
string RunByUserID FK
|
|
324
|
+
datetime StartDate
|
|
325
|
+
string Status
|
|
326
|
+
string Description
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
RECOMMENDATIONS {
|
|
330
|
+
string ID PK
|
|
331
|
+
string RecommendationRunID FK
|
|
332
|
+
string SourceEntityID FK
|
|
333
|
+
string SourceEntityRecordID
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
RECOMMENDATION_ITEMS {
|
|
337
|
+
string ID PK
|
|
338
|
+
string RecommendationID FK
|
|
339
|
+
string DestinationEntityID FK
|
|
340
|
+
string DestinationEntityRecordID
|
|
341
|
+
float MatchProbability
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
LISTS {
|
|
345
|
+
string ID PK
|
|
346
|
+
string Name
|
|
347
|
+
string EntityID FK
|
|
348
|
+
string UserID FK
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
RECOMMENDATION_PROVIDERS ||--o{ RECOMMENDATION_RUNS : has
|
|
352
|
+
RECOMMENDATION_RUNS ||--o{ RECOMMENDATIONS : contains
|
|
353
|
+
RECOMMENDATIONS ||--o{ RECOMMENDATION_ITEMS : produces
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Dependencies
|
|
357
|
+
|
|
358
|
+
| Package | Purpose |
|
|
359
|
+
|---|---|
|
|
360
|
+
| `@memberjunction/core` | `BaseEngine`, `Metadata`, `RunView`, `UserInfo`, `LogStatus` |
|
|
361
|
+
| `@memberjunction/core-entities` | `RecommendationEntity`, `RecommendationRunEntity`, `RecommendationItemEntity`, `RecommendationProviderEntity`, `ListEntity` |
|
|
362
|
+
| `@memberjunction/global` | `MJGlobal` class factory for provider discovery |
|
|
363
|
+
|
|
364
|
+
## Development
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Build
|
|
368
|
+
npm run build
|
|
369
|
+
|
|
370
|
+
# Development mode
|
|
371
|
+
npm run start
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
376
|
+
ISC
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ai-recommendations",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.2.0",
|
|
5
5
|
"description": "MemberJunction Recommendations Engine",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"typescript": "^5.9.3"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@memberjunction/global": "5.
|
|
25
|
-
"@memberjunction/core": "5.
|
|
26
|
-
"@memberjunction/core-entities": "5.
|
|
27
|
-
"@memberjunction/ai": "5.
|
|
24
|
+
"@memberjunction/global": "5.2.0",
|
|
25
|
+
"@memberjunction/core": "5.2.0",
|
|
26
|
+
"@memberjunction/core-entities": "5.2.0",
|
|
27
|
+
"@memberjunction/ai": "5.2.0"
|
|
28
28
|
},
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|