import { DyFM_Log } from '@futdevpro/fsm-dynamo'; import { LVS_Search_Mode } from '../_enums/lvs-search-mode.enum'; import { LVS_SearchResult } from '../_models/lvs-search-result.interface'; import { DyNTS_LVS_VectorPersist } from '../_models/data-models/lvs-vector-persist.data-model'; import { DyNTS_LVS_VectorPersist_DataService } from './lvs-vector-persist.data-service'; import { LVS_VectorPool_ControlService } from './lvs-vector-pool.control-service'; /** * `DyNTS_LVS_PersistentVectorPool_ControlService` (BFR-AM-001) — egy `LVS_VectorPool_ControlService` in-memory pool + * MongoDB-perzisztencia **kompozíciója**. A FAM (fdp-agent-memory) `FAM_VectorSearch_ControlService` * persist+hydrate workaround-ját generalizálja bedrock-szintre: a vektorok a SAJÁT Mongo-ban * (`dynts_lvs_vector`) élnek, boot-kor a memória-pool-ba hidratálódnak — **NEM** MongoDB Atlas * Vector Search. * * **Miért KOMPOZÍCIÓ (NEM a pool-class módosítása):** a `LVS_VectorPool_ControlService`-t egy másik, * párhuzamos WIP (hybrid cosine+BM25) is érinti; a pool-class változtatása merge-konfliktust okozna. * Ez a wrapper a pool-t VÁLTOZATLANUL hagyja (delegál: `addVector`/`removeVector`/`updateVector`/ * `search`), és a perzisztencia-mellékhatást a wrapper-metódusokban végzi. Egy wrapper-instance egy * `collectionKey` (logikai pool); több pool = több wrapper. * * **FIGYELEM (memory: dynts_dataservice_eager_resolve):** a `DyNTS_DataService` base-ctor EAGER * `getDBService`-t hív, ezért a wrapper NEM tart élő data-service-mezőt — minden DB-művelet előtt lazy * `new DyNTS_LVS_VectorPersist_DataService(...)` (a `getPersistService` ezt adja). */ export class DyNTS_LVS_PersistentVectorPool_ControlService { /** A wrapped in-memory pool (a Dynamo LVS engine; VÁLTOZATLAN — kompozíció, nem módosítás). */ private readonly pool: LVS_VectorPool_ControlService; /** A pool-particionálás kulcsa (a Mongo-rekordok `collectionKey`-e + a hidratálás-szűrő). */ private readonly collectionKey: string; /** A perzisztencia-műveletek issuer-e (audit). */ private readonly issuer: string; constructor(set: { collectionKey: string, issuer: string, pool?: LVS_VectorPool_ControlService, }) { this.collectionKey = set.collectionKey; this.issuer = set.issuer; // Saját pool, ha a hívó nem ad be egyet (a wrapper nem módosítja a pool-osztályt — kompozíció). this.pool = set.pool ?? new LVS_VectorPool_ControlService(); } /** A wrapped in-memory pool (közvetlen olvasáshoz — pl. más search-mód, getAll). */ getPool(): LVS_VectorPool_ControlService { return this.pool; } /** * Egy vektor felvétele a memória-pool-ba ÉS tartós upsert-je Mongo-ba (persist-on-write, FAM-minta). * A pool `addVector`-ja upsert-szemantikájú (azonos kulcson felülír), a Mongo-oldal is `(collectionKey, * vectorId)`-upsert. A Mongo-írás MELLÉKHATÁS a memória-művelet után (a memória a forrás, a Mongo a tár). */ async addVector(vectorId: string, vector: number[], metadata?: unknown): Promise { this.pool.addVector(vectorId, vector); await this.getPersistService().upsertVector({ collectionKey: this.collectionKey, vectorId: vectorId, embedding: vector, metadata: metadata, }); } /** * Egy MEGLÉVŐ vektor frissítése a memória-pool-ban + tartós upsert Mongo-ba. A pool `updateVector`-ja * dob, ha a kulcs nem létezik (a pool-szerződés változatlan) — a Mongo-írás csak siker után fut. */ async updateVector(vectorId: string, vector: number[], metadata?: unknown): Promise { this.pool.updateVector(vectorId, vector); await this.getPersistService().upsertVector({ collectionKey: this.collectionKey, vectorId: vectorId, embedding: vector, metadata: metadata, }); } /** Egy vektor eltávolítása a memória-pool-ból ÉS a tartós tárból (`(collectionKey, vectorId)`). */ async removeVector(vectorId: string): Promise { this.pool.removeVector(vectorId); await this.getPersistService().removeVector(this.collectionKey, vectorId); } /** Keresés a memória-pool-on (a wrapped pool `search`-jét delegálja — VÁLTOZATLAN engine). */ search(query: number[], k: number, mode: LVS_Search_Mode): LVS_SearchResult[] { return this.pool.search(query, k, mode); } /** * BOOT-HIDRATÁLÁS (FAM-minta, BFR-AM-001): a `collectionKey` ÖSSZES perzistált vektorát betölti a * MEMÓRIA-pool-ba (`pool.addVector`), így a szerver-restart utáni in-memory index újraépül. A pool-t * előbb üríti (idempotens hidratálás). A hiányos (üres `vectorId`/`embedding`) rekordokat átugorja. * Visszaadja a betöltött vektorok számát. **NEM** Atlas — saját Mongo + in-memory pool. */ async hydrateFromMongo(): Promise { const records: DyNTS_LVS_VectorPersist[] = await this.getPersistService().findByCollectionKey(this.collectionKey); this.pool.clearPool(); let loaded: number = 0; for (const record of records) { if (!record.vectorId || !record.embedding?.length) { continue; } this.pool.addVector(record.vectorId, record.embedding); loaded++; } DyFM_Log.log( `[DyNTS LVS hydrate] '${this.collectionKey}' pool: ${loaded} vektor betöltve az in-memory pool-ba.`, ); return loaded; } /** * Perzistált rekordok közvetlen hidratálása a memória-pool-ba (Mongo-olvasás NÉLKÜL) — ha a hívó már * kezében van a rekord-halmaz (pl. batch-boot). A pool-t előbb üríti (idempotens). Visszaadja a * betöltött vektorok számát. */ hydrate(records: DyNTS_LVS_VectorPersist[]): number { this.pool.clearPool(); let loaded: number = 0; for (const record of records) { if (!record.vectorId || !record.embedding?.length) { continue; } this.pool.addVector(record.vectorId, record.embedding); loaded++; } return loaded; } /** * Lazy persist-data-service (memory: dynts_dataservice_eager_resolve — a base-ctor eager * `getDBService`-e miatt NEM tartható élő mezőként). */ private getPersistService(): DyNTS_LVS_VectorPersist_DataService { return new DyNTS_LVS_VectorPersist_DataService({ issuer: this.issuer }); } }