cozo-memory 1.2.0 → 1.2.2
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/dist/emotional-salience.js +295 -0
- package/dist/index.js +726 -9
- package/dist/memory-activation.js +64 -30
- package/dist/memory-service.js +68 -0
- package/dist/pre-storage-reasoning.js +351 -0
- package/dist/temporal-conflict-resolution.js +10 -6
- package/dist/test-activation-mcp.js +118 -0
- package/dist/test-advanced-search-mcp.js +204 -0
- package/dist/test-conflicts-mcp.js +173 -0
- package/dist/test-emotional-salience.js +177 -0
- package/dist/test-hierarchical-mcp.js +135 -0
- package/dist/test-logical-edges-mcp.js +215 -0
- package/dist/test-metadata-check.js +69 -0
- package/dist/test-metadata-update.js +92 -0
- package/dist/test-pre-storage-reasoning.js +149 -0
- package/dist/test-salience-mcp.js +94 -0
- package/dist/test-spreading-mcp.js +155 -0
- package/dist/test-suggest-connections-mcp.js +172 -0
- package/dist/test-zettelkasten-evolution.js +255 -0
- package/dist/test-zettelkasten-fixed.js +74 -0
- package/dist/test-zettelkasten-live.js +117 -0
- package/dist/test-zettelkasten-mcp.js +96 -0
- package/dist/zettelkasten-evolution.js +342 -0
- package/package.json +1 -1
|
@@ -18,22 +18,27 @@ class MemoryActivationService {
|
|
|
18
18
|
/**
|
|
19
19
|
* Calculate activation score for an observation
|
|
20
20
|
* Formula: R = e^(-t/S)
|
|
21
|
+
* With emotional salience: decay rate is reduced by salienceBoostDecay
|
|
21
22
|
*/
|
|
22
|
-
calculateActivation(timeSinceAccess, strength) {
|
|
23
|
+
calculateActivation(timeSinceAccess, strength, salienceBoostDecay = 0.0) {
|
|
23
24
|
if (timeSinceAccess === 0)
|
|
24
25
|
return 1.0;
|
|
25
|
-
|
|
26
|
+
// Apply salience decay reduction (slower decay for emotionally salient memories)
|
|
27
|
+
const effectiveTime = timeSinceAccess * (1.0 - salienceBoostDecay);
|
|
28
|
+
const exponent = -effectiveTime / strength;
|
|
26
29
|
const activation = Math.pow(this.config.decayBase, exponent);
|
|
27
30
|
return Math.max(0, Math.min(1, activation));
|
|
28
31
|
}
|
|
29
32
|
/**
|
|
30
33
|
* Get current memory strength for an observation
|
|
31
34
|
* S = initialStrength + (accessCount * strengthIncrement)
|
|
35
|
+
* With emotional salience boost: S = S * salienceBoostStrength
|
|
32
36
|
*/
|
|
33
|
-
calculateStrength(accessCount) {
|
|
34
|
-
const
|
|
37
|
+
calculateStrength(accessCount, salienceBoostStrength = 1.0) {
|
|
38
|
+
const baseStrength = this.config.initialStrength +
|
|
35
39
|
(accessCount * this.config.strengthIncrement);
|
|
36
|
-
|
|
40
|
+
const boostedStrength = baseStrength * salienceBoostStrength;
|
|
41
|
+
return Math.min(boostedStrength, this.config.maxStrength);
|
|
37
42
|
}
|
|
38
43
|
/**
|
|
39
44
|
* Convert timestamp to time units since last access
|
|
@@ -57,12 +62,12 @@ class MemoryActivationService {
|
|
|
57
62
|
const query = entityId
|
|
58
63
|
? `
|
|
59
64
|
?[id, entity_id, text, metadata, created_at] :=
|
|
60
|
-
*observation{id, entity_id, text, metadata, created_at},
|
|
65
|
+
*observation{id, entity_id, session_id, task_id, text, metadata, created_at, @ "NOW"},
|
|
61
66
|
entity_id == $entity_id
|
|
62
67
|
`
|
|
63
68
|
: `
|
|
64
69
|
?[id, entity_id, text, metadata, created_at] :=
|
|
65
|
-
*observation{id, entity_id, text, metadata, created_at}
|
|
70
|
+
*observation{id, entity_id, session_id, task_id, text, metadata, created_at, @ "NOW"}
|
|
66
71
|
`;
|
|
67
72
|
const result = await this.db.run(query, entityId ? { entity_id: entityId } : {});
|
|
68
73
|
const observations = result.rows;
|
|
@@ -71,15 +76,22 @@ class MemoryActivationService {
|
|
|
71
76
|
// Extract access metadata
|
|
72
77
|
const accessCount = (metadata?.access_count || 0);
|
|
73
78
|
const lastAccessTime = (metadata?.last_access_time || created_at);
|
|
74
|
-
//
|
|
79
|
+
// Extract emotional salience boosts (if present)
|
|
80
|
+
const salienceBoostStrength = (metadata?.salience_boost_strength || 1.0);
|
|
81
|
+
const salienceBoostDecay = (metadata?.salience_boost_decay || 0.0);
|
|
82
|
+
const salienceScore = (metadata?.emotional_salience || 0.0);
|
|
83
|
+
// Calculate activation with salience boosts
|
|
75
84
|
const timeSinceAccess = this.getTimeSinceAccess(lastAccessTime);
|
|
76
|
-
const strength = this.calculateStrength(accessCount);
|
|
77
|
-
const activation = this.calculateActivation(timeSinceAccess, strength);
|
|
85
|
+
const strength = this.calculateStrength(accessCount, salienceBoostStrength);
|
|
86
|
+
const activation = this.calculateActivation(timeSinceAccess, strength, salienceBoostDecay);
|
|
78
87
|
// Determine retention
|
|
79
88
|
const shouldRetain = activation >= this.config.retentionThreshold;
|
|
89
|
+
const salienceInfo = salienceScore > 0
|
|
90
|
+
? ` [Salience: ${salienceScore.toFixed(2)}, Boost: ×${salienceBoostStrength.toFixed(2)}]`
|
|
91
|
+
: '';
|
|
80
92
|
const reason = shouldRetain
|
|
81
|
-
? `Active memory (activation: ${activation.toFixed(3)}, strength: ${strength.toFixed(1)})`
|
|
82
|
-
: `Below threshold (activation: ${activation.toFixed(3)} < ${this.config.retentionThreshold})`;
|
|
93
|
+
? `Active memory (activation: ${activation.toFixed(3)}, strength: ${strength.toFixed(1)})${salienceInfo}`
|
|
94
|
+
: `Below threshold (activation: ${activation.toFixed(3)} < ${this.config.retentionThreshold})${salienceInfo}`;
|
|
83
95
|
scores.push({
|
|
84
96
|
observationId: id,
|
|
85
97
|
entityId: entity_id,
|
|
@@ -105,15 +117,15 @@ class MemoryActivationService {
|
|
|
105
117
|
try {
|
|
106
118
|
// Get current observation
|
|
107
119
|
const result = await this.db.run(`
|
|
108
|
-
?[id, entity_id, text, metadata, created_at] :=
|
|
109
|
-
*observation{id, entity_id, text, metadata, created_at},
|
|
120
|
+
?[id, entity_id, session_id, task_id, text, metadata, created_at] :=
|
|
121
|
+
*observation{id, entity_id, session_id, task_id, text, metadata, created_at, @ "NOW"},
|
|
110
122
|
id == $id
|
|
111
123
|
`, { id: observationId });
|
|
112
124
|
if (result.rows.length === 0) {
|
|
113
125
|
console.warn(`[MemoryActivation] Observation ${observationId} not found`);
|
|
114
126
|
return;
|
|
115
127
|
}
|
|
116
|
-
const [id, entity_id, text, metadata, created_at] = result.rows[0];
|
|
128
|
+
const [id, entity_id, session_id, task_id, text, metadata, created_at] = result.rows[0];
|
|
117
129
|
const currentMetadata = (metadata || {});
|
|
118
130
|
// Update access metadata
|
|
119
131
|
const accessCount = (currentMetadata.access_count || 0) + 1;
|
|
@@ -123,16 +135,28 @@ class MemoryActivationService {
|
|
|
123
135
|
access_count: accessCount,
|
|
124
136
|
last_access_time: lastAccessTime,
|
|
125
137
|
};
|
|
138
|
+
// Get embedding (we need it for the update)
|
|
139
|
+
const embResult = await this.db.run(`
|
|
140
|
+
?[embedding] := *observation{id: $id, embedding, @ "NOW"}
|
|
141
|
+
`, { id: observationId });
|
|
142
|
+
if (embResult.rows.length === 0) {
|
|
143
|
+
console.warn(`[MemoryActivation] Could not get embedding for observation ${observationId}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const embedding = embResult.rows[0][0];
|
|
126
147
|
// Update observation with new metadata
|
|
127
148
|
await this.db.run(`
|
|
128
|
-
?[id, entity_id, text, metadata, created_at] <- [
|
|
129
|
-
[$id, $entity_id, $text, $metadata, $created_at]
|
|
149
|
+
?[id, entity_id, session_id, task_id, text, embedding, metadata, created_at] <- [
|
|
150
|
+
[$id, $entity_id, $session_id, $task_id, $text, $embedding, $metadata, $created_at]
|
|
130
151
|
]
|
|
131
|
-
:put observation {id, entity_id, text
|
|
152
|
+
:put observation {id, created_at => entity_id, session_id, task_id, text, embedding, metadata}
|
|
132
153
|
`, {
|
|
133
154
|
id,
|
|
134
155
|
entity_id,
|
|
156
|
+
session_id,
|
|
157
|
+
task_id,
|
|
135
158
|
text,
|
|
159
|
+
embedding,
|
|
136
160
|
metadata: updatedMetadata,
|
|
137
161
|
created_at
|
|
138
162
|
});
|
|
@@ -159,12 +183,12 @@ class MemoryActivationService {
|
|
|
159
183
|
// Actually delete weak memories
|
|
160
184
|
let pruned = 0;
|
|
161
185
|
for (const candidate of candidates) {
|
|
162
|
-
// Delete the observation
|
|
186
|
+
// Delete the observation using :rm
|
|
163
187
|
await this.db.run(`
|
|
164
|
-
?[id, entity_id, text, metadata, created_at] :=
|
|
165
|
-
*observation{id, entity_id, text, metadata, created_at},
|
|
188
|
+
?[id, entity_id, session_id, task_id, text, metadata, created_at] :=
|
|
189
|
+
*observation{id, entity_id, session_id, task_id, text, metadata, created_at, @ "NOW"},
|
|
166
190
|
id == $id
|
|
167
|
-
:rm observation {id, entity_id,
|
|
191
|
+
:rm observation {id, created_at => entity_id, session_id, task_id, text, metadata}
|
|
168
192
|
`, { id: candidate.observationId });
|
|
169
193
|
pruned++;
|
|
170
194
|
}
|
|
@@ -244,8 +268,8 @@ class MemoryActivationService {
|
|
|
244
268
|
try {
|
|
245
269
|
// Get the observation
|
|
246
270
|
const result = await this.db.run(`
|
|
247
|
-
?[id, entity_id, text, metadata, created_at] :=
|
|
248
|
-
*observation{id, entity_id, text, metadata, created_at},
|
|
271
|
+
?[id, entity_id, session_id, task_id, text, metadata, created_at] :=
|
|
272
|
+
*observation{id, entity_id, session_id, task_id, text, metadata, created_at, @ "NOW"},
|
|
249
273
|
id == $id
|
|
250
274
|
`, { id: observationId });
|
|
251
275
|
if (result.rows.length === 0) {
|
|
@@ -255,13 +279,13 @@ class MemoryActivationService {
|
|
|
255
279
|
const [id, entity_id] = result.rows[0];
|
|
256
280
|
// Get all observations for the same entity (excluding the current one)
|
|
257
281
|
const relatedResult = await this.db.run(`
|
|
258
|
-
?[id, entity_id, text, metadata, created_at] :=
|
|
259
|
-
*observation{id, entity_id, text, metadata, created_at},
|
|
282
|
+
?[id, entity_id, session_id, task_id, text, metadata, created_at] :=
|
|
283
|
+
*observation{id, entity_id, session_id, task_id, text, metadata, created_at, @ "NOW"},
|
|
260
284
|
entity_id == $entity_id,
|
|
261
285
|
id != $id
|
|
262
286
|
`, { entity_id, id: observationId });
|
|
263
287
|
let boosted = 0;
|
|
264
|
-
for (const [relId, relEntityId, relText, relMetadata, relCreatedAt] of relatedResult.rows) {
|
|
288
|
+
for (const [relId, relEntityId, relSessionId, relTaskId, relText, relMetadata, relCreatedAt] of relatedResult.rows) {
|
|
265
289
|
const currentMetadata = (relMetadata || {});
|
|
266
290
|
// Simulate a partial access (priming effect)
|
|
267
291
|
const accessCount = (currentMetadata.access_count || 0) + boostFactor;
|
|
@@ -269,16 +293,26 @@ class MemoryActivationService {
|
|
|
269
293
|
...currentMetadata,
|
|
270
294
|
access_count: accessCount,
|
|
271
295
|
};
|
|
296
|
+
// Get embedding
|
|
297
|
+
const embResult = await this.db.run(`
|
|
298
|
+
?[embedding] := *observation{id: $id, embedding, @ "NOW"}
|
|
299
|
+
`, { id: relId });
|
|
300
|
+
if (embResult.rows.length === 0)
|
|
301
|
+
continue;
|
|
302
|
+
const relEmbedding = embResult.rows[0][0];
|
|
272
303
|
// Update the related observation
|
|
273
304
|
await this.db.run(`
|
|
274
|
-
?[id, entity_id, text, metadata, created_at] <- [
|
|
275
|
-
[$id, $entity_id, $text, $metadata, $created_at]
|
|
305
|
+
?[id, entity_id, session_id, task_id, text, embedding, metadata, created_at] <- [
|
|
306
|
+
[$id, $entity_id, $session_id, $task_id, $text, $embedding, $metadata, $created_at]
|
|
276
307
|
]
|
|
277
|
-
:put observation {id, entity_id, text
|
|
308
|
+
:put observation {id, created_at => entity_id, session_id, task_id, text, embedding, metadata}
|
|
278
309
|
`, {
|
|
279
310
|
id: relId,
|
|
280
311
|
entity_id: relEntityId,
|
|
312
|
+
session_id: relSessionId,
|
|
313
|
+
task_id: relTaskId,
|
|
281
314
|
text: relText,
|
|
315
|
+
embedding: relEmbedding,
|
|
282
316
|
metadata: updatedMetadata,
|
|
283
317
|
created_at: relCreatedAt
|
|
284
318
|
});
|
package/dist/memory-service.js
CHANGED
|
@@ -34,15 +34,51 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.MemoryService = void 0;
|
|
37
|
+
const emotional_salience_js_1 = require("./emotional-salience.js");
|
|
38
|
+
const zettelkasten_evolution_js_1 = require("./zettelkasten-evolution.js");
|
|
37
39
|
const uuid_1 = require("uuid");
|
|
38
40
|
const pdf_mjs_1 = require("pdfjs-dist/legacy/build/pdf.mjs");
|
|
39
41
|
const fs = __importStar(require("fs"));
|
|
40
42
|
class MemoryService {
|
|
41
43
|
db;
|
|
42
44
|
embeddings;
|
|
45
|
+
salienceService = null;
|
|
46
|
+
zettelkastenService = null;
|
|
43
47
|
constructor(db, embeddings) {
|
|
44
48
|
this.db = db;
|
|
45
49
|
this.embeddings = embeddings;
|
|
50
|
+
// Services will be initialized when needed
|
|
51
|
+
}
|
|
52
|
+
getSalienceService() {
|
|
53
|
+
if (!this.salienceService) {
|
|
54
|
+
// Create a temporary CozoDb instance for salience service
|
|
55
|
+
// In production, this should use the actual CozoDB instance
|
|
56
|
+
const { CozoDb } = require('cozo-node');
|
|
57
|
+
const tempDb = new CozoDb();
|
|
58
|
+
this.salienceService = new emotional_salience_js_1.EmotionalSalienceService(tempDb, {
|
|
59
|
+
enableSalience: true,
|
|
60
|
+
salienceBoostFactor: 2.0,
|
|
61
|
+
decaySlowdownFactor: 0.5,
|
|
62
|
+
minSalienceThreshold: 0.3
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return this.salienceService;
|
|
66
|
+
}
|
|
67
|
+
getZettelkastenService() {
|
|
68
|
+
if (!this.zettelkastenService) {
|
|
69
|
+
const { CozoDb } = require('cozo-node');
|
|
70
|
+
const tempDb = new CozoDb();
|
|
71
|
+
this.zettelkastenService = new zettelkasten_evolution_js_1.ZettelkastenEvolutionService(tempDb, this.embeddings, {
|
|
72
|
+
enableEvolution: true,
|
|
73
|
+
similarityThreshold: 0.7,
|
|
74
|
+
maxRelatedNotes: 5,
|
|
75
|
+
minKeywordFrequency: 2,
|
|
76
|
+
autoExtractKeywords: true,
|
|
77
|
+
autoBidirectionalLinks: true,
|
|
78
|
+
enrichmentDepth: 'shallow'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return this.zettelkastenService;
|
|
46
82
|
}
|
|
47
83
|
async createEntity(name, type, metadata = {}) {
|
|
48
84
|
const id = (0, uuid_1.v4)();
|
|
@@ -73,6 +109,21 @@ class MemoryService {
|
|
|
73
109
|
async addObservation(entityId, text, metadata = {}) {
|
|
74
110
|
const id = (0, uuid_1.v4)();
|
|
75
111
|
const embedding = await this.embeddings.embed(text);
|
|
112
|
+
// Calculate emotional salience automatically
|
|
113
|
+
const salienceService = this.getSalienceService();
|
|
114
|
+
const salienceResult = salienceService.calculateSalienceScore(text);
|
|
115
|
+
// Add salience metadata if score is above threshold
|
|
116
|
+
if (salienceResult.score >= 0.3) {
|
|
117
|
+
const boost = salienceService.calculateBoost(salienceResult.score);
|
|
118
|
+
metadata = {
|
|
119
|
+
...metadata,
|
|
120
|
+
emotional_salience: salienceResult.score,
|
|
121
|
+
salience_category: salienceResult.category,
|
|
122
|
+
salience_keywords: salienceResult.keywords,
|
|
123
|
+
salience_boost_strength: boost.strengthMultiplier,
|
|
124
|
+
salience_boost_decay: boost.decayReduction
|
|
125
|
+
};
|
|
126
|
+
}
|
|
76
127
|
const observation = {
|
|
77
128
|
id: id,
|
|
78
129
|
entity_id: entityId,
|
|
@@ -294,5 +345,22 @@ class MemoryService {
|
|
|
294
345
|
total_chunks: chunks.length,
|
|
295
346
|
};
|
|
296
347
|
}
|
|
348
|
+
// Emotional Salience Methods
|
|
349
|
+
async getSalienceStats() {
|
|
350
|
+
return this.getSalienceService().getSalienceStats();
|
|
351
|
+
}
|
|
352
|
+
async getObservationSalience(observationId) {
|
|
353
|
+
return this.getSalienceService().getObservationSalience(observationId);
|
|
354
|
+
}
|
|
355
|
+
async applySalienceToExistingObservations(dryRun = false) {
|
|
356
|
+
return this.getSalienceService().applySalienceMetadata(dryRun);
|
|
357
|
+
}
|
|
358
|
+
// Zettelkasten Memory Evolution Methods
|
|
359
|
+
async getZettelkastenStats() {
|
|
360
|
+
return this.getZettelkastenService().getEvolutionStats();
|
|
361
|
+
}
|
|
362
|
+
async enrichObservationWithZettelkasten(observationId, observationText, observationEmbedding, entityId) {
|
|
363
|
+
return this.getZettelkastenService().enrichObservation(observationId, observationText, observationEmbedding, entityId);
|
|
364
|
+
}
|
|
297
365
|
}
|
|
298
366
|
exports.MemoryService = MemoryService;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PreStorageReasoningService = void 0;
|
|
4
|
+
const ollama_1 = require("ollama");
|
|
5
|
+
class PreStorageReasoningService {
|
|
6
|
+
db;
|
|
7
|
+
config;
|
|
8
|
+
ollama;
|
|
9
|
+
constructor(db, config = {}) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.config = {
|
|
12
|
+
enablePreStorage: true,
|
|
13
|
+
ollamaModel: 'llama3.2:3b',
|
|
14
|
+
ollamaHost: 'http://localhost:11434',
|
|
15
|
+
extractEntities: true,
|
|
16
|
+
extractRelationships: true,
|
|
17
|
+
detectContradictions: true,
|
|
18
|
+
minConfidence: 0.6,
|
|
19
|
+
timeout: 10000,
|
|
20
|
+
...config
|
|
21
|
+
};
|
|
22
|
+
this.ollama = new ollama_1.Ollama({ host: this.config.ollamaHost });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Analyze observation text before storage
|
|
26
|
+
*/
|
|
27
|
+
async analyzeBeforeStorage(text, entityId, existingContext) {
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
if (!this.config.enablePreStorage) {
|
|
30
|
+
return {
|
|
31
|
+
text,
|
|
32
|
+
entitySuggestions: [],
|
|
33
|
+
relationshipSuggestions: [],
|
|
34
|
+
contradictionWarnings: [],
|
|
35
|
+
processingTime: 0,
|
|
36
|
+
model: 'disabled'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const analysis = {
|
|
41
|
+
text,
|
|
42
|
+
entitySuggestions: [],
|
|
43
|
+
relationshipSuggestions: [],
|
|
44
|
+
contradictionWarnings: [],
|
|
45
|
+
processingTime: 0,
|
|
46
|
+
model: this.config.ollamaModel
|
|
47
|
+
};
|
|
48
|
+
// Get entity context
|
|
49
|
+
const entityContext = await this.getEntityContext(entityId);
|
|
50
|
+
// Extract entities
|
|
51
|
+
if (this.config.extractEntities) {
|
|
52
|
+
analysis.entitySuggestions = await this.extractEntities(text, entityContext);
|
|
53
|
+
}
|
|
54
|
+
// Extract relationships
|
|
55
|
+
if (this.config.extractRelationships) {
|
|
56
|
+
analysis.relationshipSuggestions = await this.extractRelationships(text, entityContext, analysis.entitySuggestions);
|
|
57
|
+
}
|
|
58
|
+
// Detect contradictions
|
|
59
|
+
if (this.config.detectContradictions && existingContext) {
|
|
60
|
+
analysis.contradictionWarnings = await this.detectContradictions(text, existingContext);
|
|
61
|
+
}
|
|
62
|
+
analysis.processingTime = Date.now() - startTime;
|
|
63
|
+
return analysis;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error('[PreStorageReasoning] Error analyzing observation:', error);
|
|
67
|
+
return {
|
|
68
|
+
text,
|
|
69
|
+
entitySuggestions: [],
|
|
70
|
+
relationshipSuggestions: [],
|
|
71
|
+
contradictionWarnings: [],
|
|
72
|
+
processingTime: Date.now() - startTime,
|
|
73
|
+
model: this.config.ollamaModel
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Extract implied entities from text
|
|
79
|
+
*/
|
|
80
|
+
async extractEntities(text, entityContext) {
|
|
81
|
+
const prompt = `You are an entity extraction expert. Analyze the following text and extract any entities (people, organizations, technologies, concepts, etc.) that are mentioned or implied.
|
|
82
|
+
|
|
83
|
+
Context: ${entityContext}
|
|
84
|
+
|
|
85
|
+
Text to analyze: "${text}"
|
|
86
|
+
|
|
87
|
+
For each entity, provide:
|
|
88
|
+
1. Name (exact name as it appears or should be named)
|
|
89
|
+
2. Type (Person, Organization, Technology, Concept, Location, etc.)
|
|
90
|
+
3. Confidence (0.0-1.0, how confident you are this is a distinct entity)
|
|
91
|
+
4. Reason (brief explanation why this is an entity)
|
|
92
|
+
|
|
93
|
+
Respond ONLY with valid JSON array format:
|
|
94
|
+
[
|
|
95
|
+
{
|
|
96
|
+
"name": "Entity Name",
|
|
97
|
+
"type": "EntityType",
|
|
98
|
+
"confidence": 0.9,
|
|
99
|
+
"reason": "Brief reason"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
If no entities found, return empty array: []`;
|
|
104
|
+
try {
|
|
105
|
+
const response = await this.ollama.generate({
|
|
106
|
+
model: this.config.ollamaModel,
|
|
107
|
+
prompt,
|
|
108
|
+
stream: false,
|
|
109
|
+
options: {
|
|
110
|
+
temperature: 0.3,
|
|
111
|
+
num_predict: 500
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const jsonMatch = response.response.match(/\[[\s\S]*\]/);
|
|
115
|
+
if (!jsonMatch) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const entities = JSON.parse(jsonMatch[0]);
|
|
119
|
+
return entities.filter(e => e.confidence >= this.config.minConfidence);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error('[PreStorageReasoning] Error extracting entities:', error);
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract relationships from text
|
|
128
|
+
*/
|
|
129
|
+
async extractRelationships(text, entityContext, extractedEntities) {
|
|
130
|
+
const entitiesStr = extractedEntities.length > 0
|
|
131
|
+
? `\nExtracted entities: ${extractedEntities.map(e => e.name).join(', ')}`
|
|
132
|
+
: '';
|
|
133
|
+
const prompt = `You are a relationship extraction expert. Analyze the following text and extract relationships between entities.
|
|
134
|
+
|
|
135
|
+
Context: ${entityContext}${entitiesStr}
|
|
136
|
+
|
|
137
|
+
Text to analyze: "${text}"
|
|
138
|
+
|
|
139
|
+
For each relationship, provide:
|
|
140
|
+
1. fromEntity (source entity name)
|
|
141
|
+
2. toEntity (target entity name)
|
|
142
|
+
3. relationType (works_on, uses, manages, part_of, related_to, etc.)
|
|
143
|
+
4. Confidence (0.0-1.0)
|
|
144
|
+
5. Reason (brief explanation)
|
|
145
|
+
|
|
146
|
+
Respond ONLY with valid JSON array format:
|
|
147
|
+
[
|
|
148
|
+
{
|
|
149
|
+
"fromEntity": "Entity A",
|
|
150
|
+
"toEntity": "Entity B",
|
|
151
|
+
"relationType": "relationship_type",
|
|
152
|
+
"confidence": 0.8,
|
|
153
|
+
"reason": "Brief reason"
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
If no relationships found, return empty array: []`;
|
|
158
|
+
try {
|
|
159
|
+
const response = await this.ollama.generate({
|
|
160
|
+
model: this.config.ollamaModel,
|
|
161
|
+
prompt,
|
|
162
|
+
stream: false,
|
|
163
|
+
options: {
|
|
164
|
+
temperature: 0.3,
|
|
165
|
+
num_predict: 500
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
const jsonMatch = response.response.match(/\[[\s\S]*\]/);
|
|
169
|
+
if (!jsonMatch) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
const relationships = JSON.parse(jsonMatch[0]);
|
|
173
|
+
return relationships.filter(r => r.confidence >= this.config.minConfidence);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error('[PreStorageReasoning] Error extracting relationships:', error);
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Detect contradictions with existing observations
|
|
182
|
+
*/
|
|
183
|
+
async detectContradictions(newText, existingObservations) {
|
|
184
|
+
if (existingObservations.length === 0) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
const existingContext = existingObservations.slice(0, 5).join('\n- ');
|
|
188
|
+
const prompt = `You are a contradiction detection expert. Compare the new observation with existing observations and identify any contradictions.
|
|
189
|
+
|
|
190
|
+
Existing observations:
|
|
191
|
+
- ${existingContext}
|
|
192
|
+
|
|
193
|
+
New observation: "${newText}"
|
|
194
|
+
|
|
195
|
+
For each contradiction, provide:
|
|
196
|
+
1. existingObservation (the contradicting observation text)
|
|
197
|
+
2. contradiction (what contradicts)
|
|
198
|
+
3. Confidence (0.0-1.0)
|
|
199
|
+
4. Reason (brief explanation)
|
|
200
|
+
|
|
201
|
+
Respond ONLY with valid JSON array format:
|
|
202
|
+
[
|
|
203
|
+
{
|
|
204
|
+
"existingObservation": "Previous observation text",
|
|
205
|
+
"contradiction": "What contradicts",
|
|
206
|
+
"confidence": 0.9,
|
|
207
|
+
"reason": "Brief reason"
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
If no contradictions found, return empty array: []`;
|
|
212
|
+
try {
|
|
213
|
+
const response = await this.ollama.generate({
|
|
214
|
+
model: this.config.ollamaModel,
|
|
215
|
+
prompt,
|
|
216
|
+
stream: false,
|
|
217
|
+
options: {
|
|
218
|
+
temperature: 0.3,
|
|
219
|
+
num_predict: 500
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const jsonMatch = response.response.match(/\[[\s\S]*\]/);
|
|
223
|
+
if (!jsonMatch) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
const contradictions = JSON.parse(jsonMatch[0]);
|
|
227
|
+
return contradictions.filter(c => c.confidence >= this.config.minConfidence);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error('[PreStorageReasoning] Error detecting contradictions:', error);
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get entity context for analysis
|
|
236
|
+
*/
|
|
237
|
+
async getEntityContext(entityId) {
|
|
238
|
+
try {
|
|
239
|
+
const result = await this.db.run(`
|
|
240
|
+
?[name, type] := *entity{id, name, type}, id = $id
|
|
241
|
+
`, { id: entityId });
|
|
242
|
+
if (result.rows.length === 0) {
|
|
243
|
+
return 'No entity context available';
|
|
244
|
+
}
|
|
245
|
+
const [name, type] = result.rows[0];
|
|
246
|
+
return `Entity: ${name} (${type})`;
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
return 'No entity context available';
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get existing observations for contradiction detection
|
|
254
|
+
*/
|
|
255
|
+
async getExistingObservations(entityId, limit = 10) {
|
|
256
|
+
try {
|
|
257
|
+
const result = await this.db.run(`
|
|
258
|
+
?[text] := *observation{entity_id, text}, entity_id = $entity_id
|
|
259
|
+
:limit $limit
|
|
260
|
+
`, { entity_id: entityId, limit });
|
|
261
|
+
return result.rows.map((row) => row[0]);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
console.error('[PreStorageReasoning] Error getting existing observations:', error);
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Auto-apply suggestions (create entities and relationships)
|
|
270
|
+
*/
|
|
271
|
+
async autoApplySuggestions(analysis, autoCreateEntities = false, autoCreateRelationships = false) {
|
|
272
|
+
let entitiesCreated = 0;
|
|
273
|
+
let relationshipsCreated = 0;
|
|
274
|
+
// Auto-create entities
|
|
275
|
+
if (autoCreateEntities) {
|
|
276
|
+
for (const entity of analysis.entitySuggestions) {
|
|
277
|
+
try {
|
|
278
|
+
// Check if entity already exists
|
|
279
|
+
const existing = await this.db.run(`
|
|
280
|
+
?[id] := *entity{id, name}, name = $name
|
|
281
|
+
:limit 1
|
|
282
|
+
`, { name: entity.name });
|
|
283
|
+
if (existing.rows.length === 0) {
|
|
284
|
+
// Create new entity
|
|
285
|
+
const id = `entity-${Math.random().toString(36).substring(7)}`;
|
|
286
|
+
await this.db.run(`
|
|
287
|
+
?[id, name, type, metadata] <- [[
|
|
288
|
+
$id,
|
|
289
|
+
$name,
|
|
290
|
+
$type,
|
|
291
|
+
{"source": "pre_storage_reasoning", "confidence": $confidence}
|
|
292
|
+
]]
|
|
293
|
+
:put entity {id, name, type}
|
|
294
|
+
`, {
|
|
295
|
+
id,
|
|
296
|
+
name: entity.name,
|
|
297
|
+
type: entity.type,
|
|
298
|
+
confidence: entity.confidence
|
|
299
|
+
});
|
|
300
|
+
entitiesCreated++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
console.error(`[PreStorageReasoning] Error creating entity ${entity.name}:`, error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Auto-create relationships
|
|
309
|
+
if (autoCreateRelationships) {
|
|
310
|
+
for (const rel of analysis.relationshipSuggestions) {
|
|
311
|
+
try {
|
|
312
|
+
// Find entity IDs
|
|
313
|
+
const fromResult = await this.db.run(`
|
|
314
|
+
?[id] := *entity{id, name}, name = $name
|
|
315
|
+
:limit 1
|
|
316
|
+
`, { name: rel.fromEntity });
|
|
317
|
+
const toResult = await this.db.run(`
|
|
318
|
+
?[id] := *entity{id, name}, name = $name
|
|
319
|
+
:limit 1
|
|
320
|
+
`, { name: rel.toEntity });
|
|
321
|
+
if (fromResult.rows.length > 0 && toResult.rows.length > 0) {
|
|
322
|
+
const fromId = fromResult.rows[0][0];
|
|
323
|
+
const toId = toResult.rows[0][0];
|
|
324
|
+
// Create relationship
|
|
325
|
+
await this.db.run(`
|
|
326
|
+
?[from_id, to_id, relation_type, strength, metadata] <- [[
|
|
327
|
+
$from_id,
|
|
328
|
+
$to_id,
|
|
329
|
+
$relation_type,
|
|
330
|
+
$confidence,
|
|
331
|
+
{"source": "pre_storage_reasoning"}
|
|
332
|
+
]]
|
|
333
|
+
:put relationship {from_id, to_id, relation_type, strength, metadata}
|
|
334
|
+
`, {
|
|
335
|
+
from_id: fromId,
|
|
336
|
+
to_id: toId,
|
|
337
|
+
relation_type: rel.relationType,
|
|
338
|
+
confidence: rel.confidence
|
|
339
|
+
});
|
|
340
|
+
relationshipsCreated++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
console.error(`[PreStorageReasoning] Error creating relationship:`, error);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return { entitiesCreated, relationshipsCreated };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
exports.PreStorageReasoningService = PreStorageReasoningService;
|