@eucoder/rag 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,357 @@
1
+ import neo4j, { Driver, ManagedTransaction } from "neo4j-driver";
2
+ import type { Entity, Relation, AsyncKnowledgeGraph } from "./types.js";
3
+
4
+ export interface Neo4jConfig {
5
+ uri: string;
6
+ username: string;
7
+ password: string;
8
+ database?: string;
9
+ }
10
+
11
+ /**
12
+ * Knowledge Graph persistente con Neo4j
13
+ *
14
+ * Implementa un grafo di conoscenza persistente usando Neo4j come database.
15
+ * Supporta operazioni CRUD per entità e relazioni, query per trovare entità connesse,
16
+ * e gestione delle transazioni.
17
+ */
18
+ export class Neo4jKnowledgeGraph implements AsyncKnowledgeGraph {
19
+ private driver: Driver;
20
+ private database: string;
21
+
22
+ constructor(config: Neo4jConfig) {
23
+ this.driver = neo4j.driver(
24
+ config.uri,
25
+ neo4j.auth.basic(config.username, config.password)
26
+ );
27
+ this.database = config.database || "neo4j";
28
+ }
29
+
30
+ /**
31
+ * Chiude la connessione al database
32
+ */
33
+ async close(): Promise<void> {
34
+ await this.driver.close();
35
+ }
36
+
37
+ /**
38
+ * Verifica la connessione al database
39
+ */
40
+ async verifyConnectivity(): Promise<boolean> {
41
+ try {
42
+ await this.driver.verifyConnectivity();
43
+ return true;
44
+ } catch (error) {
45
+ console.error("Neo4j connectivity check failed:", error);
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Inizializza gli indici e i vincoli del database
52
+ */
53
+ async initializeSchema(): Promise<void> {
54
+ const session = this.driver.session({ database: this.database });
55
+ try {
56
+ // Crea indice univoco per Entity.id
57
+ await session.executeWrite(async (tx: ManagedTransaction) => {
58
+ await tx.run(`
59
+ CREATE INDEX entity_id_index IF NOT EXISTS
60
+ FOR (e:Entity) ON (e.id)
61
+ `);
62
+ });
63
+
64
+ // Crea indice per Entity.type
65
+ await session.executeWrite(async (tx: ManagedTransaction) => {
66
+ await tx.run(`
67
+ CREATE INDEX entity_type_index IF NOT EXISTS
68
+ FOR (e:Entity) ON (e.type)
69
+ `);
70
+ });
71
+
72
+ // Crea indice per Relation.type
73
+ await session.executeWrite(async (tx: ManagedTransaction) => {
74
+ await tx.run(`
75
+ CREATE INDEX relation_type_index IF NOT EXISTS
76
+ FOR ()-[r:RELATES_TO]-() ON (r.type)
77
+ `);
78
+ });
79
+ } finally {
80
+ await session.close();
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Aggiunge un'entità al grafo
86
+ */
87
+ async addEntity(entity: Entity): Promise<void> {
88
+ const session = this.driver.session({ database: this.database });
89
+ try {
90
+ await session.executeWrite(async (tx: ManagedTransaction) => {
91
+ await tx.run(
92
+ `
93
+ MERGE (e:Entity {id: $id})
94
+ SET e.name = $name,
95
+ e.type = $type,
96
+ e.properties = $properties,
97
+ e.updatedAt = datetime()
98
+ `,
99
+ {
100
+ id: entity.id,
101
+ name: entity.name,
102
+ type: entity.type,
103
+ properties: JSON.stringify(entity.properties || {}),
104
+ }
105
+ );
106
+ });
107
+ } finally {
108
+ await session.close();
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Aggiunge una relazione al grafo
114
+ */
115
+ async addRelation(relation: Relation): Promise<void> {
116
+ const session = this.driver.session({ database: this.database });
117
+ try {
118
+ await session.executeWrite(async (tx: ManagedTransaction) => {
119
+ await tx.run(
120
+ `
121
+ MATCH (source:Entity {id: $sourceId})
122
+ MATCH (target:Entity {id: $targetId})
123
+ MERGE (source)-[r:RELATES_TO {id: $id}]->(target)
124
+ SET r.type = $type,
125
+ r.properties = $properties,
126
+ r.updatedAt = datetime()
127
+ `,
128
+ {
129
+ id: relation.id,
130
+ sourceId: relation.source,
131
+ targetId: relation.target,
132
+ type: relation.type,
133
+ properties: JSON.stringify(relation.properties || {}),
134
+ }
135
+ );
136
+ });
137
+ } finally {
138
+ await session.close();
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Recupera un'entità per ID
144
+ */
145
+ async getEntity(id: string): Promise<Entity | undefined> {
146
+ const session = this.driver.session({ database: this.database });
147
+ try {
148
+ const result = await session.executeRead(async (tx: ManagedTransaction) => {
149
+ return await tx.run(
150
+ `
151
+ MATCH (e:Entity {id: $id})
152
+ RETURN e.id AS id, e.name AS name, e.type AS type, e.properties AS properties
153
+ `,
154
+ { id }
155
+ );
156
+ });
157
+
158
+ if (result.records.length === 0) {
159
+ return undefined;
160
+ }
161
+
162
+ const record = result.records[0];
163
+ return {
164
+ id: record.get("id"),
165
+ name: record.get("name"),
166
+ type: record.get("type"),
167
+ properties: JSON.parse(record.get("properties") || "{}"),
168
+ };
169
+ } finally {
170
+ await session.close();
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Recupera tutte le relazioni di un'entità
176
+ */
177
+ async getRelations(entityId: string): Promise<Relation[]> {
178
+ const session = this.driver.session({ database: this.database });
179
+ try {
180
+ const result = await session.executeRead(async (tx: ManagedTransaction) => {
181
+ return await tx.run(
182
+ `
183
+ MATCH (e:Entity {id: $entityId})-[r:RELATES_TO]-(other:Entity)
184
+ RETURN r.id AS id,
185
+ r.type AS type,
186
+ r.properties AS properties,
187
+ startNode(r).id AS source,
188
+ endNode(r).id AS target
189
+ `,
190
+ { entityId }
191
+ );
192
+ });
193
+
194
+ return result.records.map((record) => ({
195
+ id: record.get("id"),
196
+ type: record.get("type"),
197
+ properties: JSON.parse(record.get("properties") || "{}"),
198
+ source: record.get("source"),
199
+ target: record.get("target"),
200
+ }));
201
+ } finally {
202
+ await session.close();
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Recupera tutte le entità connesse a un'entità fino a una certa profondità
208
+ */
209
+ async getConnectedEntities(entityId: string, depth: number = 1): Promise<Entity[]> {
210
+ const session = this.driver.session({ database: this.database });
211
+ try {
212
+ const result = await session.executeRead(async (tx: ManagedTransaction) => {
213
+ return await tx.run(
214
+ `
215
+ MATCH path = (start:Entity {id: $entityId})-[:RELATES_TO*1..${depth}]-(connected:Entity)
216
+ WHERE connected.id <> $entityId
217
+ RETURN DISTINCT connected.id AS id,
218
+ connected.name AS name,
219
+ connected.type AS type,
220
+ connected.properties AS properties
221
+ `,
222
+ { entityId }
223
+ );
224
+ });
225
+
226
+ return result.records.map((record) => ({
227
+ id: record.get("id"),
228
+ name: record.get("name"),
229
+ type: record.get("type"),
230
+ properties: JSON.parse(record.get("properties") || "{}"),
231
+ }));
232
+ } finally {
233
+ await session.close();
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Cerca entità per nome o tipo
239
+ */
240
+ async searchEntities(query: { name?: string; type?: string }): Promise<Entity[]> {
241
+ const session = this.driver.session({ database: this.database });
242
+ try {
243
+ const conditions: string[] = [];
244
+ const params: Record<string, any> = {};
245
+
246
+ if (query.name) {
247
+ conditions.push("e.name CONTAINS $name");
248
+ params.name = query.name;
249
+ }
250
+
251
+ if (query.type) {
252
+ conditions.push("e.type = $type");
253
+ params.type = query.type;
254
+ }
255
+
256
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
257
+
258
+ const result = await session.executeRead(async (tx: ManagedTransaction) => {
259
+ return await tx.run(
260
+ `
261
+ MATCH (e:Entity)
262
+ ${whereClause}
263
+ RETURN e.id AS id, e.name AS name, e.type AS type, e.properties AS properties
264
+ LIMIT 100
265
+ `,
266
+ params
267
+ );
268
+ });
269
+
270
+ return result.records.map((record) => ({
271
+ id: record.get("id"),
272
+ name: record.get("name"),
273
+ type: record.get("type"),
274
+ properties: JSON.parse(record.get("properties") || "{}"),
275
+ }));
276
+ } finally {
277
+ await session.close();
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Elimina un'entità e tutte le sue relazioni
283
+ */
284
+ async deleteEntity(entityId: string): Promise<void> {
285
+ const session = this.driver.session({ database: this.database });
286
+ try {
287
+ await session.executeWrite(async (tx: ManagedTransaction) => {
288
+ await tx.run(
289
+ `
290
+ MATCH (e:Entity {id: $entityId})
291
+ DETACH DELETE e
292
+ `,
293
+ { entityId }
294
+ );
295
+ });
296
+ } finally {
297
+ await session.close();
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Elimina una relazione per ID
303
+ */
304
+ async deleteRelation(relationId: string): Promise<void> {
305
+ const session = this.driver.session({ database: this.database });
306
+ try {
307
+ await session.executeWrite(async (tx: ManagedTransaction) => {
308
+ await tx.run(
309
+ `
310
+ MATCH ()-[r:RELATES_TO {id: $relationId}]-()
311
+ DELETE r
312
+ `,
313
+ { relationId }
314
+ );
315
+ });
316
+ } finally {
317
+ await session.close();
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Recupera statistiche del grafo
323
+ */
324
+ async getStats(): Promise<{ entityCount: number; relationCount: number }> {
325
+ const session = this.driver.session({ database: this.database });
326
+ try {
327
+ const entityResult = await session.executeRead(async (tx: ManagedTransaction) => {
328
+ return await tx.run(`MATCH (e:Entity) RETURN count(e) AS count`);
329
+ });
330
+
331
+ const relationResult = await session.executeRead(async (tx: ManagedTransaction) => {
332
+ return await tx.run(`MATCH ()-[r:RELATES_TO]-() RETURN count(r) AS count`);
333
+ });
334
+
335
+ return {
336
+ entityCount: entityResult.records[0].get("count").toNumber(),
337
+ relationCount: relationResult.records[0].get("count").toNumber(),
338
+ };
339
+ } finally {
340
+ await session.close();
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Pulisce completamente il grafo (elimina tutte le entità e relazioni)
346
+ */
347
+ async clear(): Promise<void> {
348
+ const session = this.driver.session({ database: this.database });
349
+ try {
350
+ await session.executeWrite(async (tx: ManagedTransaction) => {
351
+ await tx.run(`MATCH (n) DETACH DELETE n`);
352
+ });
353
+ } finally {
354
+ await session.close();
355
+ }
356
+ }
357
+ }
package/src/types.ts CHANGED
@@ -88,6 +88,20 @@ export interface KnowledgeGraph {
88
88
  getConnectedEntities(entityId: string, depth?: number): Entity[];
89
89
  }
90
90
 
91
+ export interface AsyncKnowledgeGraph {
92
+ addEntity(entity: Entity): Promise<void>;
93
+ addRelation(relation: Relation): Promise<void>;
94
+ getEntity(id: string): Promise<Entity | undefined>;
95
+ getRelations(entityId: string): Promise<Relation[]>;
96
+ getConnectedEntities(entityId: string, depth?: number): Promise<Entity[]>;
97
+ searchEntities(query: { name?: string; type?: string }): Promise<Entity[]>;
98
+ deleteEntity(entityId: string): Promise<void>;
99
+ deleteRelation(relationId: string): Promise<void>;
100
+ getStats(): Promise<{ entityCount: number; relationCount: number }>;
101
+ clear(): Promise<void>;
102
+ close(): Promise<void>;
103
+ }
104
+
91
105
  export interface EntityExtractor {
92
106
  extract(text: string): Promise<Entity[]>;
93
107
  }