@melandlabs/memory-store 0.2.7 → 0.3.1
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 +371 -103
- package/dist/index.d.ts +3 -3
- package/dist/index.js +728 -104
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +374 -103
- package/dist/search/unified-search.d.ts +3 -101
- package/dist/search/unified-search.js +372 -104
- package/dist/server/cli-http.js +380 -103
- package/dist/server/cli-mcp.js +383 -103
- package/dist/server/cli-shared.d.ts +1 -1
- package/dist/server/cli-shared.js +13 -5
- 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 +4 -5
- package/dist/storage/sqlite-raw-message-store.d.ts +1 -1
- package/dist/storage/sqlite-raw-message-store.js +3 -4
- package/dist/storage/sqlite-vector-index.d.ts +1 -1
- package/dist/storage/sqlite-vector-index.js +3 -4
- package/package.json +11 -9
- package/dist/config-Ll6ktm4D.d.ts +0 -183
package/dist/server/cli-mcp.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { buildMemoryRecordEmbeddingDocument } from '@melandlabs/ai/memory';
|
|
3
3
|
import { ChromaVectorStore } from '@melandlabs/rag';
|
|
4
4
|
import { mkdirSync } from 'fs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import { getOpenContextPath } from '@melandlabs/env-config';
|
|
7
7
|
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
8
8
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
9
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -188,7 +188,7 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
188
188
|
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
189
189
|
if (dbPath && dbPath.length > 0) return dbPath;
|
|
190
190
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
191
|
-
return fromEnv && fromEnv.length > 0 ? fromEnv :
|
|
191
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : getOpenContextPath("memory", "store.db");
|
|
192
192
|
}
|
|
193
193
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
194
194
|
return true;
|
|
@@ -222,12 +222,11 @@ async function lexicalSearchRawMessages(input) {
|
|
|
222
222
|
}
|
|
223
223
|
return mgr.lexicalSearchMessages(input);
|
|
224
224
|
}
|
|
225
|
-
var manager
|
|
225
|
+
var manager;
|
|
226
226
|
var init_sqlite_raw_message_store = __esm({
|
|
227
227
|
"src/storage/sqlite-raw-message-store.ts"() {
|
|
228
228
|
init_chroma_memory_index();
|
|
229
229
|
manager = null;
|
|
230
|
-
DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
231
230
|
}
|
|
232
231
|
});
|
|
233
232
|
|
|
@@ -241,7 +240,7 @@ __export(raw_message_store_exports, {
|
|
|
241
240
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
242
241
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
243
242
|
});
|
|
244
|
-
function resolveBackend(
|
|
243
|
+
function resolveBackend(_env) {
|
|
245
244
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
246
245
|
if (explicit === "postgres") return "postgres";
|
|
247
246
|
return "sqlite";
|
|
@@ -374,6 +373,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
374
373
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
375
374
|
return value === "rrf" ? "rrf" : "similarity";
|
|
376
375
|
}
|
|
376
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
377
|
+
if (value === "rewrite" || value === "iterative") {
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
return defaultStrategy;
|
|
381
|
+
}
|
|
377
382
|
var DEFAULT_RRF_K = 60;
|
|
378
383
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
379
384
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -396,7 +401,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
396
401
|
}
|
|
397
402
|
}
|
|
398
403
|
}
|
|
399
|
-
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) => {
|
|
400
411
|
if (b.rrf !== a.rrf) {
|
|
401
412
|
return b.rrf - a.rrf;
|
|
402
413
|
}
|
|
@@ -441,14 +452,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
441
452
|
function deriveLexicalKeywords(query) {
|
|
442
453
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
443
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
|
+
}
|
|
444
655
|
function createUnifiedSearch(deps = {}) {
|
|
445
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
|
+
}
|
|
446
664
|
async function searchUnifiedMemory(input) {
|
|
447
665
|
const query = input.query.trim();
|
|
448
666
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
449
667
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
450
668
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
451
669
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
670
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
452
671
|
const warnings = [];
|
|
453
672
|
if (!query) {
|
|
454
673
|
return {
|
|
@@ -460,10 +679,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
460
679
|
};
|
|
461
680
|
}
|
|
462
681
|
let memorySubs;
|
|
682
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
683
|
+
if (input.dateFrom || input.dateTo) {
|
|
684
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
685
|
+
}
|
|
463
686
|
if (sources.includes("memory")) {
|
|
464
687
|
if (isRawMessageStorageAvailable()) {
|
|
465
688
|
try {
|
|
466
|
-
memorySubs = await runMemorySource(
|
|
689
|
+
memorySubs = await runMemorySource(
|
|
690
|
+
input,
|
|
691
|
+
limit,
|
|
692
|
+
threshold,
|
|
693
|
+
warnings,
|
|
694
|
+
reasoningStrategy,
|
|
695
|
+
reasoningInfo
|
|
696
|
+
);
|
|
467
697
|
} catch (error) {
|
|
468
698
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
469
699
|
warnings.push({
|
|
@@ -542,121 +772,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
542
772
|
limit,
|
|
543
773
|
strategy: mergeStrategy
|
|
544
774
|
});
|
|
545
|
-
|
|
775
|
+
const output = {
|
|
546
776
|
query: input.query,
|
|
547
777
|
sources,
|
|
548
778
|
results: merged,
|
|
549
779
|
count: merged.length,
|
|
550
780
|
warnings
|
|
551
781
|
};
|
|
782
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
783
|
+
output.reasoning = reasoningInfo;
|
|
784
|
+
}
|
|
785
|
+
return output;
|
|
552
786
|
}
|
|
553
|
-
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
|
+
}
|
|
554
862
|
if (typeof deps.embedQuery !== "function") {
|
|
555
863
|
warnings.push({
|
|
556
864
|
source: "memory",
|
|
557
865
|
code: "memory_lexical_search_fallback",
|
|
558
866
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
559
867
|
});
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const results = await Promise.all(
|
|
567
|
-
filters2.map(
|
|
568
|
-
(botId) => lexicalSearchRawMessages2({
|
|
569
|
-
userId: input.userId,
|
|
570
|
-
keywords,
|
|
571
|
-
limit: Math.ceil(limit / filters2.length),
|
|
572
|
-
botId
|
|
573
|
-
})
|
|
574
|
-
)
|
|
575
|
-
);
|
|
576
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
577
|
-
} catch (error) {
|
|
578
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
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
|
+
);
|
|
581
874
|
return { semantic: [], lexical: lexical2 };
|
|
582
875
|
}
|
|
583
|
-
const queryEmbedding = await deps.embedQuery({
|
|
584
|
-
userId: input.userId,
|
|
585
|
-
query: input.query,
|
|
586
|
-
authToken: input.authToken
|
|
587
|
-
});
|
|
588
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
589
876
|
let semantic = [];
|
|
590
|
-
|
|
877
|
+
const embedQuery = deps.embedQuery;
|
|
878
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
591
879
|
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
userId: input.userId,
|
|
597
|
-
queryEmbedding,
|
|
598
|
-
limit,
|
|
599
|
-
threshold,
|
|
600
|
-
botId
|
|
601
|
-
});
|
|
602
|
-
})
|
|
603
|
-
)).flat().map(toMemoryResult);
|
|
604
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
605
|
-
backend: "chroma",
|
|
606
|
-
dimensions: queryEmbedding.length,
|
|
607
|
-
count: semantic.length
|
|
880
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
881
|
+
query: input.query,
|
|
882
|
+
userId: input.userId,
|
|
883
|
+
authToken: input.authToken
|
|
608
884
|
});
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
+
)
|
|
613
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;
|
|
614
908
|
}
|
|
615
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
616
|
-
semantic = (await Promise.all(
|
|
617
|
-
filters.map(
|
|
618
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
619
|
-
userId: input.userId,
|
|
620
|
-
queryEmbedding,
|
|
621
|
-
limit,
|
|
622
|
-
threshold,
|
|
623
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
624
|
-
})
|
|
625
|
-
)
|
|
626
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
627
909
|
}
|
|
628
910
|
if (semantic.length === 0) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const manager2 = await getRawMessageManager2();
|
|
632
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
633
|
-
const results = await manager2.searchMessagesSemantically({
|
|
634
|
-
userId: input.userId,
|
|
635
|
-
queryEmbedding,
|
|
636
|
-
limit,
|
|
637
|
-
threshold
|
|
638
|
-
});
|
|
639
|
-
semantic = results.map((r) => ({
|
|
640
|
-
type: "memory",
|
|
641
|
-
id: r.id,
|
|
642
|
-
content: r.content,
|
|
643
|
-
similarity: r.similarity,
|
|
644
|
-
metadata: r.metadata ?? {}
|
|
645
|
-
}));
|
|
646
|
-
}
|
|
647
|
-
} catch (error) {
|
|
648
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
649
|
-
}
|
|
911
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
912
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
650
913
|
}
|
|
651
914
|
let lexical = [];
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (
|
|
915
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
916
|
+
if (keywords.length > 0) {
|
|
917
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
655
918
|
try {
|
|
656
919
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
920
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
657
921
|
lexical = (await Promise.all(
|
|
658
922
|
lexFilters.map(
|
|
659
|
-
(botId) =>
|
|
923
|
+
(botId) => searchRawMessagesLexical({
|
|
660
924
|
userId: input.userId,
|
|
661
925
|
keywords,
|
|
662
926
|
limit,
|
|
@@ -675,20 +939,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
675
939
|
message: error.message ?? "memory_lexical_search_failed"
|
|
676
940
|
});
|
|
677
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
|
+
});
|
|
678
948
|
}
|
|
679
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
680
|
-
warnings.push({
|
|
681
|
-
source: "memory",
|
|
682
|
-
code: "memory_lexical_search_not_configured",
|
|
683
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
684
|
-
});
|
|
685
949
|
}
|
|
686
|
-
return {
|
|
950
|
+
return {
|
|
951
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
952
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
953
|
+
};
|
|
687
954
|
}
|
|
688
955
|
async function searchRawMemorySemantically(input) {
|
|
689
956
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
690
957
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
691
|
-
const
|
|
958
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
959
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
692
960
|
return mergeAcrossSources({
|
|
693
961
|
memorySubs: sub,
|
|
694
962
|
insightHits: [],
|
|
@@ -815,6 +1083,7 @@ async function startMcpServer(options = {}) {
|
|
|
815
1083
|
{
|
|
816
1084
|
title: "Search Unified Memory",
|
|
817
1085
|
description: "Semantic search across raw messages (always), and optionally insights and uploaded knowledge.",
|
|
1086
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
818
1087
|
inputSchema: searchSchema
|
|
819
1088
|
},
|
|
820
1089
|
async (args) => {
|
|
@@ -830,6 +1099,7 @@ async function startMcpServer(options = {}) {
|
|
|
830
1099
|
{
|
|
831
1100
|
title: "Write Raw Message",
|
|
832
1101
|
description: "Persist a single raw message to the user's memory store.",
|
|
1102
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
833
1103
|
inputSchema: writeSchema
|
|
834
1104
|
},
|
|
835
1105
|
async (args) => {
|
|
@@ -869,6 +1139,7 @@ async function startMcpServer(options = {}) {
|
|
|
869
1139
|
{
|
|
870
1140
|
title: "Get Raw Message",
|
|
871
1141
|
description: "Fetch a single raw message by its message id.",
|
|
1142
|
+
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK expects Zod schema type
|
|
872
1143
|
inputSchema: getSchema
|
|
873
1144
|
},
|
|
874
1145
|
async (args) => {
|
|
@@ -1077,6 +1348,9 @@ async function buildUnified(args) {
|
|
|
1077
1348
|
} else if (args.memoryBackend === "chroma") {
|
|
1078
1349
|
if (!args.chromaUrl)
|
|
1079
1350
|
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1351
|
+
if (!aiRag) {
|
|
1352
|
+
throw new Error("ai-rag modules not loaded");
|
|
1353
|
+
}
|
|
1080
1354
|
const store = new aiRag.ChromaVectorStore({
|
|
1081
1355
|
url: args.chromaUrl,
|
|
1082
1356
|
collectionName: "opencontext_raw_messages"
|
|
@@ -1115,6 +1389,9 @@ async function buildUnified(args) {
|
|
|
1115
1389
|
throw new Error("--insights-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1116
1390
|
if (!unified.embedQuery)
|
|
1117
1391
|
throw new Error("--insights-backend=chroma requires --embedding-provider local|openrouter");
|
|
1392
|
+
if (!aiRag) {
|
|
1393
|
+
throw new Error("ai-rag modules not loaded");
|
|
1394
|
+
}
|
|
1118
1395
|
const store = new aiRag.ChromaVectorStore({
|
|
1119
1396
|
url: args.chromaUrl,
|
|
1120
1397
|
collectionName: args.insightsCollection
|
|
@@ -1132,6 +1409,9 @@ async function buildUnified(args) {
|
|
|
1132
1409
|
throw new Error("--knowledge-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
1133
1410
|
if (!unified.embedQuery)
|
|
1134
1411
|
throw new Error("--knowledge-backend=chroma requires --embedding-provider local|openrouter");
|
|
1412
|
+
if (!aiRag) {
|
|
1413
|
+
throw new Error("ai-rag modules not loaded");
|
|
1414
|
+
}
|
|
1135
1415
|
const store = new aiRag.ChromaVectorStore({
|
|
1136
1416
|
url: args.chromaUrl,
|
|
1137
1417
|
collectionName: args.knowledgeCollection
|