@melandlabs/memory-store 1.2.5 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -16
- package/dist/{config-Dw4aXj2a.d.ts → config-CbE2t4SW.d.ts} +172 -17
- package/dist/http.d.ts +2 -1
- package/dist/http.js +594 -270
- package/dist/index.d.ts +10 -6
- package/dist/index.js +316 -99
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +615 -250
- package/dist/search/derive.d.ts +2 -1
- package/dist/search/distill.d.ts +2 -1
- package/dist/search/unified-search.d.ts +3 -2
- package/dist/search/unified-search.js +316 -99
- package/dist/server/cli-http.js +966 -320
- package/dist/server/cli-mcp.js +987 -300
- package/dist/server/cli-shared.d.ts +20 -7
- package/dist/server/cli-shared.js +370 -48
- package/dist/storage/postgres-raw-message-factory.d.ts +2 -1
- package/dist/storage/raw-message-store.d.ts +20 -2
- package/dist/storage/sqlite-raw-message-store.d.ts +8 -1
- package/dist/storage/sqlite-vector-index.d.ts +2 -1
- package/dist/storage/sqlite-vector-index.js +1 -1
- package/dist/{unified-search-DfE8ek7W.d.ts → unified-search-2YQ6EGuQ.d.ts} +17 -6
- package/package.json +16 -12
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { U as UnifiedSearchDeps } from '../config-
|
|
2
|
+
import { U as UnifiedSearchDeps } from '../config-CbE2t4SW.js';
|
|
3
3
|
import '@melandlabs/memory-consolidation';
|
|
4
4
|
import '@melandlabs/contracts/peer';
|
|
5
5
|
import '@melandlabs/contracts';
|
|
6
6
|
import '@melandlabs/contracts/derived-fact';
|
|
7
7
|
import '@melandlabs/contracts/entity-edge';
|
|
8
|
+
import '@melandlabs/indexeddb';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Shared argv parsing + `unified.*` wiring for the memory-store CLI bins.
|
|
@@ -15,18 +16,30 @@ import '@melandlabs/contracts/entity-edge';
|
|
|
15
16
|
* `/v1/search` route does, so they must speak the same config. This
|
|
16
17
|
* module owns that surface so the two bins stay in lock-step.
|
|
17
18
|
*
|
|
18
|
-
* `--embedding-provider local` and
|
|
19
|
-
* require `@melandlabs/ai-rag
|
|
20
|
-
* `@
|
|
21
|
-
*
|
|
22
|
-
* package is missing.
|
|
19
|
+
* `--embedding-provider local` and the insight/knowledge Chroma backends
|
|
20
|
+
* require `@melandlabs/ai-rag`. Raw-message Chroma, LanceDB, and Milvus use
|
|
21
|
+
* the existing `@melandlabs/rag` stores. Missing optional dependencies fail
|
|
22
|
+
* at startup with an actionable error.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
interface UnifiedArgs {
|
|
26
26
|
embeddingProvider: "local" | "openrouter" | "none";
|
|
27
27
|
embeddingModel?: string;
|
|
28
|
+
embeddingCacheDir?: string;
|
|
29
|
+
rerankerProvider: "local" | "none";
|
|
30
|
+
rerankerModel?: string;
|
|
31
|
+
rerankerCacheDir?: string;
|
|
32
|
+
rerankerBatchSize?: number;
|
|
33
|
+
rerankerMaxTokens?: number;
|
|
28
34
|
chromaUrl?: string;
|
|
29
|
-
memoryBackend: "sqlite-vec" | "chroma" | "none";
|
|
35
|
+
memoryBackend: "sqlite-vec" | "chroma" | "lancedb" | "milvus" | "none";
|
|
36
|
+
lancedbUri?: string;
|
|
37
|
+
lancedbTable?: string;
|
|
38
|
+
milvusAddress?: string;
|
|
39
|
+
milvusToken?: string;
|
|
40
|
+
milvusDatabase?: string;
|
|
41
|
+
milvusCollection?: string;
|
|
42
|
+
milvusDimension?: number;
|
|
30
43
|
insightsBackend: "sqlite-vec" | "chroma" | "none";
|
|
31
44
|
insightsCollection: string;
|
|
32
45
|
knowledgeBackend: "chroma" | "none";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ChromaVectorStore } from '@melandlabs/rag/chroma-vector-store';
|
|
2
3
|
import { mkdirSync } from 'fs';
|
|
3
4
|
import { dirname } from 'path';
|
|
4
5
|
import { getOpenContextPath } from '@melandlabs/env-config';
|
|
@@ -346,6 +347,121 @@ function createUserVoiceRewriter(options) {
|
|
|
346
347
|
};
|
|
347
348
|
}
|
|
348
349
|
|
|
350
|
+
// src/storage/raw-message-child-vector-index.ts
|
|
351
|
+
var RawMessageChildVectorIndex = class {
|
|
352
|
+
backend;
|
|
353
|
+
store;
|
|
354
|
+
catalog;
|
|
355
|
+
constructor(options) {
|
|
356
|
+
this.backend = options.backend;
|
|
357
|
+
this.store = options.store;
|
|
358
|
+
this.catalog = options.catalog;
|
|
359
|
+
}
|
|
360
|
+
async replaceMessages(messages, chunks) {
|
|
361
|
+
const chunksByMessage = /* @__PURE__ */ new Map();
|
|
362
|
+
for (const chunk of chunks) {
|
|
363
|
+
const group = chunksByMessage.get(chunk.messageId) ?? [];
|
|
364
|
+
group.push(chunk);
|
|
365
|
+
chunksByMessage.set(chunk.messageId, group);
|
|
366
|
+
}
|
|
367
|
+
for (const message of messages) {
|
|
368
|
+
await this.store.deleteDocument(message.messageId);
|
|
369
|
+
const vectorChunks = (chunksByMessage.get(message.messageId) ?? []).filter(
|
|
370
|
+
(chunk) => Boolean(chunk.embedding?.length) && !chunk.embedding?.every((value) => value === 0)
|
|
371
|
+
).map((chunk) => ({
|
|
372
|
+
id: chunk.chunkId,
|
|
373
|
+
documentId: message.messageId,
|
|
374
|
+
content: chunk.content,
|
|
375
|
+
embedding: chunk.embedding,
|
|
376
|
+
metadata: {
|
|
377
|
+
userId: message.userId,
|
|
378
|
+
botId: message.botId,
|
|
379
|
+
platform: message.platform,
|
|
380
|
+
timestamp: message.timestamp,
|
|
381
|
+
chunkIndex: chunk.chunkIndex,
|
|
382
|
+
chunkCount: chunk.chunkCount,
|
|
383
|
+
startPosition: chunk.startPosition,
|
|
384
|
+
endPosition: chunk.endPosition,
|
|
385
|
+
contentHash: chunk.contentHash,
|
|
386
|
+
embeddingModel: chunk.embeddingModel
|
|
387
|
+
}
|
|
388
|
+
}));
|
|
389
|
+
if (vectorChunks.length > 0) await this.store.addChunks(vectorChunks);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async search(input) {
|
|
393
|
+
const results = input.hybrid && this.store.hybridSearch ? await this.store.hybridSearch({
|
|
394
|
+
text: input.query ?? "",
|
|
395
|
+
vector: input.queryEmbedding,
|
|
396
|
+
limit: input.limit,
|
|
397
|
+
candidateLimit: input.limit,
|
|
398
|
+
fusion: "rrf",
|
|
399
|
+
filter: { userId: input.userId }
|
|
400
|
+
}) : await this.store.similaritySearch(input.queryEmbedding, input.limit, input.userId);
|
|
401
|
+
return this.hydrate(results, input);
|
|
402
|
+
}
|
|
403
|
+
async getChunkCount() {
|
|
404
|
+
return this.store.getChunkCount();
|
|
405
|
+
}
|
|
406
|
+
async hydrate(results, input) {
|
|
407
|
+
const matchedChunks = await this.catalog.getRawMessageSearchChunks({
|
|
408
|
+
chunkIds: results.map((result) => result.id),
|
|
409
|
+
userId: input.userId
|
|
410
|
+
});
|
|
411
|
+
const matchedById = new Map(matchedChunks.map((chunk) => [chunk.chunkId, chunk]));
|
|
412
|
+
const messageIds = [...new Set(matchedChunks.map((chunk) => chunk.messageId))];
|
|
413
|
+
const [allChunks, parents] = await Promise.all([
|
|
414
|
+
this.catalog.getRawMessageSearchChunks({ messageIds, userId: input.userId }),
|
|
415
|
+
Promise.all(messageIds.map((messageId) => this.catalog.getMessageById(messageId)))
|
|
416
|
+
]);
|
|
417
|
+
const parentById = new Map(
|
|
418
|
+
parents.filter((parent) => Boolean(parent)).map((parent) => [parent.messageId, parent])
|
|
419
|
+
);
|
|
420
|
+
const chunksByMessage = /* @__PURE__ */ new Map();
|
|
421
|
+
for (const chunk of allChunks) {
|
|
422
|
+
const group = chunksByMessage.get(chunk.messageId) ?? [];
|
|
423
|
+
group.push(chunk);
|
|
424
|
+
chunksByMessage.set(chunk.messageId, group);
|
|
425
|
+
}
|
|
426
|
+
const strongest = /* @__PURE__ */ new Map();
|
|
427
|
+
for (const result of results) {
|
|
428
|
+
const chunk = matchedById.get(result.id);
|
|
429
|
+
if (!chunk) continue;
|
|
430
|
+
const parent = parentById.get(chunk.messageId);
|
|
431
|
+
if (!parent || input.botId && parent.botId !== input.botId) continue;
|
|
432
|
+
if (input.threshold !== void 0 && result.score < input.threshold) continue;
|
|
433
|
+
const siblings = (chunksByMessage.get(parent.messageId) ?? []).sort(
|
|
434
|
+
(a, b) => a.chunkIndex - b.chunkIndex
|
|
435
|
+
);
|
|
436
|
+
const content = chunk.chunkCount <= 1 ? parent.content : parent.content.slice(
|
|
437
|
+
siblings[Math.max(0, chunk.chunkIndex - 1)]?.startPosition ?? chunk.startPosition,
|
|
438
|
+
siblings[Math.min(siblings.length - 1, chunk.chunkIndex + 1)]?.endPosition ?? chunk.endPosition
|
|
439
|
+
);
|
|
440
|
+
const hit = {
|
|
441
|
+
id: parent.messageId,
|
|
442
|
+
content,
|
|
443
|
+
similarity: result.score,
|
|
444
|
+
metadata: {
|
|
445
|
+
...parent.metadata ?? {},
|
|
446
|
+
userId: parent.userId,
|
|
447
|
+
botId: parent.botId,
|
|
448
|
+
platform: parent.platform,
|
|
449
|
+
timestamp: parent.timestamp,
|
|
450
|
+
sourceMessageId: parent.messageId,
|
|
451
|
+
sourceChunkId: chunk.chunkId,
|
|
452
|
+
sourceChunkIndex: chunk.chunkIndex,
|
|
453
|
+
sourceChunkCount: chunk.chunkCount,
|
|
454
|
+
backend: this.backend,
|
|
455
|
+
scoring: input.hybrid ? "native-hybrid" : "dense"
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
const existing = strongest.get(parent.messageId);
|
|
459
|
+
if (!existing || hit.similarity > existing.similarity) strongest.set(parent.messageId, hit);
|
|
460
|
+
}
|
|
461
|
+
return [...strongest.values()].sort((a, b) => b.similarity - a.similarity).slice(0, input.limit);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
349
465
|
// src/storage/postgres-raw-message-factory.ts
|
|
350
466
|
var factory = null;
|
|
351
467
|
async function resolvePostgresFactory(env) {
|
|
@@ -449,7 +565,7 @@ function isInsightSQLiteVecEnabled(_env) {
|
|
|
449
565
|
}
|
|
450
566
|
async function getInsightSQLiteVecStore(_env) {
|
|
451
567
|
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
452
|
-
const { getSQLiteVecStore } = await import('@melandlabs/rag/sqlite-vec-store');
|
|
568
|
+
const { getSQLiteVecStore } = await import('@melandlabs/ai-rag/sqlite-vec-store');
|
|
453
569
|
return await getSQLiteVecStore(dbPath, void 0, {
|
|
454
570
|
collectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || "opencontext_insights"
|
|
455
571
|
});
|
|
@@ -473,17 +589,19 @@ async function searchInsightsWithSQLiteVec(input, env) {
|
|
|
473
589
|
// src/server/cli-shared.ts
|
|
474
590
|
async function loadAiRag() {
|
|
475
591
|
try {
|
|
476
|
-
const [local, chroma] = await Promise.all([
|
|
592
|
+
const [local, reranker, chroma] = await Promise.all([
|
|
477
593
|
import('@melandlabs/ai-rag/local-transformers-embedding-provider'),
|
|
594
|
+
import('@melandlabs/ai-rag/local-transformers-reranker'),
|
|
478
595
|
import('@melandlabs/ai-rag/chroma-store')
|
|
479
596
|
]);
|
|
480
597
|
return {
|
|
481
598
|
LocalTransformersEmbeddingProvider: local.LocalTransformersEmbeddingProvider,
|
|
599
|
+
LocalTransformersReranker: reranker.LocalTransformersReranker,
|
|
482
600
|
ChromaVectorStore: chroma.ChromaVectorStore
|
|
483
601
|
};
|
|
484
602
|
} catch (error) {
|
|
485
603
|
throw new Error(
|
|
486
|
-
|
|
604
|
+
`Local embedding/reranking and insight/knowledge Chroma require @melandlabs/ai-rag to be installed. Run \`pnpm add @melandlabs/ai-rag\` and try again. (${error.message})`
|
|
487
605
|
);
|
|
488
606
|
}
|
|
489
607
|
}
|
|
@@ -492,8 +610,21 @@ function parseUnifiedArgs(argv) {
|
|
|
492
610
|
const args = {
|
|
493
611
|
embeddingProvider: env.EMBEDDING_PROVIDER ?? "none",
|
|
494
612
|
embeddingModel: env.EMBEDDING_MODEL,
|
|
613
|
+
embeddingCacheDir: env.LOCAL_EMBEDDING_CACHE_DIR,
|
|
614
|
+
rerankerProvider: env.RERANKER_PROVIDER ?? "none",
|
|
615
|
+
rerankerModel: env.LOCAL_RERANKER_MODEL,
|
|
616
|
+
rerankerCacheDir: env.LOCAL_RERANKER_CACHE_DIR,
|
|
617
|
+
rerankerBatchSize: env.LOCAL_RERANKER_BATCH_SIZE ? Number.parseInt(env.LOCAL_RERANKER_BATCH_SIZE, 10) : void 0,
|
|
618
|
+
rerankerMaxTokens: env.LOCAL_RERANKER_MAX_TOKENS ? Number.parseInt(env.LOCAL_RERANKER_MAX_TOKENS, 10) : void 0,
|
|
495
619
|
chromaUrl: env.CHROMA_URL,
|
|
496
620
|
memoryBackend: env.MEMORY_BACKEND ?? "none",
|
|
621
|
+
lancedbUri: env.LANCEDB_URI,
|
|
622
|
+
lancedbTable: env.LANCEDB_TABLE,
|
|
623
|
+
milvusAddress: env.MILVUS_ADDRESS,
|
|
624
|
+
milvusToken: env.MILVUS_TOKEN,
|
|
625
|
+
milvusDatabase: env.MILVUS_DATABASE,
|
|
626
|
+
milvusCollection: env.MILVUS_COLLECTION,
|
|
627
|
+
milvusDimension: env.MILVUS_DIMENSION ? Number.parseInt(env.MILVUS_DIMENSION, 10) : void 0,
|
|
497
628
|
insightsBackend: env.INSIGHTS_BACKEND ?? "none",
|
|
498
629
|
insightsCollection: env.INSIGHTS_COLLECTION ?? "opencontext_insights",
|
|
499
630
|
knowledgeBackend: env.KNOWLEDGE_BACKEND ?? "none",
|
|
@@ -518,12 +649,51 @@ function parseUnifiedArgs(argv) {
|
|
|
518
649
|
case "--embedding-model":
|
|
519
650
|
args.embeddingModel = takeValue();
|
|
520
651
|
break;
|
|
652
|
+
case "--embedding-cache-dir":
|
|
653
|
+
args.embeddingCacheDir = takeValue();
|
|
654
|
+
break;
|
|
655
|
+
case "--reranker-provider":
|
|
656
|
+
args.rerankerProvider = takeValue();
|
|
657
|
+
break;
|
|
658
|
+
case "--reranker-model":
|
|
659
|
+
args.rerankerModel = takeValue();
|
|
660
|
+
break;
|
|
661
|
+
case "--reranker-cache-dir":
|
|
662
|
+
args.rerankerCacheDir = takeValue();
|
|
663
|
+
break;
|
|
664
|
+
case "--reranker-batch-size":
|
|
665
|
+
args.rerankerBatchSize = Number.parseInt(takeValue(), 10);
|
|
666
|
+
break;
|
|
667
|
+
case "--reranker-max-tokens":
|
|
668
|
+
args.rerankerMaxTokens = Number.parseInt(takeValue(), 10);
|
|
669
|
+
break;
|
|
521
670
|
case "--chroma-url":
|
|
522
671
|
args.chromaUrl = takeValue();
|
|
523
672
|
break;
|
|
524
673
|
case "--memory-backend":
|
|
525
674
|
args.memoryBackend = takeValue();
|
|
526
675
|
break;
|
|
676
|
+
case "--lancedb-uri":
|
|
677
|
+
args.lancedbUri = takeValue();
|
|
678
|
+
break;
|
|
679
|
+
case "--lancedb-table":
|
|
680
|
+
args.lancedbTable = takeValue();
|
|
681
|
+
break;
|
|
682
|
+
case "--milvus-address":
|
|
683
|
+
args.milvusAddress = takeValue();
|
|
684
|
+
break;
|
|
685
|
+
case "--milvus-token":
|
|
686
|
+
args.milvusToken = takeValue();
|
|
687
|
+
break;
|
|
688
|
+
case "--milvus-database":
|
|
689
|
+
args.milvusDatabase = takeValue();
|
|
690
|
+
break;
|
|
691
|
+
case "--milvus-collection":
|
|
692
|
+
args.milvusCollection = takeValue();
|
|
693
|
+
break;
|
|
694
|
+
case "--milvus-dimension":
|
|
695
|
+
args.milvusDimension = Number.parseInt(takeValue(), 10);
|
|
696
|
+
break;
|
|
527
697
|
case "--insights-backend":
|
|
528
698
|
args.insightsBackend = takeValue();
|
|
529
699
|
break;
|
|
@@ -553,31 +723,60 @@ function parseUnifiedArgs(argv) {
|
|
|
553
723
|
break;
|
|
554
724
|
}
|
|
555
725
|
}
|
|
556
|
-
|
|
557
|
-
["--embedding-provider", args.embeddingProvider],
|
|
558
|
-
["--memory-backend", args.memoryBackend],
|
|
559
|
-
["--insights-backend", args.insightsBackend],
|
|
560
|
-
["--knowledge-backend", args.knowledgeBackend]
|
|
561
|
-
]) {
|
|
562
|
-
const allowed = ["local", "openrouter", "none", "chroma", "sqlite-vec"];
|
|
726
|
+
const validate = (name, value, allowed) => {
|
|
563
727
|
if (!allowed.includes(value)) {
|
|
564
728
|
throw new Error(`[memory-store/cli] ${name} must be one of: ${allowed.join(", ")} (got "${value}")`);
|
|
565
729
|
}
|
|
730
|
+
};
|
|
731
|
+
validate("--embedding-provider", args.embeddingProvider, ["local", "openrouter", "none"]);
|
|
732
|
+
validate("--reranker-provider", args.rerankerProvider, ["local", "none"]);
|
|
733
|
+
validate("--memory-backend", args.memoryBackend, ["sqlite-vec", "chroma", "lancedb", "milvus", "none"]);
|
|
734
|
+
validate("--insights-backend", args.insightsBackend, ["sqlite-vec", "chroma", "none"]);
|
|
735
|
+
validate("--knowledge-backend", args.knowledgeBackend, ["chroma", "none"]);
|
|
736
|
+
if (args.milvusDimension !== void 0 && (!Number.isInteger(args.milvusDimension) || args.milvusDimension <= 0)) {
|
|
737
|
+
throw new Error("[memory-store/cli] --milvus-dimension must be a positive integer");
|
|
738
|
+
}
|
|
739
|
+
for (const [name, value] of [
|
|
740
|
+
["--reranker-batch-size", args.rerankerBatchSize],
|
|
741
|
+
["--reranker-max-tokens", args.rerankerMaxTokens]
|
|
742
|
+
]) {
|
|
743
|
+
if (value !== void 0 && (!Number.isInteger(value) || value <= 0)) {
|
|
744
|
+
throw new Error(`[memory-store/cli] ${name} must be a positive integer`);
|
|
745
|
+
}
|
|
566
746
|
}
|
|
567
747
|
return args;
|
|
568
748
|
}
|
|
569
749
|
async function buildUnified(args) {
|
|
570
750
|
const unified = {};
|
|
571
751
|
const log = (msg) => console.warn(`[memory-store/cli] ${msg}`);
|
|
572
|
-
const wantsAiRag = args.embeddingProvider === "local" || args.
|
|
752
|
+
const wantsAiRag = args.embeddingProvider === "local" || args.rerankerProvider === "local" || args.insightsBackend === "chroma" || args.knowledgeBackend === "chroma";
|
|
573
753
|
const aiRag = wantsAiRag ? await loadAiRag() : null;
|
|
574
754
|
if (args.embeddingProvider === "local") {
|
|
575
755
|
try {
|
|
576
756
|
if (!aiRag) {
|
|
577
757
|
throw new Error("ai-rag modules not loaded");
|
|
578
758
|
}
|
|
579
|
-
const provider = new aiRag.LocalTransformersEmbeddingProvider({
|
|
580
|
-
|
|
759
|
+
const provider = new aiRag.LocalTransformersEmbeddingProvider({
|
|
760
|
+
modelName: args.embeddingModel,
|
|
761
|
+
cacheDir: args.embeddingCacheDir
|
|
762
|
+
});
|
|
763
|
+
unified.embeddingInfo = {
|
|
764
|
+
provider: "local",
|
|
765
|
+
model: args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2",
|
|
766
|
+
maxTokens: 512
|
|
767
|
+
};
|
|
768
|
+
unified.embedQuery = async ({ query }) => {
|
|
769
|
+
const embedding = await provider.embedQuery(query);
|
|
770
|
+
if (unified.embeddingInfo) unified.embeddingInfo.dimensions = embedding.length;
|
|
771
|
+
return embedding;
|
|
772
|
+
};
|
|
773
|
+
unified.embedDocuments = async ({ texts }) => {
|
|
774
|
+
const embeddings = await provider.embedDocuments(texts);
|
|
775
|
+
if (unified.embeddingInfo && embeddings[0]) {
|
|
776
|
+
unified.embeddingInfo.dimensions = embeddings[0].length;
|
|
777
|
+
}
|
|
778
|
+
return embeddings;
|
|
779
|
+
};
|
|
581
780
|
log(
|
|
582
781
|
`embedQuery wired via LocalTransformersEmbeddingProvider (model=${args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2"})`
|
|
583
782
|
);
|
|
@@ -593,6 +792,7 @@ async function buildUnified(args) {
|
|
|
593
792
|
}
|
|
594
793
|
const model = args.embeddingModel ?? "text-embedding-3-small";
|
|
595
794
|
const baseURL = "https://openrouter.ai/api/v1";
|
|
795
|
+
unified.embeddingInfo = { provider: "openrouter", model };
|
|
596
796
|
unified.embedQuery = async ({ query }) => {
|
|
597
797
|
const res = await fetch(`${baseURL}/embeddings`, {
|
|
598
798
|
method: "POST",
|
|
@@ -610,49 +810,150 @@ async function buildUnified(args) {
|
|
|
610
810
|
const body = await res.json();
|
|
611
811
|
const embedding = body.data?.[0]?.embedding;
|
|
612
812
|
if (!embedding) throw new Error("openrouter embeddings response missing data[0].embedding");
|
|
813
|
+
if (unified.embeddingInfo) unified.embeddingInfo.dimensions = embedding.length;
|
|
613
814
|
return embedding;
|
|
614
815
|
};
|
|
615
816
|
log(`embedQuery wired via openrouter (model=${model})`);
|
|
616
817
|
}
|
|
617
|
-
if (args.
|
|
618
|
-
|
|
619
|
-
const
|
|
620
|
-
|
|
818
|
+
if (args.rerankerProvider === "local") {
|
|
819
|
+
if (!aiRag) throw new Error("Local reranker modules were not loaded");
|
|
820
|
+
const reranker = new aiRag.LocalTransformersReranker({
|
|
821
|
+
modelName: args.rerankerModel,
|
|
822
|
+
cacheDir: args.rerankerCacheDir,
|
|
823
|
+
batchSize: args.rerankerBatchSize,
|
|
824
|
+
maxTokens: args.rerankerMaxTokens
|
|
825
|
+
});
|
|
826
|
+
await reranker.warmup();
|
|
827
|
+
unified.reranker = reranker;
|
|
828
|
+
unified.rerankerInfo = {
|
|
829
|
+
provider: "local",
|
|
830
|
+
model: reranker.getModelName(),
|
|
831
|
+
maxTokens: reranker.getMaxTokens(),
|
|
832
|
+
ready: true
|
|
833
|
+
};
|
|
834
|
+
log(`reranker wired via local Transformers.js (model=${reranker.getModelName()})`);
|
|
835
|
+
} else {
|
|
836
|
+
unified.rerankerInfo = { provider: "none", ready: false };
|
|
837
|
+
}
|
|
838
|
+
if (args.memoryBackend !== "none") {
|
|
839
|
+
if (args.memoryBackend === "chroma" && !args.chromaUrl) {
|
|
840
|
+
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
841
|
+
}
|
|
842
|
+
if (args.memoryBackend === "lancedb" && !args.lancedbUri) {
|
|
843
|
+
throw new Error("--memory-backend=lancedb requires --lancedb-uri <uri> (or LANCEDB_URI env)");
|
|
844
|
+
}
|
|
845
|
+
if (args.memoryBackend === "milvus" && !args.milvusAddress) {
|
|
846
|
+
throw new Error("--memory-backend=milvus requires --milvus-address <address> (or MILVUS_ADDRESS env)");
|
|
847
|
+
}
|
|
848
|
+
if (args.memoryBackend === "lancedb") {
|
|
849
|
+
try {
|
|
850
|
+
await import('@lancedb/lancedb');
|
|
851
|
+
} catch (error) {
|
|
852
|
+
throw new Error(
|
|
853
|
+
`LanceDB requires the optional dependency @lancedb/lancedb (${error.message})`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (args.memoryBackend === "milvus") {
|
|
858
|
+
try {
|
|
859
|
+
await import('@zilliz/milvus2-sdk-node');
|
|
860
|
+
} catch (error) {
|
|
861
|
+
throw new Error(
|
|
862
|
+
`Milvus requires the optional dependency @zilliz/milvus2-sdk-node (${error.message})`
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const rawStore = createRawMessageStore({ env: void 0 });
|
|
867
|
+
const manager2 = await rawStore.getManager();
|
|
868
|
+
if (typeof manager2.getRawMessageSearchChunks !== "function" || typeof manager2.getRawMessageSearchIndexStats !== "function") {
|
|
621
869
|
throw new Error(
|
|
622
|
-
|
|
870
|
+
`${args.memoryBackend} child retrieval requires the SQLite raw-message catalog; the Postgres manager remains on the legacy parent fallback in this release`
|
|
623
871
|
);
|
|
624
872
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
873
|
+
if (typeof manager2.lexicalSearchMessages === "function") {
|
|
874
|
+
unified.searchRawMessagesLexical = async (input) => {
|
|
875
|
+
const rows = await manager2.lexicalSearchMessages(input);
|
|
876
|
+
return rows.map((row) => ({ ...row, metadata: row.metadata ?? {} }));
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
let externalStore;
|
|
880
|
+
if (args.memoryBackend === "chroma") {
|
|
881
|
+
externalStore = new ChromaVectorStore({
|
|
882
|
+
url: args.chromaUrl,
|
|
883
|
+
collectionName: "opencontext_raw_message_chunks"
|
|
884
|
+
});
|
|
885
|
+
} else if (args.memoryBackend === "lancedb") {
|
|
886
|
+
const { LanceDBStore } = await import('@melandlabs/rag/lancedb-store');
|
|
887
|
+
externalStore = new LanceDBStore({
|
|
888
|
+
uri: args.lancedbUri,
|
|
889
|
+
tableName: args.lancedbTable ?? "opencontext_raw_message_chunks",
|
|
890
|
+
defaultFusion: "rrf",
|
|
891
|
+
candidateMultiplier: 4
|
|
892
|
+
});
|
|
893
|
+
} else if (args.memoryBackend === "milvus") {
|
|
894
|
+
const { MilvusStore } = await import('@melandlabs/rag/milvus-store');
|
|
895
|
+
externalStore = new MilvusStore({
|
|
896
|
+
address: args.milvusAddress,
|
|
897
|
+
token: args.milvusToken,
|
|
898
|
+
database: args.milvusDatabase,
|
|
899
|
+
collectionName: args.milvusCollection ?? "opencontext_raw_message_chunks",
|
|
900
|
+
dimension: args.milvusDimension,
|
|
901
|
+
defaultFusion: "rrf",
|
|
902
|
+
candidateMultiplier: 4
|
|
632
903
|
});
|
|
633
|
-
return rows.map((r) => ({
|
|
634
|
-
id: r.id,
|
|
635
|
-
content: r.content,
|
|
636
|
-
similarity: r.similarity,
|
|
637
|
-
metadata: r.metadata ?? {}
|
|
638
|
-
}));
|
|
639
|
-
};
|
|
640
|
-
log("memory backend wired via sqlite-vec (uses the manager's searchMessagesSemantically)");
|
|
641
|
-
} else if (args.memoryBackend === "chroma") {
|
|
642
|
-
if (!args.chromaUrl)
|
|
643
|
-
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
644
|
-
if (!aiRag) {
|
|
645
|
-
throw new Error("ai-rag modules not loaded");
|
|
646
904
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
905
|
+
if (args.memoryBackend === "sqlite-vec") {
|
|
906
|
+
if (typeof manager2.searchMessagesSemantically !== "function") {
|
|
907
|
+
throw new Error("sqlite-vec child search is unavailable in the active raw-message manager");
|
|
908
|
+
}
|
|
909
|
+
unified.searchRawMessagesAnn = async ({ userId, queryEmbedding, limit, threshold, botId }) => {
|
|
910
|
+
const rows = await manager2.searchMessagesSemantically({
|
|
911
|
+
userId,
|
|
912
|
+
queryEmbedding,
|
|
913
|
+
limit,
|
|
914
|
+
threshold,
|
|
915
|
+
botId
|
|
916
|
+
});
|
|
917
|
+
return rows.map((row) => ({ ...row, metadata: row.metadata ?? {} }));
|
|
918
|
+
};
|
|
919
|
+
} else if (externalStore) {
|
|
920
|
+
const childIndex = new RawMessageChildVectorIndex({
|
|
921
|
+
backend: args.memoryBackend,
|
|
922
|
+
store: externalStore,
|
|
923
|
+
catalog: {
|
|
924
|
+
getMessageById: (messageId) => manager2.getMessageById(messageId),
|
|
925
|
+
// biome-ignore lint/style/noNonNullAssertion: guarded above
|
|
926
|
+
getRawMessageSearchChunks: (input) => manager2.getRawMessageSearchChunks(input)
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
unified.rawMessageChildIndex = childIndex;
|
|
930
|
+
unified.searchRawMessagesAnn = (input) => childIndex.search(input);
|
|
931
|
+
if (args.memoryBackend !== "chroma") {
|
|
932
|
+
unified.searchRawMessagesHybrid = (input) => childIndex.search({ ...input, hybrid: true });
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
unified.getRawMessageRetrievalStatus = async () => {
|
|
936
|
+
const stats = await manager2.getRawMessageSearchIndexStats();
|
|
937
|
+
const embeddingDimensions = unified.embeddingInfo?.dimensions;
|
|
938
|
+
const dimensionMismatch = embeddingDimensions !== void 0 && stats.embeddingDimensions.length > 0 && !stats.embeddingDimensions.includes(embeddingDimensions);
|
|
939
|
+
const semanticReady = Boolean(unified.embedQuery) && (args.memoryBackend === "sqlite-vec" ? stats.semanticReady : stats.embeddedChunkCount > 0) && !dimensionMismatch;
|
|
940
|
+
return {
|
|
941
|
+
backend: args.memoryBackend,
|
|
942
|
+
embeddingProvider: unified.embeddingInfo?.provider,
|
|
943
|
+
embeddingModel: unified.embeddingInfo?.model,
|
|
944
|
+
embeddingDimensions,
|
|
945
|
+
childCount: stats.chunkCount,
|
|
946
|
+
embeddedChildCount: stats.embeddedChunkCount,
|
|
947
|
+
indexedDimensions: stats.embeddingDimensions,
|
|
948
|
+
semanticReady,
|
|
949
|
+
lexicalReady: stats.lexicalReady,
|
|
950
|
+
semanticDegradedReason: dimensionMismatch ? "semantic_dimension_mismatch" : !unified.embedQuery ? "semantic_unavailable" : stats.embeddedChunkCount === 0 ? "semantic_child_vectors_empty" : void 0,
|
|
951
|
+
rerankerProvider: unified.rerankerInfo?.provider,
|
|
952
|
+
rerankerModel: unified.rerankerInfo?.model,
|
|
953
|
+
rerankerReady: unified.rerankerInfo?.ready ?? false
|
|
954
|
+
};
|
|
654
955
|
};
|
|
655
|
-
log(
|
|
956
|
+
log(`memory backend wired via ${args.memoryBackend} with SQLite child catalog and lexical search`);
|
|
656
957
|
}
|
|
657
958
|
if (args.insightsBackend === "sqlite-vec" && isInsightSQLiteVecEnabled()) {
|
|
658
959
|
if (!unified.embedQuery) {
|
|
@@ -779,20 +1080,41 @@ function printUnifiedHelp() {
|
|
|
779
1080
|
--embedding-model <name> Model name
|
|
780
1081
|
(env: EMBEDDING_MODEL; local \u2192 Xenova/all-MiniLM-L6-v2,
|
|
781
1082
|
openrouter \u2192 text-embedding-3-small)
|
|
1083
|
+
--embedding-cache-dir <path> Local embedding cache (env: LOCAL_EMBEDDING_CACHE_DIR)
|
|
782
1084
|
Note: "local" dynamically imports @melandlabs/ai-rag (a peer install).
|
|
783
1085
|
|
|
1086
|
+
Reranking (after RRF/source fusion, before final Top-K):
|
|
1087
|
+
--reranker-provider <name> local | none
|
|
1088
|
+
(env: RERANKER_PROVIDER, default: none)
|
|
1089
|
+
--reranker-model <name> Local sequence-classification model
|
|
1090
|
+
(env: LOCAL_RERANKER_MODEL,
|
|
1091
|
+
default: Xenova/ms-marco-MiniLM-L-6-v2)
|
|
1092
|
+
--reranker-cache-dir <path> Persistent model cache (env: LOCAL_RERANKER_CACHE_DIR)
|
|
1093
|
+
--reranker-batch-size <int> Pair scoring batch size (env: LOCAL_RERANKER_BATCH_SIZE,
|
|
1094
|
+
default: 8)
|
|
1095
|
+
--reranker-max-tokens <int> Query/document pair token limit
|
|
1096
|
+
(env: LOCAL_RERANKER_MAX_TOKENS, default: 512)
|
|
1097
|
+
|
|
784
1098
|
Cross-source search (wires unified.searchKnowledge / searchInsights / searchRawMessagesAnn):
|
|
785
1099
|
--chroma-url <url> Chroma server URL
|
|
786
1100
|
(env: CHROMA_URL; required when any *-backend=chroma)
|
|
787
|
-
--memory-backend <name> sqlite-vec | chroma | none
|
|
1101
|
+
--memory-backend <name> sqlite-vec | chroma | lancedb | milvus | none
|
|
788
1102
|
(env: MEMORY_BACKEND, default: none)
|
|
1103
|
+
--lancedb-uri <uri> LanceDB directory or URI (env: LANCEDB_URI)
|
|
1104
|
+
--lancedb-table <name> Optional table name (env: LANCEDB_TABLE)
|
|
1105
|
+
--milvus-address <address> Milvus endpoint (env: MILVUS_ADDRESS)
|
|
1106
|
+
--milvus-token <token> Optional Milvus token (env: MILVUS_TOKEN)
|
|
1107
|
+
--milvus-database <name> Optional database (env: MILVUS_DATABASE)
|
|
1108
|
+
--milvus-collection <name> Optional collection (env: MILVUS_COLLECTION)
|
|
1109
|
+
--milvus-dimension <int> Optional vector dimension (env: MILVUS_DIMENSION)
|
|
789
1110
|
--insights-backend <name> sqlite-vec | chroma | none
|
|
790
1111
|
(env: INSIGHTS_BACKEND, default: none)
|
|
791
1112
|
--insights-collection <name> Chroma collection (default: opencontext_insights)
|
|
792
1113
|
--knowledge-backend <name> chroma | none
|
|
793
1114
|
(env: KNOWLEDGE_BACKEND, default: none)
|
|
794
1115
|
--knowledge-collection <name> Chroma collection (default: opencontext_knowledge)
|
|
795
|
-
|
|
1116
|
+
Notes: memory Chroma uses @melandlabs/rag; insight/knowledge Chroma use
|
|
1117
|
+
@melandlabs/ai-rag. LanceDB and Milvus require their optional peer dependencies.
|
|
796
1118
|
|
|
797
1119
|
Reasoning (wires unified.reasoning.{queryRewriter, iterativePlanner}):
|
|
798
1120
|
--reasoning Enable the LLM reasoning layer so /v1/search and
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { a as MemoryStoreEnv } from '../config-
|
|
1
|
+
import { a as MemoryStoreEnv } from '../config-CbE2t4SW.js';
|
|
2
2
|
import '@melandlabs/memory-consolidation';
|
|
3
3
|
import '@melandlabs/contracts/peer';
|
|
4
4
|
import '@melandlabs/contracts';
|
|
5
5
|
import '@melandlabs/contracts/derived-fact';
|
|
6
6
|
import '@melandlabs/contracts/entity-edge';
|
|
7
|
+
import '@melandlabs/indexeddb';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Lazy registration slot for the host's Postgres-backed raw message
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RawMessageStorageManager } from '@melandlabs/indexeddb';
|
|
2
|
-
import { a as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-
|
|
1
|
+
import { RawMessageStorageManager, RawMessageSearchChunk, RawMessageSearchIndexStats } from '@melandlabs/indexeddb';
|
|
2
|
+
import { a as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-CbE2t4SW.js';
|
|
3
3
|
import '@melandlabs/memory-consolidation';
|
|
4
4
|
import '@melandlabs/contracts/peer';
|
|
5
5
|
import '@melandlabs/contracts';
|
|
@@ -24,6 +24,12 @@ import '@melandlabs/contracts/entity-edge';
|
|
|
24
24
|
|
|
25
25
|
type RawMessageStorageBackend = "sqlite" | "postgres";
|
|
26
26
|
type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {
|
|
27
|
+
getRawMessageSearchChunks?: (input: {
|
|
28
|
+
chunkIds?: string[];
|
|
29
|
+
messageIds?: string[];
|
|
30
|
+
userId?: string;
|
|
31
|
+
}) => Promise<RawMessageSearchChunk[]>;
|
|
32
|
+
getRawMessageSearchIndexStats?: () => Promise<RawMessageSearchIndexStats>;
|
|
27
33
|
searchMessagesSemantically?: (input: {
|
|
28
34
|
userId: string;
|
|
29
35
|
queryEmbedding: number[];
|
|
@@ -32,6 +38,12 @@ type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {
|
|
|
32
38
|
scanLimit?: number;
|
|
33
39
|
threshold?: number;
|
|
34
40
|
includeArchived?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Forward `includeDeprecated` from the unified search input so
|
|
43
|
+
* supersession-aware callers can opt into deprecated rows for
|
|
44
|
+
* audits. Default `false` preserves the current-truth behaviour.
|
|
45
|
+
*/
|
|
46
|
+
includeDeprecated?: boolean;
|
|
35
47
|
platform?: string;
|
|
36
48
|
botId?: string;
|
|
37
49
|
channel?: string;
|
|
@@ -44,6 +56,12 @@ type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {
|
|
|
44
56
|
keywords: string[];
|
|
45
57
|
limit?: number;
|
|
46
58
|
includeArchived?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Forward `includeDeprecated` from the unified search input so
|
|
61
|
+
* supersession-aware callers can opt into deprecated rows for
|
|
62
|
+
* audits. Default `false` preserves the current-truth behaviour.
|
|
63
|
+
*/
|
|
64
|
+
includeDeprecated?: boolean;
|
|
47
65
|
platform?: string;
|
|
48
66
|
botId?: string;
|
|
49
67
|
}) => Promise<unknown[]>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
|
|
2
|
-
import { a as MemoryStoreEnv } from '../config-
|
|
2
|
+
import { a as MemoryStoreEnv } from '../config-CbE2t4SW.js';
|
|
3
3
|
import '@melandlabs/memory-consolidation';
|
|
4
4
|
import '@melandlabs/contracts/peer';
|
|
5
5
|
import '@melandlabs/contracts';
|
|
6
6
|
import '@melandlabs/contracts/derived-fact';
|
|
7
7
|
import '@melandlabs/contracts/entity-edge';
|
|
8
|
+
import '@melandlabs/indexeddb';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* SQLite-backed raw message store.
|
|
@@ -48,6 +49,12 @@ declare function lexicalSearchRawMessages(input: {
|
|
|
48
49
|
keywords: string[];
|
|
49
50
|
limit?: number;
|
|
50
51
|
includeArchived?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Forward `includeDeprecated` from `UnifiedMemorySearchInput` so
|
|
54
|
+
* supersession-aware callers can opt into deprecated rows for
|
|
55
|
+
* audits. Default `false` preserves the current-truth behaviour.
|
|
56
|
+
*/
|
|
57
|
+
includeDeprecated?: boolean;
|
|
51
58
|
platform?: string;
|
|
52
59
|
botId?: string;
|
|
53
60
|
factTypes?: Array<"world" | "experience" | "mental_model">;
|