@melandlabs/memory-store 0.2.7 → 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 +367 -98
- package/dist/index.d.ts +3 -3
- package/dist/index.js +724 -99
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +370 -98
- package/dist/search/unified-search.d.ts +3 -101
- package/dist/search/unified-search.js +368 -99
- package/dist/server/cli-http.js +376 -98
- package/dist/server/cli-mcp.js +379 -98
- package/dist/server/cli-shared.d.ts +1 -1
- package/dist/server/cli-shared.js +10 -1
- package/dist/storage/postgres-raw-message-factory.d.ts +1 -1
- package/dist/storage/raw-message-store.d.ts +1 -1
- package/dist/storage/raw-message-store.js +1 -1
- package/dist/storage/sqlite-raw-message-store.d.ts +1 -1
- package/dist/storage/sqlite-vector-index.d.ts +1 -1
- package/package.json +10 -9
- package/dist/config-Ll6ktm4D.d.ts +0 -183
package/dist/server/cli-http.js
CHANGED
|
@@ -240,7 +240,7 @@ __export(raw_message_store_exports, {
|
|
|
240
240
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
241
241
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
242
242
|
});
|
|
243
|
-
function resolveBackend(
|
|
243
|
+
function resolveBackend(_env) {
|
|
244
244
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
245
245
|
if (explicit === "postgres") return "postgres";
|
|
246
246
|
return "sqlite";
|
|
@@ -373,6 +373,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
373
373
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
374
374
|
return value === "rrf" ? "rrf" : "similarity";
|
|
375
375
|
}
|
|
376
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
377
|
+
if (value === "rewrite" || value === "iterative") {
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
return defaultStrategy;
|
|
381
|
+
}
|
|
376
382
|
var DEFAULT_RRF_K = 60;
|
|
377
383
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
378
384
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -395,7 +401,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
395
401
|
}
|
|
396
402
|
}
|
|
397
403
|
}
|
|
398
|
-
const ranked = order.map((key) =>
|
|
404
|
+
const ranked = order.map((key) => {
|
|
405
|
+
const entry = scores.get(key);
|
|
406
|
+
if (!entry) {
|
|
407
|
+
throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
|
|
408
|
+
}
|
|
409
|
+
return entry;
|
|
410
|
+
}).sort((a, b) => {
|
|
399
411
|
if (b.rrf !== a.rrf) {
|
|
400
412
|
return b.rrf - a.rrf;
|
|
401
413
|
}
|
|
@@ -440,14 +452,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
440
452
|
function deriveLexicalKeywords(query) {
|
|
441
453
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
442
454
|
}
|
|
455
|
+
function parseDateBoundary(value, role) {
|
|
456
|
+
const trimmed = value.trim();
|
|
457
|
+
if (!trimmed) {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
461
|
+
if (dateOnlyMatch) {
|
|
462
|
+
const year = Number.parseInt(dateOnlyMatch[1], 10);
|
|
463
|
+
const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
|
|
464
|
+
const day = Number.parseInt(dateOnlyMatch[3], 10);
|
|
465
|
+
if (role === "from") {
|
|
466
|
+
return Date.UTC(year, month, day);
|
|
467
|
+
}
|
|
468
|
+
return Date.UTC(year, month, day + 1) - 1;
|
|
469
|
+
}
|
|
470
|
+
const parsed = Date.parse(trimmed);
|
|
471
|
+
if (Number.isNaN(parsed)) {
|
|
472
|
+
return void 0;
|
|
473
|
+
}
|
|
474
|
+
return parsed;
|
|
475
|
+
}
|
|
476
|
+
function getCandidateTimestamp(metadata) {
|
|
477
|
+
for (const key of ["timestamp", "createdAt", "time"]) {
|
|
478
|
+
const value = metadata[key];
|
|
479
|
+
if (typeof value === "number") {
|
|
480
|
+
return value;
|
|
481
|
+
}
|
|
482
|
+
if (typeof value === "string") {
|
|
483
|
+
const parsed = Date.parse(value);
|
|
484
|
+
if (!Number.isNaN(parsed)) {
|
|
485
|
+
return parsed;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return void 0;
|
|
490
|
+
}
|
|
491
|
+
function filterByDateRange(items, dateFrom, dateTo) {
|
|
492
|
+
if (!dateFrom && !dateTo) {
|
|
493
|
+
return items;
|
|
494
|
+
}
|
|
495
|
+
const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
|
|
496
|
+
const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
|
|
497
|
+
if (fromMs === void 0 && toMs === void 0) {
|
|
498
|
+
return items;
|
|
499
|
+
}
|
|
500
|
+
return items.filter((item) => {
|
|
501
|
+
const ts = getCandidateTimestamp(item.metadata);
|
|
502
|
+
if (ts === void 0) {
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
if (fromMs !== void 0 && ts < fromMs) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
if (toMs !== void 0 && ts > toMs) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
return true;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async function embedQueryVariant(embedQuery, input, query) {
|
|
515
|
+
return embedQuery({
|
|
516
|
+
userId: input.userId,
|
|
517
|
+
query,
|
|
518
|
+
authToken: input.authToken
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
522
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
523
|
+
let semantic = [];
|
|
524
|
+
if (isRawMessageChromaEnabled()) {
|
|
525
|
+
try {
|
|
526
|
+
semantic = (await Promise.all(
|
|
527
|
+
filters.map((filter) => {
|
|
528
|
+
const botId = "botId" in filter ? filter.botId : void 0;
|
|
529
|
+
return searchRawMessagesWithChroma({
|
|
530
|
+
userId: input.userId,
|
|
531
|
+
queryEmbedding,
|
|
532
|
+
limit,
|
|
533
|
+
threshold,
|
|
534
|
+
botId
|
|
535
|
+
});
|
|
536
|
+
})
|
|
537
|
+
)).flat().map(toMemoryResult);
|
|
538
|
+
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
539
|
+
backend: "chroma",
|
|
540
|
+
dimensions: queryEmbedding.length,
|
|
541
|
+
count: semantic.length
|
|
542
|
+
});
|
|
543
|
+
} catch (error) {
|
|
544
|
+
logger.warn?.(
|
|
545
|
+
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
546
|
+
error
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
550
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
551
|
+
semantic = (await Promise.all(
|
|
552
|
+
filters.map(
|
|
553
|
+
(filter) => searchRawMessagesAnn({
|
|
554
|
+
userId: input.userId,
|
|
555
|
+
queryEmbedding,
|
|
556
|
+
limit,
|
|
557
|
+
threshold,
|
|
558
|
+
botId: "botId" in filter ? filter.botId : void 0
|
|
559
|
+
})
|
|
560
|
+
)
|
|
561
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
562
|
+
}
|
|
563
|
+
if (semantic.length === 0) {
|
|
564
|
+
try {
|
|
565
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
566
|
+
const manager2 = await getRawMessageManager2();
|
|
567
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
568
|
+
const results = await manager2.searchMessagesSemantically({
|
|
569
|
+
userId: input.userId,
|
|
570
|
+
queryEmbedding,
|
|
571
|
+
limit,
|
|
572
|
+
threshold
|
|
573
|
+
});
|
|
574
|
+
semantic = results.map((r) => ({
|
|
575
|
+
type: "memory",
|
|
576
|
+
id: r.id,
|
|
577
|
+
content: r.content,
|
|
578
|
+
similarity: r.similarity,
|
|
579
|
+
metadata: r.metadata ?? {}
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
582
|
+
} catch (error) {
|
|
583
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return semantic;
|
|
587
|
+
}
|
|
588
|
+
function mergeByMaxScore(lists) {
|
|
589
|
+
const best = /* @__PURE__ */ new Map();
|
|
590
|
+
for (const list of lists) {
|
|
591
|
+
for (const hit of list) {
|
|
592
|
+
const existing = best.get(hit.id);
|
|
593
|
+
if (!existing || hit.similarity > existing.similarity) {
|
|
594
|
+
best.set(hit.id, hit);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
599
|
+
}
|
|
600
|
+
function toIterativeRecallCandidate(hit) {
|
|
601
|
+
return {
|
|
602
|
+
id: hit.id,
|
|
603
|
+
content: hit.content,
|
|
604
|
+
similarity: hit.similarity,
|
|
605
|
+
metadata: hit.metadata
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
609
|
+
if (keywords.length === 0) {
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
613
|
+
try {
|
|
614
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
615
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
616
|
+
return (await Promise.all(
|
|
617
|
+
filters.map(
|
|
618
|
+
(botId) => searchRawMessagesLexical({
|
|
619
|
+
userId: input.userId,
|
|
620
|
+
keywords,
|
|
621
|
+
limit: Math.ceil(limit / filters.length),
|
|
622
|
+
botId
|
|
623
|
+
})
|
|
624
|
+
)
|
|
625
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
626
|
+
...toMemoryResult(hit),
|
|
627
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
628
|
+
}));
|
|
629
|
+
} catch (error) {
|
|
630
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
635
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
636
|
+
return (await Promise.all(
|
|
637
|
+
filters.map(
|
|
638
|
+
(botId) => lexicalSearchRawMessages2({
|
|
639
|
+
userId: input.userId,
|
|
640
|
+
keywords,
|
|
641
|
+
limit: Math.ceil(limit / filters.length),
|
|
642
|
+
botId
|
|
643
|
+
})
|
|
644
|
+
)
|
|
645
|
+
)).flat().filter(Boolean).map(
|
|
646
|
+
(r) => toMemoryResult(
|
|
647
|
+
r
|
|
648
|
+
)
|
|
649
|
+
);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
652
|
+
}
|
|
653
|
+
return [];
|
|
654
|
+
}
|
|
443
655
|
function createUnifiedSearch(deps = {}) {
|
|
444
656
|
const logger = deps.logger ?? console;
|
|
657
|
+
function resolveReasoningStrategy(input) {
|
|
658
|
+
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
659
|
+
if (requested !== "none") {
|
|
660
|
+
return requested;
|
|
661
|
+
}
|
|
662
|
+
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
663
|
+
}
|
|
445
664
|
async function searchUnifiedMemory(input) {
|
|
446
665
|
const query = input.query.trim();
|
|
447
666
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
448
667
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
449
668
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
450
669
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
670
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
451
671
|
const warnings = [];
|
|
452
672
|
if (!query) {
|
|
453
673
|
return {
|
|
@@ -459,10 +679,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
459
679
|
};
|
|
460
680
|
}
|
|
461
681
|
let memorySubs;
|
|
682
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
683
|
+
if (input.dateFrom || input.dateTo) {
|
|
684
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
685
|
+
}
|
|
462
686
|
if (sources.includes("memory")) {
|
|
463
687
|
if (isRawMessageStorageAvailable()) {
|
|
464
688
|
try {
|
|
465
|
-
memorySubs = await runMemorySource(
|
|
689
|
+
memorySubs = await runMemorySource(
|
|
690
|
+
input,
|
|
691
|
+
limit,
|
|
692
|
+
threshold,
|
|
693
|
+
warnings,
|
|
694
|
+
reasoningStrategy,
|
|
695
|
+
reasoningInfo
|
|
696
|
+
);
|
|
466
697
|
} catch (error) {
|
|
467
698
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
468
699
|
warnings.push({
|
|
@@ -541,121 +772,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
541
772
|
limit,
|
|
542
773
|
strategy: mergeStrategy
|
|
543
774
|
});
|
|
544
|
-
|
|
775
|
+
const output = {
|
|
545
776
|
query: input.query,
|
|
546
777
|
sources,
|
|
547
778
|
results: merged,
|
|
548
779
|
count: merged.length,
|
|
549
780
|
warnings
|
|
550
781
|
};
|
|
782
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
783
|
+
output.reasoning = reasoningInfo;
|
|
784
|
+
}
|
|
785
|
+
return output;
|
|
551
786
|
}
|
|
552
|
-
async function runMemorySource(input, limit, threshold, warnings) {
|
|
787
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
788
|
+
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
789
|
+
warnings.push({
|
|
790
|
+
source: "memory",
|
|
791
|
+
code: "memory_iterative_planner_not_configured",
|
|
792
|
+
message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
|
|
793
|
+
});
|
|
794
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
795
|
+
}
|
|
796
|
+
if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
|
|
797
|
+
warnings.push({
|
|
798
|
+
source: "memory",
|
|
799
|
+
code: "memory_query_rewrite_not_configured",
|
|
800
|
+
message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
|
|
801
|
+
});
|
|
802
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
803
|
+
}
|
|
804
|
+
if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
|
|
805
|
+
const planner = deps.reasoning.iterativePlanner;
|
|
806
|
+
const result = await planner.plan({
|
|
807
|
+
query: input.query,
|
|
808
|
+
dateFrom: input.dateFrom,
|
|
809
|
+
dateTo: input.dateTo,
|
|
810
|
+
executor: {
|
|
811
|
+
search: async (request) => {
|
|
812
|
+
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
813
|
+
const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
|
|
814
|
+
let semanticHits = [];
|
|
815
|
+
if (typeof deps.embedQuery === "function") {
|
|
816
|
+
try {
|
|
817
|
+
const semanticQuery = keywords2.join(" ");
|
|
818
|
+
const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
|
|
819
|
+
semanticHits = await runSemanticSearchForEmbedding(
|
|
820
|
+
deps,
|
|
821
|
+
input,
|
|
822
|
+
queryEmbedding,
|
|
823
|
+
limit,
|
|
824
|
+
threshold,
|
|
825
|
+
logger
|
|
826
|
+
);
|
|
827
|
+
} catch (error) {
|
|
828
|
+
logger.warn?.(
|
|
829
|
+
"[memory-store] Iterative semantic search failed; using lexical results only:",
|
|
830
|
+
error
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const candidates = filterByDateRange(
|
|
835
|
+
mergeByMaxScore([
|
|
836
|
+
lexicalHits.map(toIterativeRecallCandidate),
|
|
837
|
+
semanticHits.map(toIterativeRecallCandidate)
|
|
838
|
+
]),
|
|
839
|
+
request.dateFrom,
|
|
840
|
+
request.dateTo
|
|
841
|
+
);
|
|
842
|
+
return { candidates };
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
if (reasoningInfo) {
|
|
847
|
+
reasoningInfo.iterations = result.stats.iterations;
|
|
848
|
+
reasoningInfo.evidenceCount = result.evidence.length;
|
|
849
|
+
if (planner.lastDegraded?.()) {
|
|
850
|
+
reasoningInfo.degraded = true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const semantic2 = result.evidence.map((candidate) => ({
|
|
854
|
+
type: "memory",
|
|
855
|
+
id: candidate.id,
|
|
856
|
+
content: candidate.content,
|
|
857
|
+
similarity: candidate.similarity,
|
|
858
|
+
metadata: { ...candidate.metadata, reasoning: "iterative" }
|
|
859
|
+
}));
|
|
860
|
+
return { semantic: semantic2, lexical: [] };
|
|
861
|
+
}
|
|
553
862
|
if (typeof deps.embedQuery !== "function") {
|
|
554
863
|
warnings.push({
|
|
555
864
|
source: "memory",
|
|
556
865
|
code: "memory_lexical_search_fallback",
|
|
557
866
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
558
867
|
});
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const results = await Promise.all(
|
|
566
|
-
filters2.map(
|
|
567
|
-
(botId) => lexicalSearchRawMessages2({
|
|
568
|
-
userId: input.userId,
|
|
569
|
-
keywords,
|
|
570
|
-
limit: Math.ceil(limit / filters2.length),
|
|
571
|
-
botId
|
|
572
|
-
})
|
|
573
|
-
)
|
|
574
|
-
);
|
|
575
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
576
|
-
} catch (error) {
|
|
577
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
868
|
+
const keywords2 = deriveLexicalKeywords(input.query);
|
|
869
|
+
const lexical2 = filterByDateRange(
|
|
870
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
871
|
+
input.dateFrom,
|
|
872
|
+
input.dateTo
|
|
873
|
+
);
|
|
580
874
|
return { semantic: [], lexical: lexical2 };
|
|
581
875
|
}
|
|
582
|
-
const queryEmbedding = await deps.embedQuery({
|
|
583
|
-
userId: input.userId,
|
|
584
|
-
query: input.query,
|
|
585
|
-
authToken: input.authToken
|
|
586
|
-
});
|
|
587
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
588
876
|
let semantic = [];
|
|
589
|
-
|
|
877
|
+
const embedQuery = deps.embedQuery;
|
|
878
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
590
879
|
try {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
userId: input.userId,
|
|
596
|
-
queryEmbedding,
|
|
597
|
-
limit,
|
|
598
|
-
threshold,
|
|
599
|
-
botId
|
|
600
|
-
});
|
|
601
|
-
})
|
|
602
|
-
)).flat().map(toMemoryResult);
|
|
603
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
604
|
-
backend: "chroma",
|
|
605
|
-
dimensions: queryEmbedding.length,
|
|
606
|
-
count: semantic.length
|
|
880
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
881
|
+
query: input.query,
|
|
882
|
+
userId: input.userId,
|
|
883
|
+
authToken: input.authToken
|
|
607
884
|
});
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
885
|
+
if (reasoningInfo) {
|
|
886
|
+
reasoningInfo.rewrittenQueries = variants;
|
|
887
|
+
if (deps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
888
|
+
reasoningInfo.degraded = true;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const embeddings = await Promise.all(
|
|
892
|
+
variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
|
|
893
|
+
);
|
|
894
|
+
const lists = await Promise.all(
|
|
895
|
+
embeddings.map(
|
|
896
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
897
|
+
)
|
|
612
898
|
);
|
|
899
|
+
semantic = mergeByMaxScore(lists);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
|
|
902
|
+
warnings.push({
|
|
903
|
+
source: "memory",
|
|
904
|
+
code: "memory_query_rewrite_failed",
|
|
905
|
+
message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
|
|
906
|
+
});
|
|
907
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
613
908
|
}
|
|
614
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
615
|
-
semantic = (await Promise.all(
|
|
616
|
-
filters.map(
|
|
617
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
618
|
-
userId: input.userId,
|
|
619
|
-
queryEmbedding,
|
|
620
|
-
limit,
|
|
621
|
-
threshold,
|
|
622
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
623
|
-
})
|
|
624
|
-
)
|
|
625
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
626
909
|
}
|
|
627
910
|
if (semantic.length === 0) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const manager2 = await getRawMessageManager2();
|
|
631
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
632
|
-
const results = await manager2.searchMessagesSemantically({
|
|
633
|
-
userId: input.userId,
|
|
634
|
-
queryEmbedding,
|
|
635
|
-
limit,
|
|
636
|
-
threshold
|
|
637
|
-
});
|
|
638
|
-
semantic = results.map((r) => ({
|
|
639
|
-
type: "memory",
|
|
640
|
-
id: r.id,
|
|
641
|
-
content: r.content,
|
|
642
|
-
similarity: r.similarity,
|
|
643
|
-
metadata: r.metadata ?? {}
|
|
644
|
-
}));
|
|
645
|
-
}
|
|
646
|
-
} catch (error) {
|
|
647
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
648
|
-
}
|
|
911
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
912
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
649
913
|
}
|
|
650
914
|
let lexical = [];
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (
|
|
915
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
916
|
+
if (keywords.length > 0) {
|
|
917
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
654
918
|
try {
|
|
655
919
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
920
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
656
921
|
lexical = (await Promise.all(
|
|
657
922
|
lexFilters.map(
|
|
658
|
-
(botId) =>
|
|
923
|
+
(botId) => searchRawMessagesLexical({
|
|
659
924
|
userId: input.userId,
|
|
660
925
|
keywords,
|
|
661
926
|
limit,
|
|
@@ -674,20 +939,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
674
939
|
message: error.message ?? "memory_lexical_search_failed"
|
|
675
940
|
});
|
|
676
941
|
}
|
|
942
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
943
|
+
warnings.push({
|
|
944
|
+
source: "memory",
|
|
945
|
+
code: "memory_lexical_search_not_configured",
|
|
946
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
947
|
+
});
|
|
677
948
|
}
|
|
678
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
679
|
-
warnings.push({
|
|
680
|
-
source: "memory",
|
|
681
|
-
code: "memory_lexical_search_not_configured",
|
|
682
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
683
|
-
});
|
|
684
949
|
}
|
|
685
|
-
return {
|
|
950
|
+
return {
|
|
951
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
952
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
953
|
+
};
|
|
686
954
|
}
|
|
687
955
|
async function searchRawMemorySemantically(input) {
|
|
688
956
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
689
957
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
690
|
-
const
|
|
958
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
959
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
691
960
|
return mergeAcrossSources({
|
|
692
961
|
memorySubs: sub,
|
|
693
962
|
insightHits: [],
|
|
@@ -1065,6 +1334,9 @@ async function buildUnified(args) {
|
|
|
1065
1334
|
} else if (args.memoryBackend === "chroma") {
|
|
1066
1335
|
if (!args.chromaUrl)
|
|
1067
1336
|
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1337
|
+
if (!aiRag) {
|
|
1338
|
+
throw new Error("ai-rag modules not loaded");
|
|
1339
|
+
}
|
|
1068
1340
|
const store = new aiRag.ChromaVectorStore({
|
|
1069
1341
|
url: args.chromaUrl,
|
|
1070
1342
|
collectionName: "opencontext_raw_messages"
|
|
@@ -1103,6 +1375,9 @@ async function buildUnified(args) {
|
|
|
1103
1375
|
throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1104
1376
|
if (!unified.embedQuery)
|
|
1105
1377
|
throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
|
|
1378
|
+
if (!aiRag) {
|
|
1379
|
+
throw new Error("ai-rag modules not loaded");
|
|
1380
|
+
}
|
|
1106
1381
|
const store = new aiRag.ChromaVectorStore({
|
|
1107
1382
|
url: args.chromaUrl,
|
|
1108
1383
|
collectionName: args.insightsCollection
|
|
@@ -1120,6 +1395,9 @@ async function buildUnified(args) {
|
|
|
1120
1395
|
throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1121
1396
|
if (!unified.embedQuery)
|
|
1122
1397
|
throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
|
|
1398
|
+
if (!aiRag) {
|
|
1399
|
+
throw new Error("ai-rag modules not loaded");
|
|
1400
|
+
}
|
|
1123
1401
|
const store = new aiRag.ChromaVectorStore({
|
|
1124
1402
|
url: args.chromaUrl,
|
|
1125
1403
|
collectionName: args.knowledgeCollection
|