@melandlabs/memory-store 0.2.6 → 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/config-2Q092MUz.d.ts +470 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +386 -105
- package/dist/index.d.ts +3 -3
- package/dist/index.js +745 -107
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +389 -105
- package/dist/search/unified-search.d.ts +3 -101
- package/dist/search/unified-search.js +387 -106
- package/dist/server/cli-http.js +395 -105
- package/dist/server/cli-mcp.js +398 -105
- package/dist/server/cli-shared.d.ts +1 -1
- package/dist/server/cli-shared.js +17 -5
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +3 -1
- package/dist/storage/raw-message-store.js +20 -9
- package/dist/storage/sqlite-raw-message-store.d.ts +6 -3
- package/dist/storage/sqlite-raw-message-store.js +4 -3
- package/dist/storage/sqlite-vector-index.d.ts +1 -1
- package/dist/storage/sqlite-vector-index.js +1 -1
- package/package.json +10 -9
- package/dist/config-BYfTzG3h.d.ts +0 -170
|
@@ -108,16 +108,17 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
108
108
|
lexicalSearchRawMessages: () => lexicalSearchRawMessages,
|
|
109
109
|
resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
|
|
110
110
|
});
|
|
111
|
-
function resolveSQLiteRawMessageDbPath() {
|
|
111
|
+
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
112
|
+
if (dbPath && dbPath.length > 0) return dbPath;
|
|
112
113
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
113
114
|
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
114
115
|
}
|
|
115
116
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
116
117
|
return true;
|
|
117
118
|
}
|
|
118
|
-
async function getSQLiteRawMessageManager(
|
|
119
|
+
async function getSQLiteRawMessageManager(options = {}) {
|
|
119
120
|
if (!manager) {
|
|
120
|
-
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
121
|
+
const dbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
|
|
121
122
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
122
123
|
manager = new SQLiteRawMessageManager({
|
|
123
124
|
dbPath,
|
|
@@ -163,7 +164,7 @@ __export(raw_message_store_exports, {
|
|
|
163
164
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
164
165
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
165
166
|
});
|
|
166
|
-
function resolveBackend(
|
|
167
|
+
function resolveBackend(_env) {
|
|
167
168
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
168
169
|
if (explicit === "postgres") return "postgres";
|
|
169
170
|
return "sqlite";
|
|
@@ -190,33 +191,43 @@ function createRawMessageStore(options = {}) {
|
|
|
190
191
|
}
|
|
191
192
|
return pg;
|
|
192
193
|
}
|
|
193
|
-
return await getSQLiteRawMessageManager(
|
|
194
|
+
return await getSQLiteRawMessageManager({
|
|
195
|
+
dbPath: options.dbPath
|
|
196
|
+
});
|
|
194
197
|
},
|
|
195
198
|
async close() {
|
|
196
199
|
await closeSQLiteRawMessageManager();
|
|
197
200
|
}
|
|
198
201
|
};
|
|
199
202
|
}
|
|
203
|
+
function resolveDbPath(config) {
|
|
204
|
+
if (!config) return void 0;
|
|
205
|
+
if (config.dbPath) return config.dbPath;
|
|
206
|
+
if (config.db && typeof config.db === "object" && "path" in config.db) {
|
|
207
|
+
return config.db.path;
|
|
208
|
+
}
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
200
211
|
function configureRawMessageStore(config) {
|
|
201
212
|
moduleConfig = config;
|
|
202
|
-
moduleStore = createRawMessageStore({ env: config.env });
|
|
213
|
+
moduleStore = createRawMessageStore({ env: config.env, dbPath: resolveDbPath(config) });
|
|
203
214
|
return moduleStore;
|
|
204
215
|
}
|
|
205
216
|
function getRawMessageStorageBackend() {
|
|
206
217
|
if (!moduleStore) {
|
|
207
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
218
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
208
219
|
}
|
|
209
220
|
return moduleStore.getBackend();
|
|
210
221
|
}
|
|
211
222
|
function isRawMessageStorageAvailable() {
|
|
212
223
|
if (!moduleStore) {
|
|
213
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
224
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
214
225
|
}
|
|
215
226
|
return moduleStore.isAvailable();
|
|
216
227
|
}
|
|
217
228
|
async function getRawMessageManager() {
|
|
218
229
|
if (!moduleStore) {
|
|
219
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
230
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
220
231
|
}
|
|
221
232
|
return moduleStore.getManager();
|
|
222
233
|
}
|
|
@@ -286,6 +297,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
286
297
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
287
298
|
return value === "rrf" ? "rrf" : "similarity";
|
|
288
299
|
}
|
|
300
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
301
|
+
if (value === "rewrite" || value === "iterative") {
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
return defaultStrategy;
|
|
305
|
+
}
|
|
289
306
|
var DEFAULT_RRF_K = 60;
|
|
290
307
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
291
308
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -308,7 +325,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
308
325
|
}
|
|
309
326
|
}
|
|
310
327
|
}
|
|
311
|
-
const ranked = order.map((key) =>
|
|
328
|
+
const ranked = order.map((key) => {
|
|
329
|
+
const entry = scores.get(key);
|
|
330
|
+
if (!entry) {
|
|
331
|
+
throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
|
|
332
|
+
}
|
|
333
|
+
return entry;
|
|
334
|
+
}).sort((a, b) => {
|
|
312
335
|
if (b.rrf !== a.rrf) {
|
|
313
336
|
return b.rrf - a.rrf;
|
|
314
337
|
}
|
|
@@ -353,14 +376,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
353
376
|
function deriveLexicalKeywords(query) {
|
|
354
377
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
355
378
|
}
|
|
379
|
+
function parseDateBoundary(value, role) {
|
|
380
|
+
const trimmed = value.trim();
|
|
381
|
+
if (!trimmed) {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
385
|
+
if (dateOnlyMatch) {
|
|
386
|
+
const year = Number.parseInt(dateOnlyMatch[1], 10);
|
|
387
|
+
const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
|
|
388
|
+
const day = Number.parseInt(dateOnlyMatch[3], 10);
|
|
389
|
+
if (role === "from") {
|
|
390
|
+
return Date.UTC(year, month, day);
|
|
391
|
+
}
|
|
392
|
+
return Date.UTC(year, month, day + 1) - 1;
|
|
393
|
+
}
|
|
394
|
+
const parsed = Date.parse(trimmed);
|
|
395
|
+
if (Number.isNaN(parsed)) {
|
|
396
|
+
return void 0;
|
|
397
|
+
}
|
|
398
|
+
return parsed;
|
|
399
|
+
}
|
|
400
|
+
function getCandidateTimestamp(metadata) {
|
|
401
|
+
for (const key of ["timestamp", "createdAt", "time"]) {
|
|
402
|
+
const value = metadata[key];
|
|
403
|
+
if (typeof value === "number") {
|
|
404
|
+
return value;
|
|
405
|
+
}
|
|
406
|
+
if (typeof value === "string") {
|
|
407
|
+
const parsed = Date.parse(value);
|
|
408
|
+
if (!Number.isNaN(parsed)) {
|
|
409
|
+
return parsed;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
function filterByDateRange(items, dateFrom, dateTo) {
|
|
416
|
+
if (!dateFrom && !dateTo) {
|
|
417
|
+
return items;
|
|
418
|
+
}
|
|
419
|
+
const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
|
|
420
|
+
const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
|
|
421
|
+
if (fromMs === void 0 && toMs === void 0) {
|
|
422
|
+
return items;
|
|
423
|
+
}
|
|
424
|
+
return items.filter((item) => {
|
|
425
|
+
const ts = getCandidateTimestamp(item.metadata);
|
|
426
|
+
if (ts === void 0) {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
if (fromMs !== void 0 && ts < fromMs) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
if (toMs !== void 0 && ts > toMs) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
return true;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
async function embedQueryVariant(embedQuery, input, query) {
|
|
439
|
+
return embedQuery({
|
|
440
|
+
userId: input.userId,
|
|
441
|
+
query,
|
|
442
|
+
authToken: input.authToken
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
446
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
447
|
+
let semantic = [];
|
|
448
|
+
if (isRawMessageChromaEnabled()) {
|
|
449
|
+
try {
|
|
450
|
+
semantic = (await Promise.all(
|
|
451
|
+
filters.map((filter) => {
|
|
452
|
+
const botId = "botId" in filter ? filter.botId : void 0;
|
|
453
|
+
return searchRawMessagesWithChroma({
|
|
454
|
+
userId: input.userId,
|
|
455
|
+
queryEmbedding,
|
|
456
|
+
limit,
|
|
457
|
+
threshold,
|
|
458
|
+
botId
|
|
459
|
+
});
|
|
460
|
+
})
|
|
461
|
+
)).flat().map(toMemoryResult);
|
|
462
|
+
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
463
|
+
backend: "chroma",
|
|
464
|
+
dimensions: queryEmbedding.length,
|
|
465
|
+
count: semantic.length
|
|
466
|
+
});
|
|
467
|
+
} catch (error) {
|
|
468
|
+
logger.warn?.(
|
|
469
|
+
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
470
|
+
error
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
474
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
475
|
+
semantic = (await Promise.all(
|
|
476
|
+
filters.map(
|
|
477
|
+
(filter) => searchRawMessagesAnn({
|
|
478
|
+
userId: input.userId,
|
|
479
|
+
queryEmbedding,
|
|
480
|
+
limit,
|
|
481
|
+
threshold,
|
|
482
|
+
botId: "botId" in filter ? filter.botId : void 0
|
|
483
|
+
})
|
|
484
|
+
)
|
|
485
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
486
|
+
}
|
|
487
|
+
if (semantic.length === 0) {
|
|
488
|
+
try {
|
|
489
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
490
|
+
const manager2 = await getRawMessageManager2();
|
|
491
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
492
|
+
const results = await manager2.searchMessagesSemantically({
|
|
493
|
+
userId: input.userId,
|
|
494
|
+
queryEmbedding,
|
|
495
|
+
limit,
|
|
496
|
+
threshold
|
|
497
|
+
});
|
|
498
|
+
semantic = results.map((r) => ({
|
|
499
|
+
type: "memory",
|
|
500
|
+
id: r.id,
|
|
501
|
+
content: r.content,
|
|
502
|
+
similarity: r.similarity,
|
|
503
|
+
metadata: r.metadata ?? {}
|
|
504
|
+
}));
|
|
505
|
+
}
|
|
506
|
+
} catch (error) {
|
|
507
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return semantic;
|
|
511
|
+
}
|
|
512
|
+
function mergeByMaxScore(lists) {
|
|
513
|
+
const best = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const list of lists) {
|
|
515
|
+
for (const hit of list) {
|
|
516
|
+
const existing = best.get(hit.id);
|
|
517
|
+
if (!existing || hit.similarity > existing.similarity) {
|
|
518
|
+
best.set(hit.id, hit);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
523
|
+
}
|
|
524
|
+
function toIterativeRecallCandidate(hit) {
|
|
525
|
+
return {
|
|
526
|
+
id: hit.id,
|
|
527
|
+
content: hit.content,
|
|
528
|
+
similarity: hit.similarity,
|
|
529
|
+
metadata: hit.metadata
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
533
|
+
if (keywords.length === 0) {
|
|
534
|
+
return [];
|
|
535
|
+
}
|
|
536
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
537
|
+
try {
|
|
538
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
539
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
540
|
+
return (await Promise.all(
|
|
541
|
+
filters.map(
|
|
542
|
+
(botId) => searchRawMessagesLexical({
|
|
543
|
+
userId: input.userId,
|
|
544
|
+
keywords,
|
|
545
|
+
limit: Math.ceil(limit / filters.length),
|
|
546
|
+
botId
|
|
547
|
+
})
|
|
548
|
+
)
|
|
549
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
550
|
+
...toMemoryResult(hit),
|
|
551
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
552
|
+
}));
|
|
553
|
+
} catch (error) {
|
|
554
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
559
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
560
|
+
return (await Promise.all(
|
|
561
|
+
filters.map(
|
|
562
|
+
(botId) => lexicalSearchRawMessages2({
|
|
563
|
+
userId: input.userId,
|
|
564
|
+
keywords,
|
|
565
|
+
limit: Math.ceil(limit / filters.length),
|
|
566
|
+
botId
|
|
567
|
+
})
|
|
568
|
+
)
|
|
569
|
+
)).flat().filter(Boolean).map(
|
|
570
|
+
(r) => toMemoryResult(
|
|
571
|
+
r
|
|
572
|
+
)
|
|
573
|
+
);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
576
|
+
}
|
|
577
|
+
return [];
|
|
578
|
+
}
|
|
356
579
|
function createUnifiedSearch(deps = {}) {
|
|
357
580
|
const logger = deps.logger ?? console;
|
|
581
|
+
function resolveReasoningStrategy(input) {
|
|
582
|
+
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
583
|
+
if (requested !== "none") {
|
|
584
|
+
return requested;
|
|
585
|
+
}
|
|
586
|
+
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
587
|
+
}
|
|
358
588
|
async function searchUnifiedMemory(input) {
|
|
359
589
|
const query = input.query.trim();
|
|
360
590
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
361
591
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
362
592
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
363
593
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
594
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
364
595
|
const warnings = [];
|
|
365
596
|
if (!query) {
|
|
366
597
|
return {
|
|
@@ -372,10 +603,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
372
603
|
};
|
|
373
604
|
}
|
|
374
605
|
let memorySubs;
|
|
606
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
607
|
+
if (input.dateFrom || input.dateTo) {
|
|
608
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
609
|
+
}
|
|
375
610
|
if (sources.includes("memory")) {
|
|
376
611
|
if (isRawMessageStorageAvailable()) {
|
|
377
612
|
try {
|
|
378
|
-
memorySubs = await runMemorySource(
|
|
613
|
+
memorySubs = await runMemorySource(
|
|
614
|
+
input,
|
|
615
|
+
limit,
|
|
616
|
+
threshold,
|
|
617
|
+
warnings,
|
|
618
|
+
reasoningStrategy,
|
|
619
|
+
reasoningInfo
|
|
620
|
+
);
|
|
379
621
|
} catch (error) {
|
|
380
622
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
381
623
|
warnings.push({
|
|
@@ -454,120 +696,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
454
696
|
limit,
|
|
455
697
|
strategy: mergeStrategy
|
|
456
698
|
});
|
|
457
|
-
|
|
699
|
+
const output = {
|
|
458
700
|
query: input.query,
|
|
459
701
|
sources,
|
|
460
702
|
results: merged,
|
|
461
703
|
count: merged.length,
|
|
462
704
|
warnings
|
|
463
705
|
};
|
|
706
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
707
|
+
output.reasoning = reasoningInfo;
|
|
708
|
+
}
|
|
709
|
+
return output;
|
|
464
710
|
}
|
|
465
|
-
async function runMemorySource(input, limit, threshold, warnings) {
|
|
711
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
712
|
+
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
713
|
+
warnings.push({
|
|
714
|
+
source: "memory",
|
|
715
|
+
code: "memory_iterative_planner_not_configured",
|
|
716
|
+
message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
|
|
717
|
+
});
|
|
718
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
719
|
+
}
|
|
720
|
+
if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
|
|
721
|
+
warnings.push({
|
|
722
|
+
source: "memory",
|
|
723
|
+
code: "memory_query_rewrite_not_configured",
|
|
724
|
+
message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
|
|
725
|
+
});
|
|
726
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
727
|
+
}
|
|
728
|
+
if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
|
|
729
|
+
const planner = deps.reasoning.iterativePlanner;
|
|
730
|
+
const result = await planner.plan({
|
|
731
|
+
query: input.query,
|
|
732
|
+
dateFrom: input.dateFrom,
|
|
733
|
+
dateTo: input.dateTo,
|
|
734
|
+
executor: {
|
|
735
|
+
search: async (request) => {
|
|
736
|
+
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
737
|
+
const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
|
|
738
|
+
let semanticHits = [];
|
|
739
|
+
if (typeof deps.embedQuery === "function") {
|
|
740
|
+
try {
|
|
741
|
+
const semanticQuery = keywords2.join(" ");
|
|
742
|
+
const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
|
|
743
|
+
semanticHits = await runSemanticSearchForEmbedding(
|
|
744
|
+
deps,
|
|
745
|
+
input,
|
|
746
|
+
queryEmbedding,
|
|
747
|
+
limit,
|
|
748
|
+
threshold,
|
|
749
|
+
logger
|
|
750
|
+
);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
logger.warn?.(
|
|
753
|
+
"[memory-store] Iterative semantic search failed; using lexical results only:",
|
|
754
|
+
error
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const candidates = filterByDateRange(
|
|
759
|
+
mergeByMaxScore([
|
|
760
|
+
lexicalHits.map(toIterativeRecallCandidate),
|
|
761
|
+
semanticHits.map(toIterativeRecallCandidate)
|
|
762
|
+
]),
|
|
763
|
+
request.dateFrom,
|
|
764
|
+
request.dateTo
|
|
765
|
+
);
|
|
766
|
+
return { candidates };
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
if (reasoningInfo) {
|
|
771
|
+
reasoningInfo.iterations = result.stats.iterations;
|
|
772
|
+
reasoningInfo.evidenceCount = result.evidence.length;
|
|
773
|
+
if (planner.lastDegraded?.()) {
|
|
774
|
+
reasoningInfo.degraded = true;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const semantic2 = result.evidence.map((candidate) => ({
|
|
778
|
+
type: "memory",
|
|
779
|
+
id: candidate.id,
|
|
780
|
+
content: candidate.content,
|
|
781
|
+
similarity: candidate.similarity,
|
|
782
|
+
metadata: { ...candidate.metadata, reasoning: "iterative" }
|
|
783
|
+
}));
|
|
784
|
+
return { semantic: semantic2, lexical: [] };
|
|
785
|
+
}
|
|
466
786
|
if (typeof deps.embedQuery !== "function") {
|
|
467
787
|
warnings.push({
|
|
468
788
|
source: "memory",
|
|
469
789
|
code: "memory_lexical_search_fallback",
|
|
470
790
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
471
791
|
});
|
|
472
|
-
const
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const results = await Promise.all(
|
|
479
|
-
filters2.map(
|
|
480
|
-
(botId) => lexicalSearchRawMessages2({
|
|
481
|
-
userId: input.userId,
|
|
482
|
-
keywords,
|
|
483
|
-
limit: Math.ceil(limit / filters2.length),
|
|
484
|
-
botId
|
|
485
|
-
})
|
|
486
|
-
)
|
|
487
|
-
);
|
|
488
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
489
|
-
} catch (error) {
|
|
490
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
792
|
+
const keywords2 = deriveLexicalKeywords(input.query);
|
|
793
|
+
const lexical2 = filterByDateRange(
|
|
794
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
795
|
+
input.dateFrom,
|
|
796
|
+
input.dateTo
|
|
797
|
+
);
|
|
493
798
|
return { semantic: [], lexical: lexical2 };
|
|
494
799
|
}
|
|
495
|
-
const queryEmbedding = await deps.embedQuery({
|
|
496
|
-
userId: input.userId,
|
|
497
|
-
query: input.query,
|
|
498
|
-
authToken: input.authToken
|
|
499
|
-
});
|
|
500
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
501
800
|
let semantic = [];
|
|
502
|
-
|
|
801
|
+
const embedQuery = deps.embedQuery;
|
|
802
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
503
803
|
try {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
userId: input.userId,
|
|
509
|
-
queryEmbedding,
|
|
510
|
-
limit,
|
|
511
|
-
threshold,
|
|
512
|
-
botId
|
|
513
|
-
});
|
|
514
|
-
})
|
|
515
|
-
)).flat().map(toMemoryResult);
|
|
516
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
517
|
-
backend: "chroma",
|
|
518
|
-
dimensions: queryEmbedding.length,
|
|
519
|
-
count: semantic.length
|
|
804
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
805
|
+
query: input.query,
|
|
806
|
+
userId: input.userId,
|
|
807
|
+
authToken: input.authToken
|
|
520
808
|
});
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
809
|
+
if (reasoningInfo) {
|
|
810
|
+
reasoningInfo.rewrittenQueries = variants;
|
|
811
|
+
if (deps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
812
|
+
reasoningInfo.degraded = true;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const embeddings = await Promise.all(
|
|
816
|
+
variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
|
|
817
|
+
);
|
|
818
|
+
const lists = await Promise.all(
|
|
819
|
+
embeddings.map(
|
|
820
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
821
|
+
)
|
|
525
822
|
);
|
|
823
|
+
semantic = mergeByMaxScore(lists);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
|
|
826
|
+
warnings.push({
|
|
827
|
+
source: "memory",
|
|
828
|
+
code: "memory_query_rewrite_failed",
|
|
829
|
+
message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
|
|
830
|
+
});
|
|
831
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
526
832
|
}
|
|
527
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
528
|
-
semantic = (await Promise.all(
|
|
529
|
-
filters.map(
|
|
530
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
531
|
-
userId: input.userId,
|
|
532
|
-
queryEmbedding,
|
|
533
|
-
limit,
|
|
534
|
-
threshold,
|
|
535
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
536
|
-
})
|
|
537
|
-
)
|
|
538
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
539
833
|
}
|
|
540
834
|
if (semantic.length === 0) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const manager2 = await getRawMessageManager2();
|
|
544
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
545
|
-
const results = await manager2.searchMessagesSemantically({
|
|
546
|
-
userId: input.userId,
|
|
547
|
-
queryEmbedding,
|
|
548
|
-
limit
|
|
549
|
-
});
|
|
550
|
-
semantic = results.map((r) => ({
|
|
551
|
-
type: "memory",
|
|
552
|
-
id: r.id,
|
|
553
|
-
content: r.content,
|
|
554
|
-
similarity: r.similarity,
|
|
555
|
-
metadata: r.metadata ?? {}
|
|
556
|
-
}));
|
|
557
|
-
}
|
|
558
|
-
} catch (error) {
|
|
559
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
560
|
-
}
|
|
835
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
836
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
561
837
|
}
|
|
562
838
|
let lexical = [];
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (
|
|
839
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
840
|
+
if (keywords.length > 0) {
|
|
841
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
566
842
|
try {
|
|
567
843
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
844
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
568
845
|
lexical = (await Promise.all(
|
|
569
846
|
lexFilters.map(
|
|
570
|
-
(botId) =>
|
|
847
|
+
(botId) => searchRawMessagesLexical({
|
|
571
848
|
userId: input.userId,
|
|
572
849
|
keywords,
|
|
573
850
|
limit,
|
|
@@ -586,20 +863,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
586
863
|
message: error.message ?? "memory_lexical_search_failed"
|
|
587
864
|
});
|
|
588
865
|
}
|
|
866
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
867
|
+
warnings.push({
|
|
868
|
+
source: "memory",
|
|
869
|
+
code: "memory_lexical_search_not_configured",
|
|
870
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
871
|
+
});
|
|
589
872
|
}
|
|
590
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
591
|
-
warnings.push({
|
|
592
|
-
source: "memory",
|
|
593
|
-
code: "memory_lexical_search_not_configured",
|
|
594
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
595
|
-
});
|
|
596
873
|
}
|
|
597
|
-
return {
|
|
874
|
+
return {
|
|
875
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
876
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
877
|
+
};
|
|
598
878
|
}
|
|
599
879
|
async function searchRawMemorySemantically(input) {
|
|
600
880
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
601
881
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
602
|
-
const
|
|
882
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
883
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
603
884
|
return mergeAcrossSources({
|
|
604
885
|
memorySubs: sub,
|
|
605
886
|
insightHits: [],
|
|
@@ -640,4 +921,4 @@ function mergeAcrossSources(input) {
|
|
|
640
921
|
return mergeUnifiedMemorySearchResultsRrf(lists, input.limit);
|
|
641
922
|
}
|
|
642
923
|
|
|
643
|
-
export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryMergeStrategy, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };
|
|
924
|
+
export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, createUnifiedSearch, isRawMemorySemanticResult, mergeUnifiedMemorySearchResults, mergeUnifiedMemorySearchResultsRrf, normalizeUnifiedMemoryMergeStrategy, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, toKnowledgeResult, toMemoryResult };
|