@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
|
@@ -249,6 +249,69 @@ var init_raw_message_store = __esm({
|
|
|
249
249
|
init_chroma_memory_index();
|
|
250
250
|
init_raw_message_store();
|
|
251
251
|
|
|
252
|
+
// src/search/applicability.ts
|
|
253
|
+
var APPLICABILITY_SCOPES = /* @__PURE__ */ new Set([
|
|
254
|
+
"global",
|
|
255
|
+
"task",
|
|
256
|
+
"conversation",
|
|
257
|
+
"channel",
|
|
258
|
+
"project",
|
|
259
|
+
"custom"
|
|
260
|
+
]);
|
|
261
|
+
function isRecord(value) {
|
|
262
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
263
|
+
}
|
|
264
|
+
function invalidRuntimeContext(message) {
|
|
265
|
+
throw new TypeError(`memory-store search runtime context: ${message}`);
|
|
266
|
+
}
|
|
267
|
+
function validateApplicabilityContext(value, index) {
|
|
268
|
+
const path = `applicabilityContexts[${index}]`;
|
|
269
|
+
if (!isRecord(value)) {
|
|
270
|
+
invalidRuntimeContext(`${path} must be an object`);
|
|
271
|
+
}
|
|
272
|
+
const scope = value.scope;
|
|
273
|
+
if (typeof scope !== "string" || !APPLICABILITY_SCOPES.has(scope)) {
|
|
274
|
+
invalidRuntimeContext(`${path}.scope is not supported`);
|
|
275
|
+
}
|
|
276
|
+
if (value.key !== void 0 && typeof value.key !== "string") {
|
|
277
|
+
invalidRuntimeContext(`${path}.key must be a string when provided`);
|
|
278
|
+
}
|
|
279
|
+
if (scope !== "global" && (typeof value.key !== "string" || value.key.trim().length === 0)) {
|
|
280
|
+
invalidRuntimeContext(`${path}.key must be non-empty for scope "${scope}"`);
|
|
281
|
+
}
|
|
282
|
+
for (const field of ["validFrom", "validUntil"]) {
|
|
283
|
+
const boundary = value[field];
|
|
284
|
+
if (boundary !== void 0 && (typeof boundary !== "number" || !Number.isFinite(boundary))) {
|
|
285
|
+
invalidRuntimeContext(`${path}.${field} must be a finite epoch-millisecond number`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function resolveSearchRuntimeContext(input, runtimeContext, now = Date.now) {
|
|
290
|
+
if (runtimeContext === void 0) {
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
if (!isRecord(runtimeContext) || !Array.isArray(runtimeContext.applicabilityContexts)) {
|
|
294
|
+
invalidRuntimeContext("applicabilityContexts must be an array");
|
|
295
|
+
}
|
|
296
|
+
for (const [index, context] of runtimeContext.applicabilityContexts.entries()) {
|
|
297
|
+
validateApplicabilityContext(context, index);
|
|
298
|
+
}
|
|
299
|
+
let applicabilityAt;
|
|
300
|
+
if (input.asOf === void 0) {
|
|
301
|
+
applicabilityAt = now();
|
|
302
|
+
} else {
|
|
303
|
+
const parsed = typeof input.asOf === "string" ? Date.parse(input.asOf) : Number.NaN;
|
|
304
|
+
if (!Number.isFinite(parsed)) {
|
|
305
|
+
invalidRuntimeContext("asOf must be a parseable timestamp when a runtime context is supplied");
|
|
306
|
+
}
|
|
307
|
+
applicabilityAt = parsed;
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
applicabilityContexts: runtimeContext.applicabilityContexts,
|
|
311
|
+
applicabilityAt
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
252
315
|
// src/search/utilities.ts
|
|
253
316
|
function deriveLexicalKeywords(query) {
|
|
254
317
|
return query.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((token) => token.length >= 2).slice(0, 16);
|
|
@@ -300,7 +363,7 @@ function normalizeUnifiedMemoryMergeStrategy(value) {
|
|
|
300
363
|
return value === "rrf" ? "rrf" : "similarity";
|
|
301
364
|
}
|
|
302
365
|
function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none") {
|
|
303
|
-
if (value === "rewrite" || value === "iterative") {
|
|
366
|
+
if (value === "rewrite" || value === "iterative" || value === "union") {
|
|
304
367
|
return value;
|
|
305
368
|
}
|
|
306
369
|
return defaultStrategy;
|
|
@@ -407,6 +470,7 @@ function materializeSignals(lists, hit) {
|
|
|
407
470
|
function listNameToChannel(name) {
|
|
408
471
|
if (name === "memory-semantic") return "semantic";
|
|
409
472
|
if (name === "memory-bm25" || name === "memory-lexical") return "lexical";
|
|
473
|
+
if (name === "memory-hybrid") return "hybrid";
|
|
410
474
|
if (name === "memory-entity") return "entity";
|
|
411
475
|
return void 0;
|
|
412
476
|
}
|
|
@@ -616,7 +680,6 @@ async function applyReranker(reranker, query, results) {
|
|
|
616
680
|
|
|
617
681
|
// src/search/gather-evidence.ts
|
|
618
682
|
var DEFAULT_TIERS2 = ["summary", "raw", "insight", "knowledge"];
|
|
619
|
-
var MAX_EVIDENCE_CHARS = 600;
|
|
620
683
|
function toSummaryResult(row) {
|
|
621
684
|
const start = typeof row.startTimestamp === "number" ? row.startTimestamp : void 0;
|
|
622
685
|
const end = typeof row.endTimestamp === "number" ? row.endTimestamp : void 0;
|
|
@@ -634,7 +697,7 @@ function toSummaryResult(row) {
|
|
|
634
697
|
}
|
|
635
698
|
};
|
|
636
699
|
}
|
|
637
|
-
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers) {
|
|
700
|
+
async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers, runtimeContext) {
|
|
638
701
|
if (typeof deps.searchSummaries !== "function") {
|
|
639
702
|
return { hits: [], warnings: [] };
|
|
640
703
|
}
|
|
@@ -647,7 +710,8 @@ async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers)
|
|
|
647
710
|
limit,
|
|
648
711
|
threshold,
|
|
649
712
|
authToken: input.authToken,
|
|
650
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
713
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
714
|
+
...runtimeContext ?? {}
|
|
651
715
|
});
|
|
652
716
|
const hits = rows.map(toSummaryResult);
|
|
653
717
|
return { hits, warnings: [] };
|
|
@@ -665,12 +729,6 @@ async function gatherSummaries(deps, input, limit, threshold, logger, peerPeers)
|
|
|
665
729
|
};
|
|
666
730
|
}
|
|
667
731
|
}
|
|
668
|
-
function truncate(input, max = MAX_EVIDENCE_CHARS) {
|
|
669
|
-
if (input.length <= max) {
|
|
670
|
-
return input;
|
|
671
|
-
}
|
|
672
|
-
return `${input.slice(0, max - 1)}\u2026`;
|
|
673
|
-
}
|
|
674
732
|
function buildSynthesisPrompt(input) {
|
|
675
733
|
const grouped = /* @__PURE__ */ new Map();
|
|
676
734
|
for (const item of input.evidence) {
|
|
@@ -684,9 +742,10 @@ function buildSynthesisPrompt(input) {
|
|
|
684
742
|
if (items.length === 0) {
|
|
685
743
|
continue;
|
|
686
744
|
}
|
|
687
|
-
const lines = items.map(
|
|
688
|
-
|
|
689
|
-
|
|
745
|
+
const lines = items.map((item, index) => {
|
|
746
|
+
const factSource = typeof item.metadata?.source === "string" && item.metadata.source.length > 0 ? item.metadata.source : item.source ?? "unknown";
|
|
747
|
+
return ` [${index + 1}] (${item.id}, source=${factSource}, score=${item.score.toFixed(4)}) ${item.snippet}`;
|
|
748
|
+
});
|
|
690
749
|
sections.push(`## ${tier}
|
|
691
750
|
${lines.join("\n")}`);
|
|
692
751
|
}
|
|
@@ -768,12 +827,15 @@ function coerceAnswer(text, schema) {
|
|
|
768
827
|
return text;
|
|
769
828
|
}
|
|
770
829
|
async function synthesizeAnswer(input) {
|
|
771
|
-
const { query,
|
|
772
|
-
const evidence =
|
|
773
|
-
id:
|
|
774
|
-
source:
|
|
775
|
-
snippet:
|
|
776
|
-
score:
|
|
830
|
+
const { query, responseSchema, deps, logger } = input;
|
|
831
|
+
const evidence = input.evidence.map((item) => ({
|
|
832
|
+
id: item.id,
|
|
833
|
+
source: mapEvidenceSourceToTier(item.source),
|
|
834
|
+
snippet: item.snippet,
|
|
835
|
+
score: item.score,
|
|
836
|
+
// Forward per-fact provenance (e.g. `metadata.source` from
|
|
837
|
+
// `opencontext add --source`) so the synthesis prompt can cite it.
|
|
838
|
+
metadata: item.metadata
|
|
777
839
|
}));
|
|
778
840
|
const warnings = [];
|
|
779
841
|
const complete = deps.reasoning?.complete;
|
|
@@ -804,21 +866,10 @@ async function synthesizeAnswer(input) {
|
|
|
804
866
|
return { answer: "", warnings };
|
|
805
867
|
}
|
|
806
868
|
}
|
|
807
|
-
function
|
|
808
|
-
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
}
|
|
812
|
-
if (hit.type === "memory") {
|
|
813
|
-
return "raw";
|
|
814
|
-
}
|
|
815
|
-
if (hit.type === "insight") {
|
|
816
|
-
return "insight";
|
|
817
|
-
}
|
|
818
|
-
if (hit.type === "knowledge") {
|
|
819
|
-
return "knowledge";
|
|
820
|
-
}
|
|
821
|
-
return hit.type;
|
|
869
|
+
function mapEvidenceSourceToTier(source) {
|
|
870
|
+
if (source === "memory") return "raw";
|
|
871
|
+
if (source === "insights") return "insight";
|
|
872
|
+
return source;
|
|
822
873
|
}
|
|
823
874
|
async function resolveSearchScopePeers(input) {
|
|
824
875
|
return resolveScopePeer({
|
|
@@ -842,7 +893,7 @@ function deriveEntityKeywords(query) {
|
|
|
842
893
|
}
|
|
843
894
|
return out.slice(0, 16);
|
|
844
895
|
}
|
|
845
|
-
async function runEntitySearchForQuery(deps, input, limit, logger, peerPeers = []) {
|
|
896
|
+
async function runEntitySearchForQuery(deps, input, limit, logger, peerPeers = [], runtimeContext) {
|
|
846
897
|
if (typeof deps.entitySearch !== "function") {
|
|
847
898
|
return [];
|
|
848
899
|
}
|
|
@@ -860,7 +911,8 @@ async function runEntitySearchForQuery(deps, input, limit, logger, peerPeers = [
|
|
|
860
911
|
keywords,
|
|
861
912
|
limit: Math.ceil(limit / filters.length),
|
|
862
913
|
botId,
|
|
863
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
914
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
915
|
+
...runtimeContext ?? {}
|
|
864
916
|
})
|
|
865
917
|
)
|
|
866
918
|
)).flat();
|
|
@@ -948,6 +1000,16 @@ function filterByDateRange(items, dateFrom, dateTo) {
|
|
|
948
1000
|
return true;
|
|
949
1001
|
});
|
|
950
1002
|
}
|
|
1003
|
+
function warnApplicabilityNotEnforced(warnings) {
|
|
1004
|
+
if (warnings.some((warning) => warning.code === "memory_applicability_not_enforced")) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
warnings.push({
|
|
1008
|
+
source: "memory",
|
|
1009
|
+
code: "memory_applicability_not_enforced",
|
|
1010
|
+
message: "Built-in raw-message retrieval was skipped because it cannot enforce the supplied applicability context. Configure an applicability-aware raw-message provider."
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
951
1013
|
async function embedQueryVariant(embedQuery, input, query) {
|
|
952
1014
|
return embedQuery({
|
|
953
1015
|
userId: input.userId,
|
|
@@ -955,10 +1017,29 @@ async function embedQueryVariant(embedQuery, input, query) {
|
|
|
955
1017
|
authToken: input.authToken
|
|
956
1018
|
});
|
|
957
1019
|
}
|
|
958
|
-
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers
|
|
1020
|
+
async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext) {
|
|
959
1021
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
|
|
960
1022
|
let semantic = [];
|
|
961
|
-
if (
|
|
1023
|
+
if (typeof deps.searchRawMessagesAnn === "function") {
|
|
1024
|
+
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
1025
|
+
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1026
|
+
const includeDeprecated = input.includeDeprecated === true;
|
|
1027
|
+
semantic = (await Promise.all(
|
|
1028
|
+
filters.map(
|
|
1029
|
+
(filter) => searchRawMessagesAnn({
|
|
1030
|
+
userId: input.userId,
|
|
1031
|
+
queryEmbedding,
|
|
1032
|
+
limit,
|
|
1033
|
+
threshold,
|
|
1034
|
+
botId: "botId" in filter ? filter.botId : void 0,
|
|
1035
|
+
includeDeprecated,
|
|
1036
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1037
|
+
...factTypes ? { factTypes } : {},
|
|
1038
|
+
...runtimeContext ?? {}
|
|
1039
|
+
})
|
|
1040
|
+
)
|
|
1041
|
+
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1042
|
+
} else if (runtimeContext === void 0 && isRawMessageChromaEnabled()) {
|
|
962
1043
|
try {
|
|
963
1044
|
semantic = (await Promise.all(
|
|
964
1045
|
filters.map((filter) => {
|
|
@@ -983,24 +1064,12 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
983
1064
|
error
|
|
984
1065
|
);
|
|
985
1066
|
}
|
|
986
|
-
} else if (typeof deps.searchRawMessagesAnn === "function") {
|
|
987
|
-
const searchRawMessagesAnn = deps.searchRawMessagesAnn;
|
|
988
|
-
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
989
|
-
semantic = (await Promise.all(
|
|
990
|
-
filters.map(
|
|
991
|
-
(filter) => searchRawMessagesAnn({
|
|
992
|
-
userId: input.userId,
|
|
993
|
-
queryEmbedding,
|
|
994
|
-
limit,
|
|
995
|
-
threshold,
|
|
996
|
-
botId: "botId" in filter ? filter.botId : void 0,
|
|
997
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
998
|
-
...factTypes ? { factTypes } : {}
|
|
999
|
-
})
|
|
1000
|
-
)
|
|
1001
|
-
)).flat().filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1002
1067
|
}
|
|
1003
|
-
if (semantic.length === 0) {
|
|
1068
|
+
if (semantic.length === 0 && runtimeContext !== void 0) {
|
|
1069
|
+
warnApplicabilityNotEnforced(warnings);
|
|
1070
|
+
return [];
|
|
1071
|
+
}
|
|
1072
|
+
if (semantic.length === 0 && typeof deps.searchRawMessagesAnn !== "function") {
|
|
1004
1073
|
try {
|
|
1005
1074
|
const { getRawMessageManager: getRawMessageManager2 } = await Promise.resolve().then(() => (init_raw_message_store(), raw_message_store_exports));
|
|
1006
1075
|
const manager2 = await getRawMessageManager2();
|
|
@@ -1011,6 +1080,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
|
|
|
1011
1080
|
queryEmbedding,
|
|
1012
1081
|
limit,
|
|
1013
1082
|
threshold,
|
|
1083
|
+
includeDeprecated: input.includeDeprecated === true,
|
|
1014
1084
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1015
1085
|
...factTypes ? { factTypes } : {}
|
|
1016
1086
|
});
|
|
@@ -1040,6 +1110,18 @@ function mergeByMaxScore(lists) {
|
|
|
1040
1110
|
}
|
|
1041
1111
|
return Array.from(best.values()).sort((a, b) => b.similarity - a.similarity);
|
|
1042
1112
|
}
|
|
1113
|
+
function dedupeChannelByParent(hits) {
|
|
1114
|
+
const strongest = /* @__PURE__ */ new Map();
|
|
1115
|
+
for (const hit of hits) {
|
|
1116
|
+
const sourceMessageId = hit.metadata?.sourceMessageId;
|
|
1117
|
+
const parentId = typeof sourceMessageId === "string" && sourceMessageId.length > 0 ? sourceMessageId : hit.id;
|
|
1118
|
+
const normalized = hit.type === "memory" && hit.id !== parentId ? { ...hit, id: parentId } : hit;
|
|
1119
|
+
const key = `${normalized.type}:${parentId}`;
|
|
1120
|
+
const current = strongest.get(key);
|
|
1121
|
+
if (!current || normalized.similarity > current.similarity) strongest.set(key, normalized);
|
|
1122
|
+
}
|
|
1123
|
+
return [...strongest.values()].sort((a, b) => b.similarity - a.similarity);
|
|
1124
|
+
}
|
|
1043
1125
|
function toIterativeRecallCandidate(hit) {
|
|
1044
1126
|
return {
|
|
1045
1127
|
id: hit.id,
|
|
@@ -1048,7 +1130,7 @@ function toIterativeRecallCandidate(hit) {
|
|
|
1048
1130
|
metadata: hit.metadata
|
|
1049
1131
|
};
|
|
1050
1132
|
}
|
|
1051
|
-
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers
|
|
1133
|
+
async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext) {
|
|
1052
1134
|
if (keywords.length === 0) {
|
|
1053
1135
|
return [];
|
|
1054
1136
|
}
|
|
@@ -1057,6 +1139,7 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1057
1139
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1058
1140
|
const searchRawMessagesLexical = deps.searchRawMessagesLexical;
|
|
1059
1141
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1142
|
+
const includeDeprecated = input.includeDeprecated === true;
|
|
1060
1143
|
return (await Promise.all(
|
|
1061
1144
|
filters.map(
|
|
1062
1145
|
(botId) => searchRawMessagesLexical({
|
|
@@ -1064,8 +1147,10 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1064
1147
|
keywords,
|
|
1065
1148
|
limit: Math.ceil(limit / filters.length),
|
|
1066
1149
|
botId,
|
|
1150
|
+
includeDeprecated,
|
|
1067
1151
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1068
|
-
...factTypes ? { factTypes } : {}
|
|
1152
|
+
...factTypes ? { factTypes } : {},
|
|
1153
|
+
...runtimeContext ?? {}
|
|
1069
1154
|
})
|
|
1070
1155
|
)
|
|
1071
1156
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -1076,10 +1161,15 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1076
1161
|
logger.warn?.("[memory-store] lexical memory search failed:", error);
|
|
1077
1162
|
}
|
|
1078
1163
|
}
|
|
1164
|
+
if (runtimeContext !== void 0) {
|
|
1165
|
+
warnApplicabilityNotEnforced(warnings);
|
|
1166
|
+
return [];
|
|
1167
|
+
}
|
|
1079
1168
|
try {
|
|
1080
1169
|
const { lexicalSearchRawMessages: lexicalSearchRawMessages2 } = await Promise.resolve().then(() => (init_sqlite_raw_message_store(), sqlite_raw_message_store_exports));
|
|
1081
1170
|
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1082
1171
|
const factTypes = input.factTypes?.length ? input.factTypes : void 0;
|
|
1172
|
+
const includeDeprecated = input.includeDeprecated === true;
|
|
1083
1173
|
return (await Promise.all(
|
|
1084
1174
|
filters.map(
|
|
1085
1175
|
(botId) => lexicalSearchRawMessages2({
|
|
@@ -1087,6 +1177,7 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
|
|
|
1087
1177
|
keywords,
|
|
1088
1178
|
limit: Math.ceil(limit / filters.length),
|
|
1089
1179
|
botId,
|
|
1180
|
+
includeDeprecated,
|
|
1090
1181
|
...factTypes ? { factTypes } : {}
|
|
1091
1182
|
})
|
|
1092
1183
|
)
|
|
@@ -1117,7 +1208,9 @@ function searchInputToUnified(input) {
|
|
|
1117
1208
|
mergeStrategy: input.mergeStrategy,
|
|
1118
1209
|
peerFilter: input.peerFilter,
|
|
1119
1210
|
reasoningStrategy: input.reasoningStrategy,
|
|
1120
|
-
factTypes: input.factTypes
|
|
1211
|
+
factTypes: input.factTypes,
|
|
1212
|
+
includeRetrievalDiagnostics: input.includeRetrievalDiagnostics,
|
|
1213
|
+
includeDeprecated: input.includeDeprecated
|
|
1121
1214
|
};
|
|
1122
1215
|
}
|
|
1123
1216
|
function mapHitToEvidenceSource(hit) {
|
|
@@ -1145,8 +1238,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1145
1238
|
}
|
|
1146
1239
|
return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
|
|
1147
1240
|
}
|
|
1148
|
-
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = []) {
|
|
1149
|
-
if (reasoningStrategy === "iterative" && !deps.reasoning?.iterativePlanner) {
|
|
1241
|
+
async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = [], runtimeContext) {
|
|
1242
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !deps.reasoning?.iterativePlanner) {
|
|
1150
1243
|
warnings.push({
|
|
1151
1244
|
source: "memory",
|
|
1152
1245
|
code: "memory_iterative_planner_not_configured",
|
|
@@ -1162,7 +1255,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1162
1255
|
});
|
|
1163
1256
|
if (reasoningInfo) reasoningInfo.degraded = true;
|
|
1164
1257
|
}
|
|
1165
|
-
|
|
1258
|
+
let unionEvidence = null;
|
|
1259
|
+
if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && deps.reasoning?.iterativePlanner) {
|
|
1166
1260
|
const planner = deps.reasoning.iterativePlanner;
|
|
1167
1261
|
const result = await planner.plan({
|
|
1168
1262
|
query: input.query,
|
|
@@ -1177,7 +1271,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1177
1271
|
keywords2,
|
|
1178
1272
|
limit,
|
|
1179
1273
|
logger,
|
|
1180
|
-
peerPeers
|
|
1274
|
+
peerPeers,
|
|
1275
|
+
warnings,
|
|
1276
|
+
runtimeContext
|
|
1181
1277
|
);
|
|
1182
1278
|
let semanticHits = [];
|
|
1183
1279
|
if (typeof deps.embedQuery === "function") {
|
|
@@ -1191,7 +1287,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1191
1287
|
limit,
|
|
1192
1288
|
threshold,
|
|
1193
1289
|
logger,
|
|
1194
|
-
peerPeers
|
|
1290
|
+
peerPeers,
|
|
1291
|
+
warnings,
|
|
1292
|
+
runtimeContext
|
|
1195
1293
|
);
|
|
1196
1294
|
} catch (error) {
|
|
1197
1295
|
logger.warn?.(
|
|
@@ -1219,16 +1317,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1219
1317
|
reasoningInfo.degraded = true;
|
|
1220
1318
|
}
|
|
1221
1319
|
}
|
|
1222
|
-
const
|
|
1320
|
+
const evidence = result.evidence.map((candidate) => ({
|
|
1223
1321
|
type: "memory",
|
|
1224
1322
|
id: candidate.id,
|
|
1225
1323
|
content: candidate.content,
|
|
1226
1324
|
similarity: candidate.similarity,
|
|
1227
|
-
metadata: { ...candidate.metadata, reasoning:
|
|
1325
|
+
metadata: { ...candidate.metadata, reasoning: reasoningStrategy }
|
|
1228
1326
|
}));
|
|
1229
|
-
|
|
1327
|
+
if (reasoningStrategy === "iterative") {
|
|
1328
|
+
return { semantic: evidence, lexical: [] };
|
|
1329
|
+
}
|
|
1330
|
+
unionEvidence = evidence;
|
|
1230
1331
|
}
|
|
1231
1332
|
if (typeof deps.embedQuery !== "function") {
|
|
1333
|
+
warnings.push({
|
|
1334
|
+
source: "memory",
|
|
1335
|
+
code: "semantic_unavailable",
|
|
1336
|
+
message: "Semantic search is unavailable because no embedding provider is configured."
|
|
1337
|
+
});
|
|
1232
1338
|
warnings.push({
|
|
1233
1339
|
source: "memory",
|
|
1234
1340
|
code: "memory_lexical_search_fallback",
|
|
@@ -1236,7 +1342,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1236
1342
|
});
|
|
1237
1343
|
const keywords2 = deriveLexicalKeywords(input.query);
|
|
1238
1344
|
const lexical2 = filterByDateRange(
|
|
1239
|
-
await runLexicalSearchForKeywords(
|
|
1345
|
+
await runLexicalSearchForKeywords(
|
|
1346
|
+
deps,
|
|
1347
|
+
input,
|
|
1348
|
+
keywords2,
|
|
1349
|
+
limit,
|
|
1350
|
+
logger,
|
|
1351
|
+
peerPeers,
|
|
1352
|
+
warnings,
|
|
1353
|
+
runtimeContext
|
|
1354
|
+
),
|
|
1240
1355
|
input.dateFrom,
|
|
1241
1356
|
input.dateTo
|
|
1242
1357
|
);
|
|
@@ -1244,6 +1359,26 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1244
1359
|
}
|
|
1245
1360
|
let semantic = [];
|
|
1246
1361
|
const embedQuery = deps.embedQuery;
|
|
1362
|
+
const activeMergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1363
|
+
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1364
|
+
);
|
|
1365
|
+
if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof deps.searchRawMessagesHybrid === "function") {
|
|
1366
|
+
const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
|
|
1367
|
+
const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
|
|
1368
|
+
const hybrid = (await Promise.all(
|
|
1369
|
+
filters.map(
|
|
1370
|
+
(botId) => deps.searchRawMessagesHybrid?.({
|
|
1371
|
+
userId: input.userId,
|
|
1372
|
+
query: input.query,
|
|
1373
|
+
queryEmbedding,
|
|
1374
|
+
limit: Math.ceil(limit / filters.length),
|
|
1375
|
+
botId,
|
|
1376
|
+
...runtimeContext ?? {}
|
|
1377
|
+
})
|
|
1378
|
+
)
|
|
1379
|
+
)).flatMap((items) => items ?? []).filter(isRawMemorySemanticResult).map(toMemoryResult);
|
|
1380
|
+
return { semantic: [], lexical: [], hybrid: filterByDateRange(hybrid, input.dateFrom, input.dateTo) };
|
|
1381
|
+
}
|
|
1247
1382
|
if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
|
|
1248
1383
|
try {
|
|
1249
1384
|
const variants = await deps.reasoning.queryRewriter.rewrite({
|
|
@@ -1262,7 +1397,17 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1262
1397
|
);
|
|
1263
1398
|
const lists = await Promise.all(
|
|
1264
1399
|
embeddings.map(
|
|
1265
|
-
(embedding) => runSemanticSearchForEmbedding(
|
|
1400
|
+
(embedding) => runSemanticSearchForEmbedding(
|
|
1401
|
+
deps,
|
|
1402
|
+
input,
|
|
1403
|
+
embedding,
|
|
1404
|
+
limit,
|
|
1405
|
+
threshold,
|
|
1406
|
+
logger,
|
|
1407
|
+
peerPeers,
|
|
1408
|
+
warnings,
|
|
1409
|
+
runtimeContext
|
|
1410
|
+
)
|
|
1266
1411
|
)
|
|
1267
1412
|
);
|
|
1268
1413
|
semantic = mergeByMaxScore(lists);
|
|
@@ -1285,7 +1430,9 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1285
1430
|
limit,
|
|
1286
1431
|
threshold,
|
|
1287
1432
|
logger,
|
|
1288
|
-
peerPeers
|
|
1433
|
+
peerPeers,
|
|
1434
|
+
warnings,
|
|
1435
|
+
runtimeContext
|
|
1289
1436
|
);
|
|
1290
1437
|
}
|
|
1291
1438
|
let lexical = [];
|
|
@@ -1304,7 +1451,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1304
1451
|
limit,
|
|
1305
1452
|
botId,
|
|
1306
1453
|
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1307
|
-
...factTypes ? { factTypes } : {}
|
|
1454
|
+
...factTypes ? { factTypes } : {},
|
|
1455
|
+
...runtimeContext ?? {}
|
|
1308
1456
|
})
|
|
1309
1457
|
)
|
|
1310
1458
|
)).flat().filter(isRawMemorySemanticResult).map((hit) => ({
|
|
@@ -1330,7 +1478,7 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1330
1478
|
let entity;
|
|
1331
1479
|
if (typeof deps.entitySearch === "function") {
|
|
1332
1480
|
entity = filterByDateRange(
|
|
1333
|
-
await runEntitySearchForQuery(deps, input, limit, logger, peerPeers),
|
|
1481
|
+
await runEntitySearchForQuery(deps, input, limit, logger, peerPeers, runtimeContext),
|
|
1334
1482
|
input.dateFrom,
|
|
1335
1483
|
input.dateTo
|
|
1336
1484
|
);
|
|
@@ -1352,6 +1500,37 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1352
1500
|
message: "RRF merge requested but entity search is not configured; falling back to semantic+lexical RRF."
|
|
1353
1501
|
});
|
|
1354
1502
|
}
|
|
1503
|
+
if (unionEvidence) {
|
|
1504
|
+
const plannerHits = unionEvidence;
|
|
1505
|
+
const base = mergeByMaxScore([
|
|
1506
|
+
filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
1507
|
+
filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
1508
|
+
]);
|
|
1509
|
+
const maxBase = base.length > 0 ? Math.max(...base.map((h) => h.similarity)) : 0;
|
|
1510
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1511
|
+
const merged = [];
|
|
1512
|
+
plannerHits.forEach((hit, i) => {
|
|
1513
|
+
if (seen.has(hit.id)) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
seen.add(hit.id);
|
|
1517
|
+
merged.push({
|
|
1518
|
+
...hit,
|
|
1519
|
+
similarity: maxBase + (plannerHits.length - i) * 1e-6
|
|
1520
|
+
});
|
|
1521
|
+
});
|
|
1522
|
+
for (const hit of base) {
|
|
1523
|
+
if (merged.length >= limit) {
|
|
1524
|
+
break;
|
|
1525
|
+
}
|
|
1526
|
+
if (seen.has(hit.id)) {
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
seen.add(hit.id);
|
|
1530
|
+
merged.push(hit);
|
|
1531
|
+
}
|
|
1532
|
+
return { semantic: merged.slice(0, limit), lexical: [] };
|
|
1533
|
+
}
|
|
1355
1534
|
const out = {
|
|
1356
1535
|
semantic: filterByDateRange(semantic, input.dateFrom, input.dateTo),
|
|
1357
1536
|
lexical: filterByDateRange(lexical, input.dateFrom, input.dateTo)
|
|
@@ -1361,14 +1540,16 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1361
1540
|
}
|
|
1362
1541
|
return out;
|
|
1363
1542
|
}
|
|
1364
|
-
async function
|
|
1543
|
+
async function searchUnifiedMemoryWithRuntime(input, runtimeContext) {
|
|
1365
1544
|
const query = input.query.trim();
|
|
1366
1545
|
const sources = normalizeUnifiedMemorySearchSources(input.sources);
|
|
1367
1546
|
const limit = clampUnifiedMemorySearchLimit(input.limit);
|
|
1368
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1369
1547
|
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1370
1548
|
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1371
1549
|
);
|
|
1550
|
+
const candidateLimit = Math.min(50, Math.max(limit, limit * 4));
|
|
1551
|
+
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1552
|
+
const memoryThreshold = input.threshold === void 0 && mergeStrategy === "rrf" ? Number.NEGATIVE_INFINITY : threshold;
|
|
1372
1553
|
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1373
1554
|
const warnings = [];
|
|
1374
1555
|
const peerScope = input.peerFilter && input.peerFilter.length > 0 ? await resolveScopePeer({
|
|
@@ -1388,23 +1569,46 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1388
1569
|
};
|
|
1389
1570
|
}
|
|
1390
1571
|
let memorySubs;
|
|
1572
|
+
let retrievalStatus;
|
|
1391
1573
|
const reasoningInfo = { strategy: reasoningStrategy };
|
|
1392
1574
|
if (input.dateFrom || input.dateTo) {
|
|
1393
1575
|
reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
|
|
1394
1576
|
}
|
|
1395
1577
|
if (sources.includes("memory")) {
|
|
1396
|
-
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1578
|
+
const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesHybrid === "function" || typeof deps.searchRawMessagesLexical === "function";
|
|
1397
1579
|
if (isRawMessageStorageAvailable() || hasRawProviders) {
|
|
1398
1580
|
try {
|
|
1399
1581
|
memorySubs = await runMemorySource(
|
|
1400
1582
|
input,
|
|
1401
|
-
|
|
1402
|
-
|
|
1583
|
+
candidateLimit,
|
|
1584
|
+
memoryThreshold,
|
|
1403
1585
|
warnings,
|
|
1404
1586
|
reasoningStrategy,
|
|
1405
1587
|
reasoningInfo,
|
|
1406
|
-
peerPeers
|
|
1588
|
+
peerPeers,
|
|
1589
|
+
runtimeContext
|
|
1407
1590
|
);
|
|
1591
|
+
memorySubs = {
|
|
1592
|
+
semantic: dedupeChannelByParent(memorySubs.semantic),
|
|
1593
|
+
lexical: dedupeChannelByParent(memorySubs.lexical),
|
|
1594
|
+
...memorySubs.hybrid ? { hybrid: dedupeChannelByParent(memorySubs.hybrid) } : {},
|
|
1595
|
+
...memorySubs.entity ? { entity: dedupeChannelByParent(memorySubs.entity) } : {}
|
|
1596
|
+
};
|
|
1597
|
+
if (deps.getRawMessageRetrievalStatus) {
|
|
1598
|
+
retrievalStatus = await deps.getRawMessageRetrievalStatus();
|
|
1599
|
+
}
|
|
1600
|
+
if (memorySubs.semantic.length === 0 && (memorySubs.hybrid?.length ?? 0) === 0 && typeof deps.embedQuery === "function") {
|
|
1601
|
+
let code = "semantic_candidates_empty";
|
|
1602
|
+
let message = "Semantic search was available but returned no candidates.";
|
|
1603
|
+
if (retrievalStatus?.embeddedChildCount === 0) {
|
|
1604
|
+
code = "semantic_unavailable";
|
|
1605
|
+
message = "No embedded RawMessage children are available for semantic search.";
|
|
1606
|
+
} else if (retrievalStatus?.embeddingDimensions && retrievalStatus.indexedDimensions.length > 0 && !retrievalStatus.indexedDimensions.includes(retrievalStatus.embeddingDimensions)) {
|
|
1607
|
+
code = "semantic_dimension_mismatch";
|
|
1608
|
+
message = `Query embedding dimension ${retrievalStatus.embeddingDimensions} does not match indexed child dimensions ${retrievalStatus.indexedDimensions.join(", ")}.`;
|
|
1609
|
+
}
|
|
1610
|
+
warnings.push({ source: "memory", code, message });
|
|
1611
|
+
}
|
|
1408
1612
|
} catch (error) {
|
|
1409
1613
|
logger.warn?.("[memory-store] memory source failed:", error);
|
|
1410
1614
|
warnings.push({
|
|
@@ -1427,12 +1631,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1427
1631
|
const hits = await deps.searchInsights({
|
|
1428
1632
|
userId: input.userId,
|
|
1429
1633
|
query: input.query,
|
|
1430
|
-
limit,
|
|
1634
|
+
limit: candidateLimit,
|
|
1431
1635
|
threshold,
|
|
1432
1636
|
botIds: input.botIds,
|
|
1433
1637
|
includeArchived: input.includeArchivedInsights,
|
|
1434
1638
|
authToken: input.authToken,
|
|
1435
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1639
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1640
|
+
...runtimeContext ?? {}
|
|
1436
1641
|
});
|
|
1437
1642
|
for (const hit of hits) {
|
|
1438
1643
|
insightHits.push({
|
|
@@ -1459,12 +1664,13 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1459
1664
|
userId: input.userId,
|
|
1460
1665
|
query: input.query,
|
|
1461
1666
|
options: {
|
|
1462
|
-
limit,
|
|
1667
|
+
limit: candidateLimit,
|
|
1463
1668
|
threshold,
|
|
1464
1669
|
documentIds: input.documentIds
|
|
1465
1670
|
},
|
|
1466
1671
|
authToken: input.authToken,
|
|
1467
|
-
...peerPeers.length > 0 ? { peers: peerPeers } : {}
|
|
1672
|
+
...peerPeers.length > 0 ? { peers: peerPeers } : {},
|
|
1673
|
+
...runtimeContext ?? {}
|
|
1468
1674
|
});
|
|
1469
1675
|
for (const hit of hits) {
|
|
1470
1676
|
knowledgeHits.push(toKnowledgeResult(hit));
|
|
@@ -1482,10 +1688,14 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1482
1688
|
memorySubs,
|
|
1483
1689
|
insightHits,
|
|
1484
1690
|
knowledgeHits,
|
|
1485
|
-
limit,
|
|
1691
|
+
limit: candidateLimit,
|
|
1486
1692
|
strategy: mergeStrategy
|
|
1487
1693
|
});
|
|
1488
|
-
const
|
|
1694
|
+
const rerankerStartedAt = deps.reranker ? Date.now() : void 0;
|
|
1695
|
+
const reranked = await applyReranker(deps.reranker, input.query, merged);
|
|
1696
|
+
const rerankerLatencyMs = rerankerStartedAt === void 0 ? 0 : Date.now() - rerankerStartedAt;
|
|
1697
|
+
const rerankerOrderChanged = Boolean(deps.reranker) && merged.some((hit, index) => hit.id !== reranked[index]?.id);
|
|
1698
|
+
const ranked = reranked.slice(0, limit);
|
|
1489
1699
|
const output = {
|
|
1490
1700
|
query: input.query,
|
|
1491
1701
|
sources,
|
|
@@ -1493,28 +1703,51 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1493
1703
|
count: ranked.length,
|
|
1494
1704
|
warnings
|
|
1495
1705
|
};
|
|
1706
|
+
if (input.includeRetrievalDiagnostics) {
|
|
1707
|
+
output.retrievalDiagnostics = {
|
|
1708
|
+
mergeStrategy,
|
|
1709
|
+
candidateLimit,
|
|
1710
|
+
backend: retrievalStatus?.backend,
|
|
1711
|
+
semanticDegradedReason: retrievalStatus?.semanticDegradedReason ?? warnings.find((warning) => warning.code.startsWith("semantic_"))?.code,
|
|
1712
|
+
candidateCounts: {
|
|
1713
|
+
semantic: memorySubs?.semantic.length ?? 0,
|
|
1714
|
+
lexical: memorySubs?.lexical.length ?? 0,
|
|
1715
|
+
hybrid: memorySubs?.hybrid?.length ?? 0,
|
|
1716
|
+
entity: memorySubs?.entity?.length ?? 0,
|
|
1717
|
+
fused: merged.length,
|
|
1718
|
+
final: ranked.length
|
|
1719
|
+
},
|
|
1720
|
+
channels: {
|
|
1721
|
+
semantic: memorySubs?.semantic ?? [],
|
|
1722
|
+
lexical: memorySubs?.lexical ?? [],
|
|
1723
|
+
...memorySubs?.hybrid ? { hybrid: memorySubs.hybrid } : {},
|
|
1724
|
+
...memorySubs?.entity ? { entity: memorySubs.entity } : {}
|
|
1725
|
+
},
|
|
1726
|
+
fusedBeforeRerank: merged,
|
|
1727
|
+
reranker: {
|
|
1728
|
+
enabled: Boolean(deps.reranker),
|
|
1729
|
+
provider: deps.rerankerInfo?.provider,
|
|
1730
|
+
model: deps.rerankerInfo?.model,
|
|
1731
|
+
inputCount: merged.length,
|
|
1732
|
+
outputCount: reranked.length,
|
|
1733
|
+
latencyMs: rerankerLatencyMs,
|
|
1734
|
+
orderChanged: rerankerOrderChanged
|
|
1735
|
+
},
|
|
1736
|
+
final: ranked
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1496
1739
|
if (reasoningInfo && (reasoningInfo.strategy !== "none" || reasoningInfo.dateRange !== void 0)) {
|
|
1497
1740
|
output.reasoning = reasoningInfo;
|
|
1498
1741
|
}
|
|
1499
1742
|
return output;
|
|
1500
1743
|
}
|
|
1744
|
+
async function searchUnifiedMemory(input) {
|
|
1745
|
+
return searchUnifiedMemoryWithRuntime(input);
|
|
1746
|
+
}
|
|
1501
1747
|
async function searchRawMemorySemantically(input) {
|
|
1502
|
-
|
|
1503
|
-
const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
|
|
1504
|
-
const reasoningStrategy = resolveReasoningStrategy(input);
|
|
1505
|
-
const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
|
|
1506
|
-
input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
|
|
1507
|
-
);
|
|
1508
|
-
const sub = await runMemorySource(input, limit, threshold, [], reasoningStrategy);
|
|
1509
|
-
return mergeAcrossSources({
|
|
1510
|
-
memorySubs: sub,
|
|
1511
|
-
insightHits: [],
|
|
1512
|
-
knowledgeHits: [],
|
|
1513
|
-
limit,
|
|
1514
|
-
strategy: mergeStrategy
|
|
1515
|
-
});
|
|
1748
|
+
return (await searchUnifiedMemory({ ...input, sources: ["memory"] })).results;
|
|
1516
1749
|
}
|
|
1517
|
-
async function
|
|
1750
|
+
async function searchWithResolvedRuntime(input, runtimeContext) {
|
|
1518
1751
|
const query = input.query.trim();
|
|
1519
1752
|
const wantsSynthesis = typeof input.synthesize === "boolean" ? input.synthesize : typeof input.synthesize === "object" && input.synthesize !== null;
|
|
1520
1753
|
const synthesisSchema = typeof input.synthesize === "object" && input.synthesize !== null ? input.synthesize.responseSchema ?? input.responseSchema : input.responseSchema;
|
|
@@ -1546,19 +1779,24 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1546
1779
|
let unifiedHits = [];
|
|
1547
1780
|
let sources = input.sources ? [...input.sources] : [];
|
|
1548
1781
|
let unifiedReasoning;
|
|
1782
|
+
let retrievalDiagnostics;
|
|
1549
1783
|
if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
|
|
1550
1784
|
const inferredSources = sources.length > 0 ? sources : [
|
|
1551
1785
|
...wantsRawTier ? ["memory"] : [],
|
|
1552
1786
|
...wantsInsightTier ? ["insights"] : [],
|
|
1553
1787
|
...wantsKnowledgeTier ? ["knowledge"] : []
|
|
1554
1788
|
];
|
|
1555
|
-
const unified = await
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1789
|
+
const unified = await searchUnifiedMemoryWithRuntime(
|
|
1790
|
+
{
|
|
1791
|
+
...searchInputToUnified(input),
|
|
1792
|
+
sources: inferredSources.length > 0 ? inferredSources : void 0
|
|
1793
|
+
},
|
|
1794
|
+
runtimeContext
|
|
1795
|
+
);
|
|
1559
1796
|
unifiedHits = unified.results;
|
|
1560
1797
|
sources = unified.sources;
|
|
1561
1798
|
unifiedReasoning = unified.reasoning;
|
|
1799
|
+
retrievalDiagnostics = unified.retrievalDiagnostics;
|
|
1562
1800
|
warnings.push(...unified.warnings);
|
|
1563
1801
|
}
|
|
1564
1802
|
let summaryHits = [];
|
|
@@ -1569,7 +1807,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1569
1807
|
limit,
|
|
1570
1808
|
threshold,
|
|
1571
1809
|
logger,
|
|
1572
|
-
peerScope.peers
|
|
1810
|
+
peerScope.peers,
|
|
1811
|
+
runtimeContext
|
|
1573
1812
|
);
|
|
1574
1813
|
warnings.push(...summaryBucket.warnings);
|
|
1575
1814
|
summaryHits = summaryBucket.hits;
|
|
@@ -1594,7 +1833,8 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1594
1833
|
source: mapHitToEvidenceSource(hit),
|
|
1595
1834
|
snippet: hit.content,
|
|
1596
1835
|
score: hit.similarity,
|
|
1597
|
-
timestamp: getCandidateTimestamp(hit.metadata)
|
|
1836
|
+
timestamp: getCandidateTimestamp(hit.metadata),
|
|
1837
|
+
metadata: hit.metadata
|
|
1598
1838
|
}));
|
|
1599
1839
|
const base = {
|
|
1600
1840
|
query,
|
|
@@ -1607,12 +1847,15 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1607
1847
|
if (unifiedReasoning) {
|
|
1608
1848
|
base.reasoning = unifiedReasoning;
|
|
1609
1849
|
}
|
|
1850
|
+
if (retrievalDiagnostics) {
|
|
1851
|
+
base.retrievalDiagnostics = retrievalDiagnostics;
|
|
1852
|
+
}
|
|
1610
1853
|
if (!wantsSynthesis) {
|
|
1611
1854
|
return base;
|
|
1612
1855
|
}
|
|
1613
1856
|
const { answer, warnings: synthWarnings } = await synthesizeAnswer({
|
|
1614
1857
|
query,
|
|
1615
|
-
|
|
1858
|
+
evidence,
|
|
1616
1859
|
responseSchema: synthesisSchema,
|
|
1617
1860
|
deps,
|
|
1618
1861
|
logger
|
|
@@ -1621,6 +1864,10 @@ function createUnifiedSearch(deps = {}) {
|
|
|
1621
1864
|
base.warnings = [...base.warnings, ...synthWarnings];
|
|
1622
1865
|
return base;
|
|
1623
1866
|
}
|
|
1867
|
+
async function search(input, runtimeContext) {
|
|
1868
|
+
const resolvedRuntime = resolveSearchRuntimeContext(input, runtimeContext);
|
|
1869
|
+
return searchWithResolvedRuntime(input, resolvedRuntime);
|
|
1870
|
+
}
|
|
1624
1871
|
return {
|
|
1625
1872
|
search,
|
|
1626
1873
|
searchUnifiedMemory,
|
|
@@ -1636,6 +1883,7 @@ function mergeAcrossSources(input) {
|
|
|
1636
1883
|
for (const hit of [
|
|
1637
1884
|
...memorySubs.semantic,
|
|
1638
1885
|
...memorySubs.lexical,
|
|
1886
|
+
...memorySubs.hybrid ?? [],
|
|
1639
1887
|
...input.insightHits,
|
|
1640
1888
|
...input.knowledgeHits
|
|
1641
1889
|
]) {
|
|
@@ -1651,6 +1899,7 @@ function mergeAcrossSources(input) {
|
|
|
1651
1899
|
const all = [
|
|
1652
1900
|
...memorySubs.semantic,
|
|
1653
1901
|
...memorySubs.lexical,
|
|
1902
|
+
...memorySubs.hybrid ?? [],
|
|
1654
1903
|
...memorySubs.entity ?? [],
|
|
1655
1904
|
...input.insightHits,
|
|
1656
1905
|
...input.knowledgeHits
|
|
@@ -1662,6 +1911,9 @@ function buildChannelLists(memorySubs, insightHits, knowledgeHits) {
|
|
|
1662
1911
|
const lists = [];
|
|
1663
1912
|
if (memorySubs.semantic.length > 0) lists.push({ name: "memory-semantic", hits: memorySubs.semantic });
|
|
1664
1913
|
if (memorySubs.lexical.length > 0) lists.push({ name: "memory-bm25", hits: memorySubs.lexical });
|
|
1914
|
+
if (memorySubs.hybrid && memorySubs.hybrid.length > 0) {
|
|
1915
|
+
lists.push({ name: "memory-hybrid", hits: memorySubs.hybrid });
|
|
1916
|
+
}
|
|
1665
1917
|
if (memorySubs.entity && memorySubs.entity.length > 0) {
|
|
1666
1918
|
lists.push({ name: "memory-entity", hits: memorySubs.entity });
|
|
1667
1919
|
}
|