@melandlabs/memory-store 1.2.4 → 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-DfvVZ--0.d.ts → config-CbE2t4SW.d.ts} +173 -18
- package/dist/http.d.ts +2 -1
- package/dist/http.js +636 -277
- package/dist/index.d.ts +10 -6
- package/dist/index.js +357 -105
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +659 -259
- 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 +357 -105
- package/dist/server/cli-http.js +1008 -327
- package/dist/server/cli-mcp.js +1031 -309
- 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-A8Q3JHwU.d.ts → unified-search-2YQ6EGuQ.d.ts} +17 -6
- package/package.json +15 -11
package/dist/server/cli-http.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { isFactType, vsaNormalizeVector, vsaAssertSameDim } from '@melandlabs/contracts';
|
|
3
|
-
import
|
|
4
|
-
import { ChromaVectorStore } from '@melandlabs/rag';
|
|
3
|
+
import '@melandlabs/ai/memory';
|
|
4
|
+
import { ChromaVectorStore as ChromaVectorStore$1 } from '@melandlabs/rag';
|
|
5
5
|
import { mkdirSync } from 'fs';
|
|
6
6
|
import { dirname } from 'path';
|
|
7
7
|
import { getOpenContextPath } from '@melandlabs/env-config';
|
|
@@ -12,6 +12,8 @@ import { applyReflectedConsolidationPlan } from '@melandlabs/memory-consolidatio
|
|
|
12
12
|
import { isDerivedKind } from '@melandlabs/contracts/derived-fact';
|
|
13
13
|
import { isEntityKind } from '@melandlabs/contracts/entity-edge';
|
|
14
14
|
import { superpose, bind, unbind, cleanup, cosineSimilarity } from '@melandlabs/vsa';
|
|
15
|
+
import { createHash } from 'crypto';
|
|
16
|
+
import { ChromaVectorStore } from '@melandlabs/rag/chroma-vector-store';
|
|
15
17
|
|
|
16
18
|
var __defProp = Object.defineProperty;
|
|
17
19
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -22,27 +24,6 @@ var __export = (target, all) => {
|
|
|
22
24
|
for (var name in all)
|
|
23
25
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
26
|
};
|
|
25
|
-
function normalizeTimestampToMs(value) {
|
|
26
|
-
if (!Number.isFinite(value)) {
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
if (value < 1e11) {
|
|
30
|
-
return Math.floor(value * 1e3);
|
|
31
|
-
}
|
|
32
|
-
return Math.floor(value);
|
|
33
|
-
}
|
|
34
|
-
function rawMessageToMemoryRecord(message) {
|
|
35
|
-
return {
|
|
36
|
-
id: message.messageId,
|
|
37
|
-
userId: message.userId,
|
|
38
|
-
timestamp: normalizeTimestampToMs(message.timestamp),
|
|
39
|
-
text: message.archivedAt ? void 0 : message.content,
|
|
40
|
-
mediaRefs: message.attachments?.map((item) => item.url).filter(Boolean),
|
|
41
|
-
embedding: message.embedding,
|
|
42
|
-
embeddingModel: message.embeddingModel,
|
|
43
|
-
embeddingContentHash: message.embeddingContentHash
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
27
|
var init_contracts = __esm({
|
|
47
28
|
"src/contracts.ts"() {
|
|
48
29
|
}
|
|
@@ -63,66 +44,14 @@ function getRawMessagesCollectionName() {
|
|
|
63
44
|
return process.env.CHROMA_RAW_MESSAGES_COLLECTION || process.env.CHROMA_MEMORY_COLLECTION || DEFAULT_RAW_MESSAGES_COLLECTION;
|
|
64
45
|
}
|
|
65
46
|
function getChromaStore(collectionName) {
|
|
66
|
-
return new ChromaVectorStore({
|
|
47
|
+
return new ChromaVectorStore$1({
|
|
67
48
|
url: process.env.CHROMA_URL,
|
|
68
49
|
collectionName
|
|
69
50
|
});
|
|
70
51
|
}
|
|
71
|
-
function normalizeTimestampToMs2(value) {
|
|
72
|
-
if (!Number.isFinite(value)) {
|
|
73
|
-
return 0;
|
|
74
|
-
}
|
|
75
|
-
if (value < 1e11) {
|
|
76
|
-
return Math.floor(value * 1e3);
|
|
77
|
-
}
|
|
78
|
-
return Math.floor(value);
|
|
79
|
-
}
|
|
80
52
|
function toBooleanValue(value) {
|
|
81
53
|
return value === true || value === "true" || value === 1 || value === "1";
|
|
82
54
|
}
|
|
83
|
-
function rawMessageToChunk(message) {
|
|
84
|
-
if (!message.embedding || message.embedding.length === 0) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const record = rawMessageToMemoryRecord(message);
|
|
88
|
-
const document = buildMemoryRecordEmbeddingDocument(record);
|
|
89
|
-
if (!document.content) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
id: message.messageId,
|
|
94
|
-
documentId: message.messageId,
|
|
95
|
-
content: document.content,
|
|
96
|
-
embedding: message.embedding,
|
|
97
|
-
metadata: {
|
|
98
|
-
sourceType: "raw_message",
|
|
99
|
-
userId: message.userId,
|
|
100
|
-
platform: message.platform,
|
|
101
|
-
botId: message.botId,
|
|
102
|
-
channel: message.channel,
|
|
103
|
-
person: message.person,
|
|
104
|
-
timestamp: normalizeTimestampToMs2(message.timestamp),
|
|
105
|
-
memoryStage: message.memoryStage,
|
|
106
|
-
embeddingModel: message.embeddingModel,
|
|
107
|
-
embeddingDimensions: message.embeddingDimensions,
|
|
108
|
-
contentHash: message.embeddingContentHash,
|
|
109
|
-
archived: Boolean(message.archivedAt),
|
|
110
|
-
role: typeof message.metadata?.role === "string" ? message.metadata.role : void 0
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
async function upsertRawMessagesToChroma(messages) {
|
|
115
|
-
if (!isRawMessageChromaEnabled() || messages.length === 0) {
|
|
116
|
-
return 0;
|
|
117
|
-
}
|
|
118
|
-
const chunks = messages.map(rawMessageToChunk).filter((chunk) => chunk !== null);
|
|
119
|
-
if (chunks.length === 0) {
|
|
120
|
-
return 0;
|
|
121
|
-
}
|
|
122
|
-
const store = getChromaStore(getRawMessagesCollectionName());
|
|
123
|
-
await store.addChunks(chunks);
|
|
124
|
-
return chunks.length;
|
|
125
|
-
}
|
|
126
55
|
async function searchRawMessagesWithChroma(input) {
|
|
127
56
|
if (!isRawMessageChromaEnabled() || input.queryEmbedding.length === 0) {
|
|
128
57
|
return [];
|
|
@@ -324,59 +253,73 @@ var init_raw_message_store = __esm({
|
|
|
324
253
|
}
|
|
325
254
|
});
|
|
326
255
|
|
|
327
|
-
// src/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
256
|
+
// src/search/unified-search.ts
|
|
257
|
+
init_chroma_memory_index();
|
|
258
|
+
init_raw_message_store();
|
|
259
|
+
|
|
260
|
+
// src/search/applicability.ts
|
|
261
|
+
var APPLICABILITY_SCOPES = /* @__PURE__ */ new Set([
|
|
262
|
+
"global",
|
|
263
|
+
"task",
|
|
264
|
+
"conversation",
|
|
265
|
+
"channel",
|
|
266
|
+
"project",
|
|
267
|
+
"custom"
|
|
268
|
+
]);
|
|
269
|
+
function isRecord(value) {
|
|
270
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
271
|
+
}
|
|
272
|
+
function invalidRuntimeContext(message) {
|
|
273
|
+
throw new TypeError(`memory-store search runtime context: ${message}`);
|
|
274
|
+
}
|
|
275
|
+
function validateApplicabilityContext(value, index) {
|
|
276
|
+
const path = `applicabilityContexts[${index}]`;
|
|
277
|
+
if (!isRecord(value)) {
|
|
278
|
+
invalidRuntimeContext(`${path} must be an object`);
|
|
331
279
|
}
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
280
|
+
const scope = value.scope;
|
|
281
|
+
if (typeof scope !== "string" || !APPLICABILITY_SCOPES.has(scope)) {
|
|
282
|
+
invalidRuntimeContext(`${path}.scope is not supported`);
|
|
283
|
+
}
|
|
284
|
+
if (value.key !== void 0 && typeof value.key !== "string") {
|
|
285
|
+
invalidRuntimeContext(`${path}.key must be a string when provided`);
|
|
286
|
+
}
|
|
287
|
+
if (scope !== "global" && (typeof value.key !== "string" || value.key.trim().length === 0)) {
|
|
288
|
+
invalidRuntimeContext(`${path}.key must be non-empty for scope "${scope}"`);
|
|
289
|
+
}
|
|
290
|
+
for (const field of ["validFrom", "validUntil"]) {
|
|
291
|
+
const boundary = value[field];
|
|
292
|
+
if (boundary !== void 0 && (typeof boundary !== "number" || !Number.isFinite(boundary))) {
|
|
293
|
+
invalidRuntimeContext(`${path}.${field} must be a finite epoch-millisecond number`);
|
|
341
294
|
}
|
|
342
|
-
const vector = await deps.embedQuery({
|
|
343
|
-
userId: message.userId,
|
|
344
|
-
query: message.content
|
|
345
|
-
});
|
|
346
|
-
out.push({
|
|
347
|
-
...message,
|
|
348
|
-
embedding: vector,
|
|
349
|
-
embeddingModel: message.embeddingModel ?? "server",
|
|
350
|
-
embeddingDimensions: vector.length,
|
|
351
|
-
embeddingUpdatedAt: Date.now()
|
|
352
|
-
});
|
|
353
295
|
}
|
|
354
|
-
return out;
|
|
355
296
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
297
|
+
function resolveSearchRuntimeContext(input, runtimeContext, now = Date.now) {
|
|
298
|
+
if (runtimeContext === void 0) {
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
if (!isRecord(runtimeContext) || !Array.isArray(runtimeContext.applicabilityContexts)) {
|
|
302
|
+
invalidRuntimeContext("applicabilityContexts must be an array");
|
|
303
|
+
}
|
|
304
|
+
for (const [index, context] of runtimeContext.applicabilityContexts.entries()) {
|
|
305
|
+
validateApplicabilityContext(context, index);
|
|
306
|
+
}
|
|
307
|
+
let applicabilityAt;
|
|
308
|
+
if (input.asOf === void 0) {
|
|
309
|
+
applicabilityAt = now();
|
|
310
|
+
} else {
|
|
311
|
+
const parsed = typeof input.asOf === "string" ? Date.parse(input.asOf) : Number.NaN;
|
|
312
|
+
if (!Number.isFinite(parsed)) {
|
|
313
|
+
invalidRuntimeContext("asOf must be a parseable timestamp when a runtime context is supplied");
|
|
314
|
+
}
|
|
315
|
+
applicabilityAt = parsed;
|
|
372
316
|
}
|
|
373
|
-
return {
|
|
317
|
+
return {
|
|
318
|
+
applicabilityContexts: runtimeContext.applicabilityContexts,
|
|
319
|
+
applicabilityAt
|
|
320
|
+
};
|
|
374
321
|
}
|
|
375
322
|
|
|
376
|
-
// src/search/unified-search.ts
|
|
377
|
-
init_chroma_memory_index();
|
|
378
|
-
init_raw_message_store();
|
|
379
|
-
|
|
380
323
|
// src/search/reranker.ts
|
|
381
324
|
async function applyReranker(reranker, query, results) {
|
|
382
325
|
if (!reranker || results.length === 0) {
|
|
@@ -463,7 +406,7 @@ function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
|
463
406
|
return value === "rrf" ? "rrf" : "similarity";
|
|
464
407
|
}
|
|
465
408
|
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
466
|
-
if (value === "rewrite" || value === "iterative") {
|
|
409
|
+
if (value === "rewrite" || value === "iterative" || value === "union") {
|
|
467
410
|
return value;
|
|
468
411
|
}
|
|
469
412
|
return defaultStrategy;
|
|
@@ -570,6 +513,7 @@ function materializeSignals(lists, hit) {
|
|
|
570
513
|
function listNameToChannel(name) {
|
|
571
514
|
if (name === "memory-semantic") return "semantic";
|
|
572
515
|
if (name === "memory-bm25" || name === "memory-lexical") return "lexical";
|
|
516
|
+
if (name === "memory-hybrid") return "hybrid";
|
|
573
517
|
if (name === "memory-entity") return "entity";
|
|
574
518
|
return void 0;
|
|
575
519
|
}
|
|
@@ -627,7 +571,6 @@ async function resolveScopePeer(input) {
|
|
|
627
571
|
|
|
628
572
|
// src/search/gather-evidence.ts
|
|
629
573
|
var DEFAULT_TIERS = ["summary", "raw", "insight", "knowledge"];
|
|
630
|
-
var MAX_EVIDENCE_CHARS = 600;
|
|
631
574
|
function toSummaryResult(row) {
|
|
632
575
|
const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
|
|
633
576
|
const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
|
|
@@ -645,7 +588,7 @@ function toSummaryResult(row) {
|
|
|
645
588
|
}
|
|
646
589
|
};
|
|
647
590
|
}
|
|
648
|
-
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
|
|
591
|
+
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers, runtimeContext) {
|
|
649
592
|
if (typeof deps.searchSummaries !== "function") {
|
|
650
593
|
return { hits: [], warnings: [] };
|
|
651
594
|
}
|
|
@@ -658,7 +601,8 @@ async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers)
|
|
|
658
601
|
limit,
|
|
659
602
|
threshold,
|
|
660
603
|
authToken: input.authToken,
|
|
661
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
604
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
605
|
+
...runtimeContext ?? {}
|
|
662
606
|
});
|
|
663
607
|
const hits = rows.map(toSummaryResult);
|
|
664
608
|
return { hits, warnings: [] };
|
|
@@ -676,12 +620,6 @@ async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers)
|
|
|
676
620
|
};
|
|
677
621
|
}
|
|
678
622
|
}
|
|
679
|
-
function truncate(input, max = MAX_EVIDENCE_CHARS) {
|
|
680
|
-
if (input.length <= max) {
|
|
681
|
-
return input;
|
|
682
|
-
}
|
|
683
|
-
return `${input.slice(0, max - 1)}\u2026`;
|
|
684
|
-
}
|
|
685
623
|
function buildSynthesisPrompt(input) {
|
|
686
624
|
const grouped = /* @__PURE__ */ new Map();
|
|
687
625
|
for (const item of input.evidence) {
|
|
@@ -695,9 +633,10 @@ function buildSynthesisPrompt(input) {
|
|
|
695
633
|
if (items.length === 0) {
|
|
696
634
|
continue;
|
|
697
635
|
}
|
|
698
|
-
const lines = items.map(
|
|
699
|
-
|
|
700
|
-
|
|
636
|
+
const lines = items.map((item, index) => {
|
|
637
|
+
const factSource = typeof item.metadata?.source === "string" && item.metadata.source.length > 0 ? item.metadata.source : item.source ?? "unknown";
|
|
638
|
+
return ` [${index + 1}] (${item.id}, source=${factSource}, score=${item.score.toFixed(4)}) ${item.snippet}`;
|
|
639
|
+
});
|
|
701
640
|
sections.push(`## ${tier}
|
|
702
641
|
${lines.join("\n")}`);
|
|
703
642
|
}
|
|
@@ -779,12 +718,15 @@ function coerceAnswer(text, schema) {
|
|
|
779
718
|
return text;
|
|
780
719
|
}
|
|
781
720
|
async function synthesizeAnswer(input) {
|
|
782
|
-
const { query,
|
|
783
|
-
const evidence =
|
|
784
|
-
id:
|
|
785
|
-
source:
|
|
786
|
-
snippet:
|
|
787
|
-
score:
|
|
721
|
+
const { query, responseSchema, deps, logger } = input;
|
|
722
|
+
const evidence = input.evidence.map((item) => ({
|
|
723
|
+
id: item.id,
|
|
724
|
+
source: mapEvidenceSourceToTier(item.source),
|
|
725
|
+
snippet: item.snippet,
|
|
726
|
+
score: item.score,
|
|
727
|
+
// Forward per-fact provenance (e.g. `metadata.source` from
|
|
728
|
+
// `opencontext add --source`) so the synthesis prompt can cite it.
|
|
729
|
+
metadata: item.metadata
|
|
788
730
|
}));
|
|
789
731
|
const warnings = [];
|
|
790
732
|
const complete = deps.reasoning?.complete;
|
|
@@ -815,21 +757,10 @@ async function synthesizeAnswer(input) {
|
|
|
815
757
|
return { answer: "", warnings };
|
|
816
758
|
}
|
|
817
759
|
}
|
|
818
|
-
function
|
|
819
|
-
|
|
820
|
-
if (
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
if (hit.type === "memory") {
|
|
824
|
-
return "raw";
|
|
825
|
-
}
|
|
826
|
-
if (hit.type === "insight") {
|
|
827
|
-
return "insight";
|
|
828
|
-
}
|
|
829
|
-
if (hit.type === "knowledge") {
|
|
830
|
-
return "knowledge";
|
|
831
|
-
}
|
|
832
|
-
return hit.type;
|
|
760
|
+
function mapEvidenceSourceToTier(source) {
|
|
761
|
+
if (source === "memory") return "raw";
|
|
762
|
+
if (source === "insights") return "insight";
|
|
763
|
+
return source;
|
|
833
764
|
}
|
|
834
765
|
async function resolveSearchScopePeers(input) {
|
|
835
766
|
return resolveScopePeer({
|
|
@@ -853,7 +784,7 @@ function deriveEntityKeywords(query) {
|
|
|
853
784
|
}
|
|
854
785
|
return out.slice(0, 16);
|
|
855
786
|
}
|
|
856
|
-
async function runEntitySearchForQuery(deps, input, limit, logger, peerPeers = []) {
|
|
787
|
+
async function runEntitySearchForQuery(deps, input, limit, logger, peerPeers = [], runtimeContext) {
|
|
857
788
|
if (typeof deps.entitySearch !== "function") {
|
|
858
789
|
return [];
|
|
859
790
|
}
|
|
@@ -871,7 +802,8 @@ async function runEntitySearchForQuery(deps, input, limit, logger, peerPeers = [
|
|
|
871
802
|
keywords,
|
|
872
803
|
limit: Math.ceil(limit / filters.length),
|
|
873
804
|
botId,
|
|
874
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
805
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
806
|
+
...runtimeContext ?? {}
|
|
875
807
|
})
|
|
876
808
|
)
|
|
877
809
|
)).flat();
|
|
@@ -959,6 +891,16 @@ function filterByDateRange(items, dateFrom, dateTo) {
|
|
|
959
891
|
return true;
|
|
960
892
|
});
|
|
961
893
|
}
|
|
894
|
+
function warnApplicabilityNotEnforced(warnings) {
|
|
895
|
+
if (warnings.some((warning) => warning.code === "memory_applicability_not_enforced")) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
warnings.push({
|
|
899
|
+
source: "memory",
|
|
900
|
+
code: "memory_applicability_not_enforced",
|
|
901
|
+
message: "Built-in raw-message retrieval was skipped because it cannot enforce the supplied applicability context. Configure an applicability-aware raw-message provider."
|
|
902
|
+
});
|
|
903
|
+
}
|
|
962
904
|
async function embedQueryVariant(embedQuery, input, query) {
|
|
963
905
|
return embedQuery({
|
|
964
906
|
userId: input.userId,
|
|
@@ -966,10 +908,29 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
966
908
|
authToken: input.authToken
|
|
967
909
|
});
|
|
968
910
|
}
|
|
969
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers
|
|
911
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext) {
|
|
970
912
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
971
913
|
let semantic = [];
|
|
972
|
-
if (
|
|
914
|
+
if (typeof deps.searchRawMessagesAnn === "function") {
|
|
915
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
916
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
917
|
+
const includeDeprecated = input.includeDeprecated === true;
|
|
918
|
+
semantic = (await Promise.all(
|
|
919
|
+
filters.map(
|
|
920
|
+
(filter) => searchRawMessagesAnn({
|
|
921
|
+
userId: input.userId,
|
|
922
|
+
queryEmbedding,
|
|
923
|
+
limit,
|
|
924
|
+
threshold,
|
|
925
|
+
botId: "botId" in filter ? filter.botId : void 0,
|
|
926
|
+
includeDeprecated,
|
|
927
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
928
|
+
...factTypes ? { factTypes } : {},
|
|
929
|
+
...runtimeContext ?? {}
|
|
930
|
+
})
|
|
931
|
+
)
|
|
932
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
933
|
+
} else if (runtimeContext === void 0 && isRawMessageChromaEnabled()) {
|
|
973
934
|
try {
|
|
974
935
|
semantic = (await Promise.all(
|
|
975
936
|
filters.map((filter) => {
|
|
@@ -994,24 +955,12 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
994
955
|
error
|
|
995
956
|
);
|
|
996
957
|
}
|
|
997
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
998
|
-
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
999
|
-
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1000
|
-
semantic = (await Promise.all(
|
|
1001
|
-
filters.map(
|
|
1002
|
-
(filter) => searchRawMessagesAnn({
|
|
1003
|
-
userId: input.userId,
|
|
1004
|
-
queryEmbedding,
|
|
1005
|
-
limit,
|
|
1006
|
-
threshold,
|
|
1007
|
-
botId: "botId" in filter ? filter.botId : void 0,
|
|
1008
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1009
|
-
...factTypes ? { factTypes } : {}
|
|
1010
|
-
})
|
|
1011
|
-
)
|
|
1012
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1013
958
|
}
|
|
1014
|
-
if (semantic.length === 0) {
|
|
959
|
+
if (semantic.length === 0 && runtimeContext !== void 0) {
|
|
960
|
+
warnApplicabilityNotEnforced(warnings);
|
|
961
|
+
return [];
|
|
962
|
+
}
|
|
963
|
+
if (semantic.length === 0 && typeof deps.searchRawMessagesAnn !== "function") {
|
|
1015
964
|
try {
|
|
1016
965
|
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
1017
966
|
const manager2 = await getRawMessageManager2();
|
|
@@ -1022,6 +971,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1022
971
|
queryEmbedding,
|
|
1023
972
|
limit,
|
|
1024
973
|
threshold,
|
|
974
|
+
includeDeprecated: input.includeDeprecated === true,
|
|
1025
975
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1026
976
|
...factTypes ? { factTypes } : {}
|
|
1027
977
|
});
|
|
@@ -1051,6 +1001,18 @@ function mergeByMaxScore(lists) {
|
|
|
1051
1001
|
}
|
|
1052
1002
|
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
1053
1003
|
}
|
|
1004
|
+
function dedupeChannelByParent(hits) {
|
|
1005
|
+
const strongest = /* @__PURE__ */ new Map();
|
|
1006
|
+
for (const hit of hits) {
|
|
1007
|
+
const sourceMessageId = hit.metadata?.sourceMessageId;
|
|
1008
|
+
const parentId = typeof sourceMessageId === "string" && sourceMessageId.length > 0 ? sourceMessageId : hit.id;
|
|
1009
|
+
const normalized = hit.type === "memory" && hit.id !== parentId ? { ...hit, id: parentId } : hit;
|
|
1010
|
+
const key = `${normalized.type}:${parentId}`;
|
|
1011
|
+
const current = strongest.get(key);
|
|
1012
|
+
if (!current || normalized.similarity > current.similarity) strongest.set(key, normalized);
|
|
1013
|
+
}
|
|
1014
|
+
return [...strongest.values()].sort((a, b) => b.similarity - a.similarity);
|
|
1015
|
+
}
|
|
1054
1016
|
function toIterativeRecallCandidate(hit) {
|
|
1055
1017
|
return {
|
|
1056
1018
|
id: hit.id,
|
|
@@ -1059,7 +1021,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
1059
1021
|
metadata: hit.metadata
|
|
1060
1022
|
};
|
|
1061
1023
|
}
|
|
1062
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers
|
|
1024
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext) {
|
|
1063
1025
|
if (keywords.length === 0) {
|
|
1064
1026
|
return [];
|
|
1065
1027
|
}
|
|
@@ -1068,6 +1030,7 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1068
1030
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1069
1031
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
1070
1032
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1033
|
+
const includeDeprecated = input.includeDeprecated === true;
|
|
1071
1034
|
return (await Promise.all(
|
|
1072
1035
|
filters.map(
|
|
1073
1036
|
(botId) => searchRawMessagesLexical({
|
|
@@ -1075,8 +1038,10 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1075
1038
|
keywords,
|
|
1076
1039
|
limit: Math.ceil(limit / filters.length),
|
|
1077
1040
|
botId,
|
|
1041
|
+
includeDeprecated,
|
|
1078
1042
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1079
|
-
...factTypes ? { factTypes } : {}
|
|
1043
|
+
...factTypes ? { factTypes } : {},
|
|
1044
|
+
...runtimeContext ?? {}
|
|
1080
1045
|
})
|
|
1081
1046
|
)
|
|
1082
1047
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -1087,10 +1052,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1087
1052
|
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
1088
1053
|
}
|
|
1089
1054
|
}
|
|
1055
|
+
if (runtimeContext !== void 0) {
|
|
1056
|
+
warnApplicabilityNotEnforced(warnings);
|
|
1057
|
+
return [];
|
|
1058
|
+
}
|
|
1090
1059
|
try {
|
|
1091
1060
|
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
1092
1061
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1093
1062
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1063
|
+
const includeDeprecated = input.includeDeprecated === true;
|
|
1094
1064
|
return (await Promise.all(
|
|
1095
1065
|
filters.map(
|
|
1096
1066
|
(botId) => lexicalSearchRawMessages2({
|
|
@@ -1098,6 +1068,7 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1098
1068
|
keywords,
|
|
1099
1069
|
limit: Math.ceil(limit / filters.length),
|
|
1100
1070
|
botId,
|
|
1071
|
+
includeDeprecated,
|
|
1101
1072
|
...factTypes ? { factTypes } : {}
|
|
1102
1073
|
})
|
|
1103
1074
|
)
|
|
@@ -1128,7 +1099,9 @@ function searchInputToUnified(input) {
|
|
|
1128
1099
|
mergeStrategy: input.mergeStrategy,
|
|
1129
1100
|
peerFilter: input.peerFilter,
|
|
1130
1101
|
reasoningStrategy: input.reasoningStrategy,
|
|
1131
|
-
factTypes: input.factTypes
|
|
1102
|
+
factTypes: input.factTypes,
|
|
1103
|
+
includeRetrievalDiagnostics: input.includeRetrievalDiagnostics,
|
|
1104
|
+
includeDeprecated: input.includeDeprecated
|
|
1132
1105
|
};
|
|
1133
1106
|
}
|
|
1134
1107
|
function mapHitToEvidenceSource(hit) {
|
|
@@ -1156,8 +1129,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1156
1129
|
}
|
|
1157
1130
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
1158
1131
|
}
|
|
1159
|
-
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
|
|
1160
|
-
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
1132
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = [], runtimeContext) {
|
|
1133
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !deps.reasoning?.iterativePlanner) {
|
|
1161
1134
|
warnings.push({
|
|
1162
1135
|
source: "memory",
|
|
1163
1136
|
code: "memory_iterative_planner_not_configured",
|
|
@@ -1173,7 +1146,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1173
1146
|
});
|
|
1174
1147
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1175
1148
|
}
|
|
1176
|
-
|
|
1149
|
+
let unionEvidence = null;
|
|
1150
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && deps.reasoning?.iterativePlanner) {
|
|
1177
1151
|
const planner = deps.reasoning.iterativePlanner;
|
|
1178
1152
|
const result = await planner.plan({
|
|
1179
1153
|
query: input.query,
|
|
@@ -1188,7 +1162,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1188
1162
|
keywords2,
|
|
1189
1163
|
limit,
|
|
1190
1164
|
logger,
|
|
1191
|
-
peerPeers
|
|
1165
|
+
peerPeers,
|
|
1166
|
+
warnings,
|
|
1167
|
+
runtimeContext
|
|
1192
1168
|
);
|
|
1193
1169
|
let semanticHits = [];
|
|
1194
1170
|
if (typeof deps.embedQuery === "function") {
|
|
@@ -1202,7 +1178,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1202
1178
|
limit,
|
|
1203
1179
|
threshold,
|
|
1204
1180
|
logger,
|
|
1205
|
-
peerPeers
|
|
1181
|
+
peerPeers,
|
|
1182
|
+
warnings,
|
|
1183
|
+
runtimeContext
|
|
1206
1184
|
);
|
|
1207
1185
|
} catch (error) {
|
|
1208
1186
|
logger.warn?.(
|
|
@@ -1230,16 +1208,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1230
1208
|
reasoningInfo.degraded = true;
|
|
1231
1209
|
}
|
|
1232
1210
|
}
|
|
1233
|
-
const
|
|
1211
|
+
const evidence = result.evidence.map((candidate) => ({
|
|
1234
1212
|
type: "memory",
|
|
1235
1213
|
id: candidate.id,
|
|
1236
1214
|
content: candidate.content,
|
|
1237
1215
|
similarity: candidate.similarity,
|
|
1238
|
-
metadata: { ...candidate.metadata, reasoning:
|
|
1216
|
+
metadata: { ...candidate.metadata, reasoning: reasoningStrategy }
|
|
1239
1217
|
}));
|
|
1240
|
-
|
|
1218
|
+
if (reasoningStrategy === "iterative") {
|
|
1219
|
+
return { semantic: evidence, lexical: [] };
|
|
1220
|
+
}
|
|
1221
|
+
unionEvidence = evidence;
|
|
1241
1222
|
}
|
|
1242
1223
|
if (typeof deps.embedQuery !== "function") {
|
|
1224
|
+
warnings.push({
|
|
1225
|
+
source: "memory",
|
|
1226
|
+
code: "semantic_unavailable",
|
|
1227
|
+
message: "Semantic search is unavailable because no embedding provider is configured."
|
|
1228
|
+
});
|
|
1243
1229
|
warnings.push({
|
|
1244
1230
|
source: "memory",
|
|
1245
1231
|
code: "memory_lexical_search_fallback",
|
|
@@ -1247,7 +1233,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1247
1233
|
});
|
|
1248
1234
|
const keywords2 = deriveLexicalKeywords(input.query);
|
|
1249
1235
|
const lexical2 = filterByDateRange(
|
|
1250
|
-
await runLexicalSearchForKeywords(
|
|
1236
|
+
await runLexicalSearchForKeywords(
|
|
1237
|
+
deps,
|
|
1238
|
+
input,
|
|
1239
|
+
keywords2,
|
|
1240
|
+
limit,
|
|
1241
|
+
logger,
|
|
1242
|
+
peerPeers,
|
|
1243
|
+
warnings,
|
|
1244
|
+
runtimeContext
|
|
1245
|
+
),
|
|
1251
1246
|
input.dateFrom,
|
|
1252
1247
|
input.dateTo
|
|
1253
1248
|
);
|
|
@@ -1255,6 +1250,26 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1255
1250
|
}
|
|
1256
1251
|
let semantic = [];
|
|
1257
1252
|
const embedQuery = deps.embedQuery;
|
|
1253
|
+
const activeMergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1254
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1255
|
+
);
|
|
1256
|
+
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof deps.searchRawMessagesHybrid === "function") {
|
|
1257
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1258
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1259
|
+
const hybrid = (await Promise.all(
|
|
1260
|
+
filters.map(
|
|
1261
|
+
(botId) => deps.searchRawMessagesHybrid?.({
|
|
1262
|
+
userId: input.userId,
|
|
1263
|
+
query: input.query,
|
|
1264
|
+
queryEmbedding,
|
|
1265
|
+
limit: Math.ceil(limit / filters.length),
|
|
1266
|
+
botId,
|
|
1267
|
+
...runtimeContext ?? {}
|
|
1268
|
+
})
|
|
1269
|
+
)
|
|
1270
|
+
)).flatMap((items) => items ?? []).filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1271
|
+
return { semantic: [], lexical: [], hybrid: filterByDateRange(hybrid, input.dateFrom, input.dateTo) };
|
|
1272
|
+
}
|
|
1258
1273
|
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
1259
1274
|
try {
|
|
1260
1275
|
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
@@ -1273,7 +1288,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1273
1288
|
);
|
|
1274
1289
|
const lists = await Promise.all(
|
|
1275
1290
|
embeddings.map(
|
|
1276
|
-
(embedding) => runSemanticSearchForEmbedding(
|
|
1291
|
+
(embedding) => runSemanticSearchForEmbedding(
|
|
1292
|
+
deps,
|
|
1293
|
+
input,
|
|
1294
|
+
embedding,
|
|
1295
|
+
limit,
|
|
1296
|
+
threshold,
|
|
1297
|
+
logger,
|
|
1298
|
+
peerPeers,
|
|
1299
|
+
warnings,
|
|
1300
|
+
runtimeContext
|
|
1301
|
+
)
|
|
1277
1302
|
)
|
|
1278
1303
|
);
|
|
1279
1304
|
semantic = mergeByMaxScore(lists);
|
|
@@ -1296,7 +1321,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1296
1321
|
limit,
|
|
1297
1322
|
threshold,
|
|
1298
1323
|
logger,
|
|
1299
|
-
peerPeers
|
|
1324
|
+
peerPeers,
|
|
1325
|
+
warnings,
|
|
1326
|
+
runtimeContext
|
|
1300
1327
|
);
|
|
1301
1328
|
}
|
|
1302
1329
|
let lexical = [];
|
|
@@ -1315,7 +1342,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1315
1342
|
limit,
|
|
1316
1343
|
botId,
|
|
1317
1344
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1318
|
-
...factTypes ? { factTypes } : {}
|
|
1345
|
+
...factTypes ? { factTypes } : {},
|
|
1346
|
+
...runtimeContext ?? {}
|
|
1319
1347
|
})
|
|
1320
1348
|
)
|
|
1321
1349
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -1341,7 +1369,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1341
1369
|
let entity;
|
|
1342
1370
|
if (typeof deps.entitySearch === "function") {
|
|
1343
1371
|
entity = filterByDateRange(
|
|
1344
|
-
await runEntitySearchForQuery(deps, input, limit, logger, peerPeers),
|
|
1372
|
+
await runEntitySearchForQuery(deps, input, limit, logger, peerPeers, runtimeContext),
|
|
1345
1373
|
input.dateFrom,
|
|
1346
1374
|
input.dateTo
|
|
1347
1375
|
);
|
|
@@ -1363,6 +1391,37 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1363
1391
|
message: "RRF merge requested but entity search is not configured; falling back to semantic+lexical RRF."
|
|
1364
1392
|
});
|
|
1365
1393
|
}
|
|
1394
|
+
if (unionEvidence) {
|
|
1395
|
+
const plannerHits = unionEvidence;
|
|
1396
|
+
const base = mergeByMaxScore([
|
|
1397
|
+
filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
1398
|
+
filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
1399
|
+
]);
|
|
1400
|
+
const maxBase = base.length > 0 ? Math.max(...base.map((h) => h.similarity)) : 0;
|
|
1401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1402
|
+
const merged = [];
|
|
1403
|
+
plannerHits.forEach((hit, i) => {
|
|
1404
|
+
if (seen.has(hit.id)) {
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
seen.add(hit.id);
|
|
1408
|
+
merged.push({
|
|
1409
|
+
...hit,
|
|
1410
|
+
similarity: maxBase + (plannerHits.length - i) * 1e-6
|
|
1411
|
+
});
|
|
1412
|
+
});
|
|
1413
|
+
for (const hit of base) {
|
|
1414
|
+
if (merged.length >= limit) {
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
if (seen.has(hit.id)) {
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
seen.add(hit.id);
|
|
1421
|
+
merged.push(hit);
|
|
1422
|
+
}
|
|
1423
|
+
return { semantic: merged.slice(0, limit), lexical: [] };
|
|
1424
|
+
}
|
|
1366
1425
|
const out = {
|
|
1367
1426
|
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
1368
1427
|
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
@@ -1372,14 +1431,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1372
1431
|
}
|
|
1373
1432
|
return out;
|
|
1374
1433
|
}
|
|
1375
|
-
async function
|
|
1434
|
+
async function searchUnifiedMemoryWithRuntime(input, runtimeContext) {
|
|
1376
1435
|
const query = input.query.trim();
|
|
1377
1436
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1378
1437
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1379
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1380
1438
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1381
1439
|
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1382
1440
|
);
|
|
1441
|
+
const candidateLimit = Math.min(50, Math.max(limit, limit * 4));
|
|
1442
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1443
|
+
const memoryThreshold = input.threshold === void 0 && mergeStrategy === "rrf" ? Number.NEGATIVE_INFINITY : threshold;
|
|
1383
1444
|
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1384
1445
|
const warnings = [];
|
|
1385
1446
|
const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
|
|
@@ -1399,23 +1460,46 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1399
1460
|
};
|
|
1400
1461
|
}
|
|
1401
1462
|
let memorySubs;
|
|
1463
|
+
let retrievalStatus;
|
|
1402
1464
|
const reasoningInfo = { strategy: reasoningStrategy };
|
|
1403
1465
|
if (input.dateFrom || input.dateTo) {
|
|
1404
1466
|
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1405
1467
|
}
|
|
1406
1468
|
if (sources.includes("memory")) {
|
|
1407
|
-
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1469
|
+
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesHybrid === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1408
1470
|
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1409
1471
|
try {
|
|
1410
1472
|
memorySubs = await runMemorySource(
|
|
1411
1473
|
input,
|
|
1412
|
-
|
|
1413
|
-
|
|
1474
|
+
candidateLimit,
|
|
1475
|
+
memoryThreshold,
|
|
1414
1476
|
warnings,
|
|
1415
1477
|
reasoningStrategy,
|
|
1416
1478
|
reasoningInfo,
|
|
1417
|
-
peerPeers
|
|
1479
|
+
peerPeers,
|
|
1480
|
+
runtimeContext
|
|
1418
1481
|
);
|
|
1482
|
+
memorySubs = {
|
|
1483
|
+
semantic: dedupeChannelByParent(memorySubs.semantic),
|
|
1484
|
+
lexical: dedupeChannelByParent(memorySubs.lexical),
|
|
1485
|
+
...memorySubs.hybrid ? { hybrid: dedupeChannelByParent(memorySubs.hybrid) } : {},
|
|
1486
|
+
...memorySubs.entity ? { entity: dedupeChannelByParent(memorySubs.entity) } : {}
|
|
1487
|
+
};
|
|
1488
|
+
if (deps.getRawMessageRetrievalStatus) {
|
|
1489
|
+
retrievalStatus = await deps.getRawMessageRetrievalStatus();
|
|
1490
|
+
}
|
|
1491
|
+
if (memorySubs.semantic.length === 0 && (memorySubs.hybrid?.length ?? 0) === 0 && typeof deps.embedQuery === "function") {
|
|
1492
|
+
let code = "semantic_candidates_empty";
|
|
1493
|
+
let message = "Semantic search was available but returned no candidates.";
|
|
1494
|
+
if (retrievalStatus?.embeddedChildCount === 0) {
|
|
1495
|
+
code = "semantic_unavailable";
|
|
1496
|
+
message = "No embedded RawMessage children are available for semantic search.";
|
|
1497
|
+
} else if (retrievalStatus?.embeddingDimensions && retrievalStatus.indexedDimensions.length > 0 && !retrievalStatus.indexedDimensions.includes(retrievalStatus.embeddingDimensions)) {
|
|
1498
|
+
code = "semantic_dimension_mismatch";
|
|
1499
|
+
message = `Query embedding dimension ${retrievalStatus.embeddingDimensions} does not match indexed child dimensions ${retrievalStatus.indexedDimensions.join(", ")}.`;
|
|
1500
|
+
}
|
|
1501
|
+
warnings.push({ source: "memory", code, message });
|
|
1502
|
+
}
|
|
1419
1503
|
} catch (error) {
|
|
1420
1504
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1421
1505
|
warnings.push({
|
|
@@ -1438,12 +1522,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1438
1522
|
const hits = await deps.searchInsights({
|
|
1439
1523
|
userId: input.userId,
|
|
1440
1524
|
query: input.query,
|
|
1441
|
-
limit,
|
|
1525
|
+
limit: candidateLimit,
|
|
1442
1526
|
threshold,
|
|
1443
1527
|
botIds: input.botIds,
|
|
1444
1528
|
includeArchived: input.includeArchivedInsights,
|
|
1445
1529
|
authToken: input.authToken,
|
|
1446
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1530
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1531
|
+
...runtimeContext ?? {}
|
|
1447
1532
|
});
|
|
1448
1533
|
for (const hit of hits) {
|
|
1449
1534
|
insightHits.push({
|
|
@@ -1470,12 +1555,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1470
1555
|
userId: input.userId,
|
|
1471
1556
|
query: input.query,
|
|
1472
1557
|
options: {
|
|
1473
|
-
limit,
|
|
1558
|
+
limit: candidateLimit,
|
|
1474
1559
|
threshold,
|
|
1475
1560
|
documentIds: input.documentIds
|
|
1476
1561
|
},
|
|
1477
1562
|
authToken: input.authToken,
|
|
1478
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1563
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1564
|
+
...runtimeContext ?? {}
|
|
1479
1565
|
});
|
|
1480
1566
|
for (const hit of hits) {
|
|
1481
1567
|
knowledgeHits.push(toKnowledgeResult(hit));
|
|
@@ -1493,10 +1579,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1493
1579
|
memorySubs,
|
|
1494
1580
|
insightHits,
|
|
1495
1581
|
knowledgeHits,
|
|
1496
|
-
limit,
|
|
1582
|
+
limit: candidateLimit,
|
|
1497
1583
|
strategy: mergeStrategy
|
|
1498
1584
|
});
|
|
1499
|
-
const
|
|
1585
|
+
const rerankerStartedAt = deps.reranker ? Date.now() : void 0;
|
|
1586
|
+
const reranked = await applyReranker(deps.reranker, input.query, merged);
|
|
1587
|
+
const rerankerLatencyMs = rerankerStartedAt === void 0 ? 0 : Date.now() - rerankerStartedAt;
|
|
1588
|
+
const rerankerOrderChanged = Boolean(deps.reranker) && merged.some((hit, index) => hit.id !== reranked[index]?.id);
|
|
1589
|
+
const ranked = reranked.slice(0, limit);
|
|
1500
1590
|
const output = {
|
|
1501
1591
|
query: input.query,
|
|
1502
1592
|
sources,
|
|
@@ -1504,28 +1594,51 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1504
1594
|
count: ranked.length,
|
|
1505
1595
|
warnings
|
|
1506
1596
|
};
|
|
1597
|
+
if (input.includeRetrievalDiagnostics) {
|
|
1598
|
+
output.retrievalDiagnostics = {
|
|
1599
|
+
mergeStrategy,
|
|
1600
|
+
candidateLimit,
|
|
1601
|
+
backend: retrievalStatus?.backend,
|
|
1602
|
+
semanticDegradedReason: retrievalStatus?.semanticDegradedReason ?? warnings.find((warning) => warning.code.startsWith("semantic_"))?.code,
|
|
1603
|
+
candidateCounts: {
|
|
1604
|
+
semantic: memorySubs?.semantic.length ?? 0,
|
|
1605
|
+
lexical: memorySubs?.lexical.length ?? 0,
|
|
1606
|
+
hybrid: memorySubs?.hybrid?.length ?? 0,
|
|
1607
|
+
entity: memorySubs?.entity?.length ?? 0,
|
|
1608
|
+
fused: merged.length,
|
|
1609
|
+
final: ranked.length
|
|
1610
|
+
},
|
|
1611
|
+
channels: {
|
|
1612
|
+
semantic: memorySubs?.semantic ?? [],
|
|
1613
|
+
lexical: memorySubs?.lexical ?? [],
|
|
1614
|
+
...memorySubs?.hybrid ? { hybrid: memorySubs.hybrid } : {},
|
|
1615
|
+
...memorySubs?.entity ? { entity: memorySubs.entity } : {}
|
|
1616
|
+
},
|
|
1617
|
+
fusedBeforeRerank: merged,
|
|
1618
|
+
reranker: {
|
|
1619
|
+
enabled: Boolean(deps.reranker),
|
|
1620
|
+
provider: deps.rerankerInfo?.provider,
|
|
1621
|
+
model: deps.rerankerInfo?.model,
|
|
1622
|
+
inputCount: merged.length,
|
|
1623
|
+
outputCount: reranked.length,
|
|
1624
|
+
latencyMs: rerankerLatencyMs,
|
|
1625
|
+
orderChanged: rerankerOrderChanged
|
|
1626
|
+
},
|
|
1627
|
+
final: ranked
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1507
1630
|
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
1508
1631
|
output.reasoning = reasoningInfo;
|
|
1509
1632
|
}
|
|
1510
1633
|
return output;
|
|
1511
1634
|
}
|
|
1635
|
+
async function searchUnifiedMemory(input) {
|
|
1636
|
+
return searchUnifiedMemoryWithRuntime(input);
|
|
1637
|
+
}
|
|
1512
1638
|
async function searchRawMemorySemantically(input) {
|
|
1513
|
-
|
|
1514
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1515
|
-
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1516
|
-
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1517
|
-
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1518
|
-
);
|
|
1519
|
-
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
1520
|
-
return mergeAcrossSources({
|
|
1521
|
-
memorySubs: sub,
|
|
1522
|
-
insightHits: [],
|
|
1523
|
-
knowledgeHits: [],
|
|
1524
|
-
limit,
|
|
1525
|
-
strategy: mergeStrategy
|
|
1526
|
-
});
|
|
1639
|
+
return (await searchUnifiedMemory({ ...input, sources: ["memory"] })).results;
|
|
1527
1640
|
}
|
|
1528
|
-
async function
|
|
1641
|
+
async function searchWithResolvedRuntime(input, runtimeContext) {
|
|
1529
1642
|
const query = input.query.trim();
|
|
1530
1643
|
const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
|
|
1531
1644
|
const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
|
|
@@ -1557,19 +1670,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1557
1670
|
let unifiedHits = [];
|
|
1558
1671
|
let sources = input.sources ? [...input.sources] : [];
|
|
1559
1672
|
let unifiedReasoning;
|
|
1673
|
+
let retrievalDiagnostics;
|
|
1560
1674
|
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
|
|
1561
1675
|
const inferredSources = sources.length > 0 ? sources : [
|
|
1562
1676
|
...wantsRawTier ? ["memory"] : [],
|
|
1563
1677
|
...wantsInsightTier ? ["insights"] : [],
|
|
1564
1678
|
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
1565
1679
|
];
|
|
1566
|
-
const unified = await
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1680
|
+
const unified = await searchUnifiedMemoryWithRuntime(
|
|
1681
|
+
{
|
|
1682
|
+
...searchInputToUnified(input),
|
|
1683
|
+
sources: inferredSources.length > 0 ? inferredSources : void 0
|
|
1684
|
+
},
|
|
1685
|
+
runtimeContext
|
|
1686
|
+
);
|
|
1570
1687
|
unifiedHits = unified.results;
|
|
1571
1688
|
sources = unified.sources;
|
|
1572
1689
|
unifiedReasoning = unified.reasoning;
|
|
1690
|
+
retrievalDiagnostics = unified.retrievalDiagnostics;
|
|
1573
1691
|
warnings.push(...unified.warnings);
|
|
1574
1692
|
}
|
|
1575
1693
|
let summaryHits = [];
|
|
@@ -1580,7 +1698,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1580
1698
|
limit,
|
|
1581
1699
|
threshold,
|
|
1582
1700
|
logger,
|
|
1583
|
-
peerScope.peers
|
|
1701
|
+
peerScope.peers,
|
|
1702
|
+
runtimeContext
|
|
1584
1703
|
);
|
|
1585
1704
|
warnings.push(...summaryBucket.warnings);
|
|
1586
1705
|
summaryHits = summaryBucket.hits;
|
|
@@ -1605,7 +1724,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1605
1724
|
source: mapHitToEvidenceSource(hit),
|
|
1606
1725
|
snippet: hit.content,
|
|
1607
1726
|
score: hit.similarity,
|
|
1608
|
-
timestamp: getCandidateTimestamp(hit.metadata)
|
|
1727
|
+
timestamp: getCandidateTimestamp(hit.metadata),
|
|
1728
|
+
metadata: hit.metadata
|
|
1609
1729
|
}));
|
|
1610
1730
|
const base = {
|
|
1611
1731
|
query,
|
|
@@ -1618,12 +1738,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1618
1738
|
if (unifiedReasoning) {
|
|
1619
1739
|
base.reasoning = unifiedReasoning;
|
|
1620
1740
|
}
|
|
1741
|
+
if (retrievalDiagnostics) {
|
|
1742
|
+
base.retrievalDiagnostics = retrievalDiagnostics;
|
|
1743
|
+
}
|
|
1621
1744
|
if (!wantsSynthesis) {
|
|
1622
1745
|
return base;
|
|
1623
1746
|
}
|
|
1624
1747
|
const { answer, warnings: synthWarnings } = await synthesizeAnswer({
|
|
1625
1748
|
query,
|
|
1626
|
-
|
|
1749
|
+
evidence,
|
|
1627
1750
|
responseSchema: synthesisSchema,
|
|
1628
1751
|
deps,
|
|
1629
1752
|
logger
|
|
@@ -1632,6 +1755,10 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1632
1755
|
base.warnings = [...base.warnings, ...synthWarnings];
|
|
1633
1756
|
return base;
|
|
1634
1757
|
}
|
|
1758
|
+
async function search(input, runtimeContext) {
|
|
1759
|
+
const resolvedRuntime = resolveSearchRuntimeContext(input, runtimeContext);
|
|
1760
|
+
return searchWithResolvedRuntime(input, resolvedRuntime);
|
|
1761
|
+
}
|
|
1635
1762
|
return {
|
|
1636
1763
|
search,
|
|
1637
1764
|
searchUnifiedMemory,
|
|
@@ -1647,6 +1774,7 @@ function mergeAcrossSources(input) {
|
|
|
1647
1774
|
for (const hit of [
|
|
1648
1775
|
...memorySubs.semantic,
|
|
1649
1776
|
...memorySubs.lexical,
|
|
1777
|
+
...memorySubs.hybrid ?? [],
|
|
1650
1778
|
...input.insightHits,
|
|
1651
1779
|
...input.knowledgeHits
|
|
1652
1780
|
]) {
|
|
@@ -1662,6 +1790,7 @@ function mergeAcrossSources(input) {
|
|
|
1662
1790
|
const all = [
|
|
1663
1791
|
...memorySubs.semantic,
|
|
1664
1792
|
...memorySubs.lexical,
|
|
1793
|
+
...memorySubs.hybrid ?? [],
|
|
1665
1794
|
...memorySubs.entity ?? [],
|
|
1666
1795
|
...input.insightHits,
|
|
1667
1796
|
...input.knowledgeHits
|
|
@@ -1673,6 +1802,9 @@ function buildChannelLists(memorySubs, insightHits, knowledgeHits) {
|
|
|
1673
1802
|
const lists = [];
|
|
1674
1803
|
if (memorySubs.semantic.length > 0) lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
|
|
1675
1804
|
if (memorySubs.lexical.length > 0) lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
|
|
1805
|
+
if (memorySubs.hybrid && memorySubs.hybrid.length > 0) {
|
|
1806
|
+
lists.push({ name: "memory-hybrid", hits: memorySubs.hybrid });
|
|
1807
|
+
}
|
|
1676
1808
|
if (memorySubs.entity && memorySubs.entity.length > 0) {
|
|
1677
1809
|
lists.push({ name: "memory-entity", hits: memorySubs.entity });
|
|
1678
1810
|
}
|
|
@@ -2137,8 +2269,250 @@ function createVsaRecall(storage) {
|
|
|
2137
2269
|
};
|
|
2138
2270
|
}
|
|
2139
2271
|
|
|
2272
|
+
// ../shared/src/tokens.ts
|
|
2273
|
+
var estimateTokens = (text) => {
|
|
2274
|
+
const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
|
|
2275
|
+
const otherChars = text.length - chineseChars;
|
|
2276
|
+
const wordCount = Math.ceil(otherChars / 5);
|
|
2277
|
+
return chineseChars + wordCount;
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
// ../shared/src/text-chunking.ts
|
|
2281
|
+
var RAW_MESSAGE_CHUNK_MAX_TOKENS = 400;
|
|
2282
|
+
var RAW_MESSAGE_CHUNK_OVERLAP_TOKENS = 80;
|
|
2283
|
+
var PARAGRAPH_BOUNDARIES = ["\n\n", "\r\n\r\n"];
|
|
2284
|
+
var SENTENCE_BOUNDARY = /[.!?。!?;;]\s*$/u;
|
|
2285
|
+
function largestEndWithinTokenBudget(text, start, maxTokens) {
|
|
2286
|
+
let low = start + 1;
|
|
2287
|
+
let high = text.length;
|
|
2288
|
+
let best = low;
|
|
2289
|
+
while (low <= high) {
|
|
2290
|
+
const middle = Math.floor((low + high) / 2);
|
|
2291
|
+
if (estimateTokens(text.slice(start, middle)) <= maxTokens) {
|
|
2292
|
+
best = middle;
|
|
2293
|
+
low = middle + 1;
|
|
2294
|
+
} else {
|
|
2295
|
+
high = middle - 1;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
return best;
|
|
2299
|
+
}
|
|
2300
|
+
function preferNaturalEnd(text, start, hardEnd) {
|
|
2301
|
+
if (hardEnd >= text.length) return text.length;
|
|
2302
|
+
const searchStart = start + Math.floor((hardEnd - start) * 0.6);
|
|
2303
|
+
const candidate = text.slice(searchStart, hardEnd);
|
|
2304
|
+
for (const boundary of PARAGRAPH_BOUNDARIES) {
|
|
2305
|
+
const index = candidate.lastIndexOf(boundary);
|
|
2306
|
+
if (index >= 0) return searchStart + index + boundary.length;
|
|
2307
|
+
}
|
|
2308
|
+
for (let offset = candidate.length; offset > 0; offset -= 1) {
|
|
2309
|
+
const prefix = candidate.slice(0, offset);
|
|
2310
|
+
if (SENTENCE_BOUNDARY.test(prefix)) return searchStart + offset;
|
|
2311
|
+
}
|
|
2312
|
+
for (let offset = candidate.length - 1; offset >= 0; offset -= 1) {
|
|
2313
|
+
if (/\s/u.test(candidate[offset] ?? "")) return searchStart + offset + 1;
|
|
2314
|
+
}
|
|
2315
|
+
return hardEnd;
|
|
2316
|
+
}
|
|
2317
|
+
function overlapStart(text, chunkStart, chunkEnd, overlapTokens) {
|
|
2318
|
+
if (overlapTokens <= 0) return chunkEnd;
|
|
2319
|
+
let low = chunkStart + 1;
|
|
2320
|
+
let high = chunkEnd;
|
|
2321
|
+
let best = chunkEnd;
|
|
2322
|
+
while (low <= high) {
|
|
2323
|
+
const middle = Math.floor((low + high) / 2);
|
|
2324
|
+
if (estimateTokens(text.slice(middle, chunkEnd)) <= overlapTokens) {
|
|
2325
|
+
best = middle;
|
|
2326
|
+
high = middle - 1;
|
|
2327
|
+
} else {
|
|
2328
|
+
low = middle + 1;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return Math.min(chunkEnd, Math.max(chunkStart + 1, best));
|
|
2332
|
+
}
|
|
2333
|
+
function chunkTextByEstimatedTokens(text, options = {}) {
|
|
2334
|
+
if (text.length === 0) return [];
|
|
2335
|
+
const maxTokens = Math.max(1, Math.floor(options.maxTokens ?? RAW_MESSAGE_CHUNK_MAX_TOKENS));
|
|
2336
|
+
const overlapTokens = Math.max(0, Math.floor(options.overlapTokens ?? RAW_MESSAGE_CHUNK_OVERLAP_TOKENS));
|
|
2337
|
+
if (overlapTokens >= maxTokens) {
|
|
2338
|
+
throw new Error("overlapTokens must be smaller than maxTokens");
|
|
2339
|
+
}
|
|
2340
|
+
const chunks = [];
|
|
2341
|
+
let start = 0;
|
|
2342
|
+
while (start < text.length) {
|
|
2343
|
+
const hardEnd = largestEndWithinTokenBudget(text, start, maxTokens);
|
|
2344
|
+
const end = preferNaturalEnd(text, start, hardEnd);
|
|
2345
|
+
chunks.push({
|
|
2346
|
+
chunkIndex: chunks.length,
|
|
2347
|
+
startPosition: start,
|
|
2348
|
+
endPosition: end,
|
|
2349
|
+
content: text.slice(start, end)
|
|
2350
|
+
});
|
|
2351
|
+
if (end >= text.length) break;
|
|
2352
|
+
start = overlapStart(text, start, end, overlapTokens);
|
|
2353
|
+
}
|
|
2354
|
+
return chunks;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
// src/embed-on-insert.ts
|
|
2358
|
+
var AUTO_APPLIED_WARNING = {
|
|
2359
|
+
code: "embed_on_insert_auto_applied",
|
|
2360
|
+
message: "Caller did not explicitly enable embedOnInsert, but an embedding provider is configured. OpenContext generated the missing search embeddings."
|
|
2361
|
+
};
|
|
2362
|
+
var SEMANTIC_UNAVAILABLE_WARNING = {
|
|
2363
|
+
code: "semantic_index_unavailable",
|
|
2364
|
+
message: "No embedding provider is configured. RawMessage children were indexed for lexical search only; semantic search is unavailable until they are explicitly reindexed."
|
|
2365
|
+
};
|
|
2366
|
+
function sha256(value) {
|
|
2367
|
+
return createHash("sha256").update(value).digest("hex");
|
|
2368
|
+
}
|
|
2369
|
+
function isUsableEmbedding(embedding) {
|
|
2370
|
+
return Boolean(
|
|
2371
|
+
embedding?.length && embedding.every(Number.isFinite) && embedding.some((value) => value !== 0)
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
async function embedText(text, userId, deps) {
|
|
2375
|
+
if (typeof deps.embedQuery !== "function" || text.length === 0) return void 0;
|
|
2376
|
+
const embedding = await deps.embedQuery({ userId, query: text });
|
|
2377
|
+
return isUsableEmbedding(embedding) ? embedding : void 0;
|
|
2378
|
+
}
|
|
2379
|
+
async function embedMissingMessages(messages, deps) {
|
|
2380
|
+
if (typeof deps.embedQuery !== "function") return messages;
|
|
2381
|
+
const out = [];
|
|
2382
|
+
for (const message of messages) {
|
|
2383
|
+
if (isUsableEmbedding(message.embedding) || !message.content) {
|
|
2384
|
+
out.push(message);
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
const embedding = await embedText(message.content, message.userId, deps);
|
|
2388
|
+
out.push(
|
|
2389
|
+
embedding ? {
|
|
2390
|
+
...message,
|
|
2391
|
+
embedding,
|
|
2392
|
+
embeddingModel: message.embeddingModel ?? deps.embeddingInfo?.model ?? "server",
|
|
2393
|
+
embeddingContentHash: sha256(message.content),
|
|
2394
|
+
embeddingDimensions: embedding.length,
|
|
2395
|
+
embeddingUpdatedAt: Date.now()
|
|
2396
|
+
} : message
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2399
|
+
return out;
|
|
2400
|
+
}
|
|
2401
|
+
async function prepareRawMessageIngest(incoming, embedOnInsert, unified) {
|
|
2402
|
+
const deps = unified ?? {};
|
|
2403
|
+
const embedderAvailable = typeof deps.embedQuery === "function" || typeof deps.embedDocuments === "function";
|
|
2404
|
+
const shouldEmbed = embedOnInsert === true || embedderAvailable;
|
|
2405
|
+
const messages = [];
|
|
2406
|
+
const chunks = [];
|
|
2407
|
+
let missingSemanticEmbedding = false;
|
|
2408
|
+
let generatedEmbedding = false;
|
|
2409
|
+
const plans = incoming.map((message) => ({
|
|
2410
|
+
message,
|
|
2411
|
+
pieces: chunkTextByEstimatedTokens(message.content)
|
|
2412
|
+
}));
|
|
2413
|
+
const generatedEmbeddings = /* @__PURE__ */ new Map();
|
|
2414
|
+
const pendingByUser = /* @__PURE__ */ new Map();
|
|
2415
|
+
for (const [messageIndex, plan] of plans.entries()) {
|
|
2416
|
+
for (const piece of plan.pieces) {
|
|
2417
|
+
const canReuseParentEmbedding = plan.pieces.length === 1 && isUsableEmbedding(plan.message.embedding);
|
|
2418
|
+
if (canReuseParentEmbedding || !shouldEmbed || piece.content.length === 0) continue;
|
|
2419
|
+
const pending = pendingByUser.get(plan.message.userId) ?? [];
|
|
2420
|
+
pending.push({ key: `${messageIndex}:${piece.chunkIndex}`, text: piece.content });
|
|
2421
|
+
pendingByUser.set(plan.message.userId, pending);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
for (const [userId, pending] of pendingByUser) {
|
|
2425
|
+
let embeddings;
|
|
2426
|
+
if (typeof deps.embedDocuments === "function") {
|
|
2427
|
+
const batch = await deps.embedDocuments({
|
|
2428
|
+
userId,
|
|
2429
|
+
texts: pending.map((item) => item.text)
|
|
2430
|
+
});
|
|
2431
|
+
if (batch.length !== pending.length) {
|
|
2432
|
+
throw new Error(
|
|
2433
|
+
`Document embedding count mismatch: expected ${pending.length}, received ${batch.length}`
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
embeddings = batch.map((embedding) => isUsableEmbedding(embedding) ? embedding : void 0);
|
|
2437
|
+
} else {
|
|
2438
|
+
embeddings = [];
|
|
2439
|
+
for (const item of pending) {
|
|
2440
|
+
embeddings.push(await embedText(item.text, userId, deps));
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
for (const [index, item] of pending.entries()) {
|
|
2444
|
+
const embedding = embeddings[index];
|
|
2445
|
+
if (embedding) generatedEmbeddings.set(item.key, embedding);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
for (const [messageIndex, plan] of plans.entries()) {
|
|
2449
|
+
const incomingMessage = plan.message;
|
|
2450
|
+
const pieces = plan.pieces;
|
|
2451
|
+
let message = incomingMessage;
|
|
2452
|
+
for (const piece of pieces) {
|
|
2453
|
+
const contentHash = sha256(piece.content);
|
|
2454
|
+
const canReuseParentEmbedding = pieces.length === 1 && isUsableEmbedding(incomingMessage.embedding);
|
|
2455
|
+
const embedding = canReuseParentEmbedding ? incomingMessage.embedding : generatedEmbeddings.get(`${messageIndex}:${piece.chunkIndex}`);
|
|
2456
|
+
generatedEmbedding = generatedEmbedding || Boolean(embedding && !canReuseParentEmbedding);
|
|
2457
|
+
if (!embedding) missingSemanticEmbedding = true;
|
|
2458
|
+
const embeddingModel = embedding ? canReuseParentEmbedding ? incomingMessage.embeddingModel : deps.embeddingInfo?.model ?? incomingMessage.embeddingModel ?? "server" : void 0;
|
|
2459
|
+
chunks.push({
|
|
2460
|
+
chunkId: `${incomingMessage.messageId}:chunk:${piece.chunkIndex}:${contentHash.slice(0, 16)}`,
|
|
2461
|
+
messageId: incomingMessage.messageId,
|
|
2462
|
+
userId: incomingMessage.userId,
|
|
2463
|
+
chunkIndex: piece.chunkIndex,
|
|
2464
|
+
chunkCount: pieces.length,
|
|
2465
|
+
startPosition: piece.startPosition,
|
|
2466
|
+
endPosition: piece.endPosition,
|
|
2467
|
+
content: piece.content,
|
|
2468
|
+
contentHash,
|
|
2469
|
+
embedding,
|
|
2470
|
+
embeddingModel,
|
|
2471
|
+
embeddingDimensions: embedding?.length,
|
|
2472
|
+
embeddingUpdatedAt: embedding ? Date.now() : void 0
|
|
2473
|
+
});
|
|
2474
|
+
if (pieces.length === 1 && embedding && !incomingMessage.embedding?.length) {
|
|
2475
|
+
message = {
|
|
2476
|
+
...incomingMessage,
|
|
2477
|
+
embedding,
|
|
2478
|
+
embeddingModel,
|
|
2479
|
+
embeddingContentHash: contentHash,
|
|
2480
|
+
embeddingDimensions: embedding.length,
|
|
2481
|
+
embeddingUpdatedAt: Date.now()
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
messages.push(message);
|
|
2486
|
+
}
|
|
2487
|
+
const warnings = [];
|
|
2488
|
+
if (generatedEmbedding && embedOnInsert !== true) warnings.push(AUTO_APPLIED_WARNING);
|
|
2489
|
+
if (missingSemanticEmbedding) warnings.push(SEMANTIC_UNAVAILABLE_WARNING);
|
|
2490
|
+
return { messages, chunks, warnings };
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// src/storage/raw-message-ingest.ts
|
|
2494
|
+
async function persistRawMessages(input) {
|
|
2495
|
+
const prepared = await prepareRawMessageIngest(input.messages, input.embedOnInsert, input.unified);
|
|
2496
|
+
let ids;
|
|
2497
|
+
if (typeof input.manager.storeMessagesWithSearchChunks === "function") {
|
|
2498
|
+
ids = await input.manager.storeMessagesWithSearchChunks(prepared.messages, prepared.chunks);
|
|
2499
|
+
} else {
|
|
2500
|
+
const legacyMessages = (input.embedOnInsert === true || typeof input.unified?.embedQuery === "function") && typeof input.unified?.embedQuery === "function" ? await embedMissingMessages(prepared.messages, input.unified) : prepared.messages;
|
|
2501
|
+
if (typeof input.manager.upsertRawMessages === "function") {
|
|
2502
|
+
await input.manager.upsertRawMessages({ userId: input.userId, messages: legacyMessages });
|
|
2503
|
+
} else if (typeof input.manager.storeMessages === "function") {
|
|
2504
|
+
ids = await input.manager.storeMessages(legacyMessages);
|
|
2505
|
+
} else {
|
|
2506
|
+
throw new Error("active raw-message manager exposes no supported write method");
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
if (input.externalIndex) {
|
|
2510
|
+
await input.externalIndex.replaceMessages(prepared.messages, prepared.chunks);
|
|
2511
|
+
}
|
|
2512
|
+
return { count: prepared.messages.length, ids, warnings: prepared.warnings };
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2140
2515
|
// src/http.ts
|
|
2141
|
-
init_chroma_memory_index();
|
|
2142
2516
|
init_raw_message_store();
|
|
2143
2517
|
init_sqlite_raw_message_store();
|
|
2144
2518
|
async function startHttpServer(options = {}) {
|
|
@@ -2168,7 +2542,18 @@ async function startHttpServer(options = {}) {
|
|
|
2168
2542
|
const vsaStorage = await getSQLiteVsaStore({ dbPath: vsaDbPath });
|
|
2169
2543
|
const vsa = createVsaRecall(vsaStorage);
|
|
2170
2544
|
const app = new Hono();
|
|
2171
|
-
app.get("/health", (c) =>
|
|
2545
|
+
app.get("/health", async (c) => {
|
|
2546
|
+
const retrieval = options.unified?.getRawMessageRetrievalStatus ? await options.unified.getRawMessageRetrievalStatus().catch(() => ({
|
|
2547
|
+
backend: "unknown",
|
|
2548
|
+
childCount: 0,
|
|
2549
|
+
embeddedChildCount: 0,
|
|
2550
|
+
indexedDimensions: [],
|
|
2551
|
+
semanticReady: false,
|
|
2552
|
+
lexicalReady: false,
|
|
2553
|
+
semanticDegradedReason: "health_check_failed"
|
|
2554
|
+
})) : void 0;
|
|
2555
|
+
return c.json({ ok: true, store: "memory", ts: Date.now(), retrieval });
|
|
2556
|
+
});
|
|
2172
2557
|
app.post("/v1/search", async (c) => {
|
|
2173
2558
|
const body = await c.req.json().catch(() => ({}));
|
|
2174
2559
|
const userId = typeof body.userId === "string" ? body.userId : null;
|
|
@@ -2191,7 +2576,9 @@ async function startHttpServer(options = {}) {
|
|
|
2191
2576
|
authToken: typeof body.authToken === "string" ? body.authToken : void 0,
|
|
2192
2577
|
dateFrom: typeof body.dateFrom === "string" ? body.dateFrom : void 0,
|
|
2193
2578
|
dateTo: typeof body.dateTo === "string" ? body.dateTo : void 0,
|
|
2194
|
-
reasoningStrategy: body.reasoningStrategy === "rewrite" || body.reasoningStrategy === "iterative" ? body.reasoningStrategy : void 0,
|
|
2579
|
+
reasoningStrategy: body.reasoningStrategy === "rewrite" || body.reasoningStrategy === "iterative" || body.reasoningStrategy === "union" ? body.reasoningStrategy : void 0,
|
|
2580
|
+
mergeStrategy: body.mergeStrategy === "rrf" || body.mergeStrategy === "similarity" ? body.mergeStrategy : void 0,
|
|
2581
|
+
includeRetrievalDiagnostics: body.includeRetrievalDiagnostics === true,
|
|
2195
2582
|
...wantsSynthesis ? {
|
|
2196
2583
|
synthesize: {
|
|
2197
2584
|
...responseSchema ? { responseSchema } : {}
|
|
@@ -2295,57 +2682,20 @@ async function startHttpServer(options = {}) {
|
|
|
2295
2682
|
...m,
|
|
2296
2683
|
userId: m.userId ?? body.userId
|
|
2297
2684
|
}));
|
|
2298
|
-
const
|
|
2299
|
-
|
|
2300
|
-
body.
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
messages
|
|
2308
|
-
});
|
|
2309
|
-
} else if (typeof manager3.storeMessages === "function") {
|
|
2310
|
-
const ids = await manager3.storeMessages(messages);
|
|
2311
|
-
result = { inserted: ids.length, ids };
|
|
2312
|
-
} else {
|
|
2313
|
-
return c.json(
|
|
2314
|
-
{
|
|
2315
|
-
error: "active raw-message manager exposes neither upsertRawMessages nor storeMessages"
|
|
2316
|
-
},
|
|
2317
|
-
500
|
|
2318
|
-
);
|
|
2319
|
-
}
|
|
2320
|
-
try {
|
|
2321
|
-
await upsertRawMessagesToChroma(messages);
|
|
2322
|
-
} catch (error) {
|
|
2323
|
-
console.warn("[memory-store/http] chroma upsert failed:", error);
|
|
2324
|
-
}
|
|
2325
|
-
try {
|
|
2326
|
-
const messagesWithEmbeddings = messages.filter(
|
|
2327
|
-
(m) => Array.isArray(m.embedding) && m.embedding.length > 0
|
|
2328
|
-
);
|
|
2329
|
-
if (messagesWithEmbeddings.length > 0) {
|
|
2330
|
-
for (const message of messagesWithEmbeddings) {
|
|
2331
|
-
if (typeof manager3.upsertVectorForMessage === "function") {
|
|
2332
|
-
manager3.upsertVectorForMessage(message.messageId, message.embedding);
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
console.log(
|
|
2336
|
-
"[memory-store/http] Updated sqlite-vec vector table for",
|
|
2337
|
-
messagesWithEmbeddings.length,
|
|
2338
|
-
"message(s)"
|
|
2339
|
-
);
|
|
2340
|
-
}
|
|
2341
|
-
} catch (error) {
|
|
2342
|
-
console.warn("[memory-store/http] sqlite-vec vector update failed:", error);
|
|
2343
|
-
}
|
|
2685
|
+
const persisted = await persistRawMessages({
|
|
2686
|
+
manager: manager3,
|
|
2687
|
+
userId: body.userId,
|
|
2688
|
+
messages: incoming,
|
|
2689
|
+
embedOnInsert: body.embedOnInsert,
|
|
2690
|
+
unified: options.unified,
|
|
2691
|
+
externalIndex: options.unified?.rawMessageChildIndex
|
|
2692
|
+
});
|
|
2693
|
+
const result = persisted.ids ? { inserted: persisted.ids.length, ids: persisted.ids } : { inserted: persisted.count };
|
|
2344
2694
|
return c.json({
|
|
2345
2695
|
ok: true,
|
|
2346
|
-
count:
|
|
2696
|
+
count: persisted.count,
|
|
2347
2697
|
result,
|
|
2348
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
2698
|
+
...persisted.warnings.length > 0 ? { warnings: persisted.warnings } : {}
|
|
2349
2699
|
});
|
|
2350
2700
|
});
|
|
2351
2701
|
app.get("/v1/raw-messages/:id", async (c) => {
|
|
@@ -2444,7 +2794,17 @@ async function startHttpServer(options = {}) {
|
|
|
2444
2794
|
});
|
|
2445
2795
|
const okfHttpSpecifier = "@melandlabs/okf/http";
|
|
2446
2796
|
const { registerOkfRoutes } = await import(okfHttpSpecifier);
|
|
2447
|
-
registerOkfRoutes(app, rawStore
|
|
2797
|
+
registerOkfRoutes(app, rawStore, {
|
|
2798
|
+
writeMessages: async (userId, messages) => {
|
|
2799
|
+
await persistRawMessages({
|
|
2800
|
+
manager: manager2,
|
|
2801
|
+
userId,
|
|
2802
|
+
messages,
|
|
2803
|
+
unified: options.unified,
|
|
2804
|
+
externalIndex: options.unified?.rawMessageChildIndex
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
});
|
|
2448
2808
|
const server = serve({ fetch: app.fetch, port, hostname: host });
|
|
2449
2809
|
return {
|
|
2450
2810
|
url: `http://${host}:${port}`,
|
|
@@ -2796,6 +3156,121 @@ function createUserVoiceRewriter(options) {
|
|
|
2796
3156
|
};
|
|
2797
3157
|
}
|
|
2798
3158
|
|
|
3159
|
+
// src/storage/raw-message-child-vector-index.ts
|
|
3160
|
+
var RawMessageChildVectorIndex = class {
|
|
3161
|
+
backend;
|
|
3162
|
+
store;
|
|
3163
|
+
catalog;
|
|
3164
|
+
constructor(options) {
|
|
3165
|
+
this.backend = options.backend;
|
|
3166
|
+
this.store = options.store;
|
|
3167
|
+
this.catalog = options.catalog;
|
|
3168
|
+
}
|
|
3169
|
+
async replaceMessages(messages, chunks) {
|
|
3170
|
+
const chunksByMessage = /* @__PURE__ */ new Map();
|
|
3171
|
+
for (const chunk of chunks) {
|
|
3172
|
+
const group = chunksByMessage.get(chunk.messageId) ?? [];
|
|
3173
|
+
group.push(chunk);
|
|
3174
|
+
chunksByMessage.set(chunk.messageId, group);
|
|
3175
|
+
}
|
|
3176
|
+
for (const message of messages) {
|
|
3177
|
+
await this.store.deleteDocument(message.messageId);
|
|
3178
|
+
const vectorChunks = (chunksByMessage.get(message.messageId) ?? []).filter(
|
|
3179
|
+
(chunk) => Boolean(chunk.embedding?.length) && !chunk.embedding?.every((value) => value === 0)
|
|
3180
|
+
).map((chunk) => ({
|
|
3181
|
+
id: chunk.chunkId,
|
|
3182
|
+
documentId: message.messageId,
|
|
3183
|
+
content: chunk.content,
|
|
3184
|
+
embedding: chunk.embedding,
|
|
3185
|
+
metadata: {
|
|
3186
|
+
userId: message.userId,
|
|
3187
|
+
botId: message.botId,
|
|
3188
|
+
platform: message.platform,
|
|
3189
|
+
timestamp: message.timestamp,
|
|
3190
|
+
chunkIndex: chunk.chunkIndex,
|
|
3191
|
+
chunkCount: chunk.chunkCount,
|
|
3192
|
+
startPosition: chunk.startPosition,
|
|
3193
|
+
endPosition: chunk.endPosition,
|
|
3194
|
+
contentHash: chunk.contentHash,
|
|
3195
|
+
embeddingModel: chunk.embeddingModel
|
|
3196
|
+
}
|
|
3197
|
+
}));
|
|
3198
|
+
if (vectorChunks.length > 0) await this.store.addChunks(vectorChunks);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
async search(input) {
|
|
3202
|
+
const results = input.hybrid && this.store.hybridSearch ? await this.store.hybridSearch({
|
|
3203
|
+
text: input.query ?? "",
|
|
3204
|
+
vector: input.queryEmbedding,
|
|
3205
|
+
limit: input.limit,
|
|
3206
|
+
candidateLimit: input.limit,
|
|
3207
|
+
fusion: "rrf",
|
|
3208
|
+
filter: { userId: input.userId }
|
|
3209
|
+
}) : await this.store.similaritySearch(input.queryEmbedding, input.limit, input.userId);
|
|
3210
|
+
return this.hydrate(results, input);
|
|
3211
|
+
}
|
|
3212
|
+
async getChunkCount() {
|
|
3213
|
+
return this.store.getChunkCount();
|
|
3214
|
+
}
|
|
3215
|
+
async hydrate(results, input) {
|
|
3216
|
+
const matchedChunks = await this.catalog.getRawMessageSearchChunks({
|
|
3217
|
+
chunkIds: results.map((result) => result.id),
|
|
3218
|
+
userId: input.userId
|
|
3219
|
+
});
|
|
3220
|
+
const matchedById = new Map(matchedChunks.map((chunk) => [chunk.chunkId, chunk]));
|
|
3221
|
+
const messageIds = [...new Set(matchedChunks.map((chunk) => chunk.messageId))];
|
|
3222
|
+
const [allChunks, parents] = await Promise.all([
|
|
3223
|
+
this.catalog.getRawMessageSearchChunks({ messageIds, userId: input.userId }),
|
|
3224
|
+
Promise.all(messageIds.map((messageId) => this.catalog.getMessageById(messageId)))
|
|
3225
|
+
]);
|
|
3226
|
+
const parentById = new Map(
|
|
3227
|
+
parents.filter((parent) => Boolean(parent)).map((parent) => [parent.messageId, parent])
|
|
3228
|
+
);
|
|
3229
|
+
const chunksByMessage = /* @__PURE__ */ new Map();
|
|
3230
|
+
for (const chunk of allChunks) {
|
|
3231
|
+
const group = chunksByMessage.get(chunk.messageId) ?? [];
|
|
3232
|
+
group.push(chunk);
|
|
3233
|
+
chunksByMessage.set(chunk.messageId, group);
|
|
3234
|
+
}
|
|
3235
|
+
const strongest = /* @__PURE__ */ new Map();
|
|
3236
|
+
for (const result of results) {
|
|
3237
|
+
const chunk = matchedById.get(result.id);
|
|
3238
|
+
if (!chunk) continue;
|
|
3239
|
+
const parent = parentById.get(chunk.messageId);
|
|
3240
|
+
if (!parent || input.botId && parent.botId !== input.botId) continue;
|
|
3241
|
+
if (input.threshold !== void 0 && result.score < input.threshold) continue;
|
|
3242
|
+
const siblings = (chunksByMessage.get(parent.messageId) ?? []).sort(
|
|
3243
|
+
(a, b) => a.chunkIndex - b.chunkIndex
|
|
3244
|
+
);
|
|
3245
|
+
const content = chunk.chunkCount <= 1 ? parent.content : parent.content.slice(
|
|
3246
|
+
siblings[Math.max(0, chunk.chunkIndex - 1)]?.startPosition ?? chunk.startPosition,
|
|
3247
|
+
siblings[Math.min(siblings.length - 1, chunk.chunkIndex + 1)]?.endPosition ?? chunk.endPosition
|
|
3248
|
+
);
|
|
3249
|
+
const hit = {
|
|
3250
|
+
id: parent.messageId,
|
|
3251
|
+
content,
|
|
3252
|
+
similarity: result.score,
|
|
3253
|
+
metadata: {
|
|
3254
|
+
...parent.metadata ?? {},
|
|
3255
|
+
userId: parent.userId,
|
|
3256
|
+
botId: parent.botId,
|
|
3257
|
+
platform: parent.platform,
|
|
3258
|
+
timestamp: parent.timestamp,
|
|
3259
|
+
sourceMessageId: parent.messageId,
|
|
3260
|
+
sourceChunkId: chunk.chunkId,
|
|
3261
|
+
sourceChunkIndex: chunk.chunkIndex,
|
|
3262
|
+
sourceChunkCount: chunk.chunkCount,
|
|
3263
|
+
backend: this.backend,
|
|
3264
|
+
scoring: input.hybrid ? "native-hybrid" : "dense"
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
const existing = strongest.get(parent.messageId);
|
|
3268
|
+
if (!existing || hit.similarity > existing.similarity) strongest.set(parent.messageId, hit);
|
|
3269
|
+
}
|
|
3270
|
+
return [...strongest.values()].sort((a, b) => b.similarity - a.similarity).slice(0, input.limit);
|
|
3271
|
+
}
|
|
3272
|
+
};
|
|
3273
|
+
|
|
2799
3274
|
// src/server/cli-shared.ts
|
|
2800
3275
|
init_raw_message_store();
|
|
2801
3276
|
|
|
@@ -2810,7 +3285,7 @@ function isInsightSQLiteVecEnabled(_env) {
|
|
|
2810
3285
|
}
|
|
2811
3286
|
async function getInsightSQLiteVecStore(_env) {
|
|
2812
3287
|
const dbPath = resolveSQLiteRawMessageDbPath();
|
|
2813
|
-
const { getSQLiteVecStore } = await import('@melandlabs/rag/sqlite-vec-store');
|
|
3288
|
+
const { getSQLiteVecStore } = await import('@melandlabs/ai-rag/sqlite-vec-store');
|
|
2814
3289
|
return await getSQLiteVecStore(dbPath, void 0, {
|
|
2815
3290
|
collectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || "opencontext_insights"
|
|
2816
3291
|
});
|
|
@@ -2834,17 +3309,19 @@ async function searchInsightsWithSQLiteVec(input, env) {
|
|
|
2834
3309
|
// src/server/cli-shared.ts
|
|
2835
3310
|
async function loadAiRag() {
|
|
2836
3311
|
try {
|
|
2837
|
-
const [local, chroma] = await Promise.all([
|
|
3312
|
+
const [local, reranker, chroma] = await Promise.all([
|
|
2838
3313
|
import('@melandlabs/ai-rag/local-transformers-embedding-provider'),
|
|
3314
|
+
import('@melandlabs/ai-rag/local-transformers-reranker'),
|
|
2839
3315
|
import('@melandlabs/ai-rag/chroma-store')
|
|
2840
3316
|
]);
|
|
2841
3317
|
return {
|
|
2842
3318
|
LocalTransformersEmbeddingProvider: local.LocalTransformersEmbeddingProvider,
|
|
3319
|
+
LocalTransformersReranker: reranker.LocalTransformersReranker,
|
|
2843
3320
|
ChromaVectorStore: chroma.ChromaVectorStore
|
|
2844
3321
|
};
|
|
2845
3322
|
} catch (error) {
|
|
2846
3323
|
throw new Error(
|
|
2847
|
-
|
|
3324
|
+
`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})`
|
|
2848
3325
|
);
|
|
2849
3326
|
}
|
|
2850
3327
|
}
|
|
@@ -2853,8 +3330,21 @@ function parseUnifiedArgs(argv) {
|
|
|
2853
3330
|
const args = {
|
|
2854
3331
|
embeddingProvider: env.EMBEDDING_PROVIDER ?? "none",
|
|
2855
3332
|
embeddingModel: env.EMBEDDING_MODEL,
|
|
3333
|
+
embeddingCacheDir: env.LOCAL_EMBEDDING_CACHE_DIR,
|
|
3334
|
+
rerankerProvider: env.RERANKER_PROVIDER ?? "none",
|
|
3335
|
+
rerankerModel: env.LOCAL_RERANKER_MODEL,
|
|
3336
|
+
rerankerCacheDir: env.LOCAL_RERANKER_CACHE_DIR,
|
|
3337
|
+
rerankerBatchSize: env.LOCAL_RERANKER_BATCH_SIZE ? Number.parseInt(env.LOCAL_RERANKER_BATCH_SIZE, 10) : void 0,
|
|
3338
|
+
rerankerMaxTokens: env.LOCAL_RERANKER_MAX_TOKENS ? Number.parseInt(env.LOCAL_RERANKER_MAX_TOKENS, 10) : void 0,
|
|
2856
3339
|
chromaUrl: env.CHROMA_URL,
|
|
2857
3340
|
memoryBackend: env.MEMORY_BACKEND ?? "none",
|
|
3341
|
+
lancedbUri: env.LANCEDB_URI,
|
|
3342
|
+
lancedbTable: env.LANCEDB_TABLE,
|
|
3343
|
+
milvusAddress: env.MILVUS_ADDRESS,
|
|
3344
|
+
milvusToken: env.MILVUS_TOKEN,
|
|
3345
|
+
milvusDatabase: env.MILVUS_DATABASE,
|
|
3346
|
+
milvusCollection: env.MILVUS_COLLECTION,
|
|
3347
|
+
milvusDimension: env.MILVUS_DIMENSION ? Number.parseInt(env.MILVUS_DIMENSION, 10) : void 0,
|
|
2858
3348
|
insightsBackend: env.INSIGHTS_BACKEND ?? "none",
|
|
2859
3349
|
insightsCollection: env.INSIGHTS_COLLECTION ?? "opencontext_insights",
|
|
2860
3350
|
knowledgeBackend: env.KNOWLEDGE_BACKEND ?? "none",
|
|
@@ -2879,12 +3369,51 @@ function parseUnifiedArgs(argv) {
|
|
|
2879
3369
|
case "--embedding-model":
|
|
2880
3370
|
args.embeddingModel = takeValue();
|
|
2881
3371
|
break;
|
|
3372
|
+
case "--embedding-cache-dir":
|
|
3373
|
+
args.embeddingCacheDir = takeValue();
|
|
3374
|
+
break;
|
|
3375
|
+
case "--reranker-provider":
|
|
3376
|
+
args.rerankerProvider = takeValue();
|
|
3377
|
+
break;
|
|
3378
|
+
case "--reranker-model":
|
|
3379
|
+
args.rerankerModel = takeValue();
|
|
3380
|
+
break;
|
|
3381
|
+
case "--reranker-cache-dir":
|
|
3382
|
+
args.rerankerCacheDir = takeValue();
|
|
3383
|
+
break;
|
|
3384
|
+
case "--reranker-batch-size":
|
|
3385
|
+
args.rerankerBatchSize = Number.parseInt(takeValue(), 10);
|
|
3386
|
+
break;
|
|
3387
|
+
case "--reranker-max-tokens":
|
|
3388
|
+
args.rerankerMaxTokens = Number.parseInt(takeValue(), 10);
|
|
3389
|
+
break;
|
|
2882
3390
|
case "--chroma-url":
|
|
2883
3391
|
args.chromaUrl = takeValue();
|
|
2884
3392
|
break;
|
|
2885
3393
|
case "--memory-backend":
|
|
2886
3394
|
args.memoryBackend = takeValue();
|
|
2887
3395
|
break;
|
|
3396
|
+
case "--lancedb-uri":
|
|
3397
|
+
args.lancedbUri = takeValue();
|
|
3398
|
+
break;
|
|
3399
|
+
case "--lancedb-table":
|
|
3400
|
+
args.lancedbTable = takeValue();
|
|
3401
|
+
break;
|
|
3402
|
+
case "--milvus-address":
|
|
3403
|
+
args.milvusAddress = takeValue();
|
|
3404
|
+
break;
|
|
3405
|
+
case "--milvus-token":
|
|
3406
|
+
args.milvusToken = takeValue();
|
|
3407
|
+
break;
|
|
3408
|
+
case "--milvus-database":
|
|
3409
|
+
args.milvusDatabase = takeValue();
|
|
3410
|
+
break;
|
|
3411
|
+
case "--milvus-collection":
|
|
3412
|
+
args.milvusCollection = takeValue();
|
|
3413
|
+
break;
|
|
3414
|
+
case "--milvus-dimension":
|
|
3415
|
+
args.milvusDimension = Number.parseInt(takeValue(), 10);
|
|
3416
|
+
break;
|
|
2888
3417
|
case "--insights-backend":
|
|
2889
3418
|
args.insightsBackend = takeValue();
|
|
2890
3419
|
break;
|
|
@@ -2914,31 +3443,60 @@ function parseUnifiedArgs(argv) {
|
|
|
2914
3443
|
break;
|
|
2915
3444
|
}
|
|
2916
3445
|
}
|
|
2917
|
-
|
|
2918
|
-
["--embedding-provider", args.embeddingProvider],
|
|
2919
|
-
["--memory-backend", args.memoryBackend],
|
|
2920
|
-
["--insights-backend", args.insightsBackend],
|
|
2921
|
-
["--knowledge-backend", args.knowledgeBackend]
|
|
2922
|
-
]) {
|
|
2923
|
-
const allowed = ["local", "openrouter", "none", "chroma", "sqlite-vec"];
|
|
3446
|
+
const validate = (name, value, allowed) => {
|
|
2924
3447
|
if (!allowed.includes(value)) {
|
|
2925
3448
|
throw new Error(`[memory-store/cli] ${name} must be one of: ${allowed.join(", ")} (got "${value}")`);
|
|
2926
3449
|
}
|
|
3450
|
+
};
|
|
3451
|
+
validate("--embedding-provider", args.embeddingProvider, ["local", "openrouter", "none"]);
|
|
3452
|
+
validate("--reranker-provider", args.rerankerProvider, ["local", "none"]);
|
|
3453
|
+
validate("--memory-backend", args.memoryBackend, ["sqlite-vec", "chroma", "lancedb", "milvus", "none"]);
|
|
3454
|
+
validate("--insights-backend", args.insightsBackend, ["sqlite-vec", "chroma", "none"]);
|
|
3455
|
+
validate("--knowledge-backend", args.knowledgeBackend, ["chroma", "none"]);
|
|
3456
|
+
if (args.milvusDimension !== void 0 && (!Number.isInteger(args.milvusDimension) || args.milvusDimension <= 0)) {
|
|
3457
|
+
throw new Error("[memory-store/cli] --milvus-dimension must be a positive integer");
|
|
3458
|
+
}
|
|
3459
|
+
for (const [name, value] of [
|
|
3460
|
+
["--reranker-batch-size", args.rerankerBatchSize],
|
|
3461
|
+
["--reranker-max-tokens", args.rerankerMaxTokens]
|
|
3462
|
+
]) {
|
|
3463
|
+
if (value !== void 0 && (!Number.isInteger(value) || value <= 0)) {
|
|
3464
|
+
throw new Error(`[memory-store/cli] ${name} must be a positive integer`);
|
|
3465
|
+
}
|
|
2927
3466
|
}
|
|
2928
3467
|
return args;
|
|
2929
3468
|
}
|
|
2930
3469
|
async function buildUnified(args) {
|
|
2931
3470
|
const unified = {};
|
|
2932
3471
|
const log = (msg) => console.warn(`[memory-store/cli] ${msg}`);
|
|
2933
|
-
const wantsAiRag = args.embeddingProvider === "local" || args.
|
|
3472
|
+
const wantsAiRag = args.embeddingProvider === "local" || args.rerankerProvider === "local" || args.insightsBackend === "chroma" || args.knowledgeBackend === "chroma";
|
|
2934
3473
|
const aiRag = wantsAiRag ? await loadAiRag() : null;
|
|
2935
3474
|
if (args.embeddingProvider === "local") {
|
|
2936
3475
|
try {
|
|
2937
3476
|
if (!aiRag) {
|
|
2938
3477
|
throw new Error("ai-rag modules not loaded");
|
|
2939
3478
|
}
|
|
2940
|
-
const provider = new aiRag.LocalTransformersEmbeddingProvider({
|
|
2941
|
-
|
|
3479
|
+
const provider = new aiRag.LocalTransformersEmbeddingProvider({
|
|
3480
|
+
modelName: args.embeddingModel,
|
|
3481
|
+
cacheDir: args.embeddingCacheDir
|
|
3482
|
+
});
|
|
3483
|
+
unified.embeddingInfo = {
|
|
3484
|
+
provider: "local",
|
|
3485
|
+
model: args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2",
|
|
3486
|
+
maxTokens: 512
|
|
3487
|
+
};
|
|
3488
|
+
unified.embedQuery = async ({ query }) => {
|
|
3489
|
+
const embedding = await provider.embedQuery(query);
|
|
3490
|
+
if (unified.embeddingInfo) unified.embeddingInfo.dimensions = embedding.length;
|
|
3491
|
+
return embedding;
|
|
3492
|
+
};
|
|
3493
|
+
unified.embedDocuments = async ({ texts }) => {
|
|
3494
|
+
const embeddings = await provider.embedDocuments(texts);
|
|
3495
|
+
if (unified.embeddingInfo && embeddings[0]) {
|
|
3496
|
+
unified.embeddingInfo.dimensions = embeddings[0].length;
|
|
3497
|
+
}
|
|
3498
|
+
return embeddings;
|
|
3499
|
+
};
|
|
2942
3500
|
log(
|
|
2943
3501
|
`embedQuery wired via LocalTransformersEmbeddingProvider (model=${args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2"})`
|
|
2944
3502
|
);
|
|
@@ -2954,6 +3512,7 @@ async function buildUnified(args) {
|
|
|
2954
3512
|
}
|
|
2955
3513
|
const model = args.embeddingModel ?? "text-embedding-3-small";
|
|
2956
3514
|
const baseURL = "https://openrouter.ai/api/v1";
|
|
3515
|
+
unified.embeddingInfo = { provider: "openrouter", model };
|
|
2957
3516
|
unified.embedQuery = async ({ query }) => {
|
|
2958
3517
|
const res = await fetch(`${baseURL}/embeddings`, {
|
|
2959
3518
|
method: "POST",
|
|
@@ -2971,49 +3530,150 @@ async function buildUnified(args) {
|
|
|
2971
3530
|
const body = await res.json();
|
|
2972
3531
|
const embedding = body.data?.[0]?.embedding;
|
|
2973
3532
|
if (!embedding) throw new Error("openrouter embeddings response missing data[0].embedding");
|
|
3533
|
+
if (unified.embeddingInfo) unified.embeddingInfo.dimensions = embedding.length;
|
|
2974
3534
|
return embedding;
|
|
2975
3535
|
};
|
|
2976
3536
|
log(`embedQuery wired via openrouter (model=${model})`);
|
|
2977
3537
|
}
|
|
2978
|
-
if (args.
|
|
2979
|
-
|
|
2980
|
-
const
|
|
2981
|
-
|
|
3538
|
+
if (args.rerankerProvider === "local") {
|
|
3539
|
+
if (!aiRag) throw new Error("Local reranker modules were not loaded");
|
|
3540
|
+
const reranker = new aiRag.LocalTransformersReranker({
|
|
3541
|
+
modelName: args.rerankerModel,
|
|
3542
|
+
cacheDir: args.rerankerCacheDir,
|
|
3543
|
+
batchSize: args.rerankerBatchSize,
|
|
3544
|
+
maxTokens: args.rerankerMaxTokens
|
|
3545
|
+
});
|
|
3546
|
+
await reranker.warmup();
|
|
3547
|
+
unified.reranker = reranker;
|
|
3548
|
+
unified.rerankerInfo = {
|
|
3549
|
+
provider: "local",
|
|
3550
|
+
model: reranker.getModelName(),
|
|
3551
|
+
maxTokens: reranker.getMaxTokens(),
|
|
3552
|
+
ready: true
|
|
3553
|
+
};
|
|
3554
|
+
log(`reranker wired via local Transformers.js (model=${reranker.getModelName()})`);
|
|
3555
|
+
} else {
|
|
3556
|
+
unified.rerankerInfo = { provider: "none", ready: false };
|
|
3557
|
+
}
|
|
3558
|
+
if (args.memoryBackend !== "none") {
|
|
3559
|
+
if (args.memoryBackend === "chroma" && !args.chromaUrl) {
|
|
3560
|
+
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
3561
|
+
}
|
|
3562
|
+
if (args.memoryBackend === "lancedb" && !args.lancedbUri) {
|
|
3563
|
+
throw new Error("--memory-backend=lancedb requires --lancedb-uri <uri> (or LANCEDB_URI env)");
|
|
3564
|
+
}
|
|
3565
|
+
if (args.memoryBackend === "milvus" && !args.milvusAddress) {
|
|
3566
|
+
throw new Error("--memory-backend=milvus requires --milvus-address <address> (or MILVUS_ADDRESS env)");
|
|
3567
|
+
}
|
|
3568
|
+
if (args.memoryBackend === "lancedb") {
|
|
3569
|
+
try {
|
|
3570
|
+
await import('@lancedb/lancedb');
|
|
3571
|
+
} catch (error) {
|
|
3572
|
+
throw new Error(
|
|
3573
|
+
`LanceDB requires the optional dependency @lancedb/lancedb (${error.message})`
|
|
3574
|
+
);
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
if (args.memoryBackend === "milvus") {
|
|
3578
|
+
try {
|
|
3579
|
+
await import('@zilliz/milvus2-sdk-node');
|
|
3580
|
+
} catch (error) {
|
|
3581
|
+
throw new Error(
|
|
3582
|
+
`Milvus requires the optional dependency @zilliz/milvus2-sdk-node (${error.message})`
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
const rawStore = createRawMessageStore({ env: void 0 });
|
|
3587
|
+
const manager2 = await rawStore.getManager();
|
|
3588
|
+
if (typeof manager2.getRawMessageSearchChunks !== "function" || typeof manager2.getRawMessageSearchIndexStats !== "function") {
|
|
2982
3589
|
throw new Error(
|
|
2983
|
-
|
|
3590
|
+
`${args.memoryBackend} child retrieval requires the SQLite raw-message catalog; the Postgres manager remains on the legacy parent fallback in this release`
|
|
2984
3591
|
);
|
|
2985
3592
|
}
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
3593
|
+
if (typeof manager2.lexicalSearchMessages === "function") {
|
|
3594
|
+
unified.searchRawMessagesLexical = async (input) => {
|
|
3595
|
+
const rows = await manager2.lexicalSearchMessages(input);
|
|
3596
|
+
return rows.map((row) => ({ ...row, metadata: row.metadata ?? {} }));
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
let externalStore;
|
|
3600
|
+
if (args.memoryBackend === "chroma") {
|
|
3601
|
+
externalStore = new ChromaVectorStore({
|
|
3602
|
+
url: args.chromaUrl,
|
|
3603
|
+
collectionName: "opencontext_raw_message_chunks"
|
|
3604
|
+
});
|
|
3605
|
+
} else if (args.memoryBackend === "lancedb") {
|
|
3606
|
+
const { LanceDBStore } = await import('@melandlabs/rag/lancedb-store');
|
|
3607
|
+
externalStore = new LanceDBStore({
|
|
3608
|
+
uri: args.lancedbUri,
|
|
3609
|
+
tableName: args.lancedbTable ?? "opencontext_raw_message_chunks",
|
|
3610
|
+
defaultFusion: "rrf",
|
|
3611
|
+
candidateMultiplier: 4
|
|
3612
|
+
});
|
|
3613
|
+
} else if (args.memoryBackend === "milvus") {
|
|
3614
|
+
const { MilvusStore } = await import('@melandlabs/rag/milvus-store');
|
|
3615
|
+
externalStore = new MilvusStore({
|
|
3616
|
+
address: args.milvusAddress,
|
|
3617
|
+
token: args.milvusToken,
|
|
3618
|
+
database: args.milvusDatabase,
|
|
3619
|
+
collectionName: args.milvusCollection ?? "opencontext_raw_message_chunks",
|
|
3620
|
+
dimension: args.milvusDimension,
|
|
3621
|
+
defaultFusion: "rrf",
|
|
3622
|
+
candidateMultiplier: 4
|
|
2993
3623
|
});
|
|
2994
|
-
return rows.map((r) => ({
|
|
2995
|
-
id: r.id,
|
|
2996
|
-
content: r.content,
|
|
2997
|
-
similarity: r.similarity,
|
|
2998
|
-
metadata: r.metadata ?? {}
|
|
2999
|
-
}));
|
|
3000
|
-
};
|
|
3001
|
-
log("memory backend wired via sqlite-vec (uses the manager's searchMessagesSemantically)");
|
|
3002
|
-
} else if (args.memoryBackend === "chroma") {
|
|
3003
|
-
if (!args.chromaUrl)
|
|
3004
|
-
throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
|
|
3005
|
-
if (!aiRag) {
|
|
3006
|
-
throw new Error("ai-rag modules not loaded");
|
|
3007
3624
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3625
|
+
if (args.memoryBackend === "sqlite-vec") {
|
|
3626
|
+
if (typeof manager2.searchMessagesSemantically !== "function") {
|
|
3627
|
+
throw new Error("sqlite-vec child search is unavailable in the active raw-message manager");
|
|
3628
|
+
}
|
|
3629
|
+
unified.searchRawMessagesAnn = async ({ userId, queryEmbedding, limit, threshold, botId }) => {
|
|
3630
|
+
const rows = await manager2.searchMessagesSemantically({
|
|
3631
|
+
userId,
|
|
3632
|
+
queryEmbedding,
|
|
3633
|
+
limit,
|
|
3634
|
+
threshold,
|
|
3635
|
+
botId
|
|
3636
|
+
});
|
|
3637
|
+
return rows.map((row) => ({ ...row, metadata: row.metadata ?? {} }));
|
|
3638
|
+
};
|
|
3639
|
+
} else if (externalStore) {
|
|
3640
|
+
const childIndex = new RawMessageChildVectorIndex({
|
|
3641
|
+
backend: args.memoryBackend,
|
|
3642
|
+
store: externalStore,
|
|
3643
|
+
catalog: {
|
|
3644
|
+
getMessageById: (messageId) => manager2.getMessageById(messageId),
|
|
3645
|
+
// biome-ignore lint/style/noNonNullAssertion: guarded above
|
|
3646
|
+
getRawMessageSearchChunks: (input) => manager2.getRawMessageSearchChunks(input)
|
|
3647
|
+
}
|
|
3648
|
+
});
|
|
3649
|
+
unified.rawMessageChildIndex = childIndex;
|
|
3650
|
+
unified.searchRawMessagesAnn = (input) => childIndex.search(input);
|
|
3651
|
+
if (args.memoryBackend !== "chroma") {
|
|
3652
|
+
unified.searchRawMessagesHybrid = (input) => childIndex.search({ ...input, hybrid: true });
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
unified.getRawMessageRetrievalStatus = async () => {
|
|
3656
|
+
const stats = await manager2.getRawMessageSearchIndexStats();
|
|
3657
|
+
const embeddingDimensions = unified.embeddingInfo?.dimensions;
|
|
3658
|
+
const dimensionMismatch = embeddingDimensions !== void 0 && stats.embeddingDimensions.length > 0 && !stats.embeddingDimensions.includes(embeddingDimensions);
|
|
3659
|
+
const semanticReady = Boolean(unified.embedQuery) && (args.memoryBackend === "sqlite-vec" ? stats.semanticReady : stats.embeddedChunkCount > 0) && !dimensionMismatch;
|
|
3660
|
+
return {
|
|
3661
|
+
backend: args.memoryBackend,
|
|
3662
|
+
embeddingProvider: unified.embeddingInfo?.provider,
|
|
3663
|
+
embeddingModel: unified.embeddingInfo?.model,
|
|
3664
|
+
embeddingDimensions,
|
|
3665
|
+
childCount: stats.chunkCount,
|
|
3666
|
+
embeddedChildCount: stats.embeddedChunkCount,
|
|
3667
|
+
indexedDimensions: stats.embeddingDimensions,
|
|
3668
|
+
semanticReady,
|
|
3669
|
+
lexicalReady: stats.lexicalReady,
|
|
3670
|
+
semanticDegradedReason: dimensionMismatch ? "semantic_dimension_mismatch" : !unified.embedQuery ? "semantic_unavailable" : stats.embeddedChunkCount === 0 ? "semantic_child_vectors_empty" : void 0,
|
|
3671
|
+
rerankerProvider: unified.rerankerInfo?.provider,
|
|
3672
|
+
rerankerModel: unified.rerankerInfo?.model,
|
|
3673
|
+
rerankerReady: unified.rerankerInfo?.ready ?? false
|
|
3674
|
+
};
|
|
3015
3675
|
};
|
|
3016
|
-
log(
|
|
3676
|
+
log(`memory backend wired via ${args.memoryBackend} with SQLite child catalog and lexical search`);
|
|
3017
3677
|
}
|
|
3018
3678
|
if (args.insightsBackend === "sqlite-vec" && isInsightSQLiteVecEnabled()) {
|
|
3019
3679
|
if (!unified.embedQuery) {
|
|
@@ -3140,20 +3800,41 @@ function printUnifiedHelp() {
|
|
|
3140
3800
|
--embedding-model <name> Model name
|
|
3141
3801
|
(env: EMBEDDING_MODEL; local \u2192 Xenova/all-MiniLM-L6-v2,
|
|
3142
3802
|
openrouter \u2192 text-embedding-3-small)
|
|
3803
|
+
--embedding-cache-dir <path> Local embedding cache (env: LOCAL_EMBEDDING_CACHE_DIR)
|
|
3143
3804
|
Note: "local" dynamically imports @melandlabs/ai-rag (a peer install).
|
|
3144
3805
|
|
|
3806
|
+
Reranking (after RRF/source fusion, before final Top-K):
|
|
3807
|
+
--reranker-provider <name> local | none
|
|
3808
|
+
(env: RERANKER_PROVIDER, default: none)
|
|
3809
|
+
--reranker-model <name> Local sequence-classification model
|
|
3810
|
+
(env: LOCAL_RERANKER_MODEL,
|
|
3811
|
+
default: Xenova/ms-marco-MiniLM-L-6-v2)
|
|
3812
|
+
--reranker-cache-dir <path> Persistent model cache (env: LOCAL_RERANKER_CACHE_DIR)
|
|
3813
|
+
--reranker-batch-size <int> Pair scoring batch size (env: LOCAL_RERANKER_BATCH_SIZE,
|
|
3814
|
+
default: 8)
|
|
3815
|
+
--reranker-max-tokens <int> Query/document pair token limit
|
|
3816
|
+
(env: LOCAL_RERANKER_MAX_TOKENS, default: 512)
|
|
3817
|
+
|
|
3145
3818
|
Cross-source search (wires unified.searchKnowledge / searchInsights / searchRawMessagesAnn):
|
|
3146
3819
|
--chroma-url <url> Chroma server URL
|
|
3147
3820
|
(env: CHROMA_URL; required when any *-backend=chroma)
|
|
3148
|
-
--memory-backend <name> sqlite-vec | chroma | none
|
|
3821
|
+
--memory-backend <name> sqlite-vec | chroma | lancedb | milvus | none
|
|
3149
3822
|
(env: MEMORY_BACKEND, default: none)
|
|
3823
|
+
--lancedb-uri <uri> LanceDB directory or URI (env: LANCEDB_URI)
|
|
3824
|
+
--lancedb-table <name> Optional table name (env: LANCEDB_TABLE)
|
|
3825
|
+
--milvus-address <address> Milvus endpoint (env: MILVUS_ADDRESS)
|
|
3826
|
+
--milvus-token <token> Optional Milvus token (env: MILVUS_TOKEN)
|
|
3827
|
+
--milvus-database <name> Optional database (env: MILVUS_DATABASE)
|
|
3828
|
+
--milvus-collection <name> Optional collection (env: MILVUS_COLLECTION)
|
|
3829
|
+
--milvus-dimension <int> Optional vector dimension (env: MILVUS_DIMENSION)
|
|
3150
3830
|
--insights-backend <name> sqlite-vec | chroma | none
|
|
3151
3831
|
(env: INSIGHTS_BACKEND, default: none)
|
|
3152
3832
|
--insights-collection <name> Chroma collection (default: opencontext_insights)
|
|
3153
3833
|
--knowledge-backend <name> chroma | none
|
|
3154
3834
|
(env: KNOWLEDGE_BACKEND, default: none)
|
|
3155
3835
|
--knowledge-collection <name> Chroma collection (default: opencontext_knowledge)
|
|
3156
|
-
|
|
3836
|
+
Notes: memory Chroma uses @melandlabs/rag; insight/knowledge Chroma use
|
|
3837
|
+
@melandlabs/ai-rag. LanceDB and Milvus require their optional peer dependencies.
|
|
3157
3838
|
|
|
3158
3839
|
Reasoning (wires unified.reasoning.{queryRewriter, iterativePlanner}):
|
|
3159
3840
|
--reasoning Enable the LLM reasoning layer so /v1/search and
|