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