rust-kgdb 0.5.12 → 0.6.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/CHANGELOG.md +253 -0
- package/README.md +170 -0
- package/examples/fraud-detection-agent.js +157 -1
- package/examples/fraud-memory-hypergraph.js +658 -0
- package/examples/underwriting-agent.js +142 -2
- package/hypermind-agent.js +1199 -1
- package/index.d.ts +735 -0
- package/index.js +24 -0
- package/package.json +8 -3
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
4
|
+
* MEMORY HYPERGRAPH DEMO - Fraud Detection with Persistent Agent Memory
|
|
5
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
6
|
+
*
|
|
7
|
+
* This demonstrates the MEMORY HYPERGRAPH architecture (v0.6.0+):
|
|
8
|
+
*
|
|
9
|
+
* ┌──────────────────────────────────────────────────────────────────────────┐
|
|
10
|
+
* │ MEMORY HYPERGRAPH ARCHITECTURE │
|
|
11
|
+
* │ │
|
|
12
|
+
* │ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
13
|
+
* │ │ AGENT MEMORY LAYER │ │
|
|
14
|
+
* │ │ Episode:001 ──→ Episode:002 ──→ Episode:003 │ │
|
|
15
|
+
* │ │ (Fraud ring) (Denied claim) (Investigation) │ │
|
|
16
|
+
* │ └───────────┬─────────────┬─────────────┬─────────────────────────┘ │
|
|
17
|
+
* │ │ HyperEdge │ │ │
|
|
18
|
+
* │ ▼ ▼ ▼ │
|
|
19
|
+
* │ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
20
|
+
* │ │ KNOWLEDGE GRAPH LAYER │ │
|
|
21
|
+
* │ │ Provider:P001 ────▶ Claim:C123 ◀──── Claimant:C001 │ │
|
|
22
|
+
* │ │ SAME QUAD STORE - One SPARQL query traverses BOTH! │ │
|
|
23
|
+
* │ └─────────────────────────────────────────────────────────────────┘ │
|
|
24
|
+
* │ │
|
|
25
|
+
* │ KEY FEATURES: │
|
|
26
|
+
* │ • Temporal scoring: Recency + Relevance + Importance │
|
|
27
|
+
* │ • Rolling context window: 1h → 24h → 7d → 1y │
|
|
28
|
+
* │ • Idempotent responses: Same question = Same answer (cached) │
|
|
29
|
+
* │ • SPARQL traverses both memory AND knowledge graph │
|
|
30
|
+
* └──────────────────────────────────────────────────────────────────────────┘
|
|
31
|
+
*
|
|
32
|
+
* WHY THIS MATTERS:
|
|
33
|
+
* Without Memory Hypergraph: Agent forgets everything between sessions
|
|
34
|
+
* With Memory Hypergraph: Agent recalls past findings, linked to KG entities
|
|
35
|
+
*
|
|
36
|
+
* @version 0.6.0
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
GraphDB,
|
|
41
|
+
EmbeddingService,
|
|
42
|
+
DatalogProgram,
|
|
43
|
+
evaluateDatalog,
|
|
44
|
+
GraphFrame,
|
|
45
|
+
getVersion,
|
|
46
|
+
// Memory Layer
|
|
47
|
+
AgentState,
|
|
48
|
+
AgentRuntime,
|
|
49
|
+
MemoryManager,
|
|
50
|
+
GovernancePolicy,
|
|
51
|
+
GovernanceEngine,
|
|
52
|
+
AgentScope
|
|
53
|
+
} = require('../index.js')
|
|
54
|
+
|
|
55
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
56
|
+
// CONFIGURATION
|
|
57
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
58
|
+
|
|
59
|
+
const CONFIG = {
|
|
60
|
+
// OpenAI API Key (from user)
|
|
61
|
+
openai: {
|
|
62
|
+
apiKey: process.env.OPENAI_API_KEY || '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',
|
|
63
|
+
model: 'gpt-4o',
|
|
64
|
+
embedModel: 'text-embedding-3-small',
|
|
65
|
+
dimensions: 384
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Knowledge Graph
|
|
69
|
+
kg: {
|
|
70
|
+
baseUri: 'http://insurance.org/fraud-detection',
|
|
71
|
+
graphUri: 'http://insurance.org/fraud-kb',
|
|
72
|
+
memoryGraphUri: 'https://gonnect.ai/memory/'
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Memory Configuration
|
|
76
|
+
memory: {
|
|
77
|
+
weights: { recency: 0.3, relevance: 0.5, importance: 0.2 },
|
|
78
|
+
decayRate: 0.995,
|
|
79
|
+
maxContextTokens: 8192,
|
|
80
|
+
rollingWindows: [1, 24, 168, 8760] // 1h, 24h, 7d, 1y
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
85
|
+
// FRAUD KNOWLEDGE BASE
|
|
86
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
87
|
+
|
|
88
|
+
const FRAUD_ONTOLOGY = `
|
|
89
|
+
@prefix ins: <http://insurance.org/> .
|
|
90
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
91
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
92
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
93
|
+
|
|
94
|
+
# Providers
|
|
95
|
+
ins:P001 rdf:type ins:Provider ;
|
|
96
|
+
ins:name "Quick Care Clinic" ;
|
|
97
|
+
ins:riskScore "0.87"^^xsd:float ;
|
|
98
|
+
ins:claimVolume "847"^^xsd:integer .
|
|
99
|
+
|
|
100
|
+
ins:P002 rdf:type ins:Provider ;
|
|
101
|
+
ins:name "City Hospital" ;
|
|
102
|
+
ins:riskScore "0.35"^^xsd:float ;
|
|
103
|
+
ins:claimVolume "2341"^^xsd:integer .
|
|
104
|
+
|
|
105
|
+
# Claimants
|
|
106
|
+
ins:C001 rdf:type ins:Claimant ;
|
|
107
|
+
ins:name "John Smith" ;
|
|
108
|
+
ins:riskScore "0.85"^^xsd:float ;
|
|
109
|
+
ins:address ins:ADDR001 .
|
|
110
|
+
|
|
111
|
+
ins:C002 rdf:type ins:Claimant ;
|
|
112
|
+
ins:name "Jane Doe" ;
|
|
113
|
+
ins:riskScore "0.72"^^xsd:float ;
|
|
114
|
+
ins:address ins:ADDR001 .
|
|
115
|
+
|
|
116
|
+
ins:C003 rdf:type ins:Claimant ;
|
|
117
|
+
ins:name "Bob Wilson" ;
|
|
118
|
+
ins:riskScore "0.22"^^xsd:float ;
|
|
119
|
+
ins:address ins:ADDR002 .
|
|
120
|
+
|
|
121
|
+
# Claims
|
|
122
|
+
ins:CLM001 rdf:type ins:Claim ;
|
|
123
|
+
ins:claimant ins:C001 ;
|
|
124
|
+
ins:provider ins:P001 ;
|
|
125
|
+
ins:amount "18500"^^xsd:decimal ;
|
|
126
|
+
ins:type "bodily_injury" .
|
|
127
|
+
|
|
128
|
+
ins:CLM002 rdf:type ins:Claim ;
|
|
129
|
+
ins:claimant ins:C002 ;
|
|
130
|
+
ins:provider ins:P001 ;
|
|
131
|
+
ins:amount "22300"^^xsd:decimal ;
|
|
132
|
+
ins:type "bodily_injury" .
|
|
133
|
+
|
|
134
|
+
ins:CLM003 rdf:type ins:Claim ;
|
|
135
|
+
ins:claimant ins:C001 ;
|
|
136
|
+
ins:provider ins:P002 ;
|
|
137
|
+
ins:amount "8500"^^xsd:decimal ;
|
|
138
|
+
ins:type "collision" .
|
|
139
|
+
|
|
140
|
+
# Fraud Ring Relationships
|
|
141
|
+
ins:C001 ins:knows ins:C002 .
|
|
142
|
+
ins:C002 ins:knows ins:C001 .
|
|
143
|
+
`
|
|
144
|
+
|
|
145
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
146
|
+
// MEMORY HYPERGRAPH IMPLEMENTATION
|
|
147
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Memory Hypergraph - Connects agent episodes to knowledge graph entities
|
|
151
|
+
*/
|
|
152
|
+
class MemoryHypergraph {
|
|
153
|
+
constructor(db, config) {
|
|
154
|
+
this.db = db
|
|
155
|
+
this.config = config
|
|
156
|
+
this.episodes = []
|
|
157
|
+
this.queryCache = new Map()
|
|
158
|
+
this.embeddings = new Map()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Store an episode with hyper-edges to KG entities
|
|
163
|
+
*/
|
|
164
|
+
storeEpisode(episode) {
|
|
165
|
+
const episodeId = `episode:${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
166
|
+
const storedEpisode = {
|
|
167
|
+
id: episodeId,
|
|
168
|
+
prompt: episode.prompt,
|
|
169
|
+
result: episode.result,
|
|
170
|
+
success: episode.success,
|
|
171
|
+
kgEntities: episode.kgEntities || [],
|
|
172
|
+
createdAt: new Date(),
|
|
173
|
+
accessCount: 1,
|
|
174
|
+
lastAccessed: new Date(),
|
|
175
|
+
embedding: episode.embedding || null
|
|
176
|
+
}
|
|
177
|
+
this.episodes.push(storedEpisode)
|
|
178
|
+
|
|
179
|
+
// Store in GraphDB as RDF (memory graph)
|
|
180
|
+
const ttl = this._episodeToTtl(storedEpisode)
|
|
181
|
+
try {
|
|
182
|
+
this.db.loadTtl(ttl, this.config.memoryGraphUri)
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// Memory graph may not support all features, continue anyway
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return storedEpisode
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Retrieve similar episodes using temporal scoring
|
|
192
|
+
* Score = α × Recency + β × Relevance + γ × Importance
|
|
193
|
+
*/
|
|
194
|
+
retrieve(query, limit = 10) {
|
|
195
|
+
const weights = this.config.weights
|
|
196
|
+
const now = new Date()
|
|
197
|
+
|
|
198
|
+
return this.episodes
|
|
199
|
+
.map(ep => {
|
|
200
|
+
// Recency: decay^hours
|
|
201
|
+
const hoursElapsed = (now - ep.createdAt) / (1000 * 60 * 60)
|
|
202
|
+
const recency = Math.pow(this.config.decayRate, hoursElapsed)
|
|
203
|
+
|
|
204
|
+
// Relevance: simple text similarity (would use embeddings in production)
|
|
205
|
+
const relevance = this._textSimilarity(query, ep.prompt)
|
|
206
|
+
|
|
207
|
+
// Importance: log-normalized access count
|
|
208
|
+
const maxAccess = Math.max(...this.episodes.map(e => e.accessCount))
|
|
209
|
+
const importance = Math.log10(ep.accessCount + 1) / Math.log10(maxAccess + 1)
|
|
210
|
+
|
|
211
|
+
// Weighted score
|
|
212
|
+
const score = weights.recency * recency +
|
|
213
|
+
weights.relevance * relevance +
|
|
214
|
+
weights.importance * importance
|
|
215
|
+
|
|
216
|
+
return { episode: ep, score }
|
|
217
|
+
})
|
|
218
|
+
.filter(m => m.score > 0.1)
|
|
219
|
+
.sort((a, b) => b.score - a.score)
|
|
220
|
+
.slice(0, limit)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Rolling context window - expand time range until sufficient context
|
|
225
|
+
*/
|
|
226
|
+
buildContextWindow(query, maxTokens = 8192) {
|
|
227
|
+
const windows = this.config.rollingWindows
|
|
228
|
+
const now = new Date()
|
|
229
|
+
|
|
230
|
+
for (let i = 0; i < windows.length; i++) {
|
|
231
|
+
const windowHours = windows[i]
|
|
232
|
+
const cutoff = new Date(now - windowHours * 60 * 60 * 1000)
|
|
233
|
+
|
|
234
|
+
const episodes = this.episodes
|
|
235
|
+
.filter(ep => ep.createdAt >= cutoff)
|
|
236
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
237
|
+
|
|
238
|
+
const estimatedTokens = episodes.reduce((sum, ep) => {
|
|
239
|
+
return sum + Math.ceil((ep.prompt.length + JSON.stringify(ep.result).length) / 4)
|
|
240
|
+
}, 0)
|
|
241
|
+
|
|
242
|
+
// If we have enough episodes or reached max window, return
|
|
243
|
+
if (episodes.length >= 3 || i === windows.length - 1 || estimatedTokens >= maxTokens) {
|
|
244
|
+
return {
|
|
245
|
+
episodes: this.retrieve(query, 10).map(m => m.episode),
|
|
246
|
+
estimatedTokens,
|
|
247
|
+
timeWindowHours: windowHours,
|
|
248
|
+
searchPasses: i + 1,
|
|
249
|
+
truncated: estimatedTokens > maxTokens
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { episodes: [], estimatedTokens: 0, timeWindowHours: 0, searchPasses: 0, truncated: false }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Idempotent query - same input returns cached result
|
|
259
|
+
*/
|
|
260
|
+
getCachedResult(query) {
|
|
261
|
+
return this.queryCache.get(query)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
cacheResult(query, result) {
|
|
265
|
+
this.queryCache.set(query, {
|
|
266
|
+
result,
|
|
267
|
+
cachedAt: new Date(),
|
|
268
|
+
hash: this._simpleHash(JSON.stringify(result))
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_textSimilarity(a, b) {
|
|
273
|
+
const wordsA = new Set(a.toLowerCase().split(/\s+/))
|
|
274
|
+
const wordsB = new Set(b.toLowerCase().split(/\s+/))
|
|
275
|
+
const intersection = new Set([...wordsA].filter(x => wordsB.has(x)))
|
|
276
|
+
const union = new Set([...wordsA, ...wordsB])
|
|
277
|
+
return intersection.size / union.size
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
_simpleHash(str) {
|
|
281
|
+
let hash = 0
|
|
282
|
+
for (let i = 0; i < str.length; i++) {
|
|
283
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i)
|
|
284
|
+
hash |= 0
|
|
285
|
+
}
|
|
286
|
+
return 'sha256:' + Math.abs(hash).toString(16).padStart(16, '0')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
_episodeToTtl(episode) {
|
|
290
|
+
return `
|
|
291
|
+
@prefix am: <https://gonnect.ai/ontology/agent-memory#> .
|
|
292
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
293
|
+
|
|
294
|
+
<${episode.id}> a am:Episode ;
|
|
295
|
+
am:prompt "${episode.prompt.replace(/"/g, '\\"')}" ;
|
|
296
|
+
am:success "${episode.success}"^^xsd:boolean ;
|
|
297
|
+
am:createdAt "${episode.createdAt.toISOString()}"^^xsd:dateTime ;
|
|
298
|
+
am:accessCount "${episode.accessCount}"^^xsd:integer .
|
|
299
|
+
`
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
304
|
+
// OPENAI INTEGRATION
|
|
305
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get embedding from OpenAI
|
|
309
|
+
*/
|
|
310
|
+
async function getOpenAIEmbedding(text) {
|
|
311
|
+
try {
|
|
312
|
+
const response = await fetch('https://api.openai.com/v1/embeddings', {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
headers: {
|
|
315
|
+
'Authorization': `Bearer ${CONFIG.openai.apiKey}`,
|
|
316
|
+
'Content-Type': 'application/json'
|
|
317
|
+
},
|
|
318
|
+
body: JSON.stringify({
|
|
319
|
+
model: CONFIG.openai.embedModel,
|
|
320
|
+
input: text,
|
|
321
|
+
dimensions: CONFIG.openai.dimensions
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
const data = await response.json()
|
|
325
|
+
if (data.error) {
|
|
326
|
+
console.log(` [OpenAI] Embedding error: ${data.error.message}`)
|
|
327
|
+
return null
|
|
328
|
+
}
|
|
329
|
+
return data.data[0].embedding
|
|
330
|
+
} catch (e) {
|
|
331
|
+
console.log(` [OpenAI] Embedding failed: ${e.message}`)
|
|
332
|
+
return null
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Query OpenAI for natural language understanding
|
|
338
|
+
*/
|
|
339
|
+
async function queryOpenAI(prompt, context = '') {
|
|
340
|
+
try {
|
|
341
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
headers: {
|
|
344
|
+
'Authorization': `Bearer ${CONFIG.openai.apiKey}`,
|
|
345
|
+
'Content-Type': 'application/json'
|
|
346
|
+
},
|
|
347
|
+
body: JSON.stringify({
|
|
348
|
+
model: CONFIG.openai.model,
|
|
349
|
+
messages: [
|
|
350
|
+
{
|
|
351
|
+
role: 'system',
|
|
352
|
+
content: `You are a fraud detection assistant. You have access to a knowledge graph with insurance claims, providers, and claimants. Be concise and specific.${context ? '\n\nPrevious context:\n' + context : ''}`
|
|
353
|
+
},
|
|
354
|
+
{ role: 'user', content: prompt }
|
|
355
|
+
],
|
|
356
|
+
max_tokens: 500,
|
|
357
|
+
temperature: 0.1
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
const data = await response.json()
|
|
361
|
+
if (data.error) {
|
|
362
|
+
return { success: false, error: data.error.message }
|
|
363
|
+
}
|
|
364
|
+
return { success: true, response: data.choices[0].message.content }
|
|
365
|
+
} catch (e) {
|
|
366
|
+
return { success: false, error: e.message }
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
371
|
+
// MAIN DEMO
|
|
372
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
373
|
+
|
|
374
|
+
async function main() {
|
|
375
|
+
console.log()
|
|
376
|
+
console.log('═'.repeat(80))
|
|
377
|
+
console.log(' MEMORY HYPERGRAPH DEMO - Fraud Detection with Persistent Memory')
|
|
378
|
+
console.log(` rust-kgdb v${getVersion()} | Memory Hypergraph Architecture`)
|
|
379
|
+
console.log('═'.repeat(80))
|
|
380
|
+
console.log()
|
|
381
|
+
|
|
382
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
383
|
+
// PHASE 1: Initialize Knowledge Graph
|
|
384
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
console.log('┌─ PHASE 1: Initialize Knowledge Graph ────────────────────────────────────┐')
|
|
387
|
+
const db = new GraphDB(CONFIG.kg.baseUri)
|
|
388
|
+
db.loadTtl(FRAUD_ONTOLOGY, CONFIG.kg.graphUri)
|
|
389
|
+
console.log(` ✓ Knowledge Graph loaded: ${db.countTriples()} triples`)
|
|
390
|
+
console.log(` ✓ Graph URI: ${CONFIG.kg.graphUri}`)
|
|
391
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
392
|
+
console.log()
|
|
393
|
+
|
|
394
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
395
|
+
// PHASE 2: Initialize Memory Hypergraph
|
|
396
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
397
|
+
|
|
398
|
+
console.log('┌─ PHASE 2: Initialize Memory Hypergraph ──────────────────────────────────┐')
|
|
399
|
+
const memory = new MemoryHypergraph(db, CONFIG.memory)
|
|
400
|
+
const runtime = new AgentRuntime({
|
|
401
|
+
name: 'fraud-detector-with-memory',
|
|
402
|
+
model: CONFIG.openai.model,
|
|
403
|
+
tools: ['kg.sparql.query', 'kg.memory.recall', 'kg.memory.store'],
|
|
404
|
+
memoryCapacity: 100
|
|
405
|
+
})
|
|
406
|
+
runtime.transitionTo(AgentState.READY)
|
|
407
|
+
console.log(` ✓ Memory Hypergraph initialized`)
|
|
408
|
+
console.log(` ✓ Temporal scoring: recency=${CONFIG.memory.weights.recency}, relevance=${CONFIG.memory.weights.relevance}, importance=${CONFIG.memory.weights.importance}`)
|
|
409
|
+
console.log(` ✓ Rolling windows: ${CONFIG.memory.rollingWindows.map(h => h < 24 ? `${h}h` : h < 168 ? `${h/24}d` : `${Math.round(h/720)}mo`).join(' → ')}`)
|
|
410
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
411
|
+
console.log()
|
|
412
|
+
|
|
413
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
414
|
+
// PHASE 3: First Investigation - Fraud Ring Detection
|
|
415
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
416
|
+
|
|
417
|
+
console.log('┌─ PHASE 3: First Investigation - Fraud Ring Detection ────────────────────┐')
|
|
418
|
+
console.log('│ Simulates: Monday, Dec 10 - Initial fraud analysis │')
|
|
419
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
420
|
+
console.log()
|
|
421
|
+
|
|
422
|
+
// Query high-risk claimants
|
|
423
|
+
const highRiskQuery = `
|
|
424
|
+
PREFIX ins: <http://insurance.org/>
|
|
425
|
+
SELECT ?claimant ?name ?score WHERE {
|
|
426
|
+
?claimant a ins:Claimant ;
|
|
427
|
+
ins:name ?name ;
|
|
428
|
+
ins:riskScore ?score .
|
|
429
|
+
FILTER(?score > 0.7)
|
|
430
|
+
}
|
|
431
|
+
`
|
|
432
|
+
const highRiskResults = db.querySelect(highRiskQuery)
|
|
433
|
+
console.log(` [SPARQL] Found ${highRiskResults.length} high-risk claimants`)
|
|
434
|
+
|
|
435
|
+
// Detect triangles
|
|
436
|
+
const gf = new GraphFrame(
|
|
437
|
+
JSON.stringify([
|
|
438
|
+
{ id: 'C001', type: 'claimant' },
|
|
439
|
+
{ id: 'C002', type: 'claimant' },
|
|
440
|
+
{ id: 'P001', type: 'provider' }
|
|
441
|
+
]),
|
|
442
|
+
JSON.stringify([
|
|
443
|
+
{ src: 'C001', dst: 'C002', relationship: 'knows' },
|
|
444
|
+
{ src: 'C001', dst: 'P001', relationship: 'claims_with' },
|
|
445
|
+
{ src: 'C002', dst: 'P001', relationship: 'claims_with' }
|
|
446
|
+
])
|
|
447
|
+
)
|
|
448
|
+
const triangles = gf.triangleCount()
|
|
449
|
+
console.log(` [GraphFrame] Detected ${triangles} fraud ring triangle(s)`)
|
|
450
|
+
|
|
451
|
+
// Store first episode in memory
|
|
452
|
+
const episode1 = memory.storeEpisode({
|
|
453
|
+
prompt: 'Investigate fraud patterns for Provider P001 (Quick Care Clinic)',
|
|
454
|
+
result: {
|
|
455
|
+
highRiskClaimants: highRiskResults.length,
|
|
456
|
+
trianglesDetected: triangles,
|
|
457
|
+
findings: 'Fraud ring detected: C001 ↔ C002 ↔ P001'
|
|
458
|
+
},
|
|
459
|
+
success: true,
|
|
460
|
+
kgEntities: ['ins:P001', 'ins:C001', 'ins:C002']
|
|
461
|
+
})
|
|
462
|
+
console.log(` [Memory] Stored Episode: ${episode1.id}`)
|
|
463
|
+
console.log(` Linked to KG entities: ${episode1.kgEntities.join(', ')}`)
|
|
464
|
+
console.log()
|
|
465
|
+
|
|
466
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
467
|
+
// PHASE 4: Second Investigation - Underwriting Decision
|
|
468
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
console.log('┌─ PHASE 4: Second Investigation - Underwriting Decision ──────────────────┐')
|
|
471
|
+
console.log('│ Simulates: Wednesday, Dec 12 - Claims review │')
|
|
472
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
473
|
+
console.log()
|
|
474
|
+
|
|
475
|
+
// Check if there's relevant context from previous investigation
|
|
476
|
+
const context1 = memory.buildContextWindow('Provider P001 underwriting decision')
|
|
477
|
+
console.log(` [Memory] Rolling window search:`)
|
|
478
|
+
console.log(` - Time window: ${context1.timeWindowHours}h`)
|
|
479
|
+
console.log(` - Episodes found: ${context1.episodes.length}`)
|
|
480
|
+
console.log(` - Estimated tokens: ${context1.estimatedTokens}`)
|
|
481
|
+
|
|
482
|
+
// Use context in decision
|
|
483
|
+
const episode2 = memory.storeEpisode({
|
|
484
|
+
prompt: 'Underwriting review for new claim from Provider P001',
|
|
485
|
+
result: {
|
|
486
|
+
decision: 'DENIED',
|
|
487
|
+
reason: 'Provider linked to fraud ring (see previous investigation)',
|
|
488
|
+
priorContext: episode1.id
|
|
489
|
+
},
|
|
490
|
+
success: true,
|
|
491
|
+
kgEntities: ['ins:P001', 'ins:CLM003']
|
|
492
|
+
})
|
|
493
|
+
console.log(` [Memory] Stored Episode: ${episode2.id}`)
|
|
494
|
+
console.log(` Decision: DENIED (based on prior investigation)`)
|
|
495
|
+
console.log()
|
|
496
|
+
|
|
497
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
498
|
+
// PHASE 5: Recall - "What did we find last week?"
|
|
499
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
500
|
+
|
|
501
|
+
console.log('┌─ PHASE 5: Memory Recall - "What did we find last week?" ─────────────────┐')
|
|
502
|
+
console.log('│ Simulates: Friday, Dec 15 - Analyst asks about previous findings │')
|
|
503
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
504
|
+
console.log()
|
|
505
|
+
|
|
506
|
+
const query = "What fraud patterns did we find with Provider P001?"
|
|
507
|
+
|
|
508
|
+
// WITHOUT Memory Hypergraph (traditional approach)
|
|
509
|
+
console.log(' ┌─ WITHOUT Memory Hypergraph (LangChain approach) ─────────────────────────┐')
|
|
510
|
+
console.log(' │ Agent: "I don\'t have access to previous conversations." │')
|
|
511
|
+
console.log(' │ Cost: Re-run entire fraud detection pipeline ($5, 30s) │')
|
|
512
|
+
console.log(' └──────────────────────────────────────────────────────────────────────────┘')
|
|
513
|
+
console.log()
|
|
514
|
+
|
|
515
|
+
// WITH Memory Hypergraph
|
|
516
|
+
console.log(' ┌─ WITH Memory Hypergraph (rust-kgdb approach) ────────────────────────────┐')
|
|
517
|
+
const memories = memory.retrieve(query, 5)
|
|
518
|
+
console.log(` │ Memories retrieved: ${memories.length} │`)
|
|
519
|
+
memories.forEach((m, i) => {
|
|
520
|
+
console.log(` │ ${i+1}. Score: ${m.score.toFixed(3)} - "${m.episode.prompt.slice(0, 40)}..."`)
|
|
521
|
+
})
|
|
522
|
+
console.log(' └──────────────────────────────────────────────────────────────────────────┘')
|
|
523
|
+
console.log()
|
|
524
|
+
|
|
525
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
526
|
+
// PHASE 6: Idempotent Response Demo
|
|
527
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
console.log('┌─ PHASE 6: Idempotent Response Demo ──────────────────────────────────────┐')
|
|
530
|
+
console.log('│ Same question = Same answer (compliance requirement) │')
|
|
531
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
532
|
+
console.log()
|
|
533
|
+
|
|
534
|
+
const complianceQuery = "Analyze claims from Provider P001"
|
|
535
|
+
|
|
536
|
+
// First call - compute and cache
|
|
537
|
+
const result1 = { findings: 'Fraud ring detected', riskLevel: 'CRITICAL' }
|
|
538
|
+
memory.cacheResult(complianceQuery, result1)
|
|
539
|
+
console.log(` First call: Computed fresh result`)
|
|
540
|
+
console.log(` Hash: ${memory.getCachedResult(complianceQuery).hash}`)
|
|
541
|
+
|
|
542
|
+
// Second call - return cached
|
|
543
|
+
const cached = memory.getCachedResult(complianceQuery)
|
|
544
|
+
console.log(` Second call: Returned cached result (idempotent)`)
|
|
545
|
+
console.log(` Hash: ${cached.hash}`)
|
|
546
|
+
console.log(` Match: ${memory.getCachedResult(complianceQuery).hash === cached.hash ? '✓' : '✗'}`)
|
|
547
|
+
console.log()
|
|
548
|
+
|
|
549
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
550
|
+
// PHASE 7: OpenAI Integration with Memory Context
|
|
551
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
552
|
+
|
|
553
|
+
console.log('┌─ PHASE 7: OpenAI Integration with Memory Context ────────────────────────┐')
|
|
554
|
+
console.log('│ LLM query augmented with Memory Hypergraph context │')
|
|
555
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
556
|
+
console.log()
|
|
557
|
+
|
|
558
|
+
// Build context from memory
|
|
559
|
+
const contextWindow = memory.buildContextWindow("Provider P001 fraud investigation")
|
|
560
|
+
const contextSummary = contextWindow.episodes
|
|
561
|
+
.map(ep => `- ${ep.prompt}: ${JSON.stringify(ep.result)}`)
|
|
562
|
+
.join('\n')
|
|
563
|
+
|
|
564
|
+
console.log(` [Memory Context]`)
|
|
565
|
+
console.log(` Time window: ${contextWindow.timeWindowHours}h`)
|
|
566
|
+
console.log(` Episodes: ${contextWindow.episodes.length}`)
|
|
567
|
+
console.log(` Estimated tokens: ${contextWindow.estimatedTokens}`)
|
|
568
|
+
console.log()
|
|
569
|
+
|
|
570
|
+
// Query OpenAI with memory context
|
|
571
|
+
console.log(` [OpenAI Query with Context]`)
|
|
572
|
+
console.log(` User: "What should we do about Provider P001?"`)
|
|
573
|
+
|
|
574
|
+
const aiResponse = await queryOpenAI(
|
|
575
|
+
"What should we do about Provider P001 based on our findings?",
|
|
576
|
+
contextSummary
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if (aiResponse.success) {
|
|
580
|
+
console.log(` Agent: ${aiResponse.response.slice(0, 200)}...`)
|
|
581
|
+
|
|
582
|
+
// Store this interaction as an episode
|
|
583
|
+
memory.storeEpisode({
|
|
584
|
+
prompt: "What should we do about Provider P001 based on our findings?",
|
|
585
|
+
result: { aiResponse: aiResponse.response },
|
|
586
|
+
success: true,
|
|
587
|
+
kgEntities: ['ins:P001']
|
|
588
|
+
})
|
|
589
|
+
console.log(` [Memory] Episode stored with AI response`)
|
|
590
|
+
} else {
|
|
591
|
+
console.log(` [OpenAI] Query failed: ${aiResponse.error}`)
|
|
592
|
+
console.log(` (This is expected if API key is invalid or rate-limited)`)
|
|
593
|
+
}
|
|
594
|
+
console.log()
|
|
595
|
+
|
|
596
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
597
|
+
// PHASE 8: SPARQL Query Across Memory + KG
|
|
598
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
599
|
+
|
|
600
|
+
console.log('┌─ PHASE 8: SPARQL Across Memory + KG ─────────────────────────────────────┐')
|
|
601
|
+
console.log('│ Single query traverses BOTH memory graph AND knowledge graph │')
|
|
602
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────┘')
|
|
603
|
+
console.log()
|
|
604
|
+
|
|
605
|
+
console.log(' Example SPARQL (conceptual - both graphs in same store):')
|
|
606
|
+
console.log()
|
|
607
|
+
console.log(' PREFIX am: <https://gonnect.ai/ontology/agent-memory#>')
|
|
608
|
+
console.log(' PREFIX ins: <http://insurance.org/>')
|
|
609
|
+
console.log()
|
|
610
|
+
console.log(' SELECT ?episode ?finding ?claimAmount WHERE {')
|
|
611
|
+
console.log(' # Search memory graph')
|
|
612
|
+
console.log(' GRAPH <https://gonnect.ai/memory/> {')
|
|
613
|
+
console.log(' ?episode a am:Episode ;')
|
|
614
|
+
console.log(' am:prompt ?finding .')
|
|
615
|
+
console.log(' }')
|
|
616
|
+
console.log(' # Join with knowledge graph')
|
|
617
|
+
console.log(' ?claim ins:provider <ins:P001> ;')
|
|
618
|
+
console.log(' ins:amount ?claimAmount .')
|
|
619
|
+
console.log(' }')
|
|
620
|
+
console.log()
|
|
621
|
+
|
|
622
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
623
|
+
// SUMMARY
|
|
624
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
625
|
+
|
|
626
|
+
console.log('═'.repeat(80))
|
|
627
|
+
console.log(' MEMORY HYPERGRAPH SUMMARY')
|
|
628
|
+
console.log('═'.repeat(80))
|
|
629
|
+
console.log()
|
|
630
|
+
console.log(' ┌──────────────────────────────────────────────────────────────────────┐')
|
|
631
|
+
console.log(' │ ARCHITECTURE │')
|
|
632
|
+
console.log(' ├──────────────────────────────────────────────────────────────────────┤')
|
|
633
|
+
console.log(' │ • Memory stored in SAME quad store as knowledge graph │')
|
|
634
|
+
console.log(' │ • HyperEdges connect episodes to KG entities (direct URIs) │')
|
|
635
|
+
console.log(' │ • Single SPARQL query traverses both memory AND KG │')
|
|
636
|
+
console.log(' │ • Temporal scoring: Recency + Relevance + Importance │')
|
|
637
|
+
console.log(' │ • Rolling context window manages token limits │')
|
|
638
|
+
console.log(' │ • Idempotent responses for compliance │')
|
|
639
|
+
console.log(' └──────────────────────────────────────────────────────────────────────┘')
|
|
640
|
+
console.log()
|
|
641
|
+
console.log(' ┌──────────────────────────────────────────────────────────────────────┐')
|
|
642
|
+
console.log(' │ STATISTICS │')
|
|
643
|
+
console.log(' ├──────────────────────────────────────────────────────────────────────┤')
|
|
644
|
+
console.log(` │ Episodes stored: ${memory.episodes.length.toString().padEnd(47)}│`)
|
|
645
|
+
console.log(` │ Cached queries: ${memory.queryCache.size.toString().padEnd(47)}│`)
|
|
646
|
+
console.log(` │ KG triples: ${db.countTriples().toString().padEnd(47)}│`)
|
|
647
|
+
console.log(' └──────────────────────────────────────────────────────────────────────┘')
|
|
648
|
+
console.log()
|
|
649
|
+
console.log(' Run this demo:')
|
|
650
|
+
console.log(' node examples/fraud-memory-hypergraph.js')
|
|
651
|
+
console.log()
|
|
652
|
+
console.log('═'.repeat(80))
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
main().catch(err => {
|
|
656
|
+
console.error('Demo failed:', err.message)
|
|
657
|
+
process.exit(1)
|
|
658
|
+
})
|