@cogitator-ai/memory 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -90
- package/dist/adapters/postgres.d.ts +1 -0
- package/dist/adapters/postgres.d.ts.map +1 -1
- package/dist/adapters/postgres.js +78 -38
- package/dist/adapters/postgres.js.map +1 -1
- package/dist/adapters/qdrant.d.ts.map +1 -1
- package/dist/adapters/qdrant.js +16 -2
- package/dist/adapters/qdrant.js.map +1 -1
- package/dist/adapters/sqlite.d.ts +1 -0
- package/dist/adapters/sqlite.d.ts.map +1 -1
- package/dist/adapters/sqlite.js +17 -16
- package/dist/adapters/sqlite.js.map +1 -1
- package/dist/compaction.d.ts +14 -1
- package/dist/compaction.d.ts.map +1 -1
- package/dist/compaction.js +4 -1
- package/dist/compaction.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/knowledge-graph/entity-extractor.d.ts +24 -11
- package/dist/knowledge-graph/entity-extractor.d.ts.map +1 -1
- package/dist/knowledge-graph/entity-extractor.js +15 -3
- package/dist/knowledge-graph/entity-extractor.js.map +1 -1
- package/dist/knowledge-graph/graph-adapter.d.ts +1 -0
- package/dist/knowledge-graph/graph-adapter.d.ts.map +1 -1
- package/dist/knowledge-graph/graph-adapter.js +470 -448
- package/dist/knowledge-graph/graph-adapter.js.map +1 -1
- package/dist/knowledge-graph/sqlite-graph-adapter.d.ts +1 -0
- package/dist/knowledge-graph/sqlite-graph-adapter.d.ts.map +1 -1
- package/dist/knowledge-graph/sqlite-graph-adapter.js +459 -435
- package/dist/knowledge-graph/sqlite-graph-adapter.js.map +1 -1
- package/dist/result.d.ts +11 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +14 -0
- package/dist/result.js.map +1 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
+
import { unwrap } from '../result.js';
|
|
2
3
|
export class PostgresGraphAdapter {
|
|
3
4
|
pool;
|
|
4
5
|
schema;
|
|
@@ -109,308 +110,326 @@ export class PostgresGraphAdapter {
|
|
|
109
110
|
failure(error) {
|
|
110
111
|
return { success: false, error };
|
|
111
112
|
}
|
|
113
|
+
async run(operation) {
|
|
114
|
+
try {
|
|
115
|
+
await this.initialize();
|
|
116
|
+
return await operation();
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return this.failure(err instanceof Error ? err.message : String(err));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
112
122
|
generateId(prefix) {
|
|
113
123
|
return `${prefix}_${nanoid(12)}`;
|
|
114
124
|
}
|
|
115
125
|
async addNode(node) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
126
|
+
return this.run(async () => {
|
|
127
|
+
const id = this.generateId('node');
|
|
128
|
+
const now = new Date();
|
|
129
|
+
const embeddingStr = node.embedding ? `[${node.embedding.join(',')}]` : null;
|
|
130
|
+
await this.pool.query(`INSERT INTO ${this.schema}.graph_nodes
|
|
131
|
+
(id, agent_id, type, name, aliases, description, properties, embedding, confidence, source, metadata, created_at, updated_at, last_accessed_at, access_count)
|
|
132
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $12, $12, 0)`, [
|
|
133
|
+
id,
|
|
134
|
+
node.agentId,
|
|
135
|
+
node.type,
|
|
136
|
+
node.name,
|
|
137
|
+
node.aliases,
|
|
138
|
+
node.description ?? null,
|
|
139
|
+
node.properties,
|
|
140
|
+
embeddingStr,
|
|
141
|
+
node.confidence,
|
|
142
|
+
node.source,
|
|
143
|
+
node.metadata ?? {},
|
|
144
|
+
now,
|
|
145
|
+
]);
|
|
146
|
+
return this.success({
|
|
147
|
+
...node,
|
|
148
|
+
id,
|
|
149
|
+
createdAt: now,
|
|
150
|
+
updatedAt: now,
|
|
151
|
+
lastAccessedAt: now,
|
|
152
|
+
accessCount: 0,
|
|
153
|
+
});
|
|
143
154
|
});
|
|
144
155
|
}
|
|
145
156
|
async getNode(nodeId) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return this.success(this.rowToNode(result.rows[0]));
|
|
157
|
+
return this.run(async () => {
|
|
158
|
+
await this.pool.query(`UPDATE ${this.schema}.graph_nodes
|
|
159
|
+
SET last_accessed_at = NOW(), access_count = access_count + 1
|
|
160
|
+
WHERE id = $1`, [nodeId]);
|
|
161
|
+
const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
|
|
162
|
+
if (result.rows.length === 0)
|
|
163
|
+
return this.success(null);
|
|
164
|
+
return this.success(this.rowToNode(result.rows[0]));
|
|
165
|
+
});
|
|
156
166
|
}
|
|
157
167
|
async getNodeByName(agentId, name) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
return this.run(async () => {
|
|
169
|
+
const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_nodes
|
|
170
|
+
WHERE agent_id = $1 AND (name = $2 OR $2 = ANY(aliases))`, [agentId, name]);
|
|
171
|
+
if (result.rows.length === 0)
|
|
172
|
+
return this.success(null);
|
|
173
|
+
return this.success(this.rowToNode(result.rows[0]));
|
|
174
|
+
});
|
|
164
175
|
}
|
|
165
176
|
async updateNode(nodeId, updates) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
177
|
+
return this.run(async () => {
|
|
178
|
+
const setClauses = ['updated_at = NOW()'];
|
|
179
|
+
const params = [];
|
|
180
|
+
let paramIndex = 1;
|
|
181
|
+
if (updates.name !== undefined) {
|
|
182
|
+
setClauses.push(`name = $${paramIndex++}`);
|
|
183
|
+
params.push(updates.name);
|
|
184
|
+
}
|
|
185
|
+
if (updates.aliases !== undefined) {
|
|
186
|
+
setClauses.push(`aliases = $${paramIndex++}`);
|
|
187
|
+
params.push(updates.aliases);
|
|
188
|
+
}
|
|
189
|
+
if (updates.description !== undefined) {
|
|
190
|
+
setClauses.push(`description = $${paramIndex++}`);
|
|
191
|
+
params.push(updates.description);
|
|
192
|
+
}
|
|
193
|
+
if (updates.properties !== undefined) {
|
|
194
|
+
setClauses.push(`properties = $${paramIndex++}`);
|
|
195
|
+
params.push(updates.properties);
|
|
196
|
+
}
|
|
197
|
+
if (updates.confidence !== undefined) {
|
|
198
|
+
setClauses.push(`confidence = $${paramIndex++}`);
|
|
199
|
+
params.push(updates.confidence);
|
|
200
|
+
}
|
|
201
|
+
if (updates.metadata !== undefined) {
|
|
202
|
+
setClauses.push(`metadata = $${paramIndex++}`);
|
|
203
|
+
params.push(updates.metadata);
|
|
204
|
+
}
|
|
205
|
+
if (updates.embedding !== undefined) {
|
|
206
|
+
setClauses.push(`embedding = $${paramIndex++}`);
|
|
207
|
+
params.push(`[${updates.embedding.join(',')}]`);
|
|
208
|
+
}
|
|
209
|
+
params.push(nodeId);
|
|
210
|
+
const result = await this.pool.query(`UPDATE ${this.schema}.graph_nodes SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
|
|
211
|
+
if (result.rows.length === 0) {
|
|
212
|
+
return this.failure(`Node not found: ${nodeId}`);
|
|
213
|
+
}
|
|
214
|
+
return this.success(this.rowToNode(result.rows[0]));
|
|
215
|
+
});
|
|
204
216
|
}
|
|
205
217
|
async deleteNode(nodeId) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
218
|
+
return this.run(async () => {
|
|
219
|
+
await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE id = $1`, [nodeId]);
|
|
220
|
+
return this.success(undefined);
|
|
221
|
+
});
|
|
209
222
|
}
|
|
210
223
|
async queryNodes(query) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
224
|
+
return this.run(async () => {
|
|
225
|
+
let sql = `SELECT * FROM ${this.schema}.graph_nodes WHERE agent_id = $1`;
|
|
226
|
+
const params = [query.agentId];
|
|
227
|
+
let paramIndex = 2;
|
|
228
|
+
if (query.types && query.types.length > 0) {
|
|
229
|
+
sql += ` AND type = ANY($${paramIndex++})`;
|
|
230
|
+
params.push(query.types);
|
|
231
|
+
}
|
|
232
|
+
if (query.namePattern) {
|
|
233
|
+
sql += ` AND name ILIKE $${paramIndex++}`;
|
|
234
|
+
params.push(`%${query.namePattern.replace(/[%_\\]/g, '\\$&')}%`);
|
|
235
|
+
}
|
|
236
|
+
if (query.minConfidence !== undefined) {
|
|
237
|
+
sql += ` AND confidence >= $${paramIndex++}`;
|
|
238
|
+
params.push(query.minConfidence);
|
|
239
|
+
}
|
|
240
|
+
sql += ' ORDER BY access_count DESC, updated_at DESC';
|
|
241
|
+
if (query.limit) {
|
|
242
|
+
sql += ` LIMIT $${paramIndex}`;
|
|
243
|
+
params.push(query.limit);
|
|
244
|
+
}
|
|
245
|
+
const result = await this.pool.query(sql, params);
|
|
246
|
+
return this.success(result.rows.map((row) => this.rowToNode(row, query.includeEmbedding)));
|
|
247
|
+
});
|
|
234
248
|
}
|
|
235
249
|
async searchNodesSemantic(options) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
250
|
+
return this.run(async () => {
|
|
251
|
+
if (!options.vector) {
|
|
252
|
+
return this.failure('searchNodesSemantic requires vector');
|
|
253
|
+
}
|
|
254
|
+
const vectorStr = `[${options.vector.join(',')}]`;
|
|
255
|
+
const limit = options.limit ?? 10;
|
|
256
|
+
const threshold = options.threshold ?? 0.7;
|
|
257
|
+
let sql = `
|
|
258
|
+
SELECT *, 1 - (embedding <=> $1) as score
|
|
259
|
+
FROM ${this.schema}.graph_nodes
|
|
260
|
+
WHERE agent_id = $2 AND embedding IS NOT NULL AND 1 - (embedding <=> $1) >= $3
|
|
261
|
+
`;
|
|
262
|
+
const params = [vectorStr, options.agentId, threshold];
|
|
263
|
+
let paramIndex = 4;
|
|
264
|
+
if (options.entityTypes && options.entityTypes.length > 0) {
|
|
265
|
+
sql += ` AND type = ANY($${paramIndex++})`;
|
|
266
|
+
params.push(options.entityTypes);
|
|
267
|
+
}
|
|
268
|
+
sql += ` ORDER BY embedding <=> $1 LIMIT $${paramIndex}`;
|
|
269
|
+
params.push(limit);
|
|
270
|
+
const result = await this.pool.query(sql, params);
|
|
271
|
+
return this.success(result.rows.map((row) => ({
|
|
272
|
+
...this.rowToNode(row),
|
|
273
|
+
score: row.score,
|
|
274
|
+
})));
|
|
275
|
+
});
|
|
261
276
|
}
|
|
262
277
|
async addEdge(edge) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
278
|
+
return this.run(async () => {
|
|
279
|
+
const id = this.generateId('edge');
|
|
280
|
+
const now = new Date();
|
|
281
|
+
await this.pool.query(`INSERT INTO ${this.schema}.graph_edges
|
|
282
|
+
(id, agent_id, source_node_id, target_node_id, type, label, weight, bidirectional, properties, confidence, source, valid_from, valid_until, metadata, created_at, updated_at)
|
|
283
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $15)`, [
|
|
284
|
+
id,
|
|
285
|
+
edge.agentId,
|
|
286
|
+
edge.sourceNodeId,
|
|
287
|
+
edge.targetNodeId,
|
|
288
|
+
edge.type,
|
|
289
|
+
edge.label ?? null,
|
|
290
|
+
edge.weight,
|
|
291
|
+
edge.bidirectional,
|
|
292
|
+
edge.properties,
|
|
293
|
+
edge.confidence,
|
|
294
|
+
edge.source,
|
|
295
|
+
edge.validFrom ?? null,
|
|
296
|
+
edge.validUntil ?? null,
|
|
297
|
+
edge.metadata ?? {},
|
|
298
|
+
now,
|
|
299
|
+
]);
|
|
300
|
+
return this.success({
|
|
301
|
+
...edge,
|
|
302
|
+
id,
|
|
303
|
+
createdAt: now,
|
|
304
|
+
updatedAt: now,
|
|
305
|
+
});
|
|
290
306
|
});
|
|
291
307
|
}
|
|
292
308
|
async getEdge(edgeId) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return this.success(this.rowToEdge(result.rows[0]));
|
|
309
|
+
return this.run(async () => {
|
|
310
|
+
const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
|
|
311
|
+
if (result.rows.length === 0)
|
|
312
|
+
return this.success(null);
|
|
313
|
+
return this.success(this.rowToEdge(result.rows[0]));
|
|
314
|
+
});
|
|
300
315
|
}
|
|
301
316
|
async getEdgesBetween(sourceNodeId, targetNodeId) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
317
|
+
return this.run(async () => {
|
|
318
|
+
const result = await this.pool.query(`SELECT * FROM ${this.schema}.graph_edges
|
|
319
|
+
WHERE (source_node_id = $1 AND target_node_id = $2)
|
|
320
|
+
OR (bidirectional = TRUE AND source_node_id = $2 AND target_node_id = $1)`, [sourceNodeId, targetNodeId]);
|
|
321
|
+
return this.success(result.rows.map((row) => this.rowToEdge(row)));
|
|
322
|
+
});
|
|
307
323
|
}
|
|
308
324
|
async updateEdge(edgeId, updates) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
325
|
+
return this.run(async () => {
|
|
326
|
+
const setClauses = ['updated_at = NOW()'];
|
|
327
|
+
const params = [];
|
|
328
|
+
let paramIndex = 1;
|
|
329
|
+
if (updates.weight !== undefined) {
|
|
330
|
+
setClauses.push(`weight = $${paramIndex++}`);
|
|
331
|
+
params.push(updates.weight);
|
|
332
|
+
}
|
|
333
|
+
if (updates.label !== undefined) {
|
|
334
|
+
setClauses.push(`label = $${paramIndex++}`);
|
|
335
|
+
params.push(updates.label);
|
|
336
|
+
}
|
|
337
|
+
if (updates.properties !== undefined) {
|
|
338
|
+
setClauses.push(`properties = $${paramIndex++}`);
|
|
339
|
+
params.push(updates.properties);
|
|
340
|
+
}
|
|
341
|
+
if (updates.confidence !== undefined) {
|
|
342
|
+
setClauses.push(`confidence = $${paramIndex++}`);
|
|
343
|
+
params.push(updates.confidence);
|
|
344
|
+
}
|
|
345
|
+
if (updates.validFrom !== undefined) {
|
|
346
|
+
setClauses.push(`valid_from = $${paramIndex++}`);
|
|
347
|
+
params.push(updates.validFrom);
|
|
348
|
+
}
|
|
349
|
+
if (updates.validUntil !== undefined) {
|
|
350
|
+
setClauses.push(`valid_until = $${paramIndex++}`);
|
|
351
|
+
params.push(updates.validUntil);
|
|
352
|
+
}
|
|
353
|
+
if (updates.metadata !== undefined) {
|
|
354
|
+
setClauses.push(`metadata = $${paramIndex++}`);
|
|
355
|
+
params.push(updates.metadata);
|
|
356
|
+
}
|
|
357
|
+
params.push(edgeId);
|
|
358
|
+
const result = await this.pool.query(`UPDATE ${this.schema}.graph_edges SET ${setClauses.join(', ')} WHERE id = $${paramIndex} RETURNING *`, params);
|
|
359
|
+
if (result.rows.length === 0) {
|
|
360
|
+
return this.failure(`Edge not found: ${edgeId}`);
|
|
361
|
+
}
|
|
362
|
+
return this.success(this.rowToEdge(result.rows[0]));
|
|
363
|
+
});
|
|
347
364
|
}
|
|
348
365
|
async deleteEdge(edgeId) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
366
|
+
return this.run(async () => {
|
|
367
|
+
await this.pool.query(`DELETE FROM ${this.schema}.graph_edges WHERE id = $1`, [edgeId]);
|
|
368
|
+
return this.success(undefined);
|
|
369
|
+
});
|
|
352
370
|
}
|
|
353
371
|
async queryEdges(query) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
372
|
+
return this.run(async () => {
|
|
373
|
+
let sql = `SELECT * FROM ${this.schema}.graph_edges WHERE agent_id = $1`;
|
|
374
|
+
const params = [query.agentId];
|
|
375
|
+
let paramIndex = 2;
|
|
376
|
+
if (query.sourceNodeId) {
|
|
377
|
+
sql += ` AND source_node_id = $${paramIndex++}`;
|
|
378
|
+
params.push(query.sourceNodeId);
|
|
379
|
+
}
|
|
380
|
+
if (query.targetNodeId) {
|
|
381
|
+
sql += ` AND target_node_id = $${paramIndex++}`;
|
|
382
|
+
params.push(query.targetNodeId);
|
|
383
|
+
}
|
|
384
|
+
if (query.types && query.types.length > 0) {
|
|
385
|
+
sql += ` AND type = ANY($${paramIndex++})`;
|
|
386
|
+
params.push(query.types);
|
|
387
|
+
}
|
|
388
|
+
if (query.minWeight !== undefined) {
|
|
389
|
+
sql += ` AND weight >= $${paramIndex++}`;
|
|
390
|
+
params.push(query.minWeight);
|
|
391
|
+
}
|
|
392
|
+
if (query.minConfidence !== undefined) {
|
|
393
|
+
sql += ` AND confidence >= $${paramIndex++}`;
|
|
394
|
+
params.push(query.minConfidence);
|
|
395
|
+
}
|
|
396
|
+
if (query.bidirectionalOnly) {
|
|
397
|
+
sql += ' AND bidirectional = TRUE';
|
|
398
|
+
}
|
|
399
|
+
sql += ' ORDER BY weight DESC, confidence DESC';
|
|
400
|
+
if (query.limit) {
|
|
401
|
+
sql += ` LIMIT $${paramIndex}`;
|
|
402
|
+
params.push(query.limit);
|
|
403
|
+
}
|
|
404
|
+
const result = await this.pool.query(sql, params);
|
|
405
|
+
return this.success(result.rows.map((row) => this.rowToEdge(row)));
|
|
406
|
+
});
|
|
388
407
|
}
|
|
389
408
|
async traverse(options) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
409
|
+
return this.run(async () => {
|
|
410
|
+
const visited = new Set();
|
|
411
|
+
const visitedEdges = new Set();
|
|
412
|
+
const paths = [];
|
|
413
|
+
const allNodes = [];
|
|
414
|
+
const allEdges = [];
|
|
415
|
+
const startNode = unwrap(await this.getNode(options.startNodeId));
|
|
416
|
+
if (!startNode) {
|
|
417
|
+
return this.failure(`Start node not found: ${options.startNodeId}`);
|
|
418
|
+
}
|
|
419
|
+
visited.add(startNode.id);
|
|
420
|
+
allNodes.push(startNode);
|
|
421
|
+
await this.traverseRecursive(startNode, [], [], 0, options, visited, visitedEdges, paths, allNodes, allEdges);
|
|
422
|
+
let actualMaxDepth = 0;
|
|
423
|
+
for (const p of paths) {
|
|
424
|
+
if (p.length > actualMaxDepth)
|
|
425
|
+
actualMaxDepth = p.length;
|
|
426
|
+
}
|
|
427
|
+
return this.success({
|
|
428
|
+
paths,
|
|
429
|
+
visitedNodes: allNodes,
|
|
430
|
+
visitedEdges: allEdges,
|
|
431
|
+
depth: actualMaxDepth,
|
|
432
|
+
});
|
|
414
433
|
});
|
|
415
434
|
}
|
|
416
435
|
async traverseRecursive(currentNode, pathNodes, pathEdges, currentDepth, options, visited, visitedEdges, paths, allNodes, allEdges) {
|
|
@@ -427,11 +446,9 @@ export class PostgresGraphAdapter {
|
|
|
427
446
|
}
|
|
428
447
|
if (options.limit && paths.length >= options.limit)
|
|
429
448
|
return;
|
|
430
|
-
const
|
|
431
|
-
if (!neighborsResult.success)
|
|
432
|
-
return;
|
|
449
|
+
const neighbors = unwrap(await this.getNeighbors(currentNode.id, options.direction));
|
|
433
450
|
let hasUnvisitedNeighbors = false;
|
|
434
|
-
for (const { node, edge } of
|
|
451
|
+
for (const { node, edge } of neighbors) {
|
|
435
452
|
if (options.edgeTypes && !options.edgeTypes.includes(edge.type))
|
|
436
453
|
continue;
|
|
437
454
|
if (options.minEdgeWeight !== undefined && edge.weight < options.minEdgeWeight)
|
|
@@ -459,196 +476,201 @@ export class PostgresGraphAdapter {
|
|
|
459
476
|
}
|
|
460
477
|
}
|
|
461
478
|
async findShortestPath(agentId, startNodeId, endNodeId, maxDepth = 5) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
479
|
+
return this.run(async () => {
|
|
480
|
+
if (startNodeId === endNodeId) {
|
|
481
|
+
const node = unwrap(await this.getNode(startNodeId));
|
|
482
|
+
if (!node) {
|
|
483
|
+
return this.failure(`Node not found: ${startNodeId}`);
|
|
484
|
+
}
|
|
485
|
+
return this.success({
|
|
486
|
+
nodes: [node],
|
|
487
|
+
edges: [],
|
|
488
|
+
totalWeight: 0,
|
|
489
|
+
length: 0,
|
|
490
|
+
});
|
|
467
491
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
ARRAY[$2, CASE WHEN source_node_id = $2 THEN target_node_id ELSE source_node_id END] as path,
|
|
484
|
-
ARRAY[id] as edge_ids,
|
|
485
|
-
weight as total_weight,
|
|
486
|
-
1 as depth
|
|
487
|
-
FROM ${this.schema}.graph_edges
|
|
488
|
-
WHERE agent_id = $1
|
|
489
|
-
AND (source_node_id = $2 OR (target_node_id = $2 AND bidirectional = TRUE))
|
|
492
|
+
const result = await this.pool.query(`
|
|
493
|
+
WITH RECURSIVE path_search AS (
|
|
494
|
+
SELECT
|
|
495
|
+
source_node_id as start_node,
|
|
496
|
+
CASE
|
|
497
|
+
WHEN source_node_id = $2 THEN target_node_id
|
|
498
|
+
ELSE source_node_id
|
|
499
|
+
END as end_node,
|
|
500
|
+
ARRAY[$2, CASE WHEN source_node_id = $2 THEN target_node_id ELSE source_node_id END] as path,
|
|
501
|
+
ARRAY[id] as edge_ids,
|
|
502
|
+
weight as total_weight,
|
|
503
|
+
1 as depth
|
|
504
|
+
FROM ${this.schema}.graph_edges
|
|
505
|
+
WHERE agent_id = $1
|
|
506
|
+
AND (source_node_id = $2 OR (target_node_id = $2 AND bidirectional = TRUE))
|
|
490
507
|
|
|
491
|
-
|
|
508
|
+
UNION ALL
|
|
492
509
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
510
|
+
SELECT
|
|
511
|
+
ps.start_node,
|
|
512
|
+
CASE
|
|
513
|
+
WHEN e.source_node_id = ps.end_node THEN e.target_node_id
|
|
514
|
+
ELSE e.source_node_id
|
|
515
|
+
END,
|
|
516
|
+
ps.path || CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END,
|
|
517
|
+
ps.edge_ids || e.id,
|
|
518
|
+
ps.total_weight + e.weight,
|
|
519
|
+
ps.depth + 1
|
|
520
|
+
FROM path_search ps
|
|
521
|
+
JOIN ${this.schema}.graph_edges e ON (
|
|
522
|
+
e.source_node_id = ps.end_node
|
|
523
|
+
OR (e.target_node_id = ps.end_node AND e.bidirectional = TRUE)
|
|
524
|
+
)
|
|
525
|
+
WHERE e.agent_id = $1
|
|
526
|
+
AND NOT (CASE WHEN e.source_node_id = ps.end_node THEN e.target_node_id ELSE e.source_node_id END) = ANY(ps.path)
|
|
527
|
+
AND ps.depth < $4
|
|
507
528
|
)
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
for (const nodeId of nodeIds) {
|
|
526
|
-
const nodeResult = await this.getNode(nodeId);
|
|
527
|
-
if (nodeResult.success && nodeResult.data) {
|
|
528
|
-
nodes.push(nodeResult.data);
|
|
529
|
+
SELECT path, edge_ids, total_weight
|
|
530
|
+
FROM path_search
|
|
531
|
+
WHERE end_node = $3
|
|
532
|
+
ORDER BY depth, total_weight
|
|
533
|
+
LIMIT 1
|
|
534
|
+
`, [agentId, startNodeId, endNodeId, maxDepth]);
|
|
535
|
+
if (result.rows.length === 0)
|
|
536
|
+
return this.success(null);
|
|
537
|
+
const row = result.rows[0];
|
|
538
|
+
const nodeIds = row.path;
|
|
539
|
+
const edgeIds = row.edge_ids;
|
|
540
|
+
const nodes = [];
|
|
541
|
+
const edges = [];
|
|
542
|
+
for (const nodeId of nodeIds) {
|
|
543
|
+
const node = unwrap(await this.getNode(nodeId));
|
|
544
|
+
if (node)
|
|
545
|
+
nodes.push(node);
|
|
529
546
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
edges.push(edgeResult.data);
|
|
547
|
+
for (const edgeId of edgeIds) {
|
|
548
|
+
const edge = unwrap(await this.getEdge(edgeId));
|
|
549
|
+
if (edge)
|
|
550
|
+
edges.push(edge);
|
|
535
551
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
552
|
+
return this.success({
|
|
553
|
+
nodes,
|
|
554
|
+
edges,
|
|
555
|
+
totalWeight: row.total_weight,
|
|
556
|
+
length: edges.length,
|
|
557
|
+
});
|
|
542
558
|
});
|
|
543
559
|
}
|
|
544
560
|
async getNeighbors(nodeId, direction = 'both') {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
`;
|
|
558
|
-
if (direction === 'outgoing') {
|
|
559
|
-
sql = `
|
|
560
|
-
SELECT ${selectCols}
|
|
561
|
-
FROM ${this.schema}.graph_edges e
|
|
562
|
-
JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
|
|
563
|
-
WHERE e.source_node_id = $1
|
|
564
|
-
UNION ALL
|
|
565
|
-
SELECT ${selectCols}
|
|
566
|
-
FROM ${this.schema}.graph_edges e
|
|
567
|
-
JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
|
|
568
|
-
WHERE e.target_node_id = $1 AND e.bidirectional = TRUE
|
|
561
|
+
return this.run(async () => {
|
|
562
|
+
let sql;
|
|
563
|
+
const params = [nodeId];
|
|
564
|
+
const selectCols = `
|
|
565
|
+
n.id, n.agent_id, n.type, n.name, n.aliases, n.description,
|
|
566
|
+
n.properties, n.confidence, n.source, n.metadata,
|
|
567
|
+
n.created_at, n.updated_at, n.last_accessed_at, n.access_count,
|
|
568
|
+
e.id as edge_id, e.agent_id as edge_agent_id, e.type as edge_type,
|
|
569
|
+
e.source_node_id, e.target_node_id, e.label, e.weight, e.bidirectional,
|
|
570
|
+
e.properties as edge_properties, e.confidence as edge_confidence,
|
|
571
|
+
e.source as edge_source, e.valid_from, e.valid_until,
|
|
572
|
+
e.metadata as edge_metadata, e.created_at as edge_created_at, e.updated_at as edge_updated_at
|
|
569
573
|
`;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
574
|
+
if (direction === 'outgoing') {
|
|
575
|
+
sql = `
|
|
576
|
+
SELECT ${selectCols}
|
|
577
|
+
FROM ${this.schema}.graph_edges e
|
|
578
|
+
JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
|
|
579
|
+
WHERE e.source_node_id = $1
|
|
580
|
+
UNION ALL
|
|
581
|
+
SELECT ${selectCols}
|
|
582
|
+
FROM ${this.schema}.graph_edges e
|
|
583
|
+
JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
|
|
584
|
+
WHERE e.target_node_id = $1 AND e.bidirectional = TRUE
|
|
585
|
+
`;
|
|
586
|
+
}
|
|
587
|
+
else if (direction === 'incoming') {
|
|
588
|
+
sql = `
|
|
589
|
+
SELECT ${selectCols}
|
|
590
|
+
FROM ${this.schema}.graph_edges e
|
|
591
|
+
JOIN ${this.schema}.graph_nodes n ON n.id = e.source_node_id
|
|
592
|
+
WHERE e.target_node_id = $1
|
|
593
|
+
UNION ALL
|
|
594
|
+
SELECT ${selectCols}
|
|
595
|
+
FROM ${this.schema}.graph_edges e
|
|
596
|
+
JOIN ${this.schema}.graph_nodes n ON n.id = e.target_node_id
|
|
597
|
+
WHERE e.source_node_id = $1 AND e.bidirectional = TRUE
|
|
598
|
+
`;
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
sql = `
|
|
602
|
+
SELECT ${selectCols}
|
|
603
|
+
FROM ${this.schema}.graph_edges e
|
|
604
|
+
JOIN ${this.schema}.graph_nodes n ON (
|
|
605
|
+
(e.source_node_id = $1 AND n.id = e.target_node_id) OR
|
|
606
|
+
(e.target_node_id = $1 AND n.id = e.source_node_id)
|
|
607
|
+
)
|
|
608
|
+
WHERE e.source_node_id = $1 OR e.target_node_id = $1
|
|
609
|
+
`;
|
|
610
|
+
}
|
|
611
|
+
const result = await this.pool.query(sql, params);
|
|
612
|
+
return this.success(result.rows.map((row) => ({
|
|
613
|
+
node: this.rowToNode(row),
|
|
614
|
+
edge: this.rowToEdgeFromJoin(row),
|
|
615
|
+
})));
|
|
616
|
+
});
|
|
600
617
|
}
|
|
601
618
|
async mergeNodes(targetNodeId, sourceNodeIds) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
619
|
+
return this.run(async () => {
|
|
620
|
+
for (const sourceId of sourceNodeIds) {
|
|
621
|
+
const sourceNode = unwrap(await this.getNode(sourceId));
|
|
622
|
+
await this.pool.query(`UPDATE ${this.schema}.graph_edges
|
|
623
|
+
SET source_node_id = $1 WHERE source_node_id = $2`, [targetNodeId, sourceId]);
|
|
624
|
+
await this.pool.query(`UPDATE ${this.schema}.graph_edges
|
|
625
|
+
SET target_node_id = $1 WHERE target_node_id = $2`, [targetNodeId, sourceId]);
|
|
626
|
+
await this.pool.query(`DELETE FROM ${this.schema}.graph_edges
|
|
627
|
+
WHERE source_node_id = $1 AND target_node_id = $1`, [targetNodeId]);
|
|
628
|
+
if (sourceNode) {
|
|
629
|
+
await this.pool.query(`UPDATE ${this.schema}.graph_nodes
|
|
630
|
+
SET aliases = array_cat(aliases, $1::TEXT[])
|
|
631
|
+
WHERE id = $2`, [[sourceNode.name, ...sourceNode.aliases], targetNodeId]);
|
|
632
|
+
}
|
|
633
|
+
unwrap(await this.deleteNode(sourceId));
|
|
634
|
+
}
|
|
635
|
+
const mergedNode = unwrap(await this.getNode(targetNodeId));
|
|
636
|
+
if (!mergedNode) {
|
|
637
|
+
return this.failure(`Target node not found: ${targetNodeId}`);
|
|
638
|
+
}
|
|
639
|
+
return this.success(mergedNode);
|
|
640
|
+
});
|
|
623
641
|
}
|
|
624
642
|
async clearGraph(agentId) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
643
|
+
return this.run(async () => {
|
|
644
|
+
await this.pool.query(`DELETE FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [
|
|
645
|
+
agentId,
|
|
646
|
+
]);
|
|
647
|
+
return this.success(undefined);
|
|
648
|
+
});
|
|
628
649
|
}
|
|
629
650
|
async getGraphStats(agentId) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
651
|
+
return this.run(async () => {
|
|
652
|
+
const nodeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1`, [agentId]);
|
|
653
|
+
const edgeCountResult = await this.pool.query(`SELECT COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1`, [agentId]);
|
|
654
|
+
const nodesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_nodes WHERE agent_id = $1 GROUP BY type`, [agentId]);
|
|
655
|
+
const edgesByTypeResult = await this.pool.query(`SELECT type, COUNT(*) as count FROM ${this.schema}.graph_edges WHERE agent_id = $1 GROUP BY type`, [agentId]);
|
|
656
|
+
const nodeCount = parseInt(nodeCountResult.rows[0]?.count ?? '0', 10);
|
|
657
|
+
const edgeCount = parseInt(edgeCountResult.rows[0]?.count ?? '0', 10);
|
|
658
|
+
const nodesByType = {};
|
|
659
|
+
for (const row of nodesByTypeResult.rows) {
|
|
660
|
+
nodesByType[row.type] = parseInt(row.count, 10);
|
|
661
|
+
}
|
|
662
|
+
const edgesByType = {};
|
|
663
|
+
for (const row of edgesByTypeResult.rows) {
|
|
664
|
+
edgesByType[row.type] = parseInt(row.count, 10);
|
|
665
|
+
}
|
|
666
|
+
return this.success({
|
|
667
|
+
nodeCount,
|
|
668
|
+
edgeCount,
|
|
669
|
+
nodesByType,
|
|
670
|
+
edgesByType,
|
|
671
|
+
averageEdgesPerNode: nodeCount > 0 ? edgeCount / nodeCount : 0,
|
|
672
|
+
maxDepth: 0,
|
|
673
|
+
});
|
|
652
674
|
});
|
|
653
675
|
}
|
|
654
676
|
rowToNode(row, includeEmbedding = false) {
|