@memrosetta/core 0.2.21 → 0.3.0
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 +21 -2
- package/dist/index.js +62 -32
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import Database from 'better-sqlite3';
|
|
|
2
2
|
import { Memory, MemoryState, MemoryInput, RelationType, MemoryRelation, SearchResult, SearchFilters, SearchQuery, SearchResponse, IMemoryEngine, CompressResult, MaintenanceResult, MemoryTier, MemoryQuality, TierConfig } from '@memrosetta/types';
|
|
3
3
|
import { Embedder, ContradictionDetector } from '@memrosetta/embeddings';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a memory ID referenced in an operation does not exist.
|
|
7
|
+
*/
|
|
8
|
+
declare class MemoryNotFoundError extends Error {
|
|
9
|
+
readonly memoryId: string;
|
|
10
|
+
constructor(memoryId: string);
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
interface SchemaOptions {
|
|
6
14
|
readonly vectorEnabled?: boolean;
|
|
7
15
|
readonly embeddingDimension?: number;
|
|
@@ -169,6 +177,17 @@ declare function rrfMergeWeighted(ftsResults: readonly {
|
|
|
169
177
|
readonly memory: Memory;
|
|
170
178
|
readonly rank: number;
|
|
171
179
|
}[], k?: number, limit?: number, ftsWeight?: number, vecWeight?: number): readonly SearchResult[];
|
|
180
|
+
/**
|
|
181
|
+
* Score-level fusion: alpha * normalizedVecSim + (1 - alpha) * normalizedFtsSim.
|
|
182
|
+
*
|
|
183
|
+
* Unlike RRF (which discards score magnitude), this preserves score information.
|
|
184
|
+
* Both FTS and vector scores are min-max normalized within the result set so
|
|
185
|
+
* neither modality dominates due to scale differences.
|
|
186
|
+
*
|
|
187
|
+
* Items found by both sources get the combined score (strongest signal).
|
|
188
|
+
* Items found by only one source get at most alpha or (1-alpha) of max.
|
|
189
|
+
*/
|
|
190
|
+
declare function convexCombinationMerge(ftsResults: readonly SearchResult[], vecResults: readonly VectorSearchResult[], alpha?: number, limit?: number): readonly SearchResult[];
|
|
172
191
|
/**
|
|
173
192
|
* Generative Agents-inspired 3-factor reranking.
|
|
174
193
|
* final_score = w_recency * recency + w_importance * importance + w_relevance * relevance
|
|
@@ -209,7 +228,7 @@ declare function updateAccessTracking(db: Database.Database, memoryIds: readonly
|
|
|
209
228
|
*
|
|
210
229
|
* When queryVec is not provided, performs FTS-only search (backward compatible).
|
|
211
230
|
* When queryVec is provided, performs hybrid search combining FTS + vector
|
|
212
|
-
* results via
|
|
231
|
+
* results via convex combination score fusion.
|
|
213
232
|
*
|
|
214
233
|
* Results are weighted by activation score and access tracking is updated.
|
|
215
234
|
*/
|
|
@@ -331,4 +350,4 @@ declare function determineTier(memory: {
|
|
|
331
350
|
*/
|
|
332
351
|
declare function estimateTokens(content: string): number;
|
|
333
352
|
|
|
334
|
-
export { DEFAULT_TIER_CONFIG, type MemoryRow, type PreparedStatements, type RelationStatements, type SchemaOptions, type SearchSqlResult, type SqliteEngineOptions, SqliteMemoryEngine, type VectorSearchResult, applyKeywordBoost, applyThreeFactorReranking, bruteForceVectorSearch, buildFtsQuery, buildSearchSql, computeActivation, computeEbbinghaus, createEngine, createPreparedStatements, createRelation, createRelationStatements, deduplicateResults, deriveMemoryState, determineTier, ensureSchema, estimateTokens, extractQueryTokens, ftsSearch, generateMemoryId, getRelationsByMemory, keywordsToString, normalizeScores, nowIso, rowToMemory, rrfMerge, rrfMergeWeighted, searchMemories, serializeEmbedding, storeBatchAsync, storeBatchInTransaction, storeMemory, storeMemoryAsync, stringToKeywords, updateAccessTracking, vectorSearch };
|
|
353
|
+
export { DEFAULT_TIER_CONFIG, MemoryNotFoundError, type MemoryRow, type PreparedStatements, type RelationStatements, type SchemaOptions, type SearchSqlResult, type SqliteEngineOptions, SqliteMemoryEngine, type VectorSearchResult, applyKeywordBoost, applyThreeFactorReranking, bruteForceVectorSearch, buildFtsQuery, buildSearchSql, computeActivation, computeEbbinghaus, convexCombinationMerge, createEngine, createPreparedStatements, createRelation, createRelationStatements, deduplicateResults, deriveMemoryState, 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
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var MemoryNotFoundError = class extends Error {
|
|
3
|
+
memoryId;
|
|
4
|
+
constructor(memoryId) {
|
|
5
|
+
super(`Memory not found: ${memoryId}`);
|
|
6
|
+
this.name = "MemoryNotFoundError";
|
|
7
|
+
this.memoryId = memoryId;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
1
11
|
// src/schema.ts
|
|
2
12
|
var SCHEMA_V1 = `
|
|
3
13
|
-- memories table
|
|
@@ -443,11 +453,11 @@ function createRelationStatements(db) {
|
|
|
443
453
|
function createRelation(db, stmts, srcMemoryId, dstMemoryId, relationType, reason) {
|
|
444
454
|
const srcExists = stmts.checkMemoryExists.get(srcMemoryId);
|
|
445
455
|
if (!srcExists) {
|
|
446
|
-
throw new
|
|
456
|
+
throw new MemoryNotFoundError(srcMemoryId);
|
|
447
457
|
}
|
|
448
458
|
const dstExists = stmts.checkMemoryExists.get(dstMemoryId);
|
|
449
459
|
if (!dstExists) {
|
|
450
|
-
throw new
|
|
460
|
+
throw new MemoryNotFoundError(dstMemoryId);
|
|
451
461
|
}
|
|
452
462
|
const createdAt = nowIso();
|
|
453
463
|
stmts.insertRelation.run(
|
|
@@ -792,7 +802,7 @@ function vectorSearch(db, queryVec, userId, limit, filters, useVecTable = true)
|
|
|
792
802
|
if (!useVecTable) {
|
|
793
803
|
return bruteForceVectorSearch(db, queryVec, userId, limit, filters);
|
|
794
804
|
}
|
|
795
|
-
const candidateLimit = Math.min(limit *
|
|
805
|
+
const candidateLimit = Math.min(limit * 10, 500);
|
|
796
806
|
const vecBuf = Buffer.from(queryVec.buffer, queryVec.byteOffset, queryVec.byteLength);
|
|
797
807
|
let candidates;
|
|
798
808
|
try {
|
|
@@ -802,7 +812,10 @@ function vectorSearch(db, queryVec, userId, limit, filters, useVecTable = true)
|
|
|
802
812
|
WHERE embedding MATCH ?
|
|
803
813
|
AND k = ?
|
|
804
814
|
`).all(vecBuf, candidateLimit);
|
|
805
|
-
} catch {
|
|
815
|
+
} catch (err) {
|
|
816
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
817
|
+
process.stderr.write(`[memrosetta] vec_memories KNN unavailable, using brute-force fallback: ${msg}
|
|
818
|
+
`);
|
|
806
819
|
return bruteForceVectorSearch(db, queryVec, userId, limit, filters);
|
|
807
820
|
}
|
|
808
821
|
if (candidates.length === 0) return [];
|
|
@@ -863,6 +876,9 @@ function vectorSearch(db, queryVec, userId, limit, filters, useVecTable = true)
|
|
|
863
876
|
params.push(filters.eventDateRange.end);
|
|
864
877
|
}
|
|
865
878
|
const rows = db.prepare(sql).all(...params);
|
|
879
|
+
if (rows.length < limit) {
|
|
880
|
+
return bruteForceVectorSearch(db, queryVec, userId, limit, filters);
|
|
881
|
+
}
|
|
866
882
|
return rows.map((row) => ({
|
|
867
883
|
memory: rowToMemory(row),
|
|
868
884
|
distance: distanceMap.get(row.id) ?? Infinity
|
|
@@ -934,6 +950,42 @@ function rrfMergeWeighted(ftsResults, vecResults, k = 20, limit = 10, ftsWeight
|
|
|
934
950
|
matchType: "hybrid"
|
|
935
951
|
}));
|
|
936
952
|
}
|
|
953
|
+
function convexCombinationMerge(ftsResults, vecResults, alpha = 0.5, limit = 10) {
|
|
954
|
+
const vecSims = /* @__PURE__ */ new Map();
|
|
955
|
+
for (const vr of vecResults) {
|
|
956
|
+
vecSims.set(vr.memory.memoryId, {
|
|
957
|
+
sim: 1 - vr.distance,
|
|
958
|
+
memory: vr.memory
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
const vecValues = [...vecSims.values()].map((v) => v.sim);
|
|
962
|
+
const vecMin = vecValues.length > 0 ? Math.min(...vecValues) : 0;
|
|
963
|
+
const vecMax = vecValues.length > 0 ? Math.max(...vecValues) : 1;
|
|
964
|
+
const vecRange = vecMax - vecMin || 1;
|
|
965
|
+
const ftsValues = ftsResults.map((r) => r.score);
|
|
966
|
+
const ftsMin = ftsValues.length > 0 ? Math.min(...ftsValues) : 0;
|
|
967
|
+
const ftsMax = ftsValues.length > 0 ? Math.max(...ftsValues) : 1;
|
|
968
|
+
const ftsRange = ftsMax - ftsMin || 1;
|
|
969
|
+
const merged = /* @__PURE__ */ new Map();
|
|
970
|
+
for (const fr of ftsResults) {
|
|
971
|
+
const normFts = (fr.score - ftsMin) / ftsRange;
|
|
972
|
+
const vecEntry = vecSims.get(fr.memory.memoryId);
|
|
973
|
+
const normVec = vecEntry ? (vecEntry.sim - vecMin) / vecRange : 0;
|
|
974
|
+
const score = alpha * normVec + (1 - alpha) * normFts;
|
|
975
|
+
merged.set(fr.memory.memoryId, { score, memory: fr.memory });
|
|
976
|
+
}
|
|
977
|
+
for (const [id, entry] of vecSims) {
|
|
978
|
+
if (merged.has(id)) continue;
|
|
979
|
+
const normVec = (entry.sim - vecMin) / vecRange;
|
|
980
|
+
const score = alpha * normVec;
|
|
981
|
+
merged.set(id, { score, memory: entry.memory });
|
|
982
|
+
}
|
|
983
|
+
return [...merged.values()].sort((a, b) => b.score - a.score).slice(0, limit).map(({ score, memory }) => ({
|
|
984
|
+
memory,
|
|
985
|
+
score,
|
|
986
|
+
matchType: "hybrid"
|
|
987
|
+
}));
|
|
988
|
+
}
|
|
937
989
|
function cosineSimilarity(a, b) {
|
|
938
990
|
let dot = 0;
|
|
939
991
|
let normA = 0;
|
|
@@ -1072,34 +1124,10 @@ function searchMemories(db, query, queryVec, useVecTable = true, skipAccessTrack
|
|
|
1072
1124
|
finalResults = deduplicateResults(boosted);
|
|
1073
1125
|
} else {
|
|
1074
1126
|
const limit = query.limit ?? DEFAULT_LIMIT;
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
...r,
|
|
1080
|
-
score: r.score * (vecIds.has(r.memory.memoryId) ? 1.3 : 1)
|
|
1081
|
-
}));
|
|
1082
|
-
reranked.sort((a, b) => b.score - a.score);
|
|
1083
|
-
const weighted = applyThreeFactorReranking(reranked);
|
|
1084
|
-
const boosted = applyKeywordBoost(weighted, queryTokens);
|
|
1085
|
-
finalResults = deduplicateResults(boosted);
|
|
1086
|
-
} else {
|
|
1087
|
-
const ftsIds = new Set(ftsResults.map((r) => r.memory.memoryId));
|
|
1088
|
-
const ftsItems = [...ftsResults];
|
|
1089
|
-
for (const vr of vecResults) {
|
|
1090
|
-
if (ftsItems.length >= limit) break;
|
|
1091
|
-
if (ftsIds.has(vr.memory.memoryId)) continue;
|
|
1092
|
-
ftsItems.push({
|
|
1093
|
-
memory: vr.memory,
|
|
1094
|
-
score: (1 - vr.distance) * 0.5,
|
|
1095
|
-
// Lower score than FTS items
|
|
1096
|
-
matchType: "hybrid"
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
|
-
const weighted = applyThreeFactorReranking(ftsItems);
|
|
1100
|
-
const boosted = applyKeywordBoost(weighted, queryTokens);
|
|
1101
|
-
finalResults = deduplicateResults(boosted);
|
|
1102
|
-
}
|
|
1127
|
+
const merged = convexCombinationMerge(ftsResults, vecResults, 0.3, limit);
|
|
1128
|
+
const weighted = applyThreeFactorReranking(merged);
|
|
1129
|
+
const boosted = applyKeywordBoost(weighted, queryTokens);
|
|
1130
|
+
finalResults = deduplicateResults(boosted);
|
|
1103
1131
|
}
|
|
1104
1132
|
updateAccessTracking(db, finalResults.map((r) => r.memory.memoryId));
|
|
1105
1133
|
return {
|
|
@@ -1714,6 +1742,7 @@ function createEngine(options) {
|
|
|
1714
1742
|
}
|
|
1715
1743
|
export {
|
|
1716
1744
|
DEFAULT_TIER_CONFIG,
|
|
1745
|
+
MemoryNotFoundError,
|
|
1717
1746
|
SqliteMemoryEngine,
|
|
1718
1747
|
applyKeywordBoost,
|
|
1719
1748
|
applyThreeFactorReranking,
|
|
@@ -1722,6 +1751,7 @@ export {
|
|
|
1722
1751
|
buildSearchSql,
|
|
1723
1752
|
computeActivation,
|
|
1724
1753
|
computeEbbinghaus,
|
|
1754
|
+
convexCombinationMerge,
|
|
1725
1755
|
createEngine,
|
|
1726
1756
|
createPreparedStatements,
|
|
1727
1757
|
createRelation,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memrosetta/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"better-sqlite3": "^11.0.0",
|
|
21
21
|
"nanoid": "^5.0.0",
|
|
22
22
|
"sqlite-vec": "^0.1.7",
|
|
23
|
-
"@memrosetta/types": "0.
|
|
23
|
+
"@memrosetta/types": "0.3.0"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"@memrosetta/embeddings": "0.
|
|
26
|
+
"@memrosetta/embeddings": "0.3.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/better-sqlite3": "^7.6.0",
|