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