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