@sparkleideas/plugins 3.0.0-alpha.8
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 +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuVector PostgreSQL Bridge - Hyperbolic Embeddings Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates:
|
|
5
|
+
* - Embedding file tree structures in hyperbolic space
|
|
6
|
+
* - Embedding class hierarchies with Poincare ball model
|
|
7
|
+
* - Calculating hierarchy-aware distances
|
|
8
|
+
* - Comparing Euclidean vs hyperbolic representations
|
|
9
|
+
*
|
|
10
|
+
* Run with: npx ts-node examples/ruvector/hyperbolic-hierarchies.ts
|
|
11
|
+
*
|
|
12
|
+
* @module @sparkleideas/plugins/examples/ruvector/hyperbolic-hierarchies
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
createRuVectorBridge,
|
|
17
|
+
type RuVectorBridge,
|
|
18
|
+
} from '../../src/integrations/ruvector/index.js';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
PoincareBall,
|
|
22
|
+
PoincareEmbedding,
|
|
23
|
+
type HyperbolicConfig,
|
|
24
|
+
type HierarchyNode,
|
|
25
|
+
} from '../../src/integrations/ruvector/hyperbolic.js';
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Configuration
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
const config = {
|
|
32
|
+
connection: {
|
|
33
|
+
host: process.env.POSTGRES_HOST || 'localhost',
|
|
34
|
+
port: parseInt(process.env.POSTGRES_PORT || '5432', 10),
|
|
35
|
+
database: process.env.POSTGRES_DB || 'vectors',
|
|
36
|
+
user: process.env.POSTGRES_USER || 'postgres',
|
|
37
|
+
password: process.env.POSTGRES_PASSWORD || 'postgres',
|
|
38
|
+
},
|
|
39
|
+
hyperbolicDim: 32,
|
|
40
|
+
curvature: -1.0, // Negative curvature for hyperbolic space
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Sample Hierarchical Data
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* File system tree structure.
|
|
49
|
+
*/
|
|
50
|
+
const fileTree: HierarchyNode = {
|
|
51
|
+
id: 'root',
|
|
52
|
+
name: '/',
|
|
53
|
+
children: [
|
|
54
|
+
{
|
|
55
|
+
id: 'src',
|
|
56
|
+
name: 'src',
|
|
57
|
+
children: [
|
|
58
|
+
{
|
|
59
|
+
id: 'components',
|
|
60
|
+
name: 'components',
|
|
61
|
+
children: [
|
|
62
|
+
{ id: 'button', name: 'Button.tsx', children: [] },
|
|
63
|
+
{ id: 'input', name: 'Input.tsx', children: [] },
|
|
64
|
+
{ id: 'modal', name: 'Modal.tsx', children: [] },
|
|
65
|
+
{
|
|
66
|
+
id: 'forms',
|
|
67
|
+
name: 'forms',
|
|
68
|
+
children: [
|
|
69
|
+
{ id: 'login-form', name: 'LoginForm.tsx', children: [] },
|
|
70
|
+
{ id: 'signup-form', name: 'SignupForm.tsx', children: [] },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'services',
|
|
77
|
+
name: 'services',
|
|
78
|
+
children: [
|
|
79
|
+
{ id: 'auth-service', name: 'auth.ts', children: [] },
|
|
80
|
+
{ id: 'api-service', name: 'api.ts', children: [] },
|
|
81
|
+
{ id: 'storage-service', name: 'storage.ts', children: [] },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'utils',
|
|
86
|
+
name: 'utils',
|
|
87
|
+
children: [
|
|
88
|
+
{ id: 'format', name: 'format.ts', children: [] },
|
|
89
|
+
{ id: 'validate', name: 'validate.ts', children: [] },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'tests',
|
|
96
|
+
name: 'tests',
|
|
97
|
+
children: [
|
|
98
|
+
{ id: 'unit', name: 'unit', children: [
|
|
99
|
+
{ id: 'auth-test', name: 'auth.test.ts', children: [] },
|
|
100
|
+
{ id: 'api-test', name: 'api.test.ts', children: [] },
|
|
101
|
+
]},
|
|
102
|
+
{ id: 'integration', name: 'integration', children: [
|
|
103
|
+
{ id: 'e2e-test', name: 'e2e.test.ts', children: [] },
|
|
104
|
+
]},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'config',
|
|
109
|
+
name: 'config',
|
|
110
|
+
children: [
|
|
111
|
+
{ id: 'tsconfig', name: 'tsconfig.json', children: [] },
|
|
112
|
+
{ id: 'eslint', name: '.eslintrc.js', children: [] },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Class inheritance hierarchy (TypeScript/OOP).
|
|
120
|
+
*/
|
|
121
|
+
const classHierarchy: HierarchyNode = {
|
|
122
|
+
id: 'object',
|
|
123
|
+
name: 'Object',
|
|
124
|
+
children: [
|
|
125
|
+
{
|
|
126
|
+
id: 'error',
|
|
127
|
+
name: 'Error',
|
|
128
|
+
children: [
|
|
129
|
+
{
|
|
130
|
+
id: 'validation-error',
|
|
131
|
+
name: 'ValidationError',
|
|
132
|
+
children: [
|
|
133
|
+
{ id: 'field-error', name: 'FieldValidationError', children: [] },
|
|
134
|
+
{ id: 'schema-error', name: 'SchemaValidationError', children: [] },
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'http-error',
|
|
139
|
+
name: 'HttpError',
|
|
140
|
+
children: [
|
|
141
|
+
{ id: 'not-found', name: 'NotFoundError', children: [] },
|
|
142
|
+
{ id: 'unauthorized', name: 'UnauthorizedError', children: [] },
|
|
143
|
+
{ id: 'forbidden', name: 'ForbiddenError', children: [] },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{ id: 'database-error', name: 'DatabaseError', children: [] },
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'base-service',
|
|
151
|
+
name: 'BaseService',
|
|
152
|
+
children: [
|
|
153
|
+
{
|
|
154
|
+
id: 'crud-service',
|
|
155
|
+
name: 'CrudService',
|
|
156
|
+
children: [
|
|
157
|
+
{ id: 'user-service', name: 'UserService', children: [] },
|
|
158
|
+
{ id: 'product-service', name: 'ProductService', children: [] },
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
{ id: 'auth-svc', name: 'AuthService', children: [] },
|
|
162
|
+
{ id: 'cache-svc', name: 'CacheService', children: [] },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'base-controller',
|
|
167
|
+
name: 'BaseController',
|
|
168
|
+
children: [
|
|
169
|
+
{ id: 'user-controller', name: 'UserController', children: [] },
|
|
170
|
+
{ id: 'auth-controller', name: 'AuthController', children: [] },
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Helper Functions
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Flatten hierarchy to list with depth information.
|
|
182
|
+
*/
|
|
183
|
+
function flattenHierarchy(
|
|
184
|
+
node: HierarchyNode,
|
|
185
|
+
depth: number = 0,
|
|
186
|
+
parent: string | null = null
|
|
187
|
+
): Array<{ node: HierarchyNode; depth: number; parent: string | null }> {
|
|
188
|
+
const result: Array<{ node: HierarchyNode; depth: number; parent: string | null }> = [
|
|
189
|
+
{ node, depth, parent },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
for (const child of node.children) {
|
|
193
|
+
result.push(...flattenHierarchy(child, depth + 1, node.id));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Find path between two nodes in hierarchy.
|
|
201
|
+
*/
|
|
202
|
+
function findPath(root: HierarchyNode, targetId: string): string[] | null {
|
|
203
|
+
if (root.id === targetId) return [root.id];
|
|
204
|
+
|
|
205
|
+
for (const child of root.children) {
|
|
206
|
+
const childPath = findPath(child, targetId);
|
|
207
|
+
if (childPath) return [root.id, ...childPath];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Calculate tree distance (number of edges in path).
|
|
215
|
+
*/
|
|
216
|
+
function treeDistance(root: HierarchyNode, id1: string, id2: string): number {
|
|
217
|
+
const path1 = findPath(root, id1) || [];
|
|
218
|
+
const path2 = findPath(root, id2) || [];
|
|
219
|
+
|
|
220
|
+
// Find lowest common ancestor
|
|
221
|
+
let lcaDepth = 0;
|
|
222
|
+
while (
|
|
223
|
+
lcaDepth < path1.length &&
|
|
224
|
+
lcaDepth < path2.length &&
|
|
225
|
+
path1[lcaDepth] === path2[lcaDepth]
|
|
226
|
+
) {
|
|
227
|
+
lcaDepth++;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Distance = path from node1 to LCA + path from LCA to node2
|
|
231
|
+
return (path1.length - lcaDepth) + (path2.length - lcaDepth);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// Main Example
|
|
236
|
+
// ============================================================================
|
|
237
|
+
|
|
238
|
+
async function main(): Promise<void> {
|
|
239
|
+
console.log('RuVector PostgreSQL Bridge - Hyperbolic Embeddings Example');
|
|
240
|
+
console.log('============================================================\n');
|
|
241
|
+
|
|
242
|
+
const bridge: RuVectorBridge = createRuVectorBridge({
|
|
243
|
+
connectionString: `postgresql://${config.connection.user}:${config.connection.password}@${config.connection.host}:${config.connection.port}/${config.connection.database}`,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Initialize Poincare ball model
|
|
247
|
+
const poincare = new PoincareBall({
|
|
248
|
+
dimension: config.hyperbolicDim,
|
|
249
|
+
curvature: config.curvature,
|
|
250
|
+
epsilon: 1e-6,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
await bridge.connect();
|
|
255
|
+
console.log('Connected to PostgreSQL\n');
|
|
256
|
+
|
|
257
|
+
// ========================================================================
|
|
258
|
+
// 1. Embed File Tree Structure
|
|
259
|
+
// ========================================================================
|
|
260
|
+
console.log('1. Embedding File Tree in Hyperbolic Space');
|
|
261
|
+
console.log(' ' + '-'.repeat(50));
|
|
262
|
+
|
|
263
|
+
const fileEmbedding = new PoincareEmbedding({
|
|
264
|
+
dimension: config.hyperbolicDim,
|
|
265
|
+
curvature: config.curvature,
|
|
266
|
+
learningRate: 0.01,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Train embeddings on file hierarchy
|
|
270
|
+
console.log(' Training Poincare embeddings for file tree...');
|
|
271
|
+
const startTrain = performance.now();
|
|
272
|
+
await fileEmbedding.train(fileTree, { epochs: 100, batchSize: 16 });
|
|
273
|
+
const trainTime = performance.now() - startTrain;
|
|
274
|
+
console.log(` Training completed in ${trainTime.toFixed(0)}ms`);
|
|
275
|
+
|
|
276
|
+
// Get embeddings for all nodes
|
|
277
|
+
const flatFiles = flattenHierarchy(fileTree);
|
|
278
|
+
console.log(`\n Embedded ${flatFiles.length} nodes`);
|
|
279
|
+
|
|
280
|
+
// Show embedding norms (closer to 1 = deeper in hierarchy)
|
|
281
|
+
console.log('\n Embedding norms by depth (closer to 1 = deeper):');
|
|
282
|
+
const depthGroups = new Map<number, Array<{ name: string; norm: number }>>();
|
|
283
|
+
|
|
284
|
+
for (const { node, depth } of flatFiles) {
|
|
285
|
+
const embedding = fileEmbedding.getEmbedding(node.id);
|
|
286
|
+
if (embedding) {
|
|
287
|
+
const norm = Math.sqrt(embedding.reduce((s, v) => s + v * v, 0));
|
|
288
|
+
if (!depthGroups.has(depth)) depthGroups.set(depth, []);
|
|
289
|
+
depthGroups.get(depth)?.push({ name: node.name, norm });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
depthGroups.forEach((nodes, depth) => {
|
|
294
|
+
const avgNorm = nodes.reduce((s, n) => s + n.norm, 0) / nodes.length;
|
|
295
|
+
const samples = nodes.slice(0, 3).map(n => n.name).join(', ');
|
|
296
|
+
console.log(` Depth ${depth}: avg norm = ${avgNorm.toFixed(4)} (${samples}${nodes.length > 3 ? '...' : ''})`);
|
|
297
|
+
});
|
|
298
|
+
console.log();
|
|
299
|
+
|
|
300
|
+
// ========================================================================
|
|
301
|
+
// 2. Hyperbolic Distance vs Tree Distance
|
|
302
|
+
// ========================================================================
|
|
303
|
+
console.log('2. Comparing Hyperbolic Distance to Tree Distance');
|
|
304
|
+
console.log(' ' + '-'.repeat(50));
|
|
305
|
+
|
|
306
|
+
const testPairs = [
|
|
307
|
+
['button', 'input'], // Same directory
|
|
308
|
+
['button', 'login-form'], // Nearby (components)
|
|
309
|
+
['button', 'auth-service'], // Different subtrees
|
|
310
|
+
['button', 'auth-test'], // Far apart (src vs tests)
|
|
311
|
+
['root', 'login-form'], // Root to deep node
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
console.log(' Node Pair | Tree Dist | Hyperbolic Dist | Correlation');
|
|
315
|
+
console.log(' ' + '-'.repeat(75));
|
|
316
|
+
|
|
317
|
+
for (const [id1, id2] of testPairs) {
|
|
318
|
+
const treeDist = treeDistance(fileTree, id1, id2);
|
|
319
|
+
const emb1 = fileEmbedding.getEmbedding(id1);
|
|
320
|
+
const emb2 = fileEmbedding.getEmbedding(id2);
|
|
321
|
+
|
|
322
|
+
if (emb1 && emb2) {
|
|
323
|
+
const hypDist = poincare.distance(emb1, emb2);
|
|
324
|
+
const node1 = flatFiles.find(f => f.node.id === id1)?.node.name || id1;
|
|
325
|
+
const node2 = flatFiles.find(f => f.node.id === id2)?.node.name || id2;
|
|
326
|
+
const pairName = `${node1} <-> ${node2}`;
|
|
327
|
+
|
|
328
|
+
console.log(
|
|
329
|
+
` ${pairName.padEnd(25)} | ${treeDist.toString().padStart(9)} | ` +
|
|
330
|
+
`${hypDist.toFixed(4).padStart(15)} | ` +
|
|
331
|
+
`${treeDist > 0 ? (hypDist / treeDist).toFixed(3) : 'N/A'}`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
console.log();
|
|
336
|
+
|
|
337
|
+
// ========================================================================
|
|
338
|
+
// 3. Embed Class Hierarchy
|
|
339
|
+
// ========================================================================
|
|
340
|
+
console.log('3. Embedding Class Inheritance Hierarchy');
|
|
341
|
+
console.log(' ' + '-'.repeat(50));
|
|
342
|
+
|
|
343
|
+
const classEmbedding = new PoincareEmbedding({
|
|
344
|
+
dimension: config.hyperbolicDim,
|
|
345
|
+
curvature: config.curvature,
|
|
346
|
+
learningRate: 0.01,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
console.log(' Training Poincare embeddings for class hierarchy...');
|
|
350
|
+
await classEmbedding.train(classHierarchy, { epochs: 100, batchSize: 16 });
|
|
351
|
+
|
|
352
|
+
const flatClasses = flattenHierarchy(classHierarchy);
|
|
353
|
+
console.log(` Embedded ${flatClasses.length} classes`);
|
|
354
|
+
|
|
355
|
+
// Show class hierarchy with embeddings
|
|
356
|
+
console.log('\n Class hierarchy with embedding norms:');
|
|
357
|
+
for (const { node, depth } of flatClasses) {
|
|
358
|
+
const emb = classEmbedding.getEmbedding(node.id);
|
|
359
|
+
const norm = emb ? Math.sqrt(emb.reduce((s, v) => s + v * v, 0)) : 0;
|
|
360
|
+
const indent = ' '.repeat(depth);
|
|
361
|
+
console.log(` ${indent}${node.name} (norm: ${norm.toFixed(4)})`);
|
|
362
|
+
}
|
|
363
|
+
console.log();
|
|
364
|
+
|
|
365
|
+
// ========================================================================
|
|
366
|
+
// 4. Find Nearest Ancestors and Descendants
|
|
367
|
+
// ========================================================================
|
|
368
|
+
console.log('4. Finding Nearest Ancestors and Descendants');
|
|
369
|
+
console.log(' ' + '-'.repeat(50));
|
|
370
|
+
|
|
371
|
+
const queryClass = 'not-found'; // NotFoundError
|
|
372
|
+
const queryEmb = classEmbedding.getEmbedding(queryClass);
|
|
373
|
+
|
|
374
|
+
if (queryEmb) {
|
|
375
|
+
// Find classes by hyperbolic distance
|
|
376
|
+
const distances = flatClasses
|
|
377
|
+
.filter(c => c.node.id !== queryClass)
|
|
378
|
+
.map(({ node }) => {
|
|
379
|
+
const emb = classEmbedding.getEmbedding(node.id);
|
|
380
|
+
if (!emb) return null;
|
|
381
|
+
return {
|
|
382
|
+
id: node.id,
|
|
383
|
+
name: node.name,
|
|
384
|
+
distance: poincare.distance(queryEmb, emb),
|
|
385
|
+
isAncestor: findPath(classHierarchy, queryClass)?.includes(node.id) ?? false,
|
|
386
|
+
};
|
|
387
|
+
})
|
|
388
|
+
.filter((d): d is NonNullable<typeof d> => d !== null)
|
|
389
|
+
.sort((a, b) => a.distance - b.distance);
|
|
390
|
+
|
|
391
|
+
console.log(` Query: ${queryClass} (NotFoundError)`);
|
|
392
|
+
console.log('\n Nearest by hyperbolic distance:');
|
|
393
|
+
distances.slice(0, 5).forEach((d, i) => {
|
|
394
|
+
const relation = d.isAncestor ? '[ancestor]' : '';
|
|
395
|
+
console.log(` ${i + 1}. ${d.name} - distance: ${d.distance.toFixed(4)} ${relation}`);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
console.log('\n Actual ancestors (by tree structure):');
|
|
399
|
+
const ancestors = findPath(classHierarchy, queryClass) || [];
|
|
400
|
+
ancestors.slice(0, -1).forEach((id, i) => {
|
|
401
|
+
const node = flatClasses.find(c => c.node.id === id);
|
|
402
|
+
if (node) {
|
|
403
|
+
const emb = classEmbedding.getEmbedding(id);
|
|
404
|
+
const dist = emb ? poincare.distance(queryEmb, emb) : 0;
|
|
405
|
+
console.log(` ${i + 1}. ${node.node.name} - distance: ${dist.toFixed(4)}`);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
console.log();
|
|
410
|
+
|
|
411
|
+
// ========================================================================
|
|
412
|
+
// 5. Hyperbolic Operations
|
|
413
|
+
// ========================================================================
|
|
414
|
+
console.log('5. Hyperbolic Space Operations');
|
|
415
|
+
console.log(' ' + '-'.repeat(50));
|
|
416
|
+
|
|
417
|
+
const emb1 = classEmbedding.getEmbedding('error');
|
|
418
|
+
const emb2 = classEmbedding.getEmbedding('validation-error');
|
|
419
|
+
|
|
420
|
+
if (emb1 && emb2) {
|
|
421
|
+
// Hyperbolic midpoint (Mobius gyromidpoint)
|
|
422
|
+
const midpoint = poincare.mobius_add(
|
|
423
|
+
poincare.scalar_mult(0.5, emb1),
|
|
424
|
+
poincare.scalar_mult(0.5, emb2)
|
|
425
|
+
);
|
|
426
|
+
const midNorm = Math.sqrt(midpoint.reduce((s, v) => s + v * v, 0));
|
|
427
|
+
|
|
428
|
+
console.log(' Midpoint between Error and ValidationError:');
|
|
429
|
+
console.log(` Norm of midpoint: ${midNorm.toFixed(4)}`);
|
|
430
|
+
console.log(` Distance to Error: ${poincare.distance(midpoint, emb1).toFixed(4)}`);
|
|
431
|
+
console.log(` Distance to ValidationError: ${poincare.distance(midpoint, emb2).toFixed(4)}`);
|
|
432
|
+
|
|
433
|
+
// Exponential and logarithmic maps
|
|
434
|
+
console.log('\n Exponential map (tangent space -> hyperbolic):');
|
|
435
|
+
const tangentVector = Array.from({ length: config.hyperbolicDim }, () => Math.random() * 0.1);
|
|
436
|
+
const mapped = poincare.exp_map(tangentVector, emb1);
|
|
437
|
+
const mappedNorm = Math.sqrt(mapped.reduce((s, v) => s + v * v, 0));
|
|
438
|
+
console.log(` Input tangent vector norm: ${Math.sqrt(tangentVector.reduce((s, v) => s + v * v, 0)).toFixed(4)}`);
|
|
439
|
+
console.log(` Mapped point norm: ${mappedNorm.toFixed(4)}`);
|
|
440
|
+
}
|
|
441
|
+
console.log();
|
|
442
|
+
|
|
443
|
+
// ========================================================================
|
|
444
|
+
// 6. Store in PostgreSQL with Hyperbolic Distance
|
|
445
|
+
// ========================================================================
|
|
446
|
+
console.log('6. Storing Hyperbolic Embeddings in PostgreSQL');
|
|
447
|
+
console.log(' ' + '-'.repeat(50));
|
|
448
|
+
|
|
449
|
+
// Create collection for class embeddings
|
|
450
|
+
await bridge.createCollection('class_hierarchy_embeddings', {
|
|
451
|
+
dimensions: config.hyperbolicDim,
|
|
452
|
+
distanceMetric: 'euclidean', // Use Euclidean for storage, compute hyperbolic distance separately
|
|
453
|
+
indexType: 'hnsw',
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Store embeddings
|
|
457
|
+
for (const { node, depth, parent } of flatClasses) {
|
|
458
|
+
const emb = classEmbedding.getEmbedding(node.id);
|
|
459
|
+
if (emb) {
|
|
460
|
+
await bridge.insert('class_hierarchy_embeddings', {
|
|
461
|
+
id: node.id,
|
|
462
|
+
embedding: emb,
|
|
463
|
+
metadata: {
|
|
464
|
+
name: node.name,
|
|
465
|
+
depth,
|
|
466
|
+
parent,
|
|
467
|
+
isLeaf: node.children.length === 0,
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
console.log(` Stored ${flatClasses.length} hyperbolic embeddings`);
|
|
474
|
+
|
|
475
|
+
// Query and re-rank with hyperbolic distance
|
|
476
|
+
const queryId = 'unauthorized';
|
|
477
|
+
const queryHypEmb = classEmbedding.getEmbedding(queryId);
|
|
478
|
+
|
|
479
|
+
if (queryHypEmb) {
|
|
480
|
+
// Get candidates using Euclidean distance (fast approximation)
|
|
481
|
+
const candidates = await bridge.search('class_hierarchy_embeddings', queryHypEmb, {
|
|
482
|
+
k: 10,
|
|
483
|
+
includeMetadata: true,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Re-rank with hyperbolic distance
|
|
487
|
+
const reranked = candidates
|
|
488
|
+
.map(c => {
|
|
489
|
+
const hypDist = poincare.distance(queryHypEmb, c.embedding);
|
|
490
|
+
return { ...c, hyperbolicDistance: hypDist };
|
|
491
|
+
})
|
|
492
|
+
.sort((a, b) => a.hyperbolicDistance - b.hyperbolicDistance);
|
|
493
|
+
|
|
494
|
+
console.log(`\n Query: ${queryId} (UnauthorizedError)`);
|
|
495
|
+
console.log(' Results re-ranked by hyperbolic distance:');
|
|
496
|
+
reranked.slice(0, 5).forEach((r, i) => {
|
|
497
|
+
console.log(` ${i + 1}. ${r.metadata?.name} (hyp dist: ${r.hyperbolicDistance.toFixed(4)})`);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ========================================================================
|
|
502
|
+
// 7. Euclidean vs Hyperbolic Comparison
|
|
503
|
+
// ========================================================================
|
|
504
|
+
console.log('\n7. Euclidean vs Hyperbolic Distance Comparison');
|
|
505
|
+
console.log(' ' + '-'.repeat(50));
|
|
506
|
+
|
|
507
|
+
// Compare how well each distance metric preserves hierarchy
|
|
508
|
+
let euclideanCorrelation = 0;
|
|
509
|
+
let hyperbolicCorrelation = 0;
|
|
510
|
+
let comparisons = 0;
|
|
511
|
+
|
|
512
|
+
for (const { node: node1 } of flatClasses) {
|
|
513
|
+
for (const { node: node2 } of flatClasses) {
|
|
514
|
+
if (node1.id >= node2.id) continue;
|
|
515
|
+
|
|
516
|
+
const emb1 = classEmbedding.getEmbedding(node1.id);
|
|
517
|
+
const emb2 = classEmbedding.getEmbedding(node2.id);
|
|
518
|
+
if (!emb1 || !emb2) continue;
|
|
519
|
+
|
|
520
|
+
const treeDist = treeDistance(classHierarchy, node1.id, node2.id);
|
|
521
|
+
const eucDist = Math.sqrt(emb1.reduce((s, v, i) => s + Math.pow(v - emb2[i], 2), 0));
|
|
522
|
+
const hypDist = poincare.distance(emb1, emb2);
|
|
523
|
+
|
|
524
|
+
// Spearman-like correlation (rank agreement)
|
|
525
|
+
if (treeDist > 0) {
|
|
526
|
+
euclideanCorrelation += eucDist / treeDist;
|
|
527
|
+
hyperbolicCorrelation += hypDist / treeDist;
|
|
528
|
+
comparisons++;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
euclideanCorrelation /= comparisons;
|
|
534
|
+
hyperbolicCorrelation /= comparisons;
|
|
535
|
+
|
|
536
|
+
console.log(' Distance metric quality (lower = better preserves tree structure):');
|
|
537
|
+
console.log(` Euclidean: ${euclideanCorrelation.toFixed(4)}`);
|
|
538
|
+
console.log(` Hyperbolic: ${hyperbolicCorrelation.toFixed(4)}`);
|
|
539
|
+
console.log(` Improvement: ${((euclideanCorrelation / hyperbolicCorrelation - 1) * 100).toFixed(1)}%`);
|
|
540
|
+
|
|
541
|
+
// ========================================================================
|
|
542
|
+
// Done
|
|
543
|
+
// ========================================================================
|
|
544
|
+
console.log('\n' + '='.repeat(60));
|
|
545
|
+
console.log('Hyperbolic embeddings example completed!');
|
|
546
|
+
console.log('='.repeat(60));
|
|
547
|
+
|
|
548
|
+
} catch (error) {
|
|
549
|
+
console.error('Error:', error);
|
|
550
|
+
throw error;
|
|
551
|
+
} finally {
|
|
552
|
+
await bridge.disconnect();
|
|
553
|
+
console.log('\nDisconnected from PostgreSQL.');
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
-- RuVector PostgreSQL Bridge - Database Initialization
|
|
2
|
+
--
|
|
3
|
+
-- This script is automatically executed when the PostgreSQL container starts.
|
|
4
|
+
-- It sets up the pgvector extension and creates necessary schemas and functions.
|
|
5
|
+
|
|
6
|
+
-- Enable pgvector extension
|
|
7
|
+
CREATE EXTENSION IF NOT EXISTS vector;
|
|
8
|
+
|
|
9
|
+
-- Create a schema for RuVector-specific objects
|
|
10
|
+
CREATE SCHEMA IF NOT EXISTS ruvector;
|
|
11
|
+
|
|
12
|
+
-- Set search path to include ruvector schema
|
|
13
|
+
ALTER DATABASE vectors SET search_path TO public, ruvector;
|
|
14
|
+
|
|
15
|
+
-- Create a function to normalize vectors
|
|
16
|
+
CREATE OR REPLACE FUNCTION ruvector.normalize_vector(v vector)
|
|
17
|
+
RETURNS vector AS $$
|
|
18
|
+
DECLARE
|
|
19
|
+
magnitude float;
|
|
20
|
+
BEGIN
|
|
21
|
+
SELECT sqrt(sum(x * x)) INTO magnitude
|
|
22
|
+
FROM unnest(v::float[]) AS x;
|
|
23
|
+
|
|
24
|
+
IF magnitude = 0 THEN
|
|
25
|
+
RETURN v;
|
|
26
|
+
END IF;
|
|
27
|
+
|
|
28
|
+
RETURN (SELECT array_agg(x / magnitude)::vector
|
|
29
|
+
FROM unnest(v::float[]) AS x);
|
|
30
|
+
END;
|
|
31
|
+
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
32
|
+
|
|
33
|
+
-- Create a function for cosine similarity (returns similarity, not distance)
|
|
34
|
+
CREATE OR REPLACE FUNCTION ruvector.cosine_similarity(a vector, b vector)
|
|
35
|
+
RETURNS float AS $$
|
|
36
|
+
BEGIN
|
|
37
|
+
RETURN 1 - (a <=> b);
|
|
38
|
+
END;
|
|
39
|
+
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
40
|
+
|
|
41
|
+
-- Create a function for batch vector insertion with normalization
|
|
42
|
+
CREATE OR REPLACE FUNCTION ruvector.insert_normalized(
|
|
43
|
+
table_name text,
|
|
44
|
+
id_col text,
|
|
45
|
+
vec_col text,
|
|
46
|
+
ids text[],
|
|
47
|
+
vectors vector[]
|
|
48
|
+
) RETURNS int AS $$
|
|
49
|
+
DECLARE
|
|
50
|
+
inserted int := 0;
|
|
51
|
+
i int;
|
|
52
|
+
BEGIN
|
|
53
|
+
FOR i IN 1..array_length(ids, 1) LOOP
|
|
54
|
+
EXECUTE format(
|
|
55
|
+
'INSERT INTO %I (%I, %I) VALUES ($1, ruvector.normalize_vector($2))',
|
|
56
|
+
table_name, id_col, vec_col
|
|
57
|
+
) USING ids[i], vectors[i];
|
|
58
|
+
inserted := inserted + 1;
|
|
59
|
+
END LOOP;
|
|
60
|
+
RETURN inserted;
|
|
61
|
+
END;
|
|
62
|
+
$$ LANGUAGE plpgsql;
|
|
63
|
+
|
|
64
|
+
-- Create a function to analyze vector distribution
|
|
65
|
+
CREATE OR REPLACE FUNCTION ruvector.analyze_vectors(
|
|
66
|
+
table_name text,
|
|
67
|
+
vec_col text
|
|
68
|
+
) RETURNS TABLE (
|
|
69
|
+
total_count bigint,
|
|
70
|
+
avg_magnitude float,
|
|
71
|
+
min_magnitude float,
|
|
72
|
+
max_magnitude float,
|
|
73
|
+
dimension int
|
|
74
|
+
) AS $$
|
|
75
|
+
BEGIN
|
|
76
|
+
RETURN QUERY EXECUTE format(
|
|
77
|
+
'SELECT
|
|
78
|
+
COUNT(*)::bigint,
|
|
79
|
+
AVG(sqrt((SELECT SUM(x*x) FROM unnest(%I::float[]) AS x)))::float,
|
|
80
|
+
MIN(sqrt((SELECT SUM(x*x) FROM unnest(%I::float[]) AS x)))::float,
|
|
81
|
+
MAX(sqrt((SELECT SUM(x*x) FROM unnest(%I::float[]) AS x)))::float,
|
|
82
|
+
vector_dims(%I)
|
|
83
|
+
FROM %I
|
|
84
|
+
LIMIT 1',
|
|
85
|
+
vec_col, vec_col, vec_col, vec_col, table_name
|
|
86
|
+
);
|
|
87
|
+
END;
|
|
88
|
+
$$ LANGUAGE plpgsql;
|
|
89
|
+
|
|
90
|
+
-- Create a sample table for testing
|
|
91
|
+
CREATE TABLE IF NOT EXISTS ruvector.sample_vectors (
|
|
92
|
+
id text PRIMARY KEY,
|
|
93
|
+
embedding vector(128),
|
|
94
|
+
metadata jsonb DEFAULT '{}'::jsonb,
|
|
95
|
+
created_at timestamp DEFAULT now()
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
-- Create HNSW index on sample table
|
|
99
|
+
CREATE INDEX IF NOT EXISTS sample_vectors_embedding_idx
|
|
100
|
+
ON ruvector.sample_vectors
|
|
101
|
+
USING hnsw (embedding vector_cosine_ops)
|
|
102
|
+
WITH (m = 16, ef_construction = 64);
|
|
103
|
+
|
|
104
|
+
-- Create GIN index for metadata queries
|
|
105
|
+
CREATE INDEX IF NOT EXISTS sample_vectors_metadata_idx
|
|
106
|
+
ON ruvector.sample_vectors
|
|
107
|
+
USING gin (metadata jsonb_path_ops);
|
|
108
|
+
|
|
109
|
+
-- Grant permissions
|
|
110
|
+
GRANT ALL ON SCHEMA ruvector TO postgres;
|
|
111
|
+
GRANT ALL ON ALL TABLES IN SCHEMA ruvector TO postgres;
|
|
112
|
+
GRANT ALL ON ALL FUNCTIONS IN SCHEMA ruvector TO postgres;
|
|
113
|
+
|
|
114
|
+
-- Log completion
|
|
115
|
+
DO $$
|
|
116
|
+
BEGIN
|
|
117
|
+
RAISE NOTICE 'RuVector database initialization complete';
|
|
118
|
+
RAISE NOTICE 'pgvector version: %', (SELECT extversion FROM pg_extension WHERE extname = 'vector');
|
|
119
|
+
END $$;
|