Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
# @ruvector/router
Semantic router for AI agents - vector-based intent matching with HNSW indexing and SIMD acceleration.
## Features
- **Semantic Intent Matching**: Route queries to intents based on meaning, not keywords
- **HNSW Indexing**: Fast approximate nearest neighbor search
- **SIMD Optimized**: Native Rust performance with vectorized operations
- **Quantization**: Memory-efficient storage for large intent sets
- **Multi-Platform**: Works on Linux, macOS, and Windows
## Installation
```bash
npm install @ruvector/router
```
The package automatically installs the correct native binary for your platform.
## Quick Start
```typescript
import { SemanticRouter } from '@ruvector/router';
// Create router
const router = new SemanticRouter({ dimension: 384 });
// Add intents with example utterances
router.addIntent({
name: 'weather',
utterances: [
'What is the weather today?',
'Will it rain tomorrow?',
'How hot will it be?'
],
metadata: { handler: 'weather_agent' }
});
router.addIntent({
name: 'greeting',
utterances: [
'Hello',
'Hi there',
'Good morning',
'Hey'
],
metadata: { handler: 'greeting_agent' }
});
router.addIntent({
name: 'help',
utterances: [
'I need help',
'Can you assist me?',
'What can you do?'
],
metadata: { handler: 'help_agent' }
});
// Route a query
const results = await router.route('What will the weather be like this weekend?');
console.log(results[0].intent); // 'weather'
console.log(results[0].score); // 0.92
console.log(results[0].metadata); // { handler: 'weather_agent' }
```
## API Reference
### `SemanticRouter`
Main class for semantic routing.
#### Constructor
```typescript
new SemanticRouter(config: RouterConfig)
```
**RouterConfig:**
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `dimension` | number | required | Embedding dimension size |
| `metric` | string | 'cosine' | Distance metric: 'cosine', 'euclidean', 'dot' |
| `m` | number | 16 | HNSW M parameter |
| `efConstruction` | number | 200 | HNSW ef_construction |
| `quantization` | boolean | false | Enable memory-efficient quantization |
#### Methods
##### `addIntent(intent: Intent): void`
Add an intent to the router.
```typescript
router.addIntent({
name: 'booking',
utterances: ['Book a flight', 'Reserve a hotel'],
metadata: { department: 'travel' }
});
```
##### `route(query: string | Float32Array, k?: number): Promise<RouteResult[]>`
Route a query to matching intents.
```typescript
const results = await router.route('I want to book a vacation');
// [{ intent: 'booking', score: 0.89, metadata: {...} }]
```
##### `routeWithEmbedding(embedding: Float32Array, k?: number): RouteResult[]`
Route with a pre-computed embedding (synchronous).
```typescript
const embedding = await getEmbedding('query text');
const results = router.routeWithEmbedding(embedding, 3);
```
##### `removeIntent(name: string): boolean`
Remove an intent from the router.
##### `getIntents(): string[]`
Get all registered intent names.
##### `clear(): void`
Remove all intents.
##### `save(path: string): Promise<void>`
Persist router state to disk.
##### `load(path: string): Promise<void>`
Load router state from disk.
### Types
#### `Intent`
```typescript
interface Intent {
name: string; // Unique intent identifier
utterances: string[]; // Example utterances
embedding?: Float32Array | number[]; // Pre-computed embedding
metadata?: Record<string, unknown>; // Custom metadata
}
```
#### `RouteResult`
```typescript
interface RouteResult {
intent: string; // Matched intent name
score: number; // Similarity score (0-1)
metadata?: Record<string, unknown>; // Intent metadata
}
```
## Use Cases
### Chatbot Intent Detection
```typescript
const router = new SemanticRouter({ dimension: 384 });
// Define intents
const intents = [
{ name: 'faq', utterances: ['What are your hours?', 'How do I contact support?'] },
{ name: 'order', utterances: ['Track my order', 'Where is my package?'] },
{ name: 'return', utterances: ['I want to return this', 'How do I get a refund?'] }
];
intents.forEach(i => router.addIntent(i));
// Handle user message
async function handleMessage(text: string) {
const [result] = await router.route(text);
switch(result.intent) {
case 'faq': return handleFAQ(text);
case 'order': return handleOrder(text);
case 'return': return handleReturn(text);
default: return handleUnknown(text);
}
}
```
### Multi-Agent Orchestration
```typescript
const agents = {
'code': new CodeAgent(),
'research': new ResearchAgent(),
'creative': new CreativeAgent()
};
const router = new SemanticRouter({ dimension: 768 });
router.addIntent({
name: 'code',
utterances: ['Write code', 'Debug this', 'Implement a function'],
metadata: { agent: 'code' }
});
router.addIntent({
name: 'research',
utterances: ['Find information', 'Search for', 'Look up'],
metadata: { agent: 'research' }
});
// Route task to best agent
async function routeTask(task: string) {
const [result] = await router.route(task);
const agent = agents[result.metadata.agent];
return agent.execute(task);
}
```
## Platform Support
| Platform | Architecture | Package |
|----------|--------------|---------|
| Linux | x64 | `@ruvector/router-linux-x64-gnu` |
| Linux | ARM64 | `@ruvector/router-linux-arm64-gnu` |
| macOS | x64 | `@ruvector/router-darwin-x64` |
| macOS | ARM64 | `@ruvector/router-darwin-arm64` |
| Windows | x64 | `@ruvector/router-win32-x64-msvc` |
## Performance
- **Routing**: < 1ms per query with HNSW
- **Throughput**: 100,000+ routes/second
- **Memory**: ~1KB per intent + embeddings
## Related Packages
- [`@ruvector/core`](https://www.npmjs.com/package/@ruvector/core) - Vector database
- [`@ruvector/tiny-dancer`](https://www.npmjs.com/package/@ruvector/tiny-dancer) - Neural routing
- [`@ruvector/gnn`](https://www.npmjs.com/package/@ruvector/gnn) - Graph Neural Networks
## License
MIT

View File

@@ -0,0 +1,284 @@
/**
* Distance metric for vector similarity
*/
export enum DistanceMetric {
/** Euclidean (L2) distance */
Euclidean = 0,
/** Cosine similarity */
Cosine = 1,
/** Dot product similarity */
DotProduct = 2,
/** Manhattan (L1) distance */
Manhattan = 3
}
/**
* Options for creating a VectorDb instance
*/
export interface DbOptions {
/** Vector dimension size (required) */
dimensions: number;
/** Maximum number of elements (optional) */
maxElements?: number;
/** Distance metric for similarity (optional, default: Cosine) */
distanceMetric?: DistanceMetric;
/** HNSW M parameter (optional, default: 16) */
hnswM?: number;
/** HNSW ef_construction parameter (optional, default: 200) */
hnswEfConstruction?: number;
/** HNSW ef_search parameter (optional, default: 100) */
hnswEfSearch?: number;
/** Storage path for persistence (optional) */
storagePath?: string;
}
/**
* Search result from a vector query
*/
export interface SearchResult {
/** Vector ID */
id: string;
/** Similarity score */
score: number;
}
/**
* High-performance vector database for semantic search
*
* @example
* ```typescript
* import { VectorDb, DistanceMetric } from '@ruvector/router';
*
* // Create a vector database
* const db = new VectorDb({
* dimensions: 384,
* distanceMetric: DistanceMetric.Cosine
* });
*
* // Insert vectors
* const embedding = new Float32Array(384).fill(0.5);
* db.insert('doc-1', embedding);
*
* // Search for similar vectors
* const results = db.search(embedding, 5);
* console.log(results[0].id); // 'doc-1'
* console.log(results[0].score); // ~1.0
* ```
*/
export class VectorDb {
/**
* Create a new vector database
* @param options Database options
*/
constructor(options: DbOptions);
/**
* Insert a vector into the database
* @param id Unique identifier
* @param vector Vector data (Float32Array)
* @returns The inserted ID
*/
insert(id: string, vector: Float32Array): string;
/**
* Insert a vector asynchronously
* @param id Unique identifier
* @param vector Vector data (Float32Array)
* @returns Promise resolving to the inserted ID
*/
insertAsync(id: string, vector: Float32Array): Promise<string>;
/**
* Search for similar vectors
* @param queryVector Query embedding
* @param k Number of results to return
* @returns Array of search results
*/
search(queryVector: Float32Array, k: number): SearchResult[];
/**
* Search for similar vectors asynchronously
* @param queryVector Query embedding
* @param k Number of results to return
* @returns Promise resolving to search results
*/
searchAsync(queryVector: Float32Array, k: number): Promise<SearchResult[]>;
/**
* Delete a vector by ID
* @param id Vector ID to delete
* @returns true if deleted, false if not found
*/
delete(id: string): boolean;
/**
* Get the total count of vectors
* @returns Number of vectors in the database
*/
count(): number;
/**
* Get all vector IDs
* @returns Array of all IDs
*/
getAllIds(): string[];
}
/**
* Configuration for SemanticRouter
*/
export interface RouterConfig {
/** Embedding dimension size (required) */
dimension: number;
/** Distance metric: 'cosine', 'euclidean', 'dot', 'manhattan' (default: 'cosine') */
metric?: 'cosine' | 'euclidean' | 'dot' | 'manhattan';
/** HNSW M parameter (default: 16) */
m?: number;
/** HNSW ef_construction (default: 200) */
efConstruction?: number;
/** HNSW ef_search (default: 100) */
efSearch?: number;
/** Enable quantization (default: false) */
quantization?: boolean;
/** Minimum similarity threshold for matches (default: 0.7) */
threshold?: number;
}
/**
* Intent definition for the router
*/
export interface Intent {
/** Unique intent identifier */
name: string;
/** Example utterances for this intent */
utterances: string[];
/** Pre-computed embedding (centroid) */
embedding?: Float32Array | number[];
/** Custom metadata */
metadata?: Record<string, unknown>;
}
/**
* Result from routing a query
*/
export interface RouteResult {
/** Matched intent name */
intent: string;
/** Similarity score (0-1) */
score: number;
/** Intent metadata */
metadata?: Record<string, unknown>;
}
/**
* Embedder function type
*/
export type EmbedderFunction = (text: string) => Promise<Float32Array>;
/**
* Semantic router for AI agents - vector-based intent matching
*
* @example
* ```typescript
* import { SemanticRouter } from '@ruvector/router';
*
* // Create router
* const router = new SemanticRouter({ dimension: 384 });
*
* // Add intents with pre-computed embeddings
* router.addIntent({
* name: 'weather',
* utterances: ['What is the weather?', 'Will it rain?'],
* embedding: weatherEmbedding,
* metadata: { handler: 'weather_agent' }
* });
*
* // Route with embedding
* const results = router.routeWithEmbedding(queryEmbedding, 3);
* console.log(results[0].intent); // 'weather'
* ```
*/
export class SemanticRouter {
/**
* Create a new SemanticRouter
* @param config Router configuration
*/
constructor(config: RouterConfig);
/**
* Set the embedder function for converting text to vectors
* @param embedder Async function (text: string) => Float32Array
*/
setEmbedder(embedder: EmbedderFunction): void;
/**
* Add an intent to the router (synchronous, requires pre-computed embedding)
* @param intent Intent configuration
*/
addIntent(intent: Intent): void;
/**
* Add an intent with automatic embedding computation
* @param intent Intent configuration
*/
addIntentAsync(intent: Intent): Promise<void>;
/**
* Route a query to matching intents
* @param query Query text or embedding
* @param k Number of results to return (default: 1)
* @returns Promise resolving to route results
*/
route(query: string | Float32Array, k?: number): Promise<RouteResult[]>;
/**
* Route with a pre-computed embedding (synchronous)
* @param embedding Query embedding
* @param k Number of results to return (default: 1)
* @returns Route results
*/
routeWithEmbedding(embedding: Float32Array | number[], k?: number): RouteResult[];
/**
* Remove an intent from the router
* @param name Intent name to remove
* @returns true if removed, false if not found
*/
removeIntent(name: string): boolean;
/**
* Get all registered intent names
* @returns Array of intent names
*/
getIntents(): string[];
/**
* Get intent details
* @param name Intent name
* @returns Intent info or null if not found
*/
getIntent(name: string): { name: string; utterances: string[]; metadata: Record<string, unknown> } | null;
/**
* Clear all intents
*/
clear(): void;
/**
* Get the number of intents
* @returns Number of registered intents
*/
count(): number;
/**
* Save router state to disk
* @param filePath Path to save to
*/
save(filePath: string): Promise<void>;
/**
* Load router state from disk
* @param filePath Path to load from
*/
load(filePath: string): Promise<void>;
}

View File

@@ -0,0 +1,344 @@
const { platform, arch } = process;
const path = require('path');
// Platform mapping for @ruvector/router
const platformMap = {
'linux': {
'x64': { package: '@ruvector/router-linux-x64-gnu', file: 'ruvector-router.linux-x64-gnu.node' },
'arm64': { package: '@ruvector/router-linux-arm64-gnu', file: 'ruvector-router.linux-arm64-gnu.node' }
},
'darwin': {
'x64': { package: '@ruvector/router-darwin-x64', file: 'ruvector-router.darwin-x64.node' },
'arm64': { package: '@ruvector/router-darwin-arm64', file: 'ruvector-router.darwin-arm64.node' }
},
'win32': {
'x64': { package: '@ruvector/router-win32-x64-msvc', file: 'ruvector-router.win32-x64-msvc.node' }
}
};
function loadNativeModule() {
const platformInfo = platformMap[platform]?.[arch];
if (!platformInfo) {
throw new Error(
`Unsupported platform: ${platform}-${arch}\n` +
`@ruvector/router native module is available for:\n` +
`- Linux (x64, ARM64)\n` +
`- macOS (x64, ARM64)\n` +
`- Windows (x64)\n\n` +
`Install the package for your platform:\n` +
` npm install @ruvector/router`
);
}
// Try local .node file first (for development and bundled packages)
try {
const localPath = path.join(__dirname, platformInfo.file);
return require(localPath);
} catch (localError) {
// Fall back to platform-specific package
try {
return require(platformInfo.package);
} catch (error) {
if (error.code === 'MODULE_NOT_FOUND') {
throw new Error(
`Native module not found for ${platform}-${arch}\n` +
`Please install: npm install ${platformInfo.package}\n` +
`Or reinstall @ruvector/router to get optional dependencies`
);
}
throw error;
}
}
}
// Load native module
const native = loadNativeModule();
/**
* SemanticRouter - High-level semantic routing for AI agents
*
* Wraps the native VectorDB to provide intent-based routing.
*/
class SemanticRouter {
/**
* Create a new SemanticRouter
* @param {Object} config - Router configuration
* @param {number} config.dimension - Embedding dimension size (required)
* @param {string} [config.metric='cosine'] - Distance metric: 'cosine', 'euclidean', 'dot', 'manhattan'
* @param {number} [config.m=16] - HNSW M parameter
* @param {number} [config.efConstruction=200] - HNSW ef_construction
* @param {number} [config.efSearch=100] - HNSW ef_search
* @param {boolean} [config.quantization=false] - Enable quantization (not yet implemented)
* @param {number} [config.threshold=0.7] - Minimum similarity threshold for matches
*/
constructor(config) {
if (!config || typeof config.dimension !== 'number') {
throw new Error('SemanticRouter requires config.dimension (number)');
}
const metricMap = {
'cosine': native.DistanceMetric.Cosine,
'euclidean': native.DistanceMetric.Euclidean,
'dot': native.DistanceMetric.DotProduct,
'manhattan': native.DistanceMetric.Manhattan
};
this._db = new native.VectorDb({
dimensions: config.dimension,
distanceMetric: metricMap[config.metric] || native.DistanceMetric.Cosine,
hnswM: config.m || 16,
hnswEfConstruction: config.efConstruction || 200,
hnswEfSearch: config.efSearch || 100
});
this._intents = new Map(); // name -> { utterances, metadata, embeddings }
this._threshold = config.threshold || 0.7;
this._dimension = config.dimension;
this._embedder = null; // External embedder function
}
/**
* Set the embedder function for converting text to vectors
* @param {Function} embedder - Async function (text: string) => Float32Array
*/
setEmbedder(embedder) {
if (typeof embedder !== 'function') {
throw new Error('Embedder must be a function');
}
this._embedder = embedder;
}
/**
* Add an intent to the router
* @param {Object} intent - Intent configuration
* @param {string} intent.name - Unique intent identifier
* @param {string[]} intent.utterances - Example utterances for this intent
* @param {Float32Array|number[]} [intent.embedding] - Pre-computed embedding (centroid)
* @param {Object} [intent.metadata] - Custom metadata
*/
addIntent(intent) {
if (!intent || typeof intent.name !== 'string') {
throw new Error('Intent requires a name (string)');
}
if (!Array.isArray(intent.utterances) || intent.utterances.length === 0) {
throw new Error('Intent requires utterances (non-empty array)');
}
// Store intent info
this._intents.set(intent.name, {
utterances: intent.utterances,
metadata: intent.metadata || {},
embedding: intent.embedding || null
});
// If pre-computed embedding provided, insert directly
if (intent.embedding) {
const vector = intent.embedding instanceof Float32Array
? intent.embedding
: new Float32Array(intent.embedding);
this._db.insert(intent.name, vector);
}
}
/**
* Add intent with embedding (async version that computes embeddings)
* @param {Object} intent - Intent configuration
*/
async addIntentAsync(intent) {
if (!intent || typeof intent.name !== 'string') {
throw new Error('Intent requires a name (string)');
}
if (!Array.isArray(intent.utterances) || intent.utterances.length === 0) {
throw new Error('Intent requires utterances (non-empty array)');
}
// Store intent info
this._intents.set(intent.name, {
utterances: intent.utterances,
metadata: intent.metadata || {},
embedding: null
});
// Compute embedding if we have an embedder
if (this._embedder && !intent.embedding) {
// Compute centroid from all utterances
const embeddings = await Promise.all(
intent.utterances.map(u => this._embedder(u))
);
// Average the embeddings
const centroid = new Float32Array(this._dimension);
for (const emb of embeddings) {
for (let i = 0; i < this._dimension; i++) {
centroid[i] += emb[i] / embeddings.length;
}
}
this._intents.get(intent.name).embedding = centroid;
this._db.insert(intent.name, centroid);
} else if (intent.embedding) {
const vector = intent.embedding instanceof Float32Array
? intent.embedding
: new Float32Array(intent.embedding);
this._intents.get(intent.name).embedding = vector;
this._db.insert(intent.name, vector);
}
}
/**
* Route a query to matching intents
* @param {string|Float32Array} query - Query text or embedding
* @param {number} [k=1] - Number of results to return
* @returns {Promise<Array<{intent: string, score: number, metadata: Object}>>}
*/
async route(query, k = 1) {
let embedding;
if (query instanceof Float32Array) {
embedding = query;
} else if (typeof query === 'string') {
if (!this._embedder) {
throw new Error('No embedder set. Call setEmbedder() first or pass a Float32Array.');
}
embedding = await this._embedder(query);
} else {
throw new Error('Query must be a string or Float32Array');
}
return this.routeWithEmbedding(embedding, k);
}
/**
* Route with a pre-computed embedding (synchronous)
* @param {Float32Array} embedding - Query embedding
* @param {number} [k=1] - Number of results to return
* @returns {Array<{intent: string, score: number, metadata: Object}>}
*/
routeWithEmbedding(embedding, k = 1) {
if (!(embedding instanceof Float32Array)) {
embedding = new Float32Array(embedding);
}
const results = this._db.search(embedding, k);
return results
.filter(r => r.score >= this._threshold)
.map(r => {
const intentInfo = this._intents.get(r.id);
return {
intent: r.id,
score: r.score,
metadata: intentInfo ? intentInfo.metadata : {}
};
});
}
/**
* Remove an intent from the router
* @param {string} name - Intent name to remove
* @returns {boolean} - True if removed, false if not found
*/
removeIntent(name) {
if (!this._intents.has(name)) {
return false;
}
this._intents.delete(name);
return this._db.delete(name);
}
/**
* Get all registered intent names
* @returns {string[]}
*/
getIntents() {
return Array.from(this._intents.keys());
}
/**
* Get intent details
* @param {string} name - Intent name
* @returns {Object|null} - Intent info or null if not found
*/
getIntent(name) {
const info = this._intents.get(name);
if (!info) return null;
return {
name,
utterances: info.utterances,
metadata: info.metadata
};
}
/**
* Clear all intents
*/
clear() {
for (const name of this._intents.keys()) {
this._db.delete(name);
}
this._intents.clear();
}
/**
* Get the number of intents
* @returns {number}
*/
count() {
return this._intents.size;
}
/**
* Save router state to disk (intents only, not the index)
* @param {string} filePath - Path to save to
*/
async save(filePath) {
const fs = require('fs').promises;
const data = {
dimension: this._dimension,
threshold: this._threshold,
intents: []
};
for (const [name, info] of this._intents) {
data.intents.push({
name,
utterances: info.utterances,
metadata: info.metadata,
embedding: info.embedding ? Array.from(info.embedding) : null
});
}
await fs.writeFile(filePath, JSON.stringify(data, null, 2));
}
/**
* Load router state from disk
* @param {string} filePath - Path to load from
*/
async load(filePath) {
const fs = require('fs').promises;
const content = await fs.readFile(filePath, 'utf8');
const data = JSON.parse(content);
this.clear();
this._threshold = data.threshold || 0.7;
for (const intent of data.intents) {
this.addIntent({
name: intent.name,
utterances: intent.utterances,
metadata: intent.metadata,
embedding: intent.embedding ? new Float32Array(intent.embedding) : null
});
}
}
}
// Export native module plus SemanticRouter
module.exports = {
...native,
VectorDb: native.VectorDb,
DistanceMetric: native.DistanceMetric,
SemanticRouter
};

View File

@@ -0,0 +1,63 @@
{
"name": "@ruvector/router",
"version": "0.1.28",
"description": "Semantic router for AI agents - vector-based intent matching with HNSW indexing and SIMD acceleration",
"main": "index.js",
"types": "index.d.ts",
"author": "ruv.io Team <info@ruv.io> (https://ruv.io)",
"homepage": "https://ruv.io",
"repository": {
"type": "git",
"url": "https://github.com/ruvnet/ruvector.git",
"directory": "npm/packages/router"
},
"bugs": {
"url": "https://github.com/ruvnet/ruvector/issues"
},
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"files": [
"index.js",
"index.d.ts",
"README.md"
],
"scripts": {
"build:napi": "napi build --platform --release --cargo-cwd ../../../crates/ruvector-router-ffi",
"test": "node test.js",
"publish:platforms": "node scripts/publish-platforms.js"
},
"devDependencies": {
"@napi-rs/cli": "^2.18.0"
},
"optionalDependencies": {
"@ruvector/router-linux-x64-gnu": "0.1.27",
"@ruvector/router-linux-arm64-gnu": "0.1.27",
"@ruvector/router-darwin-x64": "0.1.27",
"@ruvector/router-darwin-arm64": "0.1.27",
"@ruvector/router-win32-x64-msvc": "0.1.27"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"semantic-router",
"intent-matching",
"ai-routing",
"agent-routing",
"vector-search",
"hnsw",
"similarity-search",
"embeddings",
"llm",
"native",
"napi",
"rust",
"simd",
"fast",
"performance",
"ruv",
"ruvector"
]
}

View File

@@ -0,0 +1,104 @@
const router = require('./index.js');
console.log('Testing @ruvector/router...');
// Check available exports
console.log(`Available exports: ${Object.keys(router).join(', ')}`);
// Test VectorDb class exists
try {
if (typeof router.VectorDb === 'function') {
console.log('✓ VectorDb class available');
// Test creating an instance with options object (in-memory, no storage path)
const db = new router.VectorDb({
dimensions: 384,
distanceMetric: router.DistanceMetric.Cosine,
maxElements: 1000
});
console.log('✓ VectorDb instance created (384 dimensions, cosine distance, in-memory)');
// Test count method
const count = db.count();
console.log(`✓ count(): ${count}`);
// Test insert and search
const testVector = new Float32Array(384).fill(0.5);
db.insert('test-1', testVector);
console.log('✓ insert() worked');
const results = db.search(testVector, 1);
console.log(`✓ search() returned ${results.length} result(s)`);
if (results.length > 0) {
console.log(` Top result: ${results[0].id} (score: ${results[0].score.toFixed(4)})`);
}
} else {
console.log('✗ VectorDb class not found');
}
} catch (e) {
console.error('✗ VectorDb test failed:', e.message);
console.error(' Note: This may be due to storage path validation. The module loads correctly.');
}
// Test DistanceMetric enum exists
try {
if (router.DistanceMetric) {
console.log('✓ DistanceMetric enum available');
console.log(` - Cosine: ${router.DistanceMetric.Cosine}`);
console.log(` - Euclidean: ${router.DistanceMetric.Euclidean}`);
console.log(` - DotProduct: ${router.DistanceMetric.DotProduct}`);
} else {
console.log('✗ DistanceMetric not found');
}
} catch (e) {
console.error('✗ DistanceMetric check failed:', e.message);
}
// Test SemanticRouter class exists (GitHub issue #67)
try {
if (typeof router.SemanticRouter === 'function') {
console.log('✓ SemanticRouter class available');
// Test creating an instance
const semanticRouter = new router.SemanticRouter({
dimension: 384,
metric: 'cosine',
threshold: 0.7
});
console.log('✓ SemanticRouter instance created');
// Test adding an intent with pre-computed embedding
const testEmbedding = new Float32Array(384).fill(0.5);
semanticRouter.addIntent({
name: 'test-intent',
utterances: ['test utterance 1', 'test utterance 2'],
embedding: testEmbedding,
metadata: { handler: 'test_handler' }
});
console.log('✓ addIntent() worked');
// Test getIntents
const intents = semanticRouter.getIntents();
console.log(`✓ getIntents() returned: ${intents.join(', ')}`);
// Test routeWithEmbedding
const results = semanticRouter.routeWithEmbedding(testEmbedding, 1);
console.log(`✓ routeWithEmbedding() returned ${results.length} result(s)`);
if (results.length > 0) {
console.log(` Top result: ${results[0].intent} (score: ${results[0].score.toFixed(4)})`);
}
// Test count
console.log(`✓ count(): ${semanticRouter.count()}`);
// Test clear
semanticRouter.clear();
console.log(`✓ clear() worked, count now: ${semanticRouter.count()}`);
} else {
console.log('✗ SemanticRouter class not found');
}
} catch (e) {
console.error('✗ SemanticRouter test failed:', e.message);
}
console.log('\nAll basic tests completed!');