Optionalprovider: IMetadataProviderOptional request-scoped metadata provider; see VectorBase constructor.
Protected_Use EndTime.
Use StartTime.
Provider-aware metadata access: returns the explicit provider when one was supplied
(constructor or Provider setter), otherwise the global default. All metadata
operations in this class hierarchy go through this getter so a bound instance never
leaks onto the global provider.
Optional metadata provider override. Pass the provider to the constructor (preferred)
or set instance.Provider = providerToUse before invoking helper methods in
multi-provider contexts. Setting it rebinds the internal RunView instance so view
execution rides the same provider. Falls back to the global default when unset.
The fallback is the global provider itself, never the Metadata wrapper: consumers
type-check what they get here (e.g. VectorDBBase.TryWireColocatedHost needs an
IColocatedVectorHost), and the wrapper fails those checks.
ProtectedProviderReturns the active metadata provider — explicit override (via this.Provider = ...)
if set, otherwise the global default. Provided as ProviderToUse for backward
compatibility with code paths that referenced this name before the base class
standardized on the Provider getter/setter.
ProtectedaddAdds configured display fields with type-aware storage: numeric SQL types stay
numeric, date fields can convert to epoch seconds/milliseconds via storeAs,
uniqueidentifier values are lowercase-normalized, and long strings are truncated.
OptionalmetadataConfig: EntityDocumentMetadataConfigProtectedaddAdds EntityIcon: opt-out by default, opt-in under the 'explicit' strategy.
ProtectedaddAdds the system-injected RecordID / Entity / TemplateID keys — omitted entirely under the 'explicit' strategy, where metadata contains only configured fields.
ProtectedaddAdds __mj_UpdatedAt for recency sorting: opt-out by default, opt-in under the 'explicit' strategy.
ProtectedAssertRefuse a batch whose vectors are not the width the index accepts, before a single record is upserted.
The Dimensions argument threaded into RunEmbedding is a request hint, not a contract:
only some providers honour it (OpenAI's text-embedding-3-* family does), a local ONNX model
is free to ignore it entirely, and nothing downstream ever compared what came back against
what the index accepts. The consequences split by provider and both are bad:
CreateRecords catch block quoting
"Vector dimension 1536 does not match the dimension of the index 512" — but it only
learns it after the API round trip, per batch, for the whole run.Throwing is deliberate and not per-record: every vector in the run comes from the same model, so a first-batch mismatch is a run-level misconfiguration. Continuing would spend the embedding budget on vectors that cannot be stored.
Skipped when the index declares no width — see ResolveIndexDimensions for when that is legitimate.
OptionalindexName: stringProtectedBuildBuilds a SQL filter from composite keys. Values are sanitized to prevent SQL injection.
ProtectedBuildBuild the SQL filter to select records that are in the given list. For single PK entities, uses a simple IN clause. For composite PK entities, uses an EXISTS clause that concatenates PK columns to match the RecordID format.
ProtectedBuildThe record-id string persisted to MJ: Entity Record Documents.RecordID and carried through
the pipeline as __mj_recordID: the entity's actual primary key column(s) read off the row,
in compact URL-segment form — the bare value for a single-column key (whatever that column
is called), F1|v1||F2|v2 for a composite key. CompositeKey.FromURLSegment reads either
form back, so a customer entity keyed by individual_id or (OrderID, LineNo) round-trips
instead of being reduced to whichever column happens to be listed first.
ProtectedBuildThe filter on the records a run vectorizes: the list's members when the run is for a list,
and the entity document's record filter (Configuration.recordFilter.extraFilter) when one
is set, ANDed together. Undefined when neither applies.
ProtectedBuildProtectedbuildBuild the vector metadata object for one embedding item.
Under 'all' / 'include' / 'exclude' strategies (the historical behavior), the system keys RecordID / Entity / TemplateID are always present, and EntityIcon / __mj_UpdatedAt default ON (opt-out via includeEntityIcon / includeUpdatedAt).
Under the 'explicit' strategy, metadata contains EXACTLY the configured fields: no system keys, and EntityIcon / __mj_UpdatedAt flip to opt-in (default false).
protected and decomposed into focused add*Metadata steps (each also protected)
so a subclass can override just the piece it cares about — e.g. a custom EntityIcon
resolution, or different chunk-aware display-field handling — without having to
reimplement this whole method.
OptionalmetadataConfig: EntityDocumentMetadataConfigProtectedCanReturns true when PageRecordsByEntityID can serve the given entity via
keyset (seek) pagination — i.e. the entity has a single-column PK on a comparable
type. Callers iterating large tables should consult this and pass AfterKey when true.
Refreshes the EntityDocumentCache and configures the AIEngine and TemplateEngineServer
If true, the cache and enginges will be refreshed even if it is already loaded
OptionalcontextUser: UserInfoThe context user to use to refresh the cache and configure the engines
This method will create a default Entity Document for the given entityID, vectorDatabase, and AIModel
ProtectedCreateProtectedEmbedEmbeds one batch of rendered texts on the entity document's model. The ModelID pin keeps the
vector index single-model: unpinned, the runner may answer from any Embeddings model.
embeddingDimensions is the index's MJ: Vector Indexes.Dimensions (undefined for the model's
native size).
Returns active Entity Documents for vectorization. Defaults to the
Record Duplicate document type for back-compat with the historical use
case (duplicate detection); pass entityDocumentType to target a different
type (e.g. 'Search' for the search-tier vector pool that backs
Provider.SearchEntity).
When multiple active EntityDocuments exist for the same entity (e.g. for different content variants), only the first row encountered is kept — the one-doc-per-entity restriction matches what existing vectorize callers already expect.
Cache source. Reads from KnowledgeHubMetadataEngine's cached
EntityDocuments array (already loaded by Config() above) — no fresh
RunView. The denormalized Type and Entity columns are available on
the cached rows, so the type and entity filters are pure client-side
predicates.
Empty result is not an error. When no Active documents match (none configured
yet, or all inactive), this returns an empty array — it does NOT throw. An unknown/
misspelled entityDocumentType is surfaced as a LogStatus warning, still returning
[]. Callers treat the empty case as "nothing to do" (e.g. VectorizeEntityAction
reports NO_DOCUMENTS success), so the unattended daily Entity Vector Sync job isn't
flagged as failed on a fresh DB with no Search documents.
OptionalentityNames: string[]If provided, only Entity Documents for the specified entities will be returned.
Name of the EntityDocumentType to filter by. Defaults to 'Record Duplicate'. Pass 'Search' for the search-tier pool.
Active, de-duped-per-entity Entity Documents of the given type; [] when none match.
ProtectedGetResolve the embedding model to generate vectors with.
Pass an id wherever one exists. The width of an embedding is a property of the model,
and the width an index accepts is fixed when the index is created, so which model this
returns decides whether the vectors fit. The no-id branch cannot know that and is a
last resort only.
The no-id branch used to be a bare find() over AIEngine.Instance.Models, which
BaseAIEngine.Config loads with no OrderBy and no IsActive filter. So it returned
whichever Embeddings row the view happened to hand back first — a different model on a
different database, or after an unrelated insert, and quite possibly a deactivated one.
An arbitrary answer is worse than a wrong one: the same code produces different widths in
different environments and nothing in the failure names the model that was chosen.
It is now deterministic — active models first, then highest PowerRank, then name — and it
says out loud which model it picked and that nobody asked for it.
Optionalid: stringOptionalContextUser: UserInfoProtectedGetProtectedGetOptionalrecordIDs: CompositeKey[]ProtectedGetThe related-entity rows a page of records' templates render; see EntityDocumentTemplateDataBuilder.LoadRelatedData.
ProtectedGetThe data one record's template renders. Built by EntityDocumentTemplateDataBuilder, which duplicate detection renders with too, so the text it queries with matches the text stored here.
ProtectedGetOptionalid: stringProtectedGetResolve the embedding driver and vector-DB driver for an Entity Document.
OptionalcreateDocumentIfNotFound: booleanOptionalvectorIndex: MJVectorIndexEntityWhen supplied, the embedding model is taken from the index
(VectorIndex.EmbeddingModelID, a NOT NULL column) rather than from
EntityDocument.AIModelID. The index's accepted width is fixed when the index is created and
the width of an embedding is a property of the model, so the index is the only party whose
opinion about the model can be right. EntityDocument.AIModelID is nullable, and when it is
null this used to fall through to GetAIModel() with no argument — an arbitrary pick out of
an unordered list, which is how an index ends up holding vectors of a width it never agreed to.
A document that names a different model is reported rather than quietly honoured.
ProtectedPageProtectedResolvePick the embedding model: the index's when we have an index, the document's otherwise.
When both are set and they disagree, the index wins and the disagreement is logged. The disagreement is a genuine misconfiguration — one of the two is going to produce vectors the index cannot hold — but it is the operator's to fix, and failing the run would strand every tenant whose document and index drifted apart while still embedding correctly.
OptionalvectorIndex: MJVectorIndexEntityProtectedResolveThe width this index accepts, or null when the index legitimately has none.
Order of preference:
VectorIndex.Dimensions when it is set. This is the operator's declaration, or the width
MJVectorIndexEntityServer learned by probing the embedding model at create time and
wrote back once the provider accepted the index. It is what the embedding request honours.ListIndexes(), written back onto the row.
This covers rows whose Dimensions is still null: indexes created before the create-time
probe existed (the Knowledge Hub create-index form never set it, and the old write-back
never ran when the provider call failed), and a row read before its detached provisioning
has finished writing back. Refusing those rows outright would strand them, so the
provider is asked first and the row is repaired.null is returned only for a driver that owns no index object
(VectorDBBase.ManagesIndexes === false): for SimpleVectorServiceProvider the
"index" is a logical pairing and the vectors live in MJ: Entity Record Documents.VectorJSON,
a JSON column with no fixed width. There is nothing to check against and nothing to refuse.
ProtectedResolveResolves the API key for a vector database provider. Checks the Credential Engine
first (if VectorDatabase.CredentialID is set), then falls back to the legacy
environment variable AI_VENDOR_API_KEY__
ProtectedRunProtectedSaveSaving an Entity in any vector related package needs the CurrentUser property to be set on the entity So this is a simple wrapper to set it before saving
ProtectedUpsertCreates or updates Entity Record Documents for a batch of source records.
Reads are batched + parallel, saves are per-record. Previously each record did its own
RunView to find its existing ERD — an N+1 read storm (~1 query per source record)
that dominated the upsert phase and tripped the sequential/duplicate-RunView telemetry.
Here we group by (EntityID, EntityDocumentID) and issue the existence checks for ALL groups
in a SINGLE RunViews batch (one round trip, run in parallel) with a RecordID IN (…) filter
per group, map the results by RecordID, then find-or-create + Save() each record (the
per-record Save() is inherent to the BaseEntity contract). Grouping is usually a single read
within one entity's pipeline; a mixed batch produces several views, now executed together.
ProtectedUpsertCreates or updates the Entity Record Document for a single source record. Thin back-compat wrapper over UpsertEntityRecordDocumentBatch; the pipeline uses the batch path directly to avoid an N+1 read per record.
ProtectedValidateThis method is resposnible for determining if the template(s) given have aligned parameters, meaning they don't have overlapping parameter names that have different meanings. It is okay for scenarios where there are > 1 template in use for a message to have different parameter names, but if they have the SAME parameter names they must not have different settings.
OptionalcontextUser: UserInfo
Class that specializes in vectorizing entities using embedding models and upserting them into Vector Databases