@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 +18 -1
- package/dist/index.js +109 -16
- package/package.json +5 -4
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
|
-
|
|
76
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(
|
|
77
|
-
|
|
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(
|
|
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(
|
|
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
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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, {
|
|
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
|
+
"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.
|
|
23
|
+
"@memrosetta/types": "0.2.5"
|
|
23
24
|
},
|
|
24
25
|
"optionalDependencies": {
|
|
25
|
-
"@memrosetta/embeddings": "0.2.
|
|
26
|
+
"@memrosetta/embeddings": "0.2.5"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@types/better-sqlite3": "^7.6.0",
|