opensentinel 2.1.1 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +354 -283
- package/dist/archiver-AVNBYCKQ.js +15340 -0
- package/dist/archiver-AVNBYCKQ.js.map +1 -0
- package/dist/audit-logger-OBPR7CRO.js +22 -0
- package/dist/auth-UOX5K2BE.js +18 -0
- package/dist/autonomy-ZXDBDQUJ.js +86 -0
- package/dist/autonomy-ZXDBDQUJ.js.map +1 -0
- package/dist/aws-s3-Q4LLZZPD.js +146 -0
- package/dist/aws-s3-Q4LLZZPD.js.map +1 -0
- package/dist/backup-restore-PZ7CYYB7.js +16 -0
- package/dist/blocks-R3PODY47.js +23 -0
- package/dist/bot-QRARP4UN.js +36 -0
- package/dist/brain-7XLLM3KC.js +56 -0
- package/dist/camera-monitor-M5CYKUU4.js +335 -0
- package/dist/camera-monitor-M5CYKUU4.js.map +1 -0
- package/dist/{charts-MMXM6BWW.js → charts-V7ARZNKF.js} +2 -2
- package/dist/chunk-22VGGA7S.js +330 -0
- package/dist/chunk-22VGGA7S.js.map +1 -0
- package/dist/chunk-35WYTA3C.js +382 -0
- package/dist/chunk-35WYTA3C.js.map +1 -0
- package/dist/chunk-3E2PSU2C.js +146 -0
- package/dist/chunk-3E2PSU2C.js.map +1 -0
- package/dist/{chunk-L3F43VPB.js → chunk-4GLYY4NN.js} +2 -2
- package/dist/{chunk-L3F43VPB.js.map → chunk-4GLYY4NN.js.map} +1 -1
- package/dist/{chunk-L3PDU3XN.js → chunk-4UOE5TUZ.js} +4 -4
- package/dist/{chunk-6SNHU3CY.js → chunk-66OJ3WB4.js} +2 -2
- package/dist/chunk-6KONMXQ6.js +297 -0
- package/dist/chunk-6KONMXQ6.js.map +1 -0
- package/dist/chunk-6PMVAAA7.js +196 -0
- package/dist/chunk-6PMVAAA7.js.map +1 -0
- package/dist/chunk-766ASQWE.js +32620 -0
- package/dist/chunk-766ASQWE.js.map +1 -0
- package/dist/chunk-7WQO5J2M.js +29 -0
- package/dist/chunk-7WQO5J2M.js.map +1 -0
- package/dist/chunk-APHSRMBS.js +148 -0
- package/dist/chunk-APHSRMBS.js.map +1 -0
- package/dist/{chunk-4LVWXUNC.js → chunk-AYUKPTSM.js} +57 -39
- package/dist/chunk-AYUKPTSM.js.map +1 -0
- package/dist/chunk-BIPYADGB.js +84 -0
- package/dist/chunk-BIPYADGB.js.map +1 -0
- package/dist/chunk-BRBWNV65.js +457 -0
- package/dist/chunk-BRBWNV65.js.map +1 -0
- package/dist/chunk-BXZ6EA52.js +382 -0
- package/dist/chunk-BXZ6EA52.js.map +1 -0
- package/dist/chunk-EVE7MIIY.js +290 -0
- package/dist/chunk-EVE7MIIY.js.map +1 -0
- package/dist/chunk-F3TTNID2.js +138 -0
- package/dist/chunk-F3TTNID2.js.map +1 -0
- package/dist/chunk-H5RQOFO2.js +190 -0
- package/dist/chunk-H5RQOFO2.js.map +1 -0
- package/dist/chunk-HN3F4WSW.js +145 -0
- package/dist/chunk-HN3F4WSW.js.map +1 -0
- package/dist/{chunk-6DRDKB45.js → chunk-I6BDYQIG.js} +20 -9
- package/dist/chunk-I6BDYQIG.js.map +1 -0
- package/dist/chunk-IZJMVV7O.js +347 -0
- package/dist/chunk-IZJMVV7O.js.map +1 -0
- package/dist/chunk-KM22GV7G.js +211 -0
- package/dist/chunk-KM22GV7G.js.map +1 -0
- package/dist/chunk-MGFBLVR7.js +103 -0
- package/dist/chunk-MGFBLVR7.js.map +1 -0
- package/dist/chunk-MQJ2ECQT.js +228 -0
- package/dist/chunk-MQJ2ECQT.js.map +1 -0
- package/dist/{chunk-F6QUZQGI.js → chunk-MXAPLSJ5.js} +2 -2
- package/dist/{chunk-GK3E2I7A.js → chunk-NHMBTUMW.js} +2 -2
- package/dist/chunk-NPRTSZIF.js +131 -0
- package/dist/chunk-NPRTSZIF.js.map +1 -0
- package/dist/chunk-O7IH7JTI.js +1898 -0
- package/dist/chunk-O7IH7JTI.js.map +1 -0
- package/dist/chunk-OCVQGBJK.js +293 -0
- package/dist/chunk-OCVQGBJK.js.map +1 -0
- package/dist/chunk-P6QINGFL.js +332 -0
- package/dist/chunk-P6QINGFL.js.map +1 -0
- package/dist/chunk-PHDZKPNE.js +91 -0
- package/dist/chunk-PHDZKPNE.js.map +1 -0
- package/dist/chunk-PLDDJCW6.js +49 -0
- package/dist/chunk-PTGTGXV2.js +164 -0
- package/dist/chunk-PTGTGXV2.js.map +1 -0
- package/dist/chunk-REMIY4U2.js +171 -0
- package/dist/chunk-REMIY4U2.js.map +1 -0
- package/dist/chunk-RZ4YESBG.js +141 -0
- package/dist/chunk-RZ4YESBG.js.map +1 -0
- package/dist/chunk-SAX5MHK4.js +111 -0
- package/dist/chunk-SAX5MHK4.js.map +1 -0
- package/dist/{chunk-GVJVEWHI.js → chunk-SJSUSJ47.js} +2 -2
- package/dist/chunk-SPPMCAKG.js +777 -0
- package/dist/chunk-SPPMCAKG.js.map +1 -0
- package/dist/chunk-SVAPX2XN.js +2441 -0
- package/dist/chunk-SVAPX2XN.js.map +1 -0
- package/dist/chunk-TVEWKIK3.js +452 -0
- package/dist/chunk-TVEWKIK3.js.map +1 -0
- package/dist/{chunk-HH2HBTQM.js → chunk-TYAGMJNV.js} +5 -5
- package/dist/{chunk-JXUP2X7V.js → chunk-VEHFVBLI.js} +2 -2
- package/dist/chunk-VNX5GMTN.js +128 -0
- package/dist/chunk-VNX5GMTN.js.map +1 -0
- package/dist/chunk-VRD5CYRL.js +1568 -0
- package/dist/chunk-VRD5CYRL.js.map +1 -0
- package/dist/chunk-WLUHNG6X.js +122 -0
- package/dist/chunk-WLUHNG6X.js.map +1 -0
- package/dist/chunk-WRAKK6K6.js +265 -0
- package/dist/chunk-WRAKK6K6.js.map +1 -0
- package/dist/chunk-XKYRH4FM.js +681 -0
- package/dist/chunk-XKYRH4FM.js.map +1 -0
- package/dist/{chunk-GUBEEYDW.js → chunk-XMCVRVTF.js} +2 -2
- package/dist/{chunk-GUBEEYDW.js.map → chunk-XMCVRVTF.js.map} +1 -1
- package/dist/chunk-ZLZKF2PM.js +310 -0
- package/dist/chunk-ZLZKF2PM.js.map +1 -0
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-ZQSFPMOB.js +21 -0
- package/dist/clipboard-manager-TEO2GEDN.js +24 -0
- package/dist/commands/setup.js +3 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.js +3 -3
- package/dist/commands/status.js +2 -2
- package/dist/commands/stop.js +2 -2
- package/dist/commands/utils.js +2 -2
- package/dist/cron-explain-HHQKPD3M.js +16 -0
- package/dist/crypto-4AP47IKC.js +14 -0
- package/dist/crypto-4AP47IKC.js.map +1 -0
- package/dist/databases-37X4CI2Y.js +21 -0
- package/dist/databases-37X4CI2Y.js.map +1 -0
- package/dist/discord-B3HUPGQ6.js +70 -0
- package/dist/discord-B3HUPGQ6.js.map +1 -0
- package/dist/dist-UISMLMFN.js +21847 -0
- package/dist/dist-UISMLMFN.js.map +1 -0
- package/dist/email-K7LO2IPB.js +268 -0
- package/dist/email-K7LO2IPB.js.map +1 -0
- package/dist/enhanced-retrieval-DNLLEM4Z.js +753 -0
- package/dist/enhanced-retrieval-DNLLEM4Z.js.map +1 -0
- package/dist/enrichment-pipeline-MNHNW65K.js +13 -0
- package/dist/enrichment-pipeline-MNHNW65K.js.map +1 -0
- package/dist/entity-resolution-Y3IUWEAT.js +24 -0
- package/dist/entity-resolution-Y3IUWEAT.js.map +1 -0
- package/dist/env-IWXUVTCB.js +12 -0
- package/dist/env-IWXUVTCB.js.map +1 -0
- package/dist/google-workspace-DKWUVNGC.js +169 -0
- package/dist/google-workspace-DKWUVNGC.js.map +1 -0
- package/dist/hash-tool-ULQYD7B5.js +22 -0
- package/dist/hash-tool-ULQYD7B5.js.map +1 -0
- package/dist/heartbeat-monitor-GCISLXI3.js +22 -0
- package/dist/heartbeat-monitor-GCISLXI3.js.map +1 -0
- package/dist/image-generation-OSU7FP6F.js +486 -0
- package/dist/image-generation-OSU7FP6F.js.map +1 -0
- package/dist/imessage-NGA2XF2V.js +35 -0
- package/dist/imessage-NGA2XF2V.js.map +1 -0
- package/dist/inbox-summarizer-NRI4S7IF.js +47 -0
- package/dist/inbox-summarizer-NRI4S7IF.js.map +1 -0
- package/dist/incident-response-C5J7Q6DT.js +244 -0
- package/dist/incident-response-C5J7Q6DT.js.map +1 -0
- package/dist/inventory-manager-352OHXWD.js +24 -0
- package/dist/inventory-manager-352OHXWD.js.map +1 -0
- package/dist/jira-GSGDBMIG.js +199 -0
- package/dist/jira-GSGDBMIG.js.map +1 -0
- package/dist/json-tool-QE2SYHEG.js +26 -0
- package/dist/json-tool-QE2SYHEG.js.map +1 -0
- package/dist/key-rotation-DPHU4ZTB.js +18 -0
- package/dist/key-rotation-DPHU4ZTB.js.map +1 -0
- package/dist/lib.d.ts +603 -11
- package/dist/lib.js +161 -35
- package/dist/lib.js.map +1 -1
- package/dist/mailchimp-KKNF6QJ7.js +152 -0
- package/dist/mailchimp-KKNF6QJ7.js.map +1 -0
- package/dist/matrix-QVHG76I7.js +279 -0
- package/dist/matrix-QVHG76I7.js.map +1 -0
- package/dist/{mcp-LS7Q3Z5W.js → mcp-3JI6W7ZE.js} +3 -3
- package/dist/mcp-3JI6W7ZE.js.map +1 -0
- package/dist/microsoft365-UCBKJHNX.js +164 -0
- package/dist/microsoft365-UCBKJHNX.js.map +1 -0
- package/dist/ocr-AC7NPX33.js +22 -0
- package/dist/ocr-AC7NPX33.js.map +1 -0
- package/dist/ollama-BOAMSPLJ.js +8 -0
- package/dist/ollama-BOAMSPLJ.js.map +1 -0
- package/dist/pages-MI523RB7.js +26 -0
- package/dist/pages-MI523RB7.js.map +1 -0
- package/dist/pair-JDFTERIK.js +24 -0
- package/dist/pair-JDFTERIK.js.map +1 -0
- package/dist/pairing-IFQYCPNS.js +10 -0
- package/dist/pairing-IFQYCPNS.js.map +1 -0
- package/dist/pdf-ALQVOEJR.js +17 -0
- package/dist/pdf-ALQVOEJR.js.map +1 -0
- package/dist/presentations-DSV5IHG5.js +1002 -0
- package/dist/presentations-DSV5IHG5.js.map +1 -0
- package/dist/prometheus-JNT2BD4L.js +10 -0
- package/dist/prometheus-JNT2BD4L.js.map +1 -0
- package/dist/providers-J4LYPHDR.js +19 -0
- package/dist/providers-J4LYPHDR.js.map +1 -0
- package/dist/qr-code-WIX4PB4U.js +16 -0
- package/dist/qr-code-WIX4PB4U.js.map +1 -0
- package/dist/quickbooks-XB4NII2S.js +190 -0
- package/dist/quickbooks-XB4NII2S.js.map +1 -0
- package/dist/regex-tool-W4ABRKGK.js +24 -0
- package/dist/regex-tool-W4ABRKGK.js.map +1 -0
- package/dist/scheduler-VK4WFERV.js +63 -0
- package/dist/scheduler-VK4WFERV.js.map +1 -0
- package/dist/search-BCLBO5E3.js +25 -0
- package/dist/search-BCLBO5E3.js.map +1 -0
- package/dist/sendgrid-RNXCAFKM.js +152 -0
- package/dist/sendgrid-RNXCAFKM.js.map +1 -0
- package/dist/shopify-NCXYJB4R.js +171 -0
- package/dist/shopify-NCXYJB4R.js.map +1 -0
- package/dist/signal-6CGDFYL2.js +35 -0
- package/dist/signal-6CGDFYL2.js.map +1 -0
- package/dist/slack-IZQWIKOH.js +75 -0
- package/dist/slack-IZQWIKOH.js.map +1 -0
- package/dist/sms-M3JIOTCW.js +23 -0
- package/dist/sms-M3JIOTCW.js.map +1 -0
- package/dist/{src-K7GASHRH.js → src-VYUE6LRA.js} +138 -32
- package/dist/src-VYUE6LRA.js.map +1 -0
- package/dist/stocks-XXWBPOCU.js +14 -0
- package/dist/stocks-XXWBPOCU.js.map +1 -0
- package/dist/text-transform-6SGUA5Z4.js +22 -0
- package/dist/text-transform-6SGUA5Z4.js.map +1 -0
- package/dist/tools-2RLEI2N6.js +38 -0
- package/dist/tools-2RLEI2N6.js.map +1 -0
- package/dist/tunnel-IWMXUML4.js +301 -0
- package/dist/tunnel-IWMXUML4.js.map +1 -0
- package/dist/twilio-53GEW5JT.js +139 -0
- package/dist/twilio-53GEW5JT.js.map +1 -0
- package/dist/unit-converter-ZYXMEZOE.js +14 -0
- package/dist/unit-converter-ZYXMEZOE.js.map +1 -0
- package/dist/whatsapp-LFX6YKCM.js +35 -0
- package/dist/whatsapp-LFX6YKCM.js.map +1 -0
- package/dist/word-document-7B6SJMAY.js +902 -0
- package/dist/word-document-7B6SJMAY.js.map +1 -0
- package/dist/xero-QYO66D45.js +162 -0
- package/dist/xero-QYO66D45.js.map +1 -0
- package/dist/zapier-webhook-TBZ5YF2A.js +106 -0
- package/dist/zapier-webhook-TBZ5YF2A.js.map +1 -0
- package/drizzle/0002_mushy_master_mold.sql +140 -0
- package/drizzle/meta/0002_snapshot.json +3637 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +100 -98
- package/dist/bot-KJ26BG56.js +0 -15
- package/dist/chunk-4LVWXUNC.js.map +0 -1
- package/dist/chunk-4TG2IG5K.js +0 -5249
- package/dist/chunk-4TG2IG5K.js.map +0 -1
- package/dist/chunk-6DRDKB45.js.map +0 -1
- package/dist/chunk-CI6Q63MM.js +0 -1613
- package/dist/chunk-CI6Q63MM.js.map +0 -1
- package/dist/chunk-KHNYJY2Z.js +0 -178
- package/dist/chunk-KHNYJY2Z.js.map +0 -1
- package/dist/chunk-NSBPE2FW.js +0 -17
- package/dist/discord-ZOJFTVTB.js +0 -49
- package/dist/imessage-JFRB6EJ7.js +0 -14
- package/dist/scheduler-EZ7CZMCS.js +0 -42
- package/dist/signal-T3MCSULM.js +0 -14
- package/dist/slack-N2M4FHAJ.js +0 -54
- package/dist/src-K7GASHRH.js.map +0 -1
- package/dist/tools-24GZHYRF.js +0 -16
- package/dist/whatsapp-VCRUPAO5.js +0 -14
- /package/dist/{bot-KJ26BG56.js.map → audit-logger-OBPR7CRO.js.map} +0 -0
- /package/dist/{chunk-NSBPE2FW.js.map → auth-UOX5K2BE.js.map} +0 -0
- /package/dist/{discord-ZOJFTVTB.js.map → backup-restore-PZ7CYYB7.js.map} +0 -0
- /package/dist/{imessage-JFRB6EJ7.js.map → blocks-R3PODY47.js.map} +0 -0
- /package/dist/{mcp-LS7Q3Z5W.js.map → bot-QRARP4UN.js.map} +0 -0
- /package/dist/{scheduler-EZ7CZMCS.js.map → brain-7XLLM3KC.js.map} +0 -0
- /package/dist/{charts-MMXM6BWW.js.map → charts-V7ARZNKF.js.map} +0 -0
- /package/dist/{chunk-L3PDU3XN.js.map → chunk-4UOE5TUZ.js.map} +0 -0
- /package/dist/{chunk-6SNHU3CY.js.map → chunk-66OJ3WB4.js.map} +0 -0
- /package/dist/{chunk-F6QUZQGI.js.map → chunk-MXAPLSJ5.js.map} +0 -0
- /package/dist/{chunk-GK3E2I7A.js.map → chunk-NHMBTUMW.js.map} +0 -0
- /package/dist/{signal-T3MCSULM.js.map → chunk-PLDDJCW6.js.map} +0 -0
- /package/dist/{chunk-GVJVEWHI.js.map → chunk-SJSUSJ47.js.map} +0 -0
- /package/dist/{chunk-HH2HBTQM.js.map → chunk-TYAGMJNV.js.map} +0 -0
- /package/dist/{chunk-JXUP2X7V.js.map → chunk-VEHFVBLI.js.map} +0 -0
- /package/dist/{slack-N2M4FHAJ.js.map → client-ZQSFPMOB.js.map} +0 -0
- /package/dist/{tools-24GZHYRF.js.map → clipboard-manager-TEO2GEDN.js.map} +0 -0
- /package/dist/{whatsapp-VCRUPAO5.js.map → cron-explain-HHQKPD3M.js.map} +0 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateEmbedding
|
|
3
|
+
} from "./chunk-IZJMVV7O.js";
|
|
4
|
+
import {
|
|
5
|
+
providerRegistry
|
|
6
|
+
} from "./chunk-BXZ6EA52.js";
|
|
7
|
+
import {
|
|
8
|
+
db
|
|
9
|
+
} from "./chunk-XKYRH4FM.js";
|
|
10
|
+
import {
|
|
11
|
+
env
|
|
12
|
+
} from "./chunk-ZLZKF2PM.js";
|
|
13
|
+
import "./chunk-35WYTA3C.js";
|
|
14
|
+
import "./chunk-PLDDJCW6.js";
|
|
15
|
+
|
|
16
|
+
// src/core/memory/hybrid-search.ts
|
|
17
|
+
import { sql } from "drizzle-orm";
|
|
18
|
+
var RRF_K = 60;
|
|
19
|
+
async function vectorSearch(query, userId, limit = 10) {
|
|
20
|
+
const queryEmbedding = await generateEmbedding(query);
|
|
21
|
+
const results = await db.execute(sql`
|
|
22
|
+
SELECT
|
|
23
|
+
id, user_id, type, content, importance, source, provenance,
|
|
24
|
+
created_at,
|
|
25
|
+
1 - (embedding <=> ${JSON.stringify(queryEmbedding)}::vector) as similarity
|
|
26
|
+
FROM memories
|
|
27
|
+
${userId ? sql`WHERE user_id = ${userId}` : sql``}
|
|
28
|
+
ORDER BY embedding <=> ${JSON.stringify(queryEmbedding)}::vector
|
|
29
|
+
LIMIT ${limit}
|
|
30
|
+
`);
|
|
31
|
+
return results;
|
|
32
|
+
}
|
|
33
|
+
async function keywordSearch(query, userId, limit = 10) {
|
|
34
|
+
const results = await db.execute(sql`
|
|
35
|
+
SELECT
|
|
36
|
+
id, user_id, type, content, importance, source, provenance,
|
|
37
|
+
created_at,
|
|
38
|
+
ts_rank(search_vector, plainto_tsquery('english', ${query})) as keyword_rank
|
|
39
|
+
FROM memories
|
|
40
|
+
WHERE search_vector IS NOT NULL
|
|
41
|
+
AND search_vector @@ plainto_tsquery('english', ${query})
|
|
42
|
+
${userId ? sql`AND user_id = ${userId}` : sql``}
|
|
43
|
+
ORDER BY keyword_rank DESC
|
|
44
|
+
LIMIT ${limit}
|
|
45
|
+
`);
|
|
46
|
+
return results.map((r) => ({
|
|
47
|
+
...r,
|
|
48
|
+
keywordRank: r.keyword_rank
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
async function graphAugmentedSearch(query, userId, limit = 10) {
|
|
52
|
+
const entities = await db.execute(sql`
|
|
53
|
+
SELECT id, name, type
|
|
54
|
+
FROM graph_entities
|
|
55
|
+
WHERE name ILIKE ${"%" + query + "%"}
|
|
56
|
+
${userId ? sql`AND user_id = ${userId}` : sql``}
|
|
57
|
+
LIMIT 5
|
|
58
|
+
`);
|
|
59
|
+
if (entities.length === 0) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const entityIds = entities.map((e) => e.id);
|
|
63
|
+
const entityNames = entities.map((e) => e.name);
|
|
64
|
+
const namePattern = entityNames.join("|");
|
|
65
|
+
const results = await db.execute(sql`
|
|
66
|
+
SELECT
|
|
67
|
+
id, user_id, type, content, importance, source, provenance,
|
|
68
|
+
created_at,
|
|
69
|
+
1.0 as graph_score
|
|
70
|
+
FROM memories
|
|
71
|
+
WHERE content ~* ${namePattern}
|
|
72
|
+
${userId ? sql`AND user_id = ${userId}` : sql``}
|
|
73
|
+
ORDER BY importance DESC, created_at DESC
|
|
74
|
+
LIMIT ${limit}
|
|
75
|
+
`);
|
|
76
|
+
return results.map((r) => ({
|
|
77
|
+
...r,
|
|
78
|
+
graphScore: r.graph_score
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
function reciprocalRankFusion(rankedLists, k = RRF_K) {
|
|
82
|
+
const scores = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const list of rankedLists) {
|
|
84
|
+
for (let rank = 0; rank < list.length; rank++) {
|
|
85
|
+
const id = list[rank].id;
|
|
86
|
+
const rrfScore = 1 / (k + rank + 1);
|
|
87
|
+
scores.set(id, (scores.get(id) || 0) + rrfScore);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return scores;
|
|
91
|
+
}
|
|
92
|
+
async function hybridSearch(query, options = {}) {
|
|
93
|
+
const {
|
|
94
|
+
userId,
|
|
95
|
+
limit = 10,
|
|
96
|
+
since,
|
|
97
|
+
until,
|
|
98
|
+
includeKeyword = true,
|
|
99
|
+
includeGraph = true
|
|
100
|
+
} = options;
|
|
101
|
+
const searchPromises = [
|
|
102
|
+
vectorSearch(query, userId, limit * 2)
|
|
103
|
+
// Fetch more for better fusion
|
|
104
|
+
];
|
|
105
|
+
if (includeKeyword) {
|
|
106
|
+
searchPromises.push(keywordSearch(query, userId, limit * 2));
|
|
107
|
+
}
|
|
108
|
+
if (includeGraph) {
|
|
109
|
+
searchPromises.push(graphAugmentedSearch(query, userId, limit));
|
|
110
|
+
}
|
|
111
|
+
const results = await Promise.all(searchPromises);
|
|
112
|
+
const [vectorResults, keywordResults, graphResults] = results;
|
|
113
|
+
const allResults = /* @__PURE__ */ new Map();
|
|
114
|
+
for (const r of vectorResults || []) {
|
|
115
|
+
allResults.set(r.id, r);
|
|
116
|
+
}
|
|
117
|
+
for (const r of keywordResults || []) {
|
|
118
|
+
if (!allResults.has(r.id)) allResults.set(r.id, r);
|
|
119
|
+
}
|
|
120
|
+
for (const r of graphResults || []) {
|
|
121
|
+
if (!allResults.has(r.id)) allResults.set(r.id, r);
|
|
122
|
+
}
|
|
123
|
+
const rankedLists = [vectorResults || []];
|
|
124
|
+
if (keywordResults) rankedLists.push(keywordResults);
|
|
125
|
+
if (graphResults) rankedLists.push(graphResults);
|
|
126
|
+
const rrfScores = reciprocalRankFusion(rankedLists);
|
|
127
|
+
let finalResults = [];
|
|
128
|
+
for (const [id, rrfScore] of rrfScores) {
|
|
129
|
+
const data = allResults.get(id);
|
|
130
|
+
if (!data) continue;
|
|
131
|
+
finalResults.push({
|
|
132
|
+
id: data.id,
|
|
133
|
+
userId: data.user_id,
|
|
134
|
+
type: data.type,
|
|
135
|
+
content: data.content,
|
|
136
|
+
importance: data.importance || 5,
|
|
137
|
+
source: data.source,
|
|
138
|
+
provenance: data.provenance,
|
|
139
|
+
similarity: data.similarity || 0,
|
|
140
|
+
keywordRank: data.keywordRank || data.keyword_rank || 0,
|
|
141
|
+
rrfScore,
|
|
142
|
+
createdAt: data.created_at
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (since) {
|
|
146
|
+
finalResults = finalResults.filter((r) => new Date(r.createdAt) >= since);
|
|
147
|
+
}
|
|
148
|
+
if (until) {
|
|
149
|
+
finalResults = finalResults.filter((r) => new Date(r.createdAt) <= until);
|
|
150
|
+
}
|
|
151
|
+
return finalResults.sort((a, b) => b.rrfScore - a.rrfScore).slice(0, limit);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/core/memory/contextual-query.ts
|
|
155
|
+
var DEFAULT_MAX_HISTORY_MESSAGES = 4;
|
|
156
|
+
var MAX_TOKENS = 200;
|
|
157
|
+
var SYSTEM_PROMPT = "Rewrite this query to be self-contained by resolving pronouns, references, and implicit context from the conversation. Return ONLY the rewritten query, nothing else.";
|
|
158
|
+
async function buildContextualQuery(query, conversationHistory, opts) {
|
|
159
|
+
try {
|
|
160
|
+
if (!env.CONTEXTUAL_QUERY_ENABLED) {
|
|
161
|
+
return query;
|
|
162
|
+
}
|
|
163
|
+
if (!conversationHistory || conversationHistory.length < 2) {
|
|
164
|
+
return query;
|
|
165
|
+
}
|
|
166
|
+
const maxHistory = opts?.maxHistoryMessages ?? DEFAULT_MAX_HISTORY_MESSAGES;
|
|
167
|
+
const recentHistory = conversationHistory.slice(-maxHistory);
|
|
168
|
+
const conversationText = recentHistory.map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`).join("\n");
|
|
169
|
+
const userPrompt = `Conversation so far:
|
|
170
|
+
${conversationText}
|
|
171
|
+
|
|
172
|
+
Current query to rewrite:
|
|
173
|
+
${query}`;
|
|
174
|
+
const provider = providerRegistry.getDefault();
|
|
175
|
+
const model = opts?.model ?? "claude-sonnet-4-5-20250929";
|
|
176
|
+
const response = await provider.createMessage({
|
|
177
|
+
model,
|
|
178
|
+
max_tokens: MAX_TOKENS,
|
|
179
|
+
system: SYSTEM_PROMPT,
|
|
180
|
+
messages: [
|
|
181
|
+
{
|
|
182
|
+
role: "user",
|
|
183
|
+
content: userPrompt
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
});
|
|
187
|
+
const rewrittenQuery = response.content.filter((block) => block.type === "text" && block.text).map((block) => block.text).join("").trim();
|
|
188
|
+
if (!rewrittenQuery) {
|
|
189
|
+
return query;
|
|
190
|
+
}
|
|
191
|
+
return rewrittenQuery;
|
|
192
|
+
} catch {
|
|
193
|
+
return query;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/core/memory/hyde.ts
|
|
198
|
+
import { sql as sql2 } from "drizzle-orm";
|
|
199
|
+
var RRF_K2 = 60;
|
|
200
|
+
var DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant. Generate a detailed document that would perfectly answer the following question. Write as if this document already exists in a knowledge base. Be specific and factual.";
|
|
201
|
+
var DEFAULT_MAX_TOKENS = 300;
|
|
202
|
+
async function generateHypotheticalDocument(query, opts) {
|
|
203
|
+
const systemPrompt = opts?.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
204
|
+
const maxTokens = opts?.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
205
|
+
const provider = providerRegistry.getDefault();
|
|
206
|
+
const response = await provider.createMessage({
|
|
207
|
+
model: "claude-sonnet-4-20250514",
|
|
208
|
+
max_tokens: maxTokens,
|
|
209
|
+
system: systemPrompt,
|
|
210
|
+
messages: [
|
|
211
|
+
{
|
|
212
|
+
role: "user",
|
|
213
|
+
content: query
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
});
|
|
217
|
+
const text = response.content.filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
|
|
218
|
+
return text;
|
|
219
|
+
}
|
|
220
|
+
async function vectorSearchWithEmbedding(embedding, userId, limit = 10) {
|
|
221
|
+
const embeddingStr = JSON.stringify(embedding);
|
|
222
|
+
const results = await db.execute(sql2`
|
|
223
|
+
SELECT
|
|
224
|
+
id, user_id, type, content, importance, source, provenance,
|
|
225
|
+
created_at,
|
|
226
|
+
1 - (embedding <=> ${embeddingStr}::vector) as similarity
|
|
227
|
+
FROM memories
|
|
228
|
+
${userId ? sql2`WHERE user_id = ${userId}` : sql2``}
|
|
229
|
+
ORDER BY embedding <=> ${embeddingStr}::vector
|
|
230
|
+
LIMIT ${limit}
|
|
231
|
+
`);
|
|
232
|
+
return results;
|
|
233
|
+
}
|
|
234
|
+
function reciprocalRankFusion2(rankedLists, k = RRF_K2) {
|
|
235
|
+
const scores = /* @__PURE__ */ new Map();
|
|
236
|
+
for (const list of rankedLists) {
|
|
237
|
+
for (let rank = 0; rank < list.length; rank++) {
|
|
238
|
+
const id = list[rank].id;
|
|
239
|
+
const rrfScore = 1 / (k + rank + 1);
|
|
240
|
+
scores.set(id, (scores.get(id) || 0) + rrfScore);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return scores;
|
|
244
|
+
}
|
|
245
|
+
async function hydeSearch(query, userId, limit = 10) {
|
|
246
|
+
if (!env.HYDE_ENABLED) {
|
|
247
|
+
const results = await hybridSearch(query, { userId, limit });
|
|
248
|
+
return results.map((r) => ({ ...r, hydeDocument: "" }));
|
|
249
|
+
}
|
|
250
|
+
let hydeDocument;
|
|
251
|
+
try {
|
|
252
|
+
hydeDocument = await generateHypotheticalDocument(query);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.warn("[HyDE] LLM generation failed, falling back to hybrid search:", error);
|
|
255
|
+
const results = await hybridSearch(query, { userId, limit });
|
|
256
|
+
return results.map((r) => ({ ...r, hydeDocument: "" }));
|
|
257
|
+
}
|
|
258
|
+
if (!hydeDocument || hydeDocument.trim().length === 0) {
|
|
259
|
+
console.warn("[HyDE] Empty hypothetical document, falling back to hybrid search");
|
|
260
|
+
const results = await hybridSearch(query, { userId, limit });
|
|
261
|
+
return results.map((r) => ({ ...r, hydeDocument: "" }));
|
|
262
|
+
}
|
|
263
|
+
let hydeEmbedding;
|
|
264
|
+
try {
|
|
265
|
+
hydeEmbedding = await generateEmbedding(hydeDocument);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.warn("[HyDE] Embedding generation failed, falling back to hybrid search:", error);
|
|
268
|
+
const results = await hybridSearch(query, { userId, limit });
|
|
269
|
+
return results.map((r) => ({ ...r, hydeDocument: "" }));
|
|
270
|
+
}
|
|
271
|
+
const fetchLimit = limit * 2;
|
|
272
|
+
const [vectorResults, kwResults] = await Promise.all([
|
|
273
|
+
vectorSearchWithEmbedding(hydeEmbedding, userId, fetchLimit),
|
|
274
|
+
keywordSearch(query, userId, fetchLimit)
|
|
275
|
+
]);
|
|
276
|
+
const allResults = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const r of vectorResults) {
|
|
278
|
+
allResults.set(r.id, r);
|
|
279
|
+
}
|
|
280
|
+
for (const r of kwResults) {
|
|
281
|
+
if (!allResults.has(r.id)) {
|
|
282
|
+
allResults.set(r.id, r);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const rrfScores = reciprocalRankFusion2([vectorResults, kwResults]);
|
|
286
|
+
const finalResults = [];
|
|
287
|
+
for (const [id, rrfScore] of rrfScores) {
|
|
288
|
+
const data = allResults.get(id);
|
|
289
|
+
if (!data) continue;
|
|
290
|
+
finalResults.push({
|
|
291
|
+
id: data.id,
|
|
292
|
+
userId: data.user_id,
|
|
293
|
+
type: data.type,
|
|
294
|
+
content: data.content,
|
|
295
|
+
importance: data.importance || 5,
|
|
296
|
+
source: data.source,
|
|
297
|
+
provenance: data.provenance,
|
|
298
|
+
similarity: data.similarity || 0,
|
|
299
|
+
keywordRank: data.keywordRank || data.keyword_rank || 0,
|
|
300
|
+
rrfScore,
|
|
301
|
+
createdAt: data.created_at,
|
|
302
|
+
hydeDocument
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return finalResults.sort((a, b) => b.rrfScore - a.rrfScore).slice(0, limit);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/core/memory/retrieval-cache.ts
|
|
309
|
+
import Redis from "ioredis";
|
|
310
|
+
import { createHash } from "crypto";
|
|
311
|
+
var KEY_PREFIX = "rag:cache:";
|
|
312
|
+
var DEFAULT_TTL_SECONDS = 3600;
|
|
313
|
+
var RetrievalCache = class {
|
|
314
|
+
redis = null;
|
|
315
|
+
connecting = false;
|
|
316
|
+
/**
|
|
317
|
+
* Lazily create / return a Redis client.
|
|
318
|
+
* Returns null if Redis is unavailable.
|
|
319
|
+
*/
|
|
320
|
+
async getClient() {
|
|
321
|
+
if (this.redis) return this.redis;
|
|
322
|
+
if (this.connecting) return null;
|
|
323
|
+
try {
|
|
324
|
+
this.connecting = true;
|
|
325
|
+
const client = new Redis(env.REDIS_URL, {
|
|
326
|
+
maxRetriesPerRequest: null,
|
|
327
|
+
lazyConnect: true
|
|
328
|
+
});
|
|
329
|
+
await client.connect();
|
|
330
|
+
this.redis = client;
|
|
331
|
+
return this.redis;
|
|
332
|
+
} catch {
|
|
333
|
+
this.redis = null;
|
|
334
|
+
return null;
|
|
335
|
+
} finally {
|
|
336
|
+
this.connecting = false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Hash an embedding vector for use as a cache key.
|
|
341
|
+
*
|
|
342
|
+
* Rounds each component to 4 decimal places to tolerate minor
|
|
343
|
+
* floating-point differences, then produces a SHA-256 hex digest.
|
|
344
|
+
*/
|
|
345
|
+
hashEmbedding(embedding) {
|
|
346
|
+
const rounded = embedding.map((v) => v.toFixed(4)).join(",");
|
|
347
|
+
return createHash("sha256").update(rounded).digest("hex");
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Look up cached search results for the given embedding.
|
|
351
|
+
* Returns null on cache miss or if the cache is disabled / unavailable.
|
|
352
|
+
*/
|
|
353
|
+
async getCachedResults(queryEmbedding) {
|
|
354
|
+
if (!env.RETRIEVAL_CACHE_ENABLED) return null;
|
|
355
|
+
try {
|
|
356
|
+
const client = await this.getClient();
|
|
357
|
+
if (!client) return null;
|
|
358
|
+
const hash = this.hashEmbedding(queryEmbedding);
|
|
359
|
+
const key = `${KEY_PREFIX}${hash}`;
|
|
360
|
+
const raw = await client.get(key);
|
|
361
|
+
if (!raw) return null;
|
|
362
|
+
const cached = JSON.parse(raw);
|
|
363
|
+
cached.results = cached.results.map((r) => ({
|
|
364
|
+
...r,
|
|
365
|
+
createdAt: new Date(r.createdAt)
|
|
366
|
+
}));
|
|
367
|
+
return cached;
|
|
368
|
+
} catch {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Store search results in Redis, keyed by embedding hash.
|
|
374
|
+
*
|
|
375
|
+
* @param queryEmbedding - The embedding vector used for the search.
|
|
376
|
+
* @param results - The hybrid search results to cache.
|
|
377
|
+
* @param ttl - Time-to-live in seconds (default 3600).
|
|
378
|
+
*/
|
|
379
|
+
async cacheResults(queryEmbedding, results, ttl = DEFAULT_TTL_SECONDS) {
|
|
380
|
+
if (!env.RETRIEVAL_CACHE_ENABLED) return;
|
|
381
|
+
try {
|
|
382
|
+
const client = await this.getClient();
|
|
383
|
+
if (!client) return;
|
|
384
|
+
const hash = this.hashEmbedding(queryEmbedding);
|
|
385
|
+
const key = `${KEY_PREFIX}${hash}`;
|
|
386
|
+
const entry = {
|
|
387
|
+
results,
|
|
388
|
+
cachedAt: Date.now(),
|
|
389
|
+
queryHash: hash
|
|
390
|
+
};
|
|
391
|
+
await client.set(key, JSON.stringify(entry), "EX", ttl);
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Remove cache entries matching a key pattern.
|
|
397
|
+
*
|
|
398
|
+
* @param pattern - Glob pattern appended to the key prefix.
|
|
399
|
+
* Defaults to `*` (all retrieval cache keys).
|
|
400
|
+
* @returns The number of keys deleted.
|
|
401
|
+
*/
|
|
402
|
+
async invalidateCache(pattern) {
|
|
403
|
+
try {
|
|
404
|
+
const client = await this.getClient();
|
|
405
|
+
if (!client) return 0;
|
|
406
|
+
const scanPattern = `${KEY_PREFIX}${pattern ?? "*"}`;
|
|
407
|
+
let deleted = 0;
|
|
408
|
+
let cursor = "0";
|
|
409
|
+
do {
|
|
410
|
+
const [nextCursor, keys] = await client.scan(
|
|
411
|
+
cursor,
|
|
412
|
+
"MATCH",
|
|
413
|
+
scanPattern,
|
|
414
|
+
"COUNT",
|
|
415
|
+
100
|
|
416
|
+
);
|
|
417
|
+
cursor = nextCursor;
|
|
418
|
+
if (keys.length > 0) {
|
|
419
|
+
deleted += await client.del(...keys);
|
|
420
|
+
}
|
|
421
|
+
} while (cursor !== "0");
|
|
422
|
+
return deleted;
|
|
423
|
+
} catch {
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
var instance = null;
|
|
429
|
+
function getRetrievalCache() {
|
|
430
|
+
if (!instance) {
|
|
431
|
+
instance = new RetrievalCache();
|
|
432
|
+
}
|
|
433
|
+
return instance;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/core/memory/reranker.ts
|
|
437
|
+
var DEFAULT_BATCH_SIZE = 5;
|
|
438
|
+
var DEFAULT_MAX_TOKENS2 = 100;
|
|
439
|
+
var DEFAULT_SCORE = 5;
|
|
440
|
+
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
441
|
+
function buildScoringPrompt(query, documents) {
|
|
442
|
+
const docList = documents.map((doc, i) => `${i + 1}. ${doc}`).join("\n");
|
|
443
|
+
return `You are a relevance judge. Given a search query and a list of retrieved documents, rate each document's relevance to the query on a scale of 0-10.
|
|
444
|
+
|
|
445
|
+
Query: ${query}
|
|
446
|
+
|
|
447
|
+
Documents:
|
|
448
|
+
${docList}
|
|
449
|
+
|
|
450
|
+
Return a JSON array of scores: [score1, score2, ...]
|
|
451
|
+
Only return the JSON array, nothing else.`;
|
|
452
|
+
}
|
|
453
|
+
function parseScores(text, expectedCount) {
|
|
454
|
+
try {
|
|
455
|
+
const parsed = JSON.parse(text.trim());
|
|
456
|
+
if (Array.isArray(parsed)) {
|
|
457
|
+
return parsed.map((s) => {
|
|
458
|
+
const n = Number(s);
|
|
459
|
+
return Number.isFinite(n) ? Math.min(10, Math.max(0, n)) : DEFAULT_SCORE;
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
const matches = text.match(/\d+(?:\.\d+)?/g);
|
|
465
|
+
if (matches && matches.length > 0) {
|
|
466
|
+
return matches.slice(0, expectedCount).map((m) => {
|
|
467
|
+
const n = Number(m);
|
|
468
|
+
return Number.isFinite(n) ? Math.min(10, Math.max(0, n)) : DEFAULT_SCORE;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
return new Array(expectedCount).fill(DEFAULT_SCORE);
|
|
472
|
+
}
|
|
473
|
+
async function batchRerank(query, results, batchSize = DEFAULT_BATCH_SIZE, model) {
|
|
474
|
+
const provider = providerRegistry.getDefault();
|
|
475
|
+
const resolvedModel = model || DEFAULT_MODEL;
|
|
476
|
+
const rankedResults = [];
|
|
477
|
+
const batches = [];
|
|
478
|
+
for (let i = 0; i < results.length; i += batchSize) {
|
|
479
|
+
batches.push(results.slice(i, i + batchSize));
|
|
480
|
+
}
|
|
481
|
+
const batchPromises = batches.map(async (batch) => {
|
|
482
|
+
const documents = batch.map((r) => r.content);
|
|
483
|
+
const prompt = buildScoringPrompt(query, documents);
|
|
484
|
+
try {
|
|
485
|
+
const response = await provider.createMessage({
|
|
486
|
+
model: resolvedModel,
|
|
487
|
+
max_tokens: DEFAULT_MAX_TOKENS2,
|
|
488
|
+
system: "You are a relevance scoring assistant. Only output valid JSON.",
|
|
489
|
+
messages: [{ role: "user", content: prompt }]
|
|
490
|
+
});
|
|
491
|
+
const responseText = response.content.filter((block) => block.type === "text").map((block) => block.text || "").join("");
|
|
492
|
+
const scores = parseScores(responseText, batch.length);
|
|
493
|
+
return batch.map((result, idx) => ({
|
|
494
|
+
...result,
|
|
495
|
+
rerankScore: idx < scores.length ? scores[idx] : DEFAULT_SCORE
|
|
496
|
+
}));
|
|
497
|
+
} catch (error) {
|
|
498
|
+
console.warn(
|
|
499
|
+
`[Reranker] LLM scoring failed for batch, assigning default score of ${DEFAULT_SCORE}:`,
|
|
500
|
+
error instanceof Error ? error.message : error
|
|
501
|
+
);
|
|
502
|
+
return batch.map((result) => ({
|
|
503
|
+
...result,
|
|
504
|
+
rerankScore: DEFAULT_SCORE
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
const batchResults = await Promise.all(batchPromises);
|
|
509
|
+
for (const batch of batchResults) {
|
|
510
|
+
rankedResults.push(...batch);
|
|
511
|
+
}
|
|
512
|
+
return rankedResults;
|
|
513
|
+
}
|
|
514
|
+
async function rerank(query, results, opts) {
|
|
515
|
+
try {
|
|
516
|
+
const minScore = opts?.minScore ?? env.RERANK_MIN_SCORE;
|
|
517
|
+
if (!env.RERANK_ENABLED) {
|
|
518
|
+
return results.map((r) => ({ ...r, rerankScore: DEFAULT_SCORE }));
|
|
519
|
+
}
|
|
520
|
+
if (results.length <= 1) {
|
|
521
|
+
return results.map((r) => ({ ...r, rerankScore: 10 }));
|
|
522
|
+
}
|
|
523
|
+
let ranked = await batchRerank(query, results, DEFAULT_BATCH_SIZE, opts?.model);
|
|
524
|
+
ranked = ranked.filter((r) => r.rerankScore >= minScore);
|
|
525
|
+
ranked.sort((a, b) => b.rerankScore - a.rerankScore);
|
|
526
|
+
if (opts?.topK && opts.topK > 0) {
|
|
527
|
+
ranked = ranked.slice(0, opts.topK);
|
|
528
|
+
}
|
|
529
|
+
return ranked;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
console.error(
|
|
532
|
+
"[Reranker] Re-ranking failed, returning original results:",
|
|
533
|
+
error instanceof Error ? error.message : error
|
|
534
|
+
);
|
|
535
|
+
return results.map((r) => ({ ...r, rerankScore: DEFAULT_SCORE }));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/core/memory/multi-step.ts
|
|
540
|
+
var COMPLETENESS_MAX_TOKENS = 300;
|
|
541
|
+
var COMPLETENESS_MODEL = "claude-sonnet-4-5-20250929";
|
|
542
|
+
async function evaluateCompleteness(query, context) {
|
|
543
|
+
try {
|
|
544
|
+
const provider = providerRegistry.getDefault();
|
|
545
|
+
const response = await provider.createMessage({
|
|
546
|
+
model: COMPLETENESS_MODEL,
|
|
547
|
+
max_tokens: COMPLETENESS_MAX_TOKENS,
|
|
548
|
+
system: "You are a retrieval evaluation assistant. Only output valid JSON.",
|
|
549
|
+
messages: [
|
|
550
|
+
{
|
|
551
|
+
role: "user",
|
|
552
|
+
content: `Analyze whether the following context sufficiently answers the query. Return a JSON object with:
|
|
553
|
+
- "complete": boolean (true if context fully answers the query)
|
|
554
|
+
- "gaps": string[] (list of missing information)
|
|
555
|
+
- "followUpQueries": string[] (search queries to fill the gaps, max 2)
|
|
556
|
+
|
|
557
|
+
Query: ${query}
|
|
558
|
+
|
|
559
|
+
Context:
|
|
560
|
+
${context}
|
|
561
|
+
|
|
562
|
+
Return only the JSON object.`
|
|
563
|
+
}
|
|
564
|
+
]
|
|
565
|
+
});
|
|
566
|
+
const responseText = response.content.filter((block) => block.type === "text").map((block) => block.text || "").join("");
|
|
567
|
+
const parsed = JSON.parse(responseText.trim());
|
|
568
|
+
return {
|
|
569
|
+
complete: Boolean(parsed.complete),
|
|
570
|
+
gaps: Array.isArray(parsed.gaps) ? parsed.gaps : [],
|
|
571
|
+
followUpQueries: Array.isArray(parsed.followUpQueries) ? parsed.followUpQueries.slice(0, 2) : []
|
|
572
|
+
};
|
|
573
|
+
} catch (error) {
|
|
574
|
+
console.warn(
|
|
575
|
+
"[MultiStepRAG] Completeness evaluation failed, assuming complete:",
|
|
576
|
+
error instanceof Error ? error.message : error
|
|
577
|
+
);
|
|
578
|
+
return { complete: true, gaps: [], followUpQueries: [] };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function multiStepRetrieve(query, initialResults, opts) {
|
|
582
|
+
if (!env.MULTISTEP_RAG_ENABLED) {
|
|
583
|
+
return {
|
|
584
|
+
results: initialResults,
|
|
585
|
+
steps: 0,
|
|
586
|
+
followUpQueries: []
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const maxSteps = opts?.maxSteps ?? env.MULTISTEP_MAX_STEPS;
|
|
590
|
+
const userId = opts?.userId;
|
|
591
|
+
const limit = opts?.limit ?? 10;
|
|
592
|
+
const resultsById = /* @__PURE__ */ new Map();
|
|
593
|
+
for (const r of initialResults) {
|
|
594
|
+
resultsById.set(r.id, r);
|
|
595
|
+
}
|
|
596
|
+
const allFollowUpQueries = [];
|
|
597
|
+
let stepsPerformed = 0;
|
|
598
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
599
|
+
const currentResults = Array.from(resultsById.values());
|
|
600
|
+
const contextText = currentResults.map((r) => r.content).join("\n\n");
|
|
601
|
+
const evaluation = await evaluateCompleteness(query, contextText);
|
|
602
|
+
if (evaluation.complete || evaluation.followUpQueries.length === 0) {
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
stepsPerformed++;
|
|
606
|
+
const followUps = evaluation.followUpQueries.slice(0, 2);
|
|
607
|
+
allFollowUpQueries.push(...followUps);
|
|
608
|
+
const retrievalPromises = followUps.map(async (followUpQuery) => {
|
|
609
|
+
const searchResults = await hybridSearch(followUpQuery, {
|
|
610
|
+
userId,
|
|
611
|
+
limit
|
|
612
|
+
});
|
|
613
|
+
const rankedResults = await rerank(followUpQuery, searchResults);
|
|
614
|
+
return rankedResults;
|
|
615
|
+
});
|
|
616
|
+
const additionalResultSets = await Promise.all(retrievalPromises);
|
|
617
|
+
for (const resultSet of additionalResultSets) {
|
|
618
|
+
for (const r of resultSet) {
|
|
619
|
+
if (!resultsById.has(r.id)) {
|
|
620
|
+
resultsById.set(r.id, r);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const mergedResults = Array.from(resultsById.values()).sort(
|
|
626
|
+
(a, b) => b.rerankScore - a.rerankScore
|
|
627
|
+
);
|
|
628
|
+
return {
|
|
629
|
+
results: mergedResults,
|
|
630
|
+
steps: stepsPerformed,
|
|
631
|
+
followUpQueries: allFollowUpQueries
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// src/core/memory/enhanced-retrieval.ts
|
|
636
|
+
function toRankedResults(results, defaultScore) {
|
|
637
|
+
return results.map((r) => ({
|
|
638
|
+
...r,
|
|
639
|
+
rerankScore: defaultScore ?? r.rrfScore * 10
|
|
640
|
+
}));
|
|
641
|
+
}
|
|
642
|
+
async function enhancedRetrieve(query, opts) {
|
|
643
|
+
const userId = opts?.userId;
|
|
644
|
+
const limit = opts?.limit ?? 10;
|
|
645
|
+
const conversationHistory = opts?.conversationHistory;
|
|
646
|
+
let steps = 0;
|
|
647
|
+
try {
|
|
648
|
+
let effectiveQuery = query;
|
|
649
|
+
if (env.CONTEXTUAL_QUERY_ENABLED && conversationHistory && conversationHistory.length >= 2) {
|
|
650
|
+
console.log("[EnhancedRetrieval] Contextual query rewrite enabled, rewriting query...");
|
|
651
|
+
effectiveQuery = await buildContextualQuery(query, conversationHistory);
|
|
652
|
+
steps++;
|
|
653
|
+
console.log(`[EnhancedRetrieval] Rewritten query: "${effectiveQuery}"`);
|
|
654
|
+
}
|
|
655
|
+
let cached = false;
|
|
656
|
+
let queryEmbedding = null;
|
|
657
|
+
if (env.RETRIEVAL_CACHE_ENABLED) {
|
|
658
|
+
console.log("[EnhancedRetrieval] Cache enabled, checking for cached results...");
|
|
659
|
+
queryEmbedding = await generateEmbedding(effectiveQuery);
|
|
660
|
+
const cache = getRetrievalCache();
|
|
661
|
+
const cachedResult = await cache.getCachedResults(queryEmbedding);
|
|
662
|
+
if (cachedResult) {
|
|
663
|
+
console.log("[EnhancedRetrieval] Cache hit, using cached results");
|
|
664
|
+
cached = true;
|
|
665
|
+
steps++;
|
|
666
|
+
let rankedResults2 = toRankedResults(cachedResult.results, 5);
|
|
667
|
+
if (env.RERANK_ENABLED) {
|
|
668
|
+
console.log("[EnhancedRetrieval] Re-ranking cached results...");
|
|
669
|
+
rankedResults2 = await rerank(effectiveQuery, cachedResult.results);
|
|
670
|
+
steps++;
|
|
671
|
+
}
|
|
672
|
+
if (env.MULTISTEP_RAG_ENABLED) {
|
|
673
|
+
console.log("[EnhancedRetrieval] Multi-step retrieval on cached results...");
|
|
674
|
+
rankedResults2 = await multiStepRetrieve(effectiveQuery, rankedResults2, { userId });
|
|
675
|
+
steps++;
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
results: rankedResults2,
|
|
679
|
+
cached: true,
|
|
680
|
+
steps,
|
|
681
|
+
queryUsed: effectiveQuery
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
console.log("[EnhancedRetrieval] Cache miss, proceeding to search");
|
|
685
|
+
}
|
|
686
|
+
let searchResults;
|
|
687
|
+
if (env.HYDE_ENABLED) {
|
|
688
|
+
console.log("[EnhancedRetrieval] HyDE enabled, generating hypothetical doc...");
|
|
689
|
+
searchResults = await hydeSearch(effectiveQuery, userId, limit);
|
|
690
|
+
steps++;
|
|
691
|
+
} else {
|
|
692
|
+
console.log("[EnhancedRetrieval] Running hybrid search...");
|
|
693
|
+
searchResults = await hybridSearch(effectiveQuery, { userId, limit });
|
|
694
|
+
steps++;
|
|
695
|
+
}
|
|
696
|
+
let rankedResults;
|
|
697
|
+
if (env.RERANK_ENABLED) {
|
|
698
|
+
console.log("[EnhancedRetrieval] Re-ranking results...");
|
|
699
|
+
rankedResults = await rerank(effectiveQuery, searchResults);
|
|
700
|
+
steps++;
|
|
701
|
+
} else {
|
|
702
|
+
rankedResults = toRankedResults(searchResults);
|
|
703
|
+
}
|
|
704
|
+
if (env.RETRIEVAL_CACHE_ENABLED && !cached) {
|
|
705
|
+
console.log("[EnhancedRetrieval] Storing results in cache...");
|
|
706
|
+
if (!queryEmbedding) {
|
|
707
|
+
queryEmbedding = await generateEmbedding(effectiveQuery);
|
|
708
|
+
}
|
|
709
|
+
const cache = getRetrievalCache();
|
|
710
|
+
await cache.cacheResults(queryEmbedding, searchResults);
|
|
711
|
+
}
|
|
712
|
+
if (env.MULTISTEP_RAG_ENABLED) {
|
|
713
|
+
console.log("[EnhancedRetrieval] Multi-step retrieval enabled, filling gaps...");
|
|
714
|
+
rankedResults = await multiStepRetrieve(effectiveQuery, rankedResults, { userId });
|
|
715
|
+
steps++;
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
results: rankedResults,
|
|
719
|
+
cached,
|
|
720
|
+
steps,
|
|
721
|
+
queryUsed: effectiveQuery
|
|
722
|
+
};
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.error(
|
|
725
|
+
"[EnhancedRetrieval] Pipeline failed, falling back to plain hybrid search:",
|
|
726
|
+
error instanceof Error ? error.message : error
|
|
727
|
+
);
|
|
728
|
+
try {
|
|
729
|
+
const fallbackResults = await hybridSearch(query, { userId, limit });
|
|
730
|
+
return {
|
|
731
|
+
results: toRankedResults(fallbackResults, 5),
|
|
732
|
+
cached: false,
|
|
733
|
+
steps: 0,
|
|
734
|
+
queryUsed: query
|
|
735
|
+
};
|
|
736
|
+
} catch (fallbackError) {
|
|
737
|
+
console.error(
|
|
738
|
+
"[EnhancedRetrieval] Fallback hybrid search also failed:",
|
|
739
|
+
fallbackError instanceof Error ? fallbackError.message : fallbackError
|
|
740
|
+
);
|
|
741
|
+
return {
|
|
742
|
+
results: [],
|
|
743
|
+
cached: false,
|
|
744
|
+
steps: 0,
|
|
745
|
+
queryUsed: query
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
export {
|
|
751
|
+
enhancedRetrieve
|
|
752
|
+
};
|
|
753
|
+
//# sourceMappingURL=enhanced-retrieval-DNLLEM4Z.js.map
|