@memrosetta/core 0.2.3 → 0.2.5

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/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Embedder, ContradictionDetector } from '@memrosetta/embeddings';
4
4
 
5
5
  interface SchemaOptions {
6
6
  readonly vectorEnabled?: boolean;
7
+ readonly embeddingDimension?: number;
7
8
  }
8
9
  declare function ensureSchema(db: Database.Database, options?: SchemaOptions): void;
9
10
 
@@ -143,6 +144,22 @@ declare function rrfMerge(ftsResults: readonly {
143
144
  readonly memory: Memory;
144
145
  readonly rank: number;
145
146
  }[], k?: number, limit?: number): readonly SearchResult[];
147
+ /**
148
+ * Weighted RRF merge: FTS results get higher weight than vector results.
149
+ *
150
+ * ftsWeight=2.0 means FTS rank contributes 2x more than vector rank.
151
+ * This preserves FTS precision while allowing vector to boost semantically
152
+ * similar results that FTS might rank lower.
153
+ *
154
+ * Items found by both FTS and vector get the combined score (strongest signal).
155
+ */
156
+ declare function rrfMergeWeighted(ftsResults: readonly {
157
+ readonly memory: Memory;
158
+ readonly rank: number;
159
+ }[], vecResults: readonly {
160
+ readonly memory: Memory;
161
+ readonly rank: number;
162
+ }[], k?: number, limit?: number, ftsWeight?: number, vecWeight?: number): readonly SearchResult[];
146
163
  /**
147
164
  * Remove duplicate search results based on content identity.
148
165
  * Keeps the first (highest-scored) occurrence when duplicates exist.
@@ -260,4 +277,4 @@ declare function determineTier(memory: {
260
277
  */
261
278
  declare function estimateTokens(content: string): number;
262
279
 
263
- export { DEFAULT_TIER_CONFIG, type MemoryRow, type PreparedStatements, type RelationStatements, type SchemaOptions, type SearchSqlResult, type SqliteEngineOptions, SqliteMemoryEngine, type VectorSearchResult, applyKeywordBoost, bruteForceVectorSearch, buildFtsQuery, buildSearchSql, computeActivation, createEngine, createPreparedStatements, createRelation, createRelationStatements, deduplicateResults, determineTier, ensureSchema, estimateTokens, extractQueryTokens, ftsSearch, generateMemoryId, getRelationsByMemory, keywordsToString, normalizeScores, nowIso, rowToMemory, rrfMerge, searchMemories, serializeEmbedding, storeBatchAsync, storeBatchInTransaction, storeMemory, storeMemoryAsync, stringToKeywords, updateAccessTracking, vectorSearch };
280
+ export { DEFAULT_TIER_CONFIG, type MemoryRow, type PreparedStatements, type RelationStatements, type SchemaOptions, type SearchSqlResult, type SqliteEngineOptions, SqliteMemoryEngine, type VectorSearchResult, applyKeywordBoost, bruteForceVectorSearch, buildFtsQuery, buildSearchSql, computeActivation, createEngine, createPreparedStatements, createRelation, createRelationStatements, deduplicateResults, determineTier, ensureSchema, estimateTokens, extractQueryTokens, ftsSearch, generateMemoryId, getRelationsByMemory, keywordsToString, normalizeScores, nowIso, rowToMemory, rrfMerge, rrfMergeWeighted, searchMemories, serializeEmbedding, storeBatchAsync, storeBatchInTransaction, storeMemory, storeMemoryAsync, stringToKeywords, updateAccessTracking, vectorSearch };
package/dist/index.js CHANGED
@@ -72,11 +72,9 @@ CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN
72
72
  INSERT INTO memories_fts(rowid, content, keywords) VALUES (new.id, new.content, new.keywords);
73
73
  END;
74
74
  `;
75
- var SCHEMA_V2 = `
76
- CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(
77
- embedding float[384]
78
- );
79
- `;
75
+ function schemaV2(dim) {
76
+ return `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding float[${dim}]);`;
77
+ }
80
78
  var SCHEMA_V3 = `
81
79
  ALTER TABLE memories ADD COLUMN event_date_start TEXT;
82
80
  ALTER TABLE memories ADD COLUMN event_date_end TEXT;
@@ -99,16 +97,17 @@ function ensureSchema(db, options) {
99
97
  const hasVersionTable = db.prepare(
100
98
  "SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'"
101
99
  ).get();
100
+ const dim = options?.embeddingDimension ?? 384;
102
101
  if (!hasVersionTable) {
103
- db.exec("CREATE TABLE schema_version (version INTEGER NOT NULL)");
102
+ db.exec("CREATE TABLE schema_version (version INTEGER NOT NULL, embedding_dimension INTEGER DEFAULT 384)");
104
103
  db.exec(SCHEMA_V1);
105
104
  let version = 1;
106
105
  if (options?.vectorEnabled) {
107
- db.exec(SCHEMA_V2);
106
+ db.exec(schemaV2(dim));
108
107
  version = 2;
109
108
  }
110
109
  version = 4;
111
- db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(version);
110
+ db.prepare("INSERT INTO schema_version (version, embedding_dimension) VALUES (?, ?)").run(version, dim);
112
111
  return;
113
112
  }
114
113
  const row = db.prepare("SELECT version FROM schema_version").get();
@@ -118,7 +117,7 @@ function ensureSchema(db, options) {
118
117
  db.prepare("UPDATE schema_version SET version = ?").run(1);
119
118
  }
120
119
  if (currentVersion < 2 && options?.vectorEnabled) {
121
- db.exec(SCHEMA_V2);
120
+ db.exec(schemaV2(dim));
122
121
  db.prepare("UPDATE schema_version SET version = ?").run(2);
123
122
  }
124
123
  if (currentVersion < 3) {
@@ -133,6 +132,39 @@ function ensureSchema(db, options) {
133
132
  }
134
133
  db.prepare("UPDATE schema_version SET version = ?").run(4);
135
134
  }
135
+ if (options?.vectorEnabled) {
136
+ const hasVecTable = db.prepare(
137
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='vec_memories'"
138
+ ).get();
139
+ if (!hasVecTable) {
140
+ try {
141
+ db.exec(schemaV2(dim));
142
+ } catch {
143
+ process.stderr.write("[memrosetta] sqlite-vec not available, vector search disabled\n");
144
+ }
145
+ }
146
+ const hasDimCol = db.prepare("PRAGMA table_info(schema_version)").all().some((col) => col.name === "embedding_dimension");
147
+ if (!hasDimCol) {
148
+ db.exec("ALTER TABLE schema_version ADD COLUMN embedding_dimension INTEGER DEFAULT 384");
149
+ }
150
+ const storedDim = db.prepare("SELECT embedding_dimension FROM schema_version").get()?.embedding_dimension ?? 384;
151
+ if (storedDim !== dim) {
152
+ process.stderr.write(
153
+ `[memrosetta] Embedding dimension changed (${storedDim} -> ${dim}). Recreating vector index...
154
+ `
155
+ );
156
+ try {
157
+ db.exec("DROP TABLE IF EXISTS vec_memories");
158
+ } catch {
159
+ }
160
+ try {
161
+ db.exec(schemaV2(dim));
162
+ db.prepare("UPDATE schema_version SET embedding_dimension = ?").run(dim);
163
+ } catch {
164
+ process.stderr.write("[memrosetta] sqlite-vec not available, vector search disabled\n");
165
+ }
166
+ }
167
+ }
136
168
  }
137
169
 
138
170
  // src/utils.ts
@@ -789,6 +821,40 @@ function rrfMerge(ftsResults, vecResults, k = 20, limit = 10) {
789
821
  matchType: "hybrid"
790
822
  }));
791
823
  }
824
+ function rrfMergeWeighted(ftsResults, vecResults, k = 20, limit = 10, ftsWeight = 2, vecWeight = 1) {
825
+ const scores = /* @__PURE__ */ new Map();
826
+ for (let i = 0; i < ftsResults.length; i++) {
827
+ const item = ftsResults[i];
828
+ const contribution = ftsWeight / (k + i + 1);
829
+ const existing = scores.get(item.memory.memoryId);
830
+ if (existing) {
831
+ existing.score += contribution;
832
+ } else {
833
+ scores.set(item.memory.memoryId, {
834
+ score: contribution,
835
+ memory: item.memory
836
+ });
837
+ }
838
+ }
839
+ for (let i = 0; i < vecResults.length; i++) {
840
+ const item = vecResults[i];
841
+ const contribution = vecWeight / (k + i + 1);
842
+ const existing = scores.get(item.memory.memoryId);
843
+ if (existing) {
844
+ existing.score += contribution;
845
+ } else {
846
+ scores.set(item.memory.memoryId, {
847
+ score: contribution,
848
+ memory: item.memory
849
+ });
850
+ }
851
+ }
852
+ return [...scores.entries()].sort((a, b) => b[1].score - a[1].score).slice(0, limit).map(([, { score, memory }]) => ({
853
+ memory,
854
+ score,
855
+ matchType: "hybrid"
856
+ }));
857
+ }
792
858
  function cosineSimilarity(a, b) {
793
859
  let dot = 0;
794
860
  let normA = 0;
@@ -897,12 +963,35 @@ function searchMemories(db, query, queryVec, useVecTable = true) {
897
963
  const boosted = applyKeywordBoost(weighted, queryTokens);
898
964
  finalResults = deduplicateResults(boosted);
899
965
  } else {
900
- const ftsRanked = ftsResults.map((r, i) => ({ memory: r.memory, rank: i }));
901
- const vecRanked = vecResults.map((r, i) => ({ memory: r.memory, rank: i }));
902
- const merged = rrfMerge(ftsRanked, vecRanked, 20, query.limit ?? DEFAULT_LIMIT);
903
- const weighted = applyActivationWeighting(merged);
904
- const boosted = applyKeywordBoost(weighted, queryTokens);
905
- finalResults = deduplicateResults(boosted);
966
+ const limit = query.limit ?? DEFAULT_LIMIT;
967
+ const ftsHasEnough = ftsResults.length >= limit;
968
+ if (ftsHasEnough) {
969
+ const vecIds = new Set(vecResults.map((r) => r.memory.memoryId));
970
+ const reranked = ftsResults.map((r) => ({
971
+ ...r,
972
+ score: r.score * (vecIds.has(r.memory.memoryId) ? 1.3 : 1)
973
+ }));
974
+ reranked.sort((a, b) => b.score - a.score);
975
+ const weighted = applyActivationWeighting(reranked);
976
+ const boosted = applyKeywordBoost(weighted, queryTokens);
977
+ finalResults = deduplicateResults(boosted);
978
+ } else {
979
+ const ftsIds = new Set(ftsResults.map((r) => r.memory.memoryId));
980
+ const ftsItems = [...ftsResults];
981
+ for (const vr of vecResults) {
982
+ if (ftsItems.length >= limit) break;
983
+ if (ftsIds.has(vr.memory.memoryId)) continue;
984
+ ftsItems.push({
985
+ memory: vr.memory,
986
+ score: (1 - vr.distance) * 0.5,
987
+ // Lower score than FTS items
988
+ matchType: "hybrid"
989
+ });
990
+ }
991
+ const weighted = applyActivationWeighting(ftsItems);
992
+ const boosted = applyKeywordBoost(weighted, queryTokens);
993
+ finalResults = deduplicateResults(boosted);
994
+ }
906
995
  }
907
996
  updateAccessTracking(db, finalResults.map((r) => r.memory.memoryId));
908
997
  return {
@@ -987,7 +1076,10 @@ var SqliteMemoryEngine = class {
987
1076
  this.vectorEnabled = false;
988
1077
  }
989
1078
  }
990
- ensureSchema(this.db, { vectorEnabled: this.vectorEnabled });
1079
+ ensureSchema(this.db, {
1080
+ vectorEnabled: this.vectorEnabled,
1081
+ embeddingDimension: this.options.embedder?.dimension ?? 384
1082
+ });
991
1083
  this.stmts = createPreparedStatements(this.db);
992
1084
  this.relStmts = createRelationStatements(this.db);
993
1085
  }
@@ -1347,6 +1439,7 @@ export {
1347
1439
  nowIso,
1348
1440
  rowToMemory,
1349
1441
  rrfMerge,
1442
+ rrfMergeWeighted,
1350
1443
  searchMemories,
1351
1444
  serializeEmbedding,
1352
1445
  storeBatchAsync,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memrosetta/core",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -8,7 +8,8 @@
8
8
  ".": {
9
9
  "import": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts"
11
- }
11
+ },
12
+ "./package.json": "./package.json"
12
13
  },
13
14
  "files": [
14
15
  "dist",
@@ -19,10 +20,10 @@
19
20
  "better-sqlite3": "^11.0.0",
20
21
  "nanoid": "^5.0.0",
21
22
  "sqlite-vec": "^0.1.7",
22
- "@memrosetta/types": "0.2.3"
23
+ "@memrosetta/types": "0.2.5"
23
24
  },
24
25
  "optionalDependencies": {
25
- "@memrosetta/embeddings": "0.2.3"
26
+ "@memrosetta/embeddings": "0.2.5"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/better-sqlite3": "^7.6.0",