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.
graph TD
subgraph Engine["@memberjunction/ai-recommendations"]
REB["RecommendationEngineBase
(singleton BaseEngine)"]
RPB["RecommendationProviderBase
(abstract)"]
RR["RecommendationRequest<T>"]
RRES["RecommendationResult"]
end
subgraph Providers["Registered Providers"]
P1["Provider A"]
P2["Provider B"]
end
subgraph MJEntities["MemberJunction Entities"]
RP["Recommendation Providers"]
RUN["Recommendation Runs"]
REC["Recommendations"]
RI["Recommendation Items"]
LIST["Lists / List Details"]
end
subgraph MJCore["MemberJunction Core"]
BE["BaseEngine"]
CF["ClassFactory"]
MD["Metadata"]
end
REB -->|extends| BE
REB -->|discovers| CF
CF -->|creates| P1
CF -->|creates| P2
P1 -->|extends| RPB
P2 -->|extends| RPB
REB --> RP
REB --> RUN
RPB --> REC
RPB --> RI
REB --> LIST
style Engine fill:#2d6a9f,stroke:#1a4971,color:#fff
style Providers fill:#2d8659,stroke:#1a5c3a,color:#fff
style MJEntities fill:#b8762f,stroke:#8a5722,color:#fff
style MJCore fill:#7c5295,stroke:#563a6b,color:#fff
npm install @memberjunction/ai-recommendations
This package provides the framework for running recommendations in MemberJunction. It follows the engine/provider pattern used throughout the platform:
BaseEngine) that loads provider metadata, selects a provider, creates Recommendation Run tracking records, and delegates the actual recommendation logicProviders are discovered at runtime through MemberJunction's ClassFactory using @RegisterClass(RecommendationProviderBase, 'ProviderName').
sequenceDiagram
participant Caller
participant Engine as RecommendationEngineBase
participant CF as ClassFactory
participant Provider as RecommendationProvider
participant DB as MJ Database
Caller->>Engine: Recommend(request)
Engine->>Engine: TryThrowIfNotLoaded()
alt Provider specified
Engine->>Engine: Use request.Provider
else No provider
Engine->>Engine: Use first available
end
Engine->>Engine: GetRecommendationEntities(request)
alt From List
Engine->>DB: Load List + List Details
Engine->>DB: Load entity records by IDs
else From EntityAndRecordsInfo
Engine->>DB: Load records by entity name + IDs
else Pre-built
Engine->>Engine: Validate Recommendations array
end
Engine->>DB: Create Recommendation Run (Status: In Progress)
opt CreateErrorList = true
Engine->>DB: Create error tracking List
end
Engine->>CF: CreateInstance(provider.Name)
CF-->>Engine: Provider instance
Engine->>Provider: Recommend(request)
loop For each recommendation
Provider->>Provider: Call external API
Provider->>DB: SaveRecommendation + Items
end
Provider-->>Engine: RecommendationResult
Engine->>DB: Update Run (Completed/Error)
Engine-->>Caller: RecommendationResult
A singleton engine that manages the recommendation lifecycle.
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
// Access the singleton
const engine = RecommendationEngineBase.Instance;
// Initialize (loads Recommendation Providers metadata)
await engine.Config(false, contextUser);
// Run recommendations
const result = await engine.Recommend(request);
Key properties and methods:
| Member | Description |
|---|---|
Instance |
Static getter for the singleton instance |
RecommendationProviders |
Array of RecommendationProviderEntity loaded from metadata |
Config(forceRefresh?, contextUser?, provider?) |
Loads provider metadata into cache |
Recommend<T>(request) |
Runs the full recommendation pipeline |
Abstract base class for implementing recommendation providers.
classDiagram
class RecommendationProviderBase {
<>
-_md : Metadata
-_ContextUser : UserInfo
+ContextUser : UserInfo
+Recommend(request)* RecommendationResult
#SaveRecommendation(rec, runID, items) boolean
}
class ConcreteProvider {
+Recommend(request) RecommendationResult
}
RecommendationProviderBase <|-- ConcreteProvider
style RecommendationProviderBase fill:#2d6a9f,stroke:#1a4971,color:#fff
style ConcreteProvider fill:#2d8659,stroke:#1a5c3a,color:#fff
The SaveRecommendation helper method handles:
RecommendationRunID on the recommendation entityRecommendationItemEntity recordsThe request object supports three ways to specify source records:
graph TD
RR["RecommendationRequest"]
OPT1["Recommendations[]
Pre-built entities"]
OPT2["EntityAndRecordsInfo
Entity name + Record IDs"]
OPT3["ListID
MJ List reference"]
RR --> OPT1
RR --> OPT2
RR --> OPT3
style RR fill:#2d6a9f,stroke:#1a4971,color:#fff
style OPT1 fill:#2d8659,stroke:#1a5c3a,color:#fff
style OPT2 fill:#2d8659,stroke:#1a5c3a,color:#fff
style OPT3 fill:#2d8659,stroke:#1a5c3a,color:#fff
| Field | Type | Description |
|---|---|---|
Recommendations |
RecommendationEntity[] |
Pre-built unsaved recommendation entities |
EntityAndRecordsInfo |
{ EntityName, RecordIDs } |
Entity name and array of record IDs to process |
ListID |
string |
ID of a MJ List whose details become the source records |
Provider |
RecommendationProviderEntity |
Specific provider to use (defaults to first available) |
CurrentUser |
UserInfo |
User context |
Options |
T |
Generic additional options passed to the provider |
CreateErrorList |
boolean |
Whether to create an error tracking list |
RunID |
string |
Set automatically by the engine |
ErrorListID |
string |
Set automatically if error list is created |
class RecommendationResult {
Request: RecommendationRequest;
RecommendationRun?: RecommendationRunEntity;
RecommendationItems?: RecommendationItemEntity[];
Success: boolean;
ErrorMessage: string;
AppendWarning(message: string): void; // Adds warning without setting Success=false
AppendError(message: string): void; // Adds error and sets Success=false
GetErrorMessages(): string[]; // Splits ErrorMessage into array
}
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
import { RecommendationRequest } from '@memberjunction/ai-recommendations';
const engine = RecommendationEngineBase.Instance;
await engine.Config(false, contextUser);
const request = new RecommendationRequest();
request.ListID = 'list-uuid';
request.CurrentUser = contextUser;
request.CreateErrorList = true;
const result = await engine.Recommend(request);
if (result.Success) {
console.log(`Generated ${result.RecommendationItems?.length ?? 0} items`);
} else {
console.error(result.ErrorMessage);
}
const request = new RecommendationRequest();
request.EntityAndRecordsInfo = {
EntityName: 'Products',
RecordIDs: ['id-1', 'id-2', 'id-3']
};
request.CurrentUser = contextUser;
const result = await engine.Recommend(request);
import { RecommendationProviderBase } from '@memberjunction/ai-recommendations';
import { RecommendationRequest, RecommendationResult } from '@memberjunction/ai-recommendations';
import { RegisterClass } from '@memberjunction/global';
import { Metadata } from '@memberjunction/core';
import { RecommendationItemEntity } from '@memberjunction/core-entities';
@RegisterClass(RecommendationProviderBase, 'My Recommendation Provider')
export class MyProvider extends RecommendationProviderBase {
async Recommend(request: RecommendationRequest): Promise<RecommendationResult> {
const result = new RecommendationResult(request);
const md = new Metadata();
for (const rec of request.Recommendations) {
// Call your recommendation API/algorithm
const suggestions = await this.getSuggestions(rec.SourceEntityRecordID);
const items: RecommendationItemEntity[] = [];
for (const suggestion of suggestions) {
const item = await md.GetEntityObject<RecommendationItemEntity>(
'Recommendation Items', request.CurrentUser
);
item.NewRecord();
item.DestinationEntityID = suggestion.entityID;
item.DestinationEntityRecordID = suggestion.recordID;
item.MatchProbability = suggestion.score;
items.push(item);
}
await this.SaveRecommendation(rec, request.RunID, items);
}
return result;
}
private async getSuggestions(recordID: string): Promise<Suggestion[]> {
// Your recommendation logic here
return [];
}
}
erDiagram
RECOMMENDATION_PROVIDERS {
string ID PK
string Name
string Description
}
RECOMMENDATION_RUNS {
string ID PK
string RecommendationProviderID FK
string RunByUserID FK
datetime StartDate
string Status
string Description
}
RECOMMENDATIONS {
string ID PK
string RecommendationRunID FK
string SourceEntityID FK
string SourceEntityRecordID
}
RECOMMENDATION_ITEMS {
string ID PK
string RecommendationID FK
string DestinationEntityID FK
string DestinationEntityRecordID
float MatchProbability
}
LISTS {
string ID PK
string Name
string EntityID FK
string UserID FK
}
RECOMMENDATION_PROVIDERS ||--o{ RECOMMENDATION_RUNS : has
RECOMMENDATION_RUNS ||--o{ RECOMMENDATIONS : contains
RECOMMENDATIONS ||--o{ RECOMMENDATION_ITEMS : produces
| Package | Purpose |
|---|---|
@memberjunction/core |
BaseEngine, Metadata, RunView, UserInfo, LogStatus |
@memberjunction/core-entities |
RecommendationEntity, RecommendationRunEntity, RecommendationItemEntity, RecommendationProviderEntity, ListEntity |
@memberjunction/global |
MJGlobal class factory for provider discovery |
# Build
npm run build
# Development mode
npm run start
ISC