# @memberjunction/ai-recommendations 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. ## Architecture ```mermaid 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 ``` ## Installation ```bash npm install @memberjunction/ai-recommendations ``` ## Overview This package provides the framework for running recommendations in MemberJunction. It follows the engine/provider pattern used throughout the platform: 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 2. **RecommendationProviderBase** -- an abstract class that concrete providers implement to generate recommendations for each source record 3. **RecommendationRequest/RecommendationResult** -- typed request and response objects that flow through the pipeline Providers are discovered at runtime through MemberJunction's `ClassFactory` using `@RegisterClass(RecommendationProviderBase, 'ProviderName')`. ## Recommendation Flow ```mermaid 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 ``` ## Core Components ### RecommendationEngineBase A singleton engine that manages the recommendation lifecycle. ```typescript 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(request)` | Runs the full recommendation pipeline | ### RecommendationProviderBase Abstract base class for implementing recommendation providers. ```mermaid 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: 1. Setting the `RecommendationRunID` on the recommendation entity 2. Saving the recommendation record 3. Linking and saving all `RecommendationItemEntity` records ### RecommendationRequest\ The request object supports three ways to specify source records: ```mermaid 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 | ### RecommendationResult ```typescript 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 } ``` ## Usage ### Running Recommendations from a List ```typescript 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); } ``` ### Running Recommendations by Entity and Record IDs ```typescript const request = new RecommendationRequest(); request.EntityAndRecordsInfo = { EntityName: 'Products', RecordIDs: ['id-1', 'id-2', 'id-3'] }; request.CurrentUser = contextUser; const result = await engine.Recommend(request); ``` ### Implementing a Provider ```typescript 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 { 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( '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 { // Your recommendation logic here return []; } } ``` ## Database Entities ```mermaid 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 ``` ## Dependencies | 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 | ## Development ```bash # Build npm run build # Development mode npm run start ``` ## License ISC