@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
package/dist/server/cli-mcp.js
CHANGED
|
@@ -185,16 +185,17 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
185
185
|
lexicalSearchRawMessages: () => lexicalSearchRawMessages,
|
|
186
186
|
resolveSQLiteRawMessageDbPath: () => resolveSQLiteRawMessageDbPath
|
|
187
187
|
});
|
|
188
|
-
function resolveSQLiteRawMessageDbPath() {
|
|
188
|
+
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
189
|
+
if (dbPath && dbPath.length > 0) return dbPath;
|
|
189
190
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
190
191
|
return fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_SQLITE_PATH;
|
|
191
192
|
}
|
|
192
193
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
193
194
|
return true;
|
|
194
195
|
}
|
|
195
|
-
async function getSQLiteRawMessageManager(
|
|
196
|
+
async function getSQLiteRawMessageManager(options = {}) {
|
|
196
197
|
if (!manager) {
|
|
197
|
-
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
198
|
+
const dbPath = resolveSQLiteRawMessageDbPath(options.dbPath);
|
|
198
199
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
199
200
|
manager = new SQLiteRawMessageManager({
|
|
200
201
|
dbPath,
|
|
@@ -240,7 +241,7 @@ __export(raw_message_store_exports, {
|
|
|
240
241
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
241
242
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
242
243
|
});
|
|
243
|
-
function resolveBackend(
|
|
244
|
+
function resolveBackend(_env) {
|
|
244
245
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
245
246
|
if (explicit === "postgres") return "postgres";
|
|
246
247
|
return "sqlite";
|
|
@@ -267,33 +268,43 @@ function createRawMessageStore(options = {}) {
|
|
|
267
268
|
}
|
|
268
269
|
return pg;
|
|
269
270
|
}
|
|
270
|
-
return await getSQLiteRawMessageManager(
|
|
271
|
+
return await getSQLiteRawMessageManager({
|
|
272
|
+
dbPath: options.dbPath
|
|
273
|
+
});
|
|
271
274
|
},
|
|
272
275
|
async close() {
|
|
273
276
|
await closeSQLiteRawMessageManager();
|
|
274
277
|
}
|
|
275
278
|
};
|
|
276
279
|
}
|
|
280
|
+
function resolveDbPath(config) {
|
|
281
|
+
if (!config) return void 0;
|
|
282
|
+
if (config.dbPath) return config.dbPath;
|
|
283
|
+
if (config.db && typeof config.db === "object" && "path" in config.db) {
|
|
284
|
+
return config.db.path;
|
|
285
|
+
}
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
277
288
|
function configureRawMessageStore(config) {
|
|
278
289
|
moduleConfig = config;
|
|
279
|
-
moduleStore = createRawMessageStore({ env: config.env });
|
|
290
|
+
moduleStore = createRawMessageStore({ env: config.env, dbPath: resolveDbPath(config) });
|
|
280
291
|
return moduleStore;
|
|
281
292
|
}
|
|
282
293
|
function getRawMessageStorageBackend() {
|
|
283
294
|
if (!moduleStore) {
|
|
284
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
295
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
285
296
|
}
|
|
286
297
|
return moduleStore.getBackend();
|
|
287
298
|
}
|
|
288
299
|
function isRawMessageStorageAvailable() {
|
|
289
300
|
if (!moduleStore) {
|
|
290
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
301
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
291
302
|
}
|
|
292
303
|
return moduleStore.isAvailable();
|
|
293
304
|
}
|
|
294
305
|
async function getRawMessageManager() {
|
|
295
306
|
if (!moduleStore) {
|
|
296
|
-
moduleStore = createRawMessageStore({ env: moduleConfig?.env });
|
|
307
|
+
moduleStore = createRawMessageStore({ env: moduleConfig?.env, dbPath: resolveDbPath(moduleConfig) });
|
|
297
308
|
}
|
|
298
309
|
return moduleStore.getManager();
|
|
299
310
|
}
|
|
@@ -363,6 +374,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
363
374
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
364
375
|
return value === "rrf" ? "rrf" : "similarity";
|
|
365
376
|
}
|
|
377
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
378
|
+
if (value === "rewrite" || value === "iterative") {
|
|
379
|
+
return value;
|
|
380
|
+
}
|
|
381
|
+
return defaultStrategy;
|
|
382
|
+
}
|
|
366
383
|
var DEFAULT_RRF_K = 60;
|
|
367
384
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
368
385
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -385,7 +402,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
385
402
|
}
|
|
386
403
|
}
|
|
387
404
|
}
|
|
388
|
-
const ranked = order.map((key) =>
|
|
405
|
+
const ranked = order.map((key) => {
|
|
406
|
+
const entry = scores.get(key);
|
|
407
|
+
if (!entry) {
|
|
408
|
+
throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
|
|
409
|
+
}
|
|
410
|
+
return entry;
|
|
411
|
+
}).sort((a, b) => {
|
|
389
412
|
if (b.rrf !== a.rrf) {
|
|
390
413
|
return b.rrf - a.rrf;
|
|
391
414
|
}
|
|
@@ -430,14 +453,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
430
453
|
function deriveLexicalKeywords(query) {
|
|
431
454
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
432
455
|
}
|
|
456
|
+
function parseDateBoundary(value, role) {
|
|
457
|
+
const trimmed = value.trim();
|
|
458
|
+
if (!trimmed) {
|
|
459
|
+
return void 0;
|
|
460
|
+
}
|
|
461
|
+
const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
462
|
+
if (dateOnlyMatch) {
|
|
463
|
+
const year = Number.parseInt(dateOnlyMatch[1], 10);
|
|
464
|
+
const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
|
|
465
|
+
const day = Number.parseInt(dateOnlyMatch[3], 10);
|
|
466
|
+
if (role === "from") {
|
|
467
|
+
return Date.UTC(year, month, day);
|
|
468
|
+
}
|
|
469
|
+
return Date.UTC(year, month, day + 1) - 1;
|
|
470
|
+
}
|
|
471
|
+
const parsed = Date.parse(trimmed);
|
|
472
|
+
if (Number.isNaN(parsed)) {
|
|
473
|
+
return void 0;
|
|
474
|
+
}
|
|
475
|
+
return parsed;
|
|
476
|
+
}
|
|
477
|
+
function getCandidateTimestamp(metadata) {
|
|
478
|
+
for (const key of ["timestamp", "createdAt", "time"]) {
|
|
479
|
+
const value = metadata[key];
|
|
480
|
+
if (typeof value === "number") {
|
|
481
|
+
return value;
|
|
482
|
+
}
|
|
483
|
+
if (typeof value === "string") {
|
|
484
|
+
const parsed = Date.parse(value);
|
|
485
|
+
if (!Number.isNaN(parsed)) {
|
|
486
|
+
return parsed;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
function filterByDateRange(items, dateFrom, dateTo) {
|
|
493
|
+
if (!dateFrom && !dateTo) {
|
|
494
|
+
return items;
|
|
495
|
+
}
|
|
496
|
+
const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
|
|
497
|
+
const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
|
|
498
|
+
if (fromMs === void 0 && toMs === void 0) {
|
|
499
|
+
return items;
|
|
500
|
+
}
|
|
501
|
+
return items.filter((item) => {
|
|
502
|
+
const ts = getCandidateTimestamp(item.metadata);
|
|
503
|
+
if (ts === void 0) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
if (fromMs !== void 0 && ts < fromMs) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
if (toMs !== void 0 && ts > toMs) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
return true;
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
async function embedQueryVariant(embedQuery, input, query) {
|
|
516
|
+
return embedQuery({
|
|
517
|
+
userId: input.userId,
|
|
518
|
+
query,
|
|
519
|
+
authToken: input.authToken
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
523
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
524
|
+
let semantic = [];
|
|
525
|
+
if (isRawMessageChromaEnabled()) {
|
|
526
|
+
try {
|
|
527
|
+
semantic = (await Promise.all(
|
|
528
|
+
filters.map((filter) => {
|
|
529
|
+
const botId = "botId" in filter ? filter.botId : void 0;
|
|
530
|
+
return searchRawMessagesWithChroma({
|
|
531
|
+
userId: input.userId,
|
|
532
|
+
queryEmbedding,
|
|
533
|
+
limit,
|
|
534
|
+
threshold,
|
|
535
|
+
botId
|
|
536
|
+
});
|
|
537
|
+
})
|
|
538
|
+
)).flat().map(toMemoryResult);
|
|
539
|
+
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
540
|
+
backend: "chroma",
|
|
541
|
+
dimensions: queryEmbedding.length,
|
|
542
|
+
count: semantic.length
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
logger.warn?.(
|
|
546
|
+
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
547
|
+
error
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
551
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
552
|
+
semantic = (await Promise.all(
|
|
553
|
+
filters.map(
|
|
554
|
+
(filter) => searchRawMessagesAnn({
|
|
555
|
+
userId: input.userId,
|
|
556
|
+
queryEmbedding,
|
|
557
|
+
limit,
|
|
558
|
+
threshold,
|
|
559
|
+
botId: "botId" in filter ? filter.botId : void 0
|
|
560
|
+
})
|
|
561
|
+
)
|
|
562
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
563
|
+
}
|
|
564
|
+
if (semantic.length === 0) {
|
|
565
|
+
try {
|
|
566
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
567
|
+
const manager2 = await getRawMessageManager2();
|
|
568
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
569
|
+
const results = await manager2.searchMessagesSemantically({
|
|
570
|
+
userId: input.userId,
|
|
571
|
+
queryEmbedding,
|
|
572
|
+
limit,
|
|
573
|
+
threshold
|
|
574
|
+
});
|
|
575
|
+
semantic = results.map((r) => ({
|
|
576
|
+
type: "memory",
|
|
577
|
+
id: r.id,
|
|
578
|
+
content: r.content,
|
|
579
|
+
similarity: r.similarity,
|
|
580
|
+
metadata: r.metadata ?? {}
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
} catch (error) {
|
|
584
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return semantic;
|
|
588
|
+
}
|
|
589
|
+
function mergeByMaxScore(lists) {
|
|
590
|
+
const best = /* @__PURE__ */ new Map();
|
|
591
|
+
for (const list of lists) {
|
|
592
|
+
for (const hit of list) {
|
|
593
|
+
const existing = best.get(hit.id);
|
|
594
|
+
if (!existing || hit.similarity > existing.similarity) {
|
|
595
|
+
best.set(hit.id, hit);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
600
|
+
}
|
|
601
|
+
function toIterativeRecallCandidate(hit) {
|
|
602
|
+
return {
|
|
603
|
+
id: hit.id,
|
|
604
|
+
content: hit.content,
|
|
605
|
+
similarity: hit.similarity,
|
|
606
|
+
metadata: hit.metadata
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
610
|
+
if (keywords.length === 0) {
|
|
611
|
+
return [];
|
|
612
|
+
}
|
|
613
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
614
|
+
try {
|
|
615
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
616
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
617
|
+
return (await Promise.all(
|
|
618
|
+
filters.map(
|
|
619
|
+
(botId) => searchRawMessagesLexical({
|
|
620
|
+
userId: input.userId,
|
|
621
|
+
keywords,
|
|
622
|
+
limit: Math.ceil(limit / filters.length),
|
|
623
|
+
botId
|
|
624
|
+
})
|
|
625
|
+
)
|
|
626
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
627
|
+
...toMemoryResult(hit),
|
|
628
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
629
|
+
}));
|
|
630
|
+
} catch (error) {
|
|
631
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
636
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
637
|
+
return (await Promise.all(
|
|
638
|
+
filters.map(
|
|
639
|
+
(botId) => lexicalSearchRawMessages2({
|
|
640
|
+
userId: input.userId,
|
|
641
|
+
keywords,
|
|
642
|
+
limit: Math.ceil(limit / filters.length),
|
|
643
|
+
botId
|
|
644
|
+
})
|
|
645
|
+
)
|
|
646
|
+
)).flat().filter(Boolean).map(
|
|
647
|
+
(r) => toMemoryResult(
|
|
648
|
+
r
|
|
649
|
+
)
|
|
650
|
+
);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
653
|
+
}
|
|
654
|
+
return [];
|
|
655
|
+
}
|
|
433
656
|
function createUnifiedSearch(deps = {}) {
|
|
434
657
|
const logger = deps.logger ?? console;
|
|
658
|
+
function resolveReasoningStrategy(input) {
|
|
659
|
+
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
660
|
+
if (requested !== "none") {
|
|
661
|
+
return requested;
|
|
662
|
+
}
|
|
663
|
+
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
664
|
+
}
|
|
435
665
|
async function searchUnifiedMemory(input) {
|
|
436
666
|
const query = input.query.trim();
|
|
437
667
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
438
668
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
439
669
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
440
670
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
671
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
441
672
|
const warnings = [];
|
|
442
673
|
if (!query) {
|
|
443
674
|
return {
|
|
@@ -449,10 +680,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
449
680
|
};
|
|
450
681
|
}
|
|
451
682
|
let memorySubs;
|
|
683
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
684
|
+
if (input.dateFrom || input.dateTo) {
|
|
685
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
686
|
+
}
|
|
452
687
|
if (sources.includes("memory")) {
|
|
453
688
|
if (isRawMessageStorageAvailable()) {
|
|
454
689
|
try {
|
|
455
|
-
memorySubs = await runMemorySource(
|
|
690
|
+
memorySubs = await runMemorySource(
|
|
691
|
+
input,
|
|
692
|
+
limit,
|
|
693
|
+
threshold,
|
|
694
|
+
warnings,
|
|
695
|
+
reasoningStrategy,
|
|
696
|
+
reasoningInfo
|
|
697
|
+
);
|
|
456
698
|
} catch (error) {
|
|
457
699
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
458
700
|
warnings.push({
|
|
@@ -531,120 +773,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
531
773
|
limit,
|
|
532
774
|
strategy: mergeStrategy
|
|
533
775
|
});
|
|
534
|
-
|
|
776
|
+
const output = {
|
|
535
777
|
query: input.query,
|
|
536
778
|
sources,
|
|
537
779
|
results: merged,
|
|
538
780
|
count: merged.length,
|
|
539
781
|
warnings
|
|
540
782
|
};
|
|
783
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
784
|
+
output.reasoning = reasoningInfo;
|
|
785
|
+
}
|
|
786
|
+
return output;
|
|
541
787
|
}
|
|
542
|
-
async function runMemorySource(input, limit, threshold, warnings) {
|
|
788
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
789
|
+
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
790
|
+
warnings.push({
|
|
791
|
+
source: "memory",
|
|
792
|
+
code: "memory_iterative_planner_not_configured",
|
|
793
|
+
message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
|
|
794
|
+
});
|
|
795
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
796
|
+
}
|
|
797
|
+
if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
|
|
798
|
+
warnings.push({
|
|
799
|
+
source: "memory",
|
|
800
|
+
code: "memory_query_rewrite_not_configured",
|
|
801
|
+
message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
|
|
802
|
+
});
|
|
803
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
804
|
+
}
|
|
805
|
+
if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
|
|
806
|
+
const planner = deps.reasoning.iterativePlanner;
|
|
807
|
+
const result = await planner.plan({
|
|
808
|
+
query: input.query,
|
|
809
|
+
dateFrom: input.dateFrom,
|
|
810
|
+
dateTo: input.dateTo,
|
|
811
|
+
executor: {
|
|
812
|
+
search: async (request) => {
|
|
813
|
+
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
814
|
+
const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
|
|
815
|
+
let semanticHits = [];
|
|
816
|
+
if (typeof deps.embedQuery === "function") {
|
|
817
|
+
try {
|
|
818
|
+
const semanticQuery = keywords2.join(" ");
|
|
819
|
+
const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
|
|
820
|
+
semanticHits = await runSemanticSearchForEmbedding(
|
|
821
|
+
deps,
|
|
822
|
+
input,
|
|
823
|
+
queryEmbedding,
|
|
824
|
+
limit,
|
|
825
|
+
threshold,
|
|
826
|
+
logger
|
|
827
|
+
);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
logger.warn?.(
|
|
830
|
+
"[memory-store] Iterative semantic search failed; using lexical results only:",
|
|
831
|
+
error
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const candidates = filterByDateRange(
|
|
836
|
+
mergeByMaxScore([
|
|
837
|
+
lexicalHits.map(toIterativeRecallCandidate),
|
|
838
|
+
semanticHits.map(toIterativeRecallCandidate)
|
|
839
|
+
]),
|
|
840
|
+
request.dateFrom,
|
|
841
|
+
request.dateTo
|
|
842
|
+
);
|
|
843
|
+
return { candidates };
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
if (reasoningInfo) {
|
|
848
|
+
reasoningInfo.iterations = result.stats.iterations;
|
|
849
|
+
reasoningInfo.evidenceCount = result.evidence.length;
|
|
850
|
+
if (planner.lastDegraded?.()) {
|
|
851
|
+
reasoningInfo.degraded = true;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const semantic2 = result.evidence.map((candidate) => ({
|
|
855
|
+
type: "memory",
|
|
856
|
+
id: candidate.id,
|
|
857
|
+
content: candidate.content,
|
|
858
|
+
similarity: candidate.similarity,
|
|
859
|
+
metadata: { ...candidate.metadata, reasoning: "iterative" }
|
|
860
|
+
}));
|
|
861
|
+
return { semantic: semantic2, lexical: [] };
|
|
862
|
+
}
|
|
543
863
|
if (typeof deps.embedQuery !== "function") {
|
|
544
864
|
warnings.push({
|
|
545
865
|
source: "memory",
|
|
546
866
|
code: "memory_lexical_search_fallback",
|
|
547
867
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
548
868
|
});
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const results = await Promise.all(
|
|
556
|
-
filters2.map(
|
|
557
|
-
(botId) => lexicalSearchRawMessages2({
|
|
558
|
-
userId: input.userId,
|
|
559
|
-
keywords,
|
|
560
|
-
limit: Math.ceil(limit / filters2.length),
|
|
561
|
-
botId
|
|
562
|
-
})
|
|
563
|
-
)
|
|
564
|
-
);
|
|
565
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
566
|
-
} catch (error) {
|
|
567
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
869
|
+
const keywords2 = deriveLexicalKeywords(input.query);
|
|
870
|
+
const lexical2 = filterByDateRange(
|
|
871
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
872
|
+
input.dateFrom,
|
|
873
|
+
input.dateTo
|
|
874
|
+
);
|
|
570
875
|
return { semantic: [], lexical: lexical2 };
|
|
571
876
|
}
|
|
572
|
-
const queryEmbedding = await deps.embedQuery({
|
|
573
|
-
userId: input.userId,
|
|
574
|
-
query: input.query,
|
|
575
|
-
authToken: input.authToken
|
|
576
|
-
});
|
|
577
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
578
877
|
let semantic = [];
|
|
579
|
-
|
|
878
|
+
const embedQuery = deps.embedQuery;
|
|
879
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
580
880
|
try {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
userId: input.userId,
|
|
586
|
-
queryEmbedding,
|
|
587
|
-
limit,
|
|
588
|
-
threshold,
|
|
589
|
-
botId
|
|
590
|
-
});
|
|
591
|
-
})
|
|
592
|
-
)).flat().map(toMemoryResult);
|
|
593
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
594
|
-
backend: "chroma",
|
|
595
|
-
dimensions: queryEmbedding.length,
|
|
596
|
-
count: semantic.length
|
|
881
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
882
|
+
query: input.query,
|
|
883
|
+
userId: input.userId,
|
|
884
|
+
authToken: input.authToken
|
|
597
885
|
});
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
886
|
+
if (reasoningInfo) {
|
|
887
|
+
reasoningInfo.rewrittenQueries = variants;
|
|
888
|
+
if (deps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
889
|
+
reasoningInfo.degraded = true;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const embeddings = await Promise.all(
|
|
893
|
+
variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
|
|
894
|
+
);
|
|
895
|
+
const lists = await Promise.all(
|
|
896
|
+
embeddings.map(
|
|
897
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
898
|
+
)
|
|
602
899
|
);
|
|
900
|
+
semantic = mergeByMaxScore(lists);
|
|
901
|
+
} catch (error) {
|
|
902
|
+
logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
|
|
903
|
+
warnings.push({
|
|
904
|
+
source: "memory",
|
|
905
|
+
code: "memory_query_rewrite_failed",
|
|
906
|
+
message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
|
|
907
|
+
});
|
|
908
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
603
909
|
}
|
|
604
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
605
|
-
semantic = (await Promise.all(
|
|
606
|
-
filters.map(
|
|
607
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
608
|
-
userId: input.userId,
|
|
609
|
-
queryEmbedding,
|
|
610
|
-
limit,
|
|
611
|
-
threshold,
|
|
612
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
613
|
-
})
|
|
614
|
-
)
|
|
615
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
616
910
|
}
|
|
617
911
|
if (semantic.length === 0) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const manager2 = await getRawMessageManager2();
|
|
621
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
622
|
-
const results = await manager2.searchMessagesSemantically({
|
|
623
|
-
userId: input.userId,
|
|
624
|
-
queryEmbedding,
|
|
625
|
-
limit
|
|
626
|
-
});
|
|
627
|
-
semantic = results.map((r) => ({
|
|
628
|
-
type: "memory",
|
|
629
|
-
id: r.id,
|
|
630
|
-
content: r.content,
|
|
631
|
-
similarity: r.similarity,
|
|
632
|
-
metadata: r.metadata ?? {}
|
|
633
|
-
}));
|
|
634
|
-
}
|
|
635
|
-
} catch (error) {
|
|
636
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
637
|
-
}
|
|
912
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
913
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
638
914
|
}
|
|
639
915
|
let lexical = [];
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (
|
|
916
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
917
|
+
if (keywords.length > 0) {
|
|
918
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
643
919
|
try {
|
|
644
920
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
921
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
645
922
|
lexical = (await Promise.all(
|
|
646
923
|
lexFilters.map(
|
|
647
|
-
(botId) =>
|
|
924
|
+
(botId) => searchRawMessagesLexical({
|
|
648
925
|
userId: input.userId,
|
|
649
926
|
keywords,
|
|
650
927
|
limit,
|
|
@@ -663,20 +940,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
663
940
|
message: error.message ?? "memory_lexical_search_failed"
|
|
664
941
|
});
|
|
665
942
|
}
|
|
943
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
944
|
+
warnings.push({
|
|
945
|
+
source: "memory",
|
|
946
|
+
code: "memory_lexical_search_not_configured",
|
|
947
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
948
|
+
});
|
|
666
949
|
}
|
|
667
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
668
|
-
warnings.push({
|
|
669
|
-
source: "memory",
|
|
670
|
-
code: "memory_lexical_search_not_configured",
|
|
671
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
672
|
-
});
|
|
673
950
|
}
|
|
674
|
-
return {
|
|
951
|
+
return {
|
|
952
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
953
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
954
|
+
};
|
|
675
955
|
}
|
|
676
956
|
async function searchRawMemorySemantically(input) {
|
|
677
957
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
678
958
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
679
|
-
const
|
|
959
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
960
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
680
961
|
return mergeAcrossSources({
|
|
681
962
|
memorySubs: sub,
|
|
682
963
|
insightHits: [],
|
|
@@ -803,6 +1084,7 @@ async function startMcpServer(options = {}) {
|
|
|
803
1084
|
{
|
|
804
1085
|
title: "Search Unified Memory",
|
|
805
1086
|
description: "Semantic search across raw messages (always), and optionally insights and uploaded knowledge.",
|
|
1087
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
806
1088
|
inputSchema: searchSchema
|
|
807
1089
|
},
|
|
808
1090
|
async (args) => {
|
|
@@ -818,6 +1100,7 @@ async function startMcpServer(options = {}) {
|
|
|
818
1100
|
{
|
|
819
1101
|
title: "Write Raw Message",
|
|
820
1102
|
description: "Persist a single raw message to the user's memory store.",
|
|
1103
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
821
1104
|
inputSchema: writeSchema
|
|
822
1105
|
},
|
|
823
1106
|
async (args) => {
|
|
@@ -857,6 +1140,7 @@ async function startMcpServer(options = {}) {
|
|
|
857
1140
|
{
|
|
858
1141
|
title: "Get Raw Message",
|
|
859
1142
|
description: "Fetch a single raw message by its message id.",
|
|
1143
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
860
1144
|
inputSchema: getSchema
|
|
861
1145
|
},
|
|
862
1146
|
async (args) => {
|
|
@@ -1065,6 +1349,9 @@ async function buildUnified(args) {
|
|
|
1065
1349
|
} else if (args.memoryBackend === "chroma") {
|
|
1066
1350
|
if (!args.chromaUrl)
|
|
1067
1351
|
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1352
|
+
if (!aiRag) {
|
|
1353
|
+
throw new Error("ai-rag modules not loaded");
|
|
1354
|
+
}
|
|
1068
1355
|
const store = new aiRag.ChromaVectorStore({
|
|
1069
1356
|
url: args.chromaUrl,
|
|
1070
1357
|
collectionName: "opencontext_raw_messages"
|
|
@@ -1103,6 +1390,9 @@ async function buildUnified(args) {
|
|
|
1103
1390
|
throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1104
1391
|
if (!unified.embedQuery)
|
|
1105
1392
|
throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
|
|
1393
|
+
if (!aiRag) {
|
|
1394
|
+
throw new Error("ai-rag modules not loaded");
|
|
1395
|
+
}
|
|
1106
1396
|
const store = new aiRag.ChromaVectorStore({
|
|
1107
1397
|
url: args.chromaUrl,
|
|
1108
1398
|
collectionName: args.insightsCollection
|
|
@@ -1120,6 +1410,9 @@ async function buildUnified(args) {
|
|
|
1120
1410
|
throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1121
1411
|
if (!unified.embedQuery)
|
|
1122
1412
|
throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
|
|
1413
|
+
if (!aiRag) {
|
|
1414
|
+
throw new Error("ai-rag modules not loaded");
|
|
1415
|
+
}
|
|
1123
1416
|
const store = new aiRag.ChromaVectorStore({
|
|
1124
1417
|
url: args.chromaUrl,
|
|
1125
1418
|
collectionName: args.knowledgeCollection
|