@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/index.js
CHANGED
|
@@ -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;
|
|
@@ -117,7 +117,7 @@ __export(sqlite_raw_message_store_exports, {
|
|
|
117
117
|
function resolveSQLiteRawMessageDbPath(dbPath) {
|
|
118
118
|
if (dbPath && dbPath.length > 0) return dbPath;
|
|
119
119
|
const fromEnv = process.env.MEMORY_STORE_DB_PATH?.trim();
|
|
120
|
-
return fromEnv && fromEnv.length > 0 ? fromEnv :
|
|
120
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : getOpenContextPath("memory", "store.db");
|
|
121
121
|
}
|
|
122
122
|
function isSQLiteRawMessageStorageAvailable(_env) {
|
|
123
123
|
return true;
|
|
@@ -151,12 +151,11 @@ async function lexicalSearchRawMessages(input) {
|
|
|
151
151
|
}
|
|
152
152
|
return mgr.lexicalSearchMessages(input);
|
|
153
153
|
}
|
|
154
|
-
var manager
|
|
154
|
+
var manager;
|
|
155
155
|
var init_sqlite_raw_message_store = __esm({
|
|
156
156
|
"src/storage/sqlite-raw-message-store.ts"() {
|
|
157
157
|
init_chroma_memory_index();
|
|
158
158
|
manager = null;
|
|
159
|
-
DEFAULT_SQLITE_PATH = join(homedir(), ".opencontext", "memory", "store.db");
|
|
160
159
|
}
|
|
161
160
|
});
|
|
162
161
|
|
|
@@ -170,7 +169,7 @@ __export(raw_message_store_exports, {
|
|
|
170
169
|
getRawMessageStorageBackend: () => getRawMessageStorageBackend,
|
|
171
170
|
isRawMessageStorageAvailable: () => isRawMessageStorageAvailable
|
|
172
171
|
});
|
|
173
|
-
function resolveBackend(
|
|
172
|
+
function resolveBackend(_env) {
|
|
174
173
|
const explicit = process.env.OPENCONTEXT_MEMORY_STORE_BACKEND?.trim().toLowerCase();
|
|
175
174
|
if (explicit === "postgres") return "postgres";
|
|
176
175
|
return "sqlite";
|
|
@@ -303,6 +302,12 @@ function mergeUnifiedMemorySearchResults(results, limit, options = {}) {
|
|
|
303
302
|
function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
304
303
|
return value === "rrf" ? "rrf" : "similarity";
|
|
305
304
|
}
|
|
305
|
+
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
306
|
+
if (value === "rewrite" || value === "iterative") {
|
|
307
|
+
return value;
|
|
308
|
+
}
|
|
309
|
+
return defaultStrategy;
|
|
310
|
+
}
|
|
306
311
|
var DEFAULT_RRF_K = 60;
|
|
307
312
|
function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
308
313
|
if (!Array.isArray(lists) || lists.length === 0) {
|
|
@@ -325,7 +330,13 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K) {
|
|
|
325
330
|
}
|
|
326
331
|
}
|
|
327
332
|
}
|
|
328
|
-
const ranked = order.map((key) =>
|
|
333
|
+
const ranked = order.map((key) => {
|
|
334
|
+
const entry = scores.get(key);
|
|
335
|
+
if (!entry) {
|
|
336
|
+
throw new Error(`Invariant violation: missing RRF score for key "${key}"`);
|
|
337
|
+
}
|
|
338
|
+
return entry;
|
|
339
|
+
}).sort((a, b) => {
|
|
329
340
|
if (b.rrf !== a.rrf) {
|
|
330
341
|
return b.rrf - a.rrf;
|
|
331
342
|
}
|
|
@@ -370,14 +381,222 @@ function isRawMemorySemanticResult(result) {
|
|
|
370
381
|
function deriveLexicalKeywords(query) {
|
|
371
382
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
372
383
|
}
|
|
384
|
+
function parseDateBoundary(value, role) {
|
|
385
|
+
const trimmed = value.trim();
|
|
386
|
+
if (!trimmed) {
|
|
387
|
+
return void 0;
|
|
388
|
+
}
|
|
389
|
+
const dateOnlyMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
390
|
+
if (dateOnlyMatch) {
|
|
391
|
+
const year = Number.parseInt(dateOnlyMatch[1], 10);
|
|
392
|
+
const month = Number.parseInt(dateOnlyMatch[2], 10) - 1;
|
|
393
|
+
const day = Number.parseInt(dateOnlyMatch[3], 10);
|
|
394
|
+
if (role === "from") {
|
|
395
|
+
return Date.UTC(year, month, day);
|
|
396
|
+
}
|
|
397
|
+
return Date.UTC(year, month, day + 1) - 1;
|
|
398
|
+
}
|
|
399
|
+
const parsed = Date.parse(trimmed);
|
|
400
|
+
if (Number.isNaN(parsed)) {
|
|
401
|
+
return void 0;
|
|
402
|
+
}
|
|
403
|
+
return parsed;
|
|
404
|
+
}
|
|
405
|
+
function getCandidateTimestamp(metadata) {
|
|
406
|
+
for (const key of ["timestamp", "createdAt", "time"]) {
|
|
407
|
+
const value = metadata[key];
|
|
408
|
+
if (typeof value === "number") {
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
if (typeof value === "string") {
|
|
412
|
+
const parsed = Date.parse(value);
|
|
413
|
+
if (!Number.isNaN(parsed)) {
|
|
414
|
+
return parsed;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return void 0;
|
|
419
|
+
}
|
|
420
|
+
function filterByDateRange(items, dateFrom, dateTo) {
|
|
421
|
+
if (!dateFrom && !dateTo) {
|
|
422
|
+
return items;
|
|
423
|
+
}
|
|
424
|
+
const fromMs = dateFrom ? parseDateBoundary(dateFrom, "from") : void 0;
|
|
425
|
+
const toMs = dateTo ? parseDateBoundary(dateTo, "to") : void 0;
|
|
426
|
+
if (fromMs === void 0 && toMs === void 0) {
|
|
427
|
+
return items;
|
|
428
|
+
}
|
|
429
|
+
return items.filter((item) => {
|
|
430
|
+
const ts = getCandidateTimestamp(item.metadata);
|
|
431
|
+
if (ts === void 0) {
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
if (fromMs !== void 0 && ts < fromMs) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
if (toMs !== void 0 && ts > toMs) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
return true;
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
async function embedQueryVariant(embedQuery, input, query) {
|
|
444
|
+
return embedQuery({
|
|
445
|
+
userId: input.userId,
|
|
446
|
+
query,
|
|
447
|
+
authToken: input.authToken
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger) {
|
|
451
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
452
|
+
let semantic = [];
|
|
453
|
+
if (isRawMessageChromaEnabled()) {
|
|
454
|
+
try {
|
|
455
|
+
semantic = (await Promise.all(
|
|
456
|
+
filters.map((filter) => {
|
|
457
|
+
const botId = "botId" in filter ? filter.botId : void 0;
|
|
458
|
+
return searchRawMessagesWithChroma({
|
|
459
|
+
userId: input.userId,
|
|
460
|
+
queryEmbedding,
|
|
461
|
+
limit,
|
|
462
|
+
threshold,
|
|
463
|
+
botId
|
|
464
|
+
});
|
|
465
|
+
})
|
|
466
|
+
)).flat().map(toMemoryResult);
|
|
467
|
+
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
468
|
+
backend: "chroma",
|
|
469
|
+
dimensions: queryEmbedding.length,
|
|
470
|
+
count: semantic.length
|
|
471
|
+
});
|
|
472
|
+
} catch (error) {
|
|
473
|
+
logger.warn?.(
|
|
474
|
+
"[memory-store] Chroma raw message search failed; falling back to database search:",
|
|
475
|
+
error
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
479
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
480
|
+
semantic = (await Promise.all(
|
|
481
|
+
filters.map(
|
|
482
|
+
(filter) => searchRawMessagesAnn({
|
|
483
|
+
userId: input.userId,
|
|
484
|
+
queryEmbedding,
|
|
485
|
+
limit,
|
|
486
|
+
threshold,
|
|
487
|
+
botId: "botId" in filter ? filter.botId : void 0
|
|
488
|
+
})
|
|
489
|
+
)
|
|
490
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
491
|
+
}
|
|
492
|
+
if (semantic.length === 0) {
|
|
493
|
+
try {
|
|
494
|
+
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
495
|
+
const manager2 = await getRawMessageManager2();
|
|
496
|
+
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
497
|
+
const results = await manager2.searchMessagesSemantically({
|
|
498
|
+
userId: input.userId,
|
|
499
|
+
queryEmbedding,
|
|
500
|
+
limit,
|
|
501
|
+
threshold
|
|
502
|
+
});
|
|
503
|
+
semantic = results.map((r) => ({
|
|
504
|
+
type: "memory",
|
|
505
|
+
id: r.id,
|
|
506
|
+
content: r.content,
|
|
507
|
+
similarity: r.similarity,
|
|
508
|
+
metadata: r.metadata ?? {}
|
|
509
|
+
}));
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return semantic;
|
|
516
|
+
}
|
|
517
|
+
function mergeByMaxScore(lists) {
|
|
518
|
+
const best = /* @__PURE__ */ new Map();
|
|
519
|
+
for (const list of lists) {
|
|
520
|
+
for (const hit of list) {
|
|
521
|
+
const existing = best.get(hit.id);
|
|
522
|
+
if (!existing || hit.similarity > existing.similarity) {
|
|
523
|
+
best.set(hit.id, hit);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
528
|
+
}
|
|
529
|
+
function toIterativeRecallCandidate(hit) {
|
|
530
|
+
return {
|
|
531
|
+
id: hit.id,
|
|
532
|
+
content: hit.content,
|
|
533
|
+
similarity: hit.similarity,
|
|
534
|
+
metadata: hit.metadata
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger) {
|
|
538
|
+
if (keywords.length === 0) {
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
542
|
+
try {
|
|
543
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
544
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
545
|
+
return (await Promise.all(
|
|
546
|
+
filters.map(
|
|
547
|
+
(botId) => searchRawMessagesLexical({
|
|
548
|
+
userId: input.userId,
|
|
549
|
+
keywords,
|
|
550
|
+
limit: Math.ceil(limit / filters.length),
|
|
551
|
+
botId
|
|
552
|
+
})
|
|
553
|
+
)
|
|
554
|
+
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
555
|
+
...toMemoryResult(hit),
|
|
556
|
+
metadata: { ...hit.metadata, scoring: "bm25" }
|
|
557
|
+
}));
|
|
558
|
+
} catch (error) {
|
|
559
|
+
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
564
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
565
|
+
return (await Promise.all(
|
|
566
|
+
filters.map(
|
|
567
|
+
(botId) => lexicalSearchRawMessages2({
|
|
568
|
+
userId: input.userId,
|
|
569
|
+
keywords,
|
|
570
|
+
limit: Math.ceil(limit / filters.length),
|
|
571
|
+
botId
|
|
572
|
+
})
|
|
573
|
+
)
|
|
574
|
+
)).flat().filter(Boolean).map(
|
|
575
|
+
(r) => toMemoryResult(
|
|
576
|
+
r
|
|
577
|
+
)
|
|
578
|
+
);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
logger.warn?.("[memory-store] SQLite lexical search failed:", error);
|
|
581
|
+
}
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
373
584
|
function createUnifiedSearch(deps = {}) {
|
|
374
585
|
const logger = deps.logger ?? console;
|
|
586
|
+
function resolveReasoningStrategy(input) {
|
|
587
|
+
const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
|
|
588
|
+
if (requested !== "none") {
|
|
589
|
+
return requested;
|
|
590
|
+
}
|
|
591
|
+
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
592
|
+
}
|
|
375
593
|
async function searchUnifiedMemory(input) {
|
|
376
594
|
const query = input.query.trim();
|
|
377
595
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
378
596
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
379
597
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
380
598
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(input.mergeStrategy);
|
|
599
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
381
600
|
const warnings = [];
|
|
382
601
|
if (!query) {
|
|
383
602
|
return {
|
|
@@ -389,10 +608,21 @@ function createUnifiedSearch(deps = {}) {
|
|
|
389
608
|
};
|
|
390
609
|
}
|
|
391
610
|
let memorySubs;
|
|
611
|
+
const reasoningInfo = { strategy: reasoningStrategy };
|
|
612
|
+
if (input.dateFrom || input.dateTo) {
|
|
613
|
+
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
614
|
+
}
|
|
392
615
|
if (sources.includes("memory")) {
|
|
393
616
|
if (isRawMessageStorageAvailable()) {
|
|
394
617
|
try {
|
|
395
|
-
memorySubs = await runMemorySource(
|
|
618
|
+
memorySubs = await runMemorySource(
|
|
619
|
+
input,
|
|
620
|
+
limit,
|
|
621
|
+
threshold,
|
|
622
|
+
warnings,
|
|
623
|
+
reasoningStrategy,
|
|
624
|
+
reasoningInfo
|
|
625
|
+
);
|
|
396
626
|
} catch (error) {
|
|
397
627
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
398
628
|
warnings.push({
|
|
@@ -471,121 +701,155 @@ function createUnifiedSearch(deps = {}) {
|
|
|
471
701
|
limit,
|
|
472
702
|
strategy: mergeStrategy
|
|
473
703
|
});
|
|
474
|
-
|
|
704
|
+
const output = {
|
|
475
705
|
query: input.query,
|
|
476
706
|
sources,
|
|
477
707
|
results: merged,
|
|
478
708
|
count: merged.length,
|
|
479
709
|
warnings
|
|
480
710
|
};
|
|
711
|
+
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
712
|
+
output.reasoning = reasoningInfo;
|
|
713
|
+
}
|
|
714
|
+
return output;
|
|
481
715
|
}
|
|
482
|
-
async function runMemorySource(input, limit, threshold, warnings) {
|
|
716
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo) {
|
|
717
|
+
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
718
|
+
warnings.push({
|
|
719
|
+
source: "memory",
|
|
720
|
+
code: "memory_iterative_planner_not_configured",
|
|
721
|
+
message: "Iterative reasoning requested but no iterativePlanner is configured; falling back to default search."
|
|
722
|
+
});
|
|
723
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
724
|
+
}
|
|
725
|
+
if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
|
|
726
|
+
warnings.push({
|
|
727
|
+
source: "memory",
|
|
728
|
+
code: "memory_query_rewrite_not_configured",
|
|
729
|
+
message: "Rewrite reasoning requested but no queryRewriter is configured; falling back to default search."
|
|
730
|
+
});
|
|
731
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
732
|
+
}
|
|
733
|
+
if (reasoningStrategy === "iterative" && deps.reasoning?.iterativePlanner) {
|
|
734
|
+
const planner = deps.reasoning.iterativePlanner;
|
|
735
|
+
const result = await planner.plan({
|
|
736
|
+
query: input.query,
|
|
737
|
+
dateFrom: input.dateFrom,
|
|
738
|
+
dateTo: input.dateTo,
|
|
739
|
+
executor: {
|
|
740
|
+
search: async (request) => {
|
|
741
|
+
const keywords2 = request.keywords.length > 0 ? request.keywords : deriveLexicalKeywords(input.query);
|
|
742
|
+
const lexicalHits = await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger);
|
|
743
|
+
let semanticHits = [];
|
|
744
|
+
if (typeof deps.embedQuery === "function") {
|
|
745
|
+
try {
|
|
746
|
+
const semanticQuery = keywords2.join(" ");
|
|
747
|
+
const queryEmbedding = await embedQueryVariant(deps.embedQuery, input, semanticQuery);
|
|
748
|
+
semanticHits = await runSemanticSearchForEmbedding(
|
|
749
|
+
deps,
|
|
750
|
+
input,
|
|
751
|
+
queryEmbedding,
|
|
752
|
+
limit,
|
|
753
|
+
threshold,
|
|
754
|
+
logger
|
|
755
|
+
);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
logger.warn?.(
|
|
758
|
+
"[memory-store] Iterative semantic search failed; using lexical results only:",
|
|
759
|
+
error
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const candidates = filterByDateRange(
|
|
764
|
+
mergeByMaxScore([
|
|
765
|
+
lexicalHits.map(toIterativeRecallCandidate),
|
|
766
|
+
semanticHits.map(toIterativeRecallCandidate)
|
|
767
|
+
]),
|
|
768
|
+
request.dateFrom,
|
|
769
|
+
request.dateTo
|
|
770
|
+
);
|
|
771
|
+
return { candidates };
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
if (reasoningInfo) {
|
|
776
|
+
reasoningInfo.iterations = result.stats.iterations;
|
|
777
|
+
reasoningInfo.evidenceCount = result.evidence.length;
|
|
778
|
+
if (planner.lastDegraded?.()) {
|
|
779
|
+
reasoningInfo.degraded = true;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const semantic2 = result.evidence.map((candidate) => ({
|
|
783
|
+
type: "memory",
|
|
784
|
+
id: candidate.id,
|
|
785
|
+
content: candidate.content,
|
|
786
|
+
similarity: candidate.similarity,
|
|
787
|
+
metadata: { ...candidate.metadata, reasoning: "iterative" }
|
|
788
|
+
}));
|
|
789
|
+
return { semantic: semantic2, lexical: [] };
|
|
790
|
+
}
|
|
483
791
|
if (typeof deps.embedQuery !== "function") {
|
|
484
792
|
warnings.push({
|
|
485
793
|
source: "memory",
|
|
486
794
|
code: "memory_lexical_search_fallback",
|
|
487
795
|
message: "Semantic search not configured, using keyword search as fallback"
|
|
488
796
|
});
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const results = await Promise.all(
|
|
496
|
-
filters2.map(
|
|
497
|
-
(botId) => lexicalSearchRawMessages2({
|
|
498
|
-
userId: input.userId,
|
|
499
|
-
keywords,
|
|
500
|
-
limit: Math.ceil(limit / filters2.length),
|
|
501
|
-
botId
|
|
502
|
-
})
|
|
503
|
-
)
|
|
504
|
-
);
|
|
505
|
-
lexical2 = results.flat().filter(Boolean).map(toMemoryResult);
|
|
506
|
-
} catch (error) {
|
|
507
|
-
logger.warn?.("[memory-store] Default lexical search failed:", error);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
797
|
+
const keywords2 = deriveLexicalKeywords(input.query);
|
|
798
|
+
const lexical2 = filterByDateRange(
|
|
799
|
+
await runLexicalSearchForKeywords(deps, input, keywords2, limit, logger),
|
|
800
|
+
input.dateFrom,
|
|
801
|
+
input.dateTo
|
|
802
|
+
);
|
|
510
803
|
return { semantic: [], lexical: lexical2 };
|
|
511
804
|
}
|
|
512
|
-
const queryEmbedding = await deps.embedQuery({
|
|
513
|
-
userId: input.userId,
|
|
514
|
-
query: input.query,
|
|
515
|
-
authToken: input.authToken
|
|
516
|
-
});
|
|
517
|
-
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
518
805
|
let semantic = [];
|
|
519
|
-
|
|
806
|
+
const embedQuery = deps.embedQuery;
|
|
807
|
+
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
520
808
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
userId: input.userId,
|
|
526
|
-
queryEmbedding,
|
|
527
|
-
limit,
|
|
528
|
-
threshold,
|
|
529
|
-
botId
|
|
530
|
-
});
|
|
531
|
-
})
|
|
532
|
-
)).flat().map(toMemoryResult);
|
|
533
|
-
logger.log?.("[memory-store] Raw message semantic search completed", {
|
|
534
|
-
backend: "chroma",
|
|
535
|
-
dimensions: queryEmbedding.length,
|
|
536
|
-
count: semantic.length
|
|
809
|
+
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
810
|
+
query: input.query,
|
|
811
|
+
userId: input.userId,
|
|
812
|
+
authToken: input.authToken
|
|
537
813
|
});
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
814
|
+
if (reasoningInfo) {
|
|
815
|
+
reasoningInfo.rewrittenQueries = variants;
|
|
816
|
+
if (deps.reasoning.queryRewriter.lastDegraded?.()) {
|
|
817
|
+
reasoningInfo.degraded = true;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const embeddings = await Promise.all(
|
|
821
|
+
variants.map((variant) => embedQueryVariant(embedQuery, input, variant))
|
|
822
|
+
);
|
|
823
|
+
const lists = await Promise.all(
|
|
824
|
+
embeddings.map(
|
|
825
|
+
(embedding) => runSemanticSearchForEmbedding(deps, input, embedding, limit, threshold, logger)
|
|
826
|
+
)
|
|
542
827
|
);
|
|
828
|
+
semantic = mergeByMaxScore(lists);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
logger.warn?.("[memory-store] Query rewriting failed; falling back to original query:", error);
|
|
831
|
+
warnings.push({
|
|
832
|
+
source: "memory",
|
|
833
|
+
code: "memory_query_rewrite_failed",
|
|
834
|
+
message: `Query rewriting failed: ${error.message ?? "Unknown error"}. Using original query.`
|
|
835
|
+
});
|
|
836
|
+
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
543
837
|
}
|
|
544
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
545
|
-
semantic = (await Promise.all(
|
|
546
|
-
filters.map(
|
|
547
|
-
(filter) => deps.searchRawMessagesAnn({
|
|
548
|
-
userId: input.userId,
|
|
549
|
-
queryEmbedding,
|
|
550
|
-
limit,
|
|
551
|
-
threshold,
|
|
552
|
-
botId: "botId" in filter ? filter.botId : void 0
|
|
553
|
-
})
|
|
554
|
-
)
|
|
555
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
556
838
|
}
|
|
557
839
|
if (semantic.length === 0) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const manager2 = await getRawMessageManager2();
|
|
561
|
-
if (typeof manager2.searchMessagesSemantically === "function") {
|
|
562
|
-
const results = await manager2.searchMessagesSemantically({
|
|
563
|
-
userId: input.userId,
|
|
564
|
-
queryEmbedding,
|
|
565
|
-
limit,
|
|
566
|
-
threshold
|
|
567
|
-
});
|
|
568
|
-
semantic = results.map((r) => ({
|
|
569
|
-
type: "memory",
|
|
570
|
-
id: r.id,
|
|
571
|
-
content: r.content,
|
|
572
|
-
similarity: r.similarity,
|
|
573
|
-
metadata: r.metadata ?? {}
|
|
574
|
-
}));
|
|
575
|
-
}
|
|
576
|
-
} catch (error) {
|
|
577
|
-
logger.warn?.("[memory-store] SQLite semantic search failed:", error);
|
|
578
|
-
}
|
|
840
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
841
|
+
semantic = await runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger);
|
|
579
842
|
}
|
|
580
843
|
let lexical = [];
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (
|
|
844
|
+
const keywords = deriveLexicalKeywords(input.query);
|
|
845
|
+
if (keywords.length > 0) {
|
|
846
|
+
if (typeof deps.searchRawMessagesLexical === "function") {
|
|
584
847
|
try {
|
|
585
848
|
const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
849
|
+
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
586
850
|
lexical = (await Promise.all(
|
|
587
851
|
lexFilters.map(
|
|
588
|
-
(botId) =>
|
|
852
|
+
(botId) => searchRawMessagesLexical({
|
|
589
853
|
userId: input.userId,
|
|
590
854
|
keywords,
|
|
591
855
|
limit,
|
|
@@ -604,20 +868,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
604
868
|
message: error.message ?? "memory_lexical_search_failed"
|
|
605
869
|
});
|
|
606
870
|
}
|
|
871
|
+
} else if (input.mergeStrategy === "rrf") {
|
|
872
|
+
warnings.push({
|
|
873
|
+
source: "memory",
|
|
874
|
+
code: "memory_lexical_search_not_configured",
|
|
875
|
+
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
876
|
+
});
|
|
607
877
|
}
|
|
608
|
-
} else if (input.mergeStrategy === "rrf") {
|
|
609
|
-
warnings.push({
|
|
610
|
-
source: "memory",
|
|
611
|
-
code: "memory_lexical_search_not_configured",
|
|
612
|
-
message: "RRF merge requested but lexical search is not configured; falling back to semantic-only RRF."
|
|
613
|
-
});
|
|
614
878
|
}
|
|
615
|
-
return {
|
|
879
|
+
return {
|
|
880
|
+
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
881
|
+
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
882
|
+
};
|
|
616
883
|
}
|
|
617
884
|
async function searchRawMemorySemantically(input) {
|
|
618
885
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
619
886
|
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
620
|
-
const
|
|
887
|
+
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
888
|
+
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
621
889
|
return mergeAcrossSources({
|
|
622
890
|
memorySubs: sub,
|
|
623
891
|
insightHits: [],
|
|
@@ -662,6 +930,362 @@ function mergeAcrossSources(input) {
|
|
|
662
930
|
init_raw_message_store();
|
|
663
931
|
init_raw_message_store();
|
|
664
932
|
init_postgres_raw_message_factory();
|
|
933
|
+
|
|
934
|
+
// src/search/query-rewriter.ts
|
|
935
|
+
var SYSTEM_PROMPT = `You are a memory retrieval assistant.
|
|
936
|
+
|
|
937
|
+
The memory store contains a user's past messages, written in the first person (e.g. "I told you about my sister's wedding", "I prefer dark mode").
|
|
938
|
+
|
|
939
|
+
Your job is NOT to answer the question. Your job is to rewrite the assistant's question into a short, natural question that the USER would ask their own memory log to find the relevant entry.
|
|
940
|
+
|
|
941
|
+
Rules:
|
|
942
|
+
- Use first person: "Did I tell you about ...?", "What did I say about ...?", "Have I mentioned ...?"
|
|
943
|
+
- Do NOT address the user as "you".
|
|
944
|
+
- Do NOT answer the question.
|
|
945
|
+
- Output each rephrasing on its own line, prefixed with "- ".
|
|
946
|
+
- Keep each rephrasing under 20 words.
|
|
947
|
+
- Output exactly the requested number of lines.`;
|
|
948
|
+
function buildPrompt(query, numVariants) {
|
|
949
|
+
const label = numVariants === 1 ? "1 alternative rephrasing" : `${numVariants} alternative rephrasings`;
|
|
950
|
+
return `${SYSTEM_PROMPT}
|
|
951
|
+
|
|
952
|
+
Output exactly ${label}.
|
|
953
|
+
|
|
954
|
+
Example:
|
|
955
|
+
Question: What is the name of my cat?
|
|
956
|
+
- Did I tell you the name of my cat?
|
|
957
|
+
|
|
958
|
+
Question: ${query}
|
|
959
|
+
Output:`;
|
|
960
|
+
}
|
|
961
|
+
function sanitizeLine(text) {
|
|
962
|
+
return text.replace(/^\s*["'`]+|["'`]+\s*$/g, "").replace(/^Rewritten:\s*/i, "").trim();
|
|
963
|
+
}
|
|
964
|
+
function parseVariants(raw, maxVariants, original) {
|
|
965
|
+
const lines = raw.split(/\r?\n/).map((line) => sanitizeLine(line.replace(/^\s*-\s+/, ""))).filter((line) => line.length > 0);
|
|
966
|
+
if (lines.length === 0) return [];
|
|
967
|
+
const seen = /* @__PURE__ */ new Set([original.toLowerCase()]);
|
|
968
|
+
const variants = [];
|
|
969
|
+
for (const line of lines) {
|
|
970
|
+
if (variants.length >= maxVariants) break;
|
|
971
|
+
const key = line.toLowerCase();
|
|
972
|
+
if (seen.has(key)) continue;
|
|
973
|
+
seen.add(key);
|
|
974
|
+
variants.push(line);
|
|
975
|
+
}
|
|
976
|
+
return variants;
|
|
977
|
+
}
|
|
978
|
+
function createUserVoiceRewriter(options) {
|
|
979
|
+
const { complete, maxVariants = 1, disabled = false } = options;
|
|
980
|
+
const wantedVariants = Math.max(0, Math.floor(maxVariants));
|
|
981
|
+
let lastDegraded = false;
|
|
982
|
+
return {
|
|
983
|
+
async rewrite(input) {
|
|
984
|
+
if (disabled || !input.query || !input.query.trim()) {
|
|
985
|
+
lastDegraded = false;
|
|
986
|
+
return [input.query];
|
|
987
|
+
}
|
|
988
|
+
const original = input.query.trim();
|
|
989
|
+
if (wantedVariants === 0) {
|
|
990
|
+
lastDegraded = false;
|
|
991
|
+
return [original];
|
|
992
|
+
}
|
|
993
|
+
try {
|
|
994
|
+
const raw = await complete(buildPrompt(original, wantedVariants));
|
|
995
|
+
const variants = parseVariants(raw, wantedVariants, original);
|
|
996
|
+
if (variants.length === 0) {
|
|
997
|
+
lastDegraded = true;
|
|
998
|
+
return [original];
|
|
999
|
+
}
|
|
1000
|
+
lastDegraded = false;
|
|
1001
|
+
return [original, ...variants];
|
|
1002
|
+
} catch {
|
|
1003
|
+
lastDegraded = true;
|
|
1004
|
+
return [original];
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
lastDegraded() {
|
|
1008
|
+
return lastDegraded;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
function createIdentityRewriter() {
|
|
1013
|
+
return {
|
|
1014
|
+
async rewrite(input) {
|
|
1015
|
+
return [input.query];
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/search/iterative-recall.ts
|
|
1021
|
+
var SYSTEM_PROMPT2 = `You are a research assistant collecting evidence from a user's conversation history. You are NOT answering the question; only gather the most useful memory.
|
|
1022
|
+
|
|
1023
|
+
Actions:
|
|
1024
|
+
search \u2014 search by concise keywords. Previously returned chunks are automatically excluded.
|
|
1025
|
+
Action Input: {"keywords":["keyword1","keyword2"],"date_from":"YYYY-MM-DD","date_to":"YYYY-MM-DD"}
|
|
1026
|
+
The date fields are optional; omit them unless an absolute date range is useful.
|
|
1027
|
+
|
|
1028
|
+
note \u2014 save relevant results from the LAST search by their 1-based result number.
|
|
1029
|
+
Action Input: {"indices":[1,3]}
|
|
1030
|
+
|
|
1031
|
+
finish \u2014 finish collecting evidence.
|
|
1032
|
+
Action Input: {}
|
|
1033
|
+
|
|
1034
|
+
Workflow: search -> note -> search again -> note -> finish.
|
|
1035
|
+
Review every result. Call note before another search because the prior result list is replaced. Use diverse keywords on later searches. Never answer the question.
|
|
1036
|
+
|
|
1037
|
+
Output exactly:
|
|
1038
|
+
Thought: one short sentence
|
|
1039
|
+
Action: one action name
|
|
1040
|
+
Action Input: valid JSON object`;
|
|
1041
|
+
function deriveKeywordsFromQuery(query) {
|
|
1042
|
+
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2);
|
|
1043
|
+
}
|
|
1044
|
+
function formatDateHint(dateFrom, dateTo) {
|
|
1045
|
+
if (!dateFrom && !dateTo) {
|
|
1046
|
+
return void 0;
|
|
1047
|
+
}
|
|
1048
|
+
const fromText = dateFrom ?? "the beginning";
|
|
1049
|
+
const toText = dateTo ?? "now";
|
|
1050
|
+
return `Restrict searches to the date range ${fromText} to ${toText}. You may still emit narrower date_from/date_to bounds when useful.`;
|
|
1051
|
+
}
|
|
1052
|
+
function buildInitialPrompt(query, dateHint) {
|
|
1053
|
+
const hintText = dateHint ? `
|
|
1054
|
+
${dateHint}` : "";
|
|
1055
|
+
return `${SYSTEM_PROMPT2}
|
|
1056
|
+
|
|
1057
|
+
Question to research: ${query}${hintText}
|
|
1058
|
+
Search the conversation history and collect all relevant evidence. Start with a broad keyword search.`;
|
|
1059
|
+
}
|
|
1060
|
+
function buildObservationPrompt(hits) {
|
|
1061
|
+
if (hits.length === 0) {
|
|
1062
|
+
return "Observation: No matching memory found.\nRespond with the next action only.";
|
|
1063
|
+
}
|
|
1064
|
+
const blocks = hits.map((hit, index) => {
|
|
1065
|
+
const lines = [`Result ${index + 1} | score=${hit.similarity.toFixed(4)}`];
|
|
1066
|
+
for (const line of hit.content.split("\n")) {
|
|
1067
|
+
if (line.trim()) lines.push(line);
|
|
1068
|
+
}
|
|
1069
|
+
return lines.join("\n");
|
|
1070
|
+
});
|
|
1071
|
+
return `Observation:
|
|
1072
|
+
${blocks.join("\n\n")}
|
|
1073
|
+
|
|
1074
|
+
Respond with the next action only.`;
|
|
1075
|
+
}
|
|
1076
|
+
function stripCodeFences(text) {
|
|
1077
|
+
return text.replace(/```(?:json)?\s*([\s\S]*?)\s*```/g, "$1");
|
|
1078
|
+
}
|
|
1079
|
+
function extractJsonObject(text) {
|
|
1080
|
+
const start = text.indexOf("{");
|
|
1081
|
+
if (start === -1) {
|
|
1082
|
+
return void 0;
|
|
1083
|
+
}
|
|
1084
|
+
let depth = 0;
|
|
1085
|
+
let inString = false;
|
|
1086
|
+
let escaped = false;
|
|
1087
|
+
for (let i = start; i < text.length; i += 1) {
|
|
1088
|
+
const ch = text[i];
|
|
1089
|
+
if (escaped) {
|
|
1090
|
+
escaped = false;
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
if (inString) {
|
|
1094
|
+
if (ch === "\\") {
|
|
1095
|
+
escaped = true;
|
|
1096
|
+
} else if (ch === '"') {
|
|
1097
|
+
inString = false;
|
|
1098
|
+
}
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (ch === '"') {
|
|
1102
|
+
inString = true;
|
|
1103
|
+
} else if (ch === "{") {
|
|
1104
|
+
depth += 1;
|
|
1105
|
+
} else if (ch === "}") {
|
|
1106
|
+
depth -= 1;
|
|
1107
|
+
if (depth === 0) {
|
|
1108
|
+
return text.slice(start, i + 1);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return void 0;
|
|
1113
|
+
}
|
|
1114
|
+
function parseAction(text) {
|
|
1115
|
+
const cleaned = stripCodeFences(text);
|
|
1116
|
+
const actionMatch = cleaned.match(/Action:\s*([\w_]+)/);
|
|
1117
|
+
if (!actionMatch) return null;
|
|
1118
|
+
const action = actionMatch[1].trim().toLowerCase();
|
|
1119
|
+
if (action === "finish") {
|
|
1120
|
+
return { type: "finish" };
|
|
1121
|
+
}
|
|
1122
|
+
const inputMatch = cleaned.match(/Action Input:\s*/s);
|
|
1123
|
+
if (!inputMatch || inputMatch.index === void 0) return null;
|
|
1124
|
+
const jsonText = extractJsonObject(cleaned.slice(inputMatch.index + inputMatch[0].length));
|
|
1125
|
+
if (!jsonText) return null;
|
|
1126
|
+
let parsedJson = null;
|
|
1127
|
+
for (const candidate of [jsonText, jsonText.replace(/'/g, '"')]) {
|
|
1128
|
+
try {
|
|
1129
|
+
parsedJson = JSON.parse(candidate);
|
|
1130
|
+
break;
|
|
1131
|
+
} catch {
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (!parsedJson) return null;
|
|
1135
|
+
if (action === "search") {
|
|
1136
|
+
const keywords = parsedJson.keywords;
|
|
1137
|
+
if (!Array.isArray(keywords) || !keywords.every((k) => typeof k === "string")) {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
return {
|
|
1141
|
+
type: "search",
|
|
1142
|
+
keywords,
|
|
1143
|
+
dateFrom: typeof parsedJson.date_from === "string" ? parsedJson.date_from : void 0,
|
|
1144
|
+
dateTo: typeof parsedJson.date_to === "string" ? parsedJson.date_to : void 0
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
if (action === "note") {
|
|
1148
|
+
const indices = parsedJson.indices;
|
|
1149
|
+
if (!Array.isArray(indices) || !indices.every((i) => typeof i === "number")) {
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
return { type: "note", indices };
|
|
1153
|
+
}
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
function createIterativeRecallPlanner(deps) {
|
|
1157
|
+
const { complete, options = {} } = deps;
|
|
1158
|
+
const defaults = {
|
|
1159
|
+
maxIterations: 4,
|
|
1160
|
+
searchTopK: 5,
|
|
1161
|
+
disabled: false,
|
|
1162
|
+
fallbackToBaseline: true
|
|
1163
|
+
};
|
|
1164
|
+
let lastDegraded = false;
|
|
1165
|
+
let iterationsWithValidAction = 0;
|
|
1166
|
+
return {
|
|
1167
|
+
lastDegraded: () => lastDegraded,
|
|
1168
|
+
async plan(input) {
|
|
1169
|
+
const opts = {
|
|
1170
|
+
...defaults,
|
|
1171
|
+
...options,
|
|
1172
|
+
...input.options
|
|
1173
|
+
};
|
|
1174
|
+
lastDegraded = false;
|
|
1175
|
+
iterationsWithValidAction = 0;
|
|
1176
|
+
if (opts.disabled || !input.query.trim()) {
|
|
1177
|
+
return { evidence: [], stats: { iterations: 0, searches: 0, notes: 0 } };
|
|
1178
|
+
}
|
|
1179
|
+
const messages = [
|
|
1180
|
+
{ role: "system", content: SYSTEM_PROMPT2 },
|
|
1181
|
+
{
|
|
1182
|
+
role: "user",
|
|
1183
|
+
content: buildInitialPrompt(input.query, formatDateHint(input.dateFrom, input.dateTo))
|
|
1184
|
+
}
|
|
1185
|
+
];
|
|
1186
|
+
const evidence = /* @__PURE__ */ new Map();
|
|
1187
|
+
const excludedIds = /* @__PURE__ */ new Set();
|
|
1188
|
+
let lastHits = [];
|
|
1189
|
+
let searches = 0;
|
|
1190
|
+
let notes = 0;
|
|
1191
|
+
let iterations = 0;
|
|
1192
|
+
let completeThrew = false;
|
|
1193
|
+
for (let i = 0; i < opts.maxIterations; i += 1) {
|
|
1194
|
+
iterations = i + 1;
|
|
1195
|
+
let reply;
|
|
1196
|
+
try {
|
|
1197
|
+
reply = await complete(messages.map((m) => `${m.role}: ${m.content}`).join("\n\n"));
|
|
1198
|
+
} catch {
|
|
1199
|
+
completeThrew = true;
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
messages.push({ role: "assistant", content: reply });
|
|
1203
|
+
const action = parseAction(reply);
|
|
1204
|
+
if (!action) {
|
|
1205
|
+
messages.push({
|
|
1206
|
+
role: "user",
|
|
1207
|
+
content: "Observation: Use exactly one valid action: search, note, or finish. Respond with the next action only."
|
|
1208
|
+
});
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
iterationsWithValidAction += 1;
|
|
1212
|
+
if (action.type === "finish") {
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
if (action.type === "search") {
|
|
1216
|
+
const result = await input.executor.search({
|
|
1217
|
+
keywords: action.keywords,
|
|
1218
|
+
dateFrom: action.dateFrom ?? input.dateFrom,
|
|
1219
|
+
dateTo: action.dateTo ?? input.dateTo
|
|
1220
|
+
});
|
|
1221
|
+
lastHits = result.candidates.filter((c) => !excludedIds.has(c.id)).slice(0, opts.searchTopK);
|
|
1222
|
+
for (const hit of lastHits) {
|
|
1223
|
+
excludedIds.add(hit.id);
|
|
1224
|
+
}
|
|
1225
|
+
searches += 1;
|
|
1226
|
+
messages.push({ role: "user", content: buildObservationPrompt(lastHits) });
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
if (action.type === "note") {
|
|
1230
|
+
let saved = 0;
|
|
1231
|
+
for (const index of action.indices) {
|
|
1232
|
+
if (index >= 1 && index <= lastHits.length) {
|
|
1233
|
+
const hit = lastHits[index - 1];
|
|
1234
|
+
if (!evidence.has(hit.id)) {
|
|
1235
|
+
evidence.set(hit.id, hit);
|
|
1236
|
+
saved += 1;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
notes += saved;
|
|
1241
|
+
messages.push({
|
|
1242
|
+
role: "user",
|
|
1243
|
+
content: `Observation: Saved ${saved} new result(s). Total notes: ${evidence.size}.
|
|
1244
|
+
Respond with the next action only.`
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
let fallbackRan = false;
|
|
1249
|
+
if (evidence.size === 0 && opts.fallbackToBaseline) {
|
|
1250
|
+
fallbackRan = true;
|
|
1251
|
+
if (lastHits.length > 0) {
|
|
1252
|
+
for (const hit of lastHits) {
|
|
1253
|
+
if (!evidence.has(hit.id)) {
|
|
1254
|
+
evidence.set(hit.id, hit);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
} else {
|
|
1258
|
+
const baseline = await input.executor.search({
|
|
1259
|
+
keywords: deriveKeywordsFromQuery(input.query),
|
|
1260
|
+
dateFrom: input.dateFrom,
|
|
1261
|
+
dateTo: input.dateTo
|
|
1262
|
+
});
|
|
1263
|
+
searches += 1;
|
|
1264
|
+
for (const hit of baseline.candidates.slice(0, opts.searchTopK)) {
|
|
1265
|
+
if (!evidence.has(hit.id)) {
|
|
1266
|
+
evidence.set(hit.id, hit);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
lastDegraded = completeThrew || iterationsWithValidAction === 0 || fallbackRan;
|
|
1272
|
+
return {
|
|
1273
|
+
evidence: Array.from(evidence.values()),
|
|
1274
|
+
stats: { iterations, searches, notes }
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function createIdentityIterativePlanner() {
|
|
1280
|
+
return {
|
|
1281
|
+
lastDegraded: () => false,
|
|
1282
|
+
async plan() {
|
|
1283
|
+
return { evidence: [], stats: { iterations: 0, searches: 0, notes: 0 } };
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/index.ts
|
|
665
1289
|
async function createMemoryStore(config = {}) {
|
|
666
1290
|
const dbPath = config.dbPath ?? (config.db && "path" in config.db ? config.db.path : void 0);
|
|
667
1291
|
const raw = createRawMessageStore({ env: config.env, dbPath });
|
|
@@ -676,4 +1300,4 @@ async function createMemoryStore(config = {}) {
|
|
|
676
1300
|
};
|
|
677
1301
|
}
|
|
678
1302
|
|
|
679
|
-
export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createMemoryStore, createRawMessageStore, createUnifiedSearch, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };
|
|
1303
|
+
export { clampUnifiedMemorySearchLimit, clampUnifiedMemorySearchThreshold, clearPostgresFactory, closeRawMessageStore, createIdentityIterativePlanner, createIdentityRewriter, createIterativeRecallPlanner, createMemoryStore, createRawMessageStore, createUnifiedSearch, createUserVoiceRewriter, getRawMessageManager, getRawMessageStorageBackend, hasPostgresFactory, isRawMemorySemanticResult, isRawMessageStorageAvailable, mergeUnifiedMemorySearchResults, normalizeUnifiedMemoryReasoningStrategy, normalizeUnifiedMemorySearchSources, registerPostgresFactory, resolvePostgresFactory, toKnowledgeResult, toMemoryResult };
|