mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-31 21:49:52 +00:00
Merge pull request #91 from ruvnet/cleanup/remove-duplicate-npm-ruvector
chore: remove duplicate npm/ruvector directory
This commit is contained in:
commit
598e2a184c
13 changed files with 0 additions and 3047 deletions
|
|
@ -1,49 +0,0 @@
|
|||
# Source files
|
||||
src/
|
||||
*.ts
|
||||
!*.d.ts
|
||||
|
||||
# Build config
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
||||
.tsup/
|
||||
|
||||
# Development
|
||||
node_modules/
|
||||
.git/
|
||||
.github/
|
||||
.gitignore
|
||||
examples/
|
||||
|
||||
# Test files
|
||||
*.test.js
|
||||
*.test.ts
|
||||
*.spec.js
|
||||
*.spec.ts
|
||||
test-*.js
|
||||
coverage/
|
||||
|
||||
# Logs and temp files
|
||||
*.log
|
||||
*.tmp
|
||||
.DS_Store
|
||||
.cache/
|
||||
*.tsbuildinfo
|
||||
|
||||
# CI/CD
|
||||
.travis.yml
|
||||
.gitlab-ci.yml
|
||||
azure-pipelines.yml
|
||||
.circleci/
|
||||
|
||||
# Documentation (keep README.md)
|
||||
docs/
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
|
@ -1,707 +0,0 @@
|
|||
# RuVector
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://www.npmjs.com/package/ruvector)
|
||||
[](https://www.npmjs.com/package/ruvector)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://nodejs.org/)
|
||||
|
||||
**A distributed vector database that learns.** Store embeddings, query with Cypher, scale horizontally, and let the index improve itself through Graph Neural Networks.
|
||||
|
||||
```bash
|
||||
npx ruvector
|
||||
```
|
||||
|
||||
> **All-in-One Package**: The `ruvector` package includes everything — vector search, graph queries, GNN layers, AI agent routing, and WASM support. No additional packages needed.
|
||||
|
||||
## Why RuVector?
|
||||
|
||||
Traditional vector databases just store and search. When you ask "find similar items," they return results but never get smarter. They can't handle complex relationships. They don't optimize your AI costs.
|
||||
|
||||
**RuVector is built for the agentic AI era:**
|
||||
|
||||
| Challenge | RuVector Solution |
|
||||
|-----------|-------------------|
|
||||
| RAG retrieval quality plateaus | **Self-learning GNN** improves results over time |
|
||||
| Knowledge graphs need separate DB | **Cypher queries** built-in (Neo4j syntax) |
|
||||
| LLM costs spiral out of control | **AI Router** sends simple queries to cheaper models |
|
||||
| Memory usage explodes at scale | **Adaptive compression** (2-32x reduction) |
|
||||
| Can't run AI in the browser | **Full WASM support** for client-side inference |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install the package
|
||||
npm install ruvector
|
||||
|
||||
# Or try instantly without installing
|
||||
npx ruvector
|
||||
|
||||
# With yarn
|
||||
yarn add ruvector
|
||||
|
||||
# With pnpm
|
||||
pnpm add ruvector
|
||||
```
|
||||
|
||||
### Basic Vector Search
|
||||
|
||||
```javascript
|
||||
const { VectorDB } = require('ruvector');
|
||||
|
||||
// Create a vector database (384 = OpenAI ada-002 dimensions)
|
||||
const db = new VectorDB(384);
|
||||
|
||||
// Insert vectors with metadata
|
||||
await db.insert('doc1', embedding1, {
|
||||
title: 'Introduction to AI',
|
||||
category: 'tech',
|
||||
date: '2024-01-15'
|
||||
});
|
||||
|
||||
// Semantic search
|
||||
const results = await db.search(queryEmbedding, 10);
|
||||
|
||||
// Filter by metadata
|
||||
const filtered = await db.search(queryEmbedding, 10, {
|
||||
category: 'tech',
|
||||
date: { $gte: '2024-01-01' }
|
||||
});
|
||||
```
|
||||
|
||||
### RAG (Retrieval-Augmented Generation)
|
||||
|
||||
```javascript
|
||||
const { VectorDB } = require('ruvector');
|
||||
const OpenAI = require('openai');
|
||||
|
||||
const db = new VectorDB(1536); // text-embedding-3-small dimensions
|
||||
const openai = new OpenAI();
|
||||
|
||||
// Index your documents
|
||||
async function indexDocument(doc) {
|
||||
const embedding = await openai.embeddings.create({
|
||||
model: 'text-embedding-3-small',
|
||||
input: doc.content
|
||||
});
|
||||
await db.insert(doc.id, embedding.data[0].embedding, {
|
||||
title: doc.title,
|
||||
content: doc.content
|
||||
});
|
||||
}
|
||||
|
||||
// RAG query
|
||||
async function ragQuery(question) {
|
||||
// 1. Embed the question
|
||||
const questionEmb = await openai.embeddings.create({
|
||||
model: 'text-embedding-3-small',
|
||||
input: question
|
||||
});
|
||||
|
||||
// 2. Retrieve relevant context
|
||||
const context = await db.search(questionEmb.data[0].embedding, 5);
|
||||
|
||||
// 3. Generate answer with context
|
||||
const response = await openai.chat.completions.create({
|
||||
model: 'gpt-4-turbo',
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: `Context:\n${context.map(c => c.metadata.content).join('\n\n')}
|
||||
|
||||
Question: ${question}
|
||||
Answer based only on the context above:`
|
||||
}]
|
||||
});
|
||||
|
||||
return response.choices[0].message.content;
|
||||
}
|
||||
```
|
||||
|
||||
### Knowledge Graphs (Cypher)
|
||||
|
||||
```javascript
|
||||
const { GraphDB } = require('ruvector');
|
||||
|
||||
const graph = new GraphDB();
|
||||
|
||||
// Create entities and relationships
|
||||
graph.execute(`
|
||||
CREATE (alice:Person {name: 'Alice', role: 'Engineer'})
|
||||
CREATE (bob:Person {name: 'Bob', role: 'Manager'})
|
||||
CREATE (techcorp:Company {name: 'TechCorp', industry: 'AI'})
|
||||
CREATE (alice)-[:WORKS_AT {since: 2022}]->(techcorp)
|
||||
CREATE (bob)-[:WORKS_AT {since: 2020}]->(techcorp)
|
||||
CREATE (alice)-[:REPORTS_TO]->(bob)
|
||||
`);
|
||||
|
||||
// Query relationships
|
||||
const team = graph.execute(`
|
||||
MATCH (p:Person)-[:WORKS_AT]->(c:Company {name: 'TechCorp'})
|
||||
RETURN p.name, p.role
|
||||
`);
|
||||
|
||||
// Find paths
|
||||
const chain = graph.execute(`
|
||||
MATCH path = (a:Person {name: 'Alice'})-[:REPORTS_TO*1..3]->(manager)
|
||||
RETURN path
|
||||
`);
|
||||
|
||||
// Combine with vector search
|
||||
const similarPeople = graph.execute(`
|
||||
MATCH (p:Person)
|
||||
WHERE vector.similarity(p.embedding, $queryEmbedding) > 0.8
|
||||
RETURN p ORDER BY vector.similarity(p.embedding, $queryEmbedding) DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
```
|
||||
|
||||
### GNN-Enhanced Search (Self-Learning)
|
||||
|
||||
```javascript
|
||||
const { GNNLayer, VectorDB } = require('ruvector');
|
||||
|
||||
// Create GNN layer for query enhancement
|
||||
const gnn = new GNNLayer(384, 512, 4); // input_dim, output_dim, num_heads
|
||||
|
||||
// The GNN learns from your search patterns
|
||||
async function enhancedSearch(query) {
|
||||
// Get initial results
|
||||
const neighbors = await db.search(query, 20);
|
||||
|
||||
// Compute attention weights based on user clicks/relevance
|
||||
const weights = computeRelevanceWeights(neighbors);
|
||||
|
||||
// GNN enhances the query using graph structure
|
||||
const enhancedQuery = gnn.forward(query,
|
||||
neighbors.map(n => n.embedding),
|
||||
weights
|
||||
);
|
||||
|
||||
// Re-rank with enhanced understanding
|
||||
return db.search(enhancedQuery, 10);
|
||||
}
|
||||
|
||||
// Train on user feedback
|
||||
gnn.train({
|
||||
queries: historicalQueries,
|
||||
clicks: userClickData,
|
||||
relevance: expertLabels
|
||||
}, { epochs: 100 });
|
||||
```
|
||||
|
||||
### AI Agent Routing (Tiny Dancer)
|
||||
|
||||
Route queries to the optimal LLM based on complexity — save 60-80% on API costs:
|
||||
|
||||
```javascript
|
||||
const { Router } = require('ruvector');
|
||||
|
||||
const router = new Router({
|
||||
confidenceThreshold: 0.85,
|
||||
maxUncertainty: 0.15,
|
||||
enableCircuitBreaker: true
|
||||
});
|
||||
|
||||
// Define your model candidates
|
||||
const models = [
|
||||
{ id: 'gpt-4-turbo', embedding: gpt4Emb, cost: 0.03, quality: 0.95 },
|
||||
{ id: 'gpt-3.5-turbo', embedding: gpt35Emb, cost: 0.002, quality: 0.80 },
|
||||
{ id: 'claude-3-haiku', embedding: haikuEmb, cost: 0.001, quality: 0.75 },
|
||||
{ id: 'llama-3-8b', embedding: llamaEmb, cost: 0.0005, quality: 0.70 }
|
||||
];
|
||||
|
||||
async function smartComplete(prompt) {
|
||||
const promptEmb = await embed(prompt);
|
||||
|
||||
// Router decides optimal model
|
||||
const decision = router.route(promptEmb, models);
|
||||
|
||||
console.log(`Routing to ${decision.candidateId} (confidence: ${decision.confidence})`);
|
||||
// Output: "Routing to gpt-3.5-turbo (confidence: 0.92)"
|
||||
|
||||
// Call the selected model
|
||||
return callModel(decision.candidateId, prompt);
|
||||
}
|
||||
```
|
||||
|
||||
### Compression (2-32x Memory Savings)
|
||||
|
||||
```javascript
|
||||
const { compress, decompress, CompressionTier } = require('ruvector');
|
||||
|
||||
// Automatic tier selection
|
||||
const auto = compress(embedding, 0.3); // 30% quality threshold
|
||||
|
||||
// Explicit tiers
|
||||
const f16 = compress(embedding, CompressionTier.F16); // 2x compression
|
||||
const pq8 = compress(embedding, CompressionTier.PQ8); // 8x compression
|
||||
const pq4 = compress(embedding, CompressionTier.PQ4); // 16x compression
|
||||
const binary = compress(embedding, CompressionTier.Binary); // 32x compression
|
||||
|
||||
// Adaptive tiering based on access frequency
|
||||
db.enableAdaptiveCompression({
|
||||
hotThreshold: 0.8, // Keep hot data in f32
|
||||
warmThreshold: 0.4, // Compress to f16
|
||||
coldThreshold: 0.1, // Compress to PQ8
|
||||
archiveThreshold: 0.01 // Compress to binary
|
||||
});
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
# Show system info and backend status
|
||||
npx ruvector info
|
||||
|
||||
# Initialize a new index
|
||||
npx ruvector init my-index --dimension 384 --type hnsw
|
||||
|
||||
# Insert vectors from JSON/JSONL
|
||||
npx ruvector insert my-index vectors.json
|
||||
npx ruvector insert my-index vectors.jsonl --format jsonl
|
||||
|
||||
# Search with a query
|
||||
npx ruvector search my-index --query "[0.1, 0.2, ...]" -k 10
|
||||
npx ruvector search my-index --text "machine learning" -k 10 # Auto-embed
|
||||
|
||||
# Show index statistics
|
||||
npx ruvector stats my-index
|
||||
|
||||
# Run performance benchmarks
|
||||
npx ruvector benchmark --dimension 384 --num-vectors 10000
|
||||
|
||||
# Export/import
|
||||
npx ruvector export my-index backup.bin
|
||||
npx ruvector import backup.bin restored-index
|
||||
```
|
||||
|
||||
## Self-Learning Hooks (Claude Code Integration)
|
||||
|
||||
RuVector includes a self-learning intelligence layer that improves AI agent decisions over time. These hooks integrate with Claude Code and other AI development tools.
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Initialize hooks in your project
|
||||
npx ruvector hooks init
|
||||
```
|
||||
|
||||
### Hook Commands
|
||||
|
||||
```bash
|
||||
# Session Management
|
||||
ruvector hooks session-start # Start session tracking
|
||||
ruvector hooks session-end # End session with export
|
||||
|
||||
# Pre/Post Edit Hooks
|
||||
ruvector hooks pre-edit <file> # Get agent suggestions before editing
|
||||
ruvector hooks post-edit <file> --success # Record edit outcomes
|
||||
|
||||
# Pre/Post Command Hooks
|
||||
ruvector hooks pre-command "cargo test" # Analyze command before running
|
||||
ruvector hooks post-command "cargo test" --success # Record command outcomes
|
||||
|
||||
# Intelligence
|
||||
ruvector hooks stats # Show learning statistics
|
||||
ruvector hooks route <task> # Get agent routing suggestion
|
||||
ruvector hooks suggest-context # Get context suggestions
|
||||
|
||||
# Memory
|
||||
ruvector hooks remember <content> -t <type> # Store in vector memory
|
||||
ruvector hooks recall <query> # Semantic search memory
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
The intelligence system uses:
|
||||
- **Q-Learning**: Learns optimal agent routing from past successes/failures
|
||||
- **Vector Memory**: Semantic storage with cosine similarity search
|
||||
- **File Sequences**: Predicts related files based on edit patterns
|
||||
- **Error Patterns**: Remembers fixes for common errors
|
||||
|
||||
### Example Output
|
||||
|
||||
```
|
||||
🧠 Intelligence Analysis:
|
||||
📁 ruvector-core/lib.rs
|
||||
🤖 Recommended: rust-developer (80% confidence)
|
||||
→ learned from past success
|
||||
📚 Similar: 3 past edits
|
||||
📎 Related: mod.rs, tests.rs
|
||||
💬 ⚡ Core lib: run cargo test --lib after changes
|
||||
```
|
||||
|
||||
### Claude Code Integration
|
||||
|
||||
Add to your `.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [{ "command": "ruvector hooks pre-edit $file" }],
|
||||
"PostToolUse": [{ "command": "ruvector hooks post-edit $file --success" }],
|
||||
"SessionStart": [{ "command": "ruvector hooks session-start" }],
|
||||
"Stop": [{ "command": "ruvector hooks session-end" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Learning Data
|
||||
|
||||
| Storage | Contents |
|
||||
|---------|----------|
|
||||
| `.ruvector/intelligence.json` | Q-table, memories, trajectories |
|
||||
| Patterns | State-action values for agent routing |
|
||||
| Memories | Vector embeddings for semantic recall |
|
||||
| Trajectories | Learning history for continuous improvement |
|
||||
|
||||
## Integrations
|
||||
|
||||
### LangChain
|
||||
|
||||
```javascript
|
||||
const { RuVectorStore } = require('ruvector/langchain');
|
||||
const { OpenAIEmbeddings } = require('@langchain/openai');
|
||||
|
||||
const vectorStore = new RuVectorStore(
|
||||
new OpenAIEmbeddings(),
|
||||
{ dimension: 1536 }
|
||||
);
|
||||
|
||||
await vectorStore.addDocuments(documents);
|
||||
const results = await vectorStore.similaritySearch("query", 5);
|
||||
```
|
||||
|
||||
### LlamaIndex
|
||||
|
||||
```javascript
|
||||
const { RuVectorIndex } = require('ruvector/llamaindex');
|
||||
|
||||
const index = new RuVectorIndex({
|
||||
dimension: 384,
|
||||
enableGNN: true
|
||||
});
|
||||
|
||||
await index.insert(documents);
|
||||
const queryEngine = index.asQueryEngine();
|
||||
const response = await queryEngine.query("What is machine learning?");
|
||||
```
|
||||
|
||||
### OpenAI / Anthropic
|
||||
|
||||
```javascript
|
||||
const { createEmbedder } = require('ruvector');
|
||||
|
||||
// OpenAI
|
||||
const openaiEmbed = createEmbedder('openai', {
|
||||
model: 'text-embedding-3-small'
|
||||
});
|
||||
|
||||
// Anthropic (via Voyage)
|
||||
const anthropicEmbed = createEmbedder('voyage', {
|
||||
model: 'voyage-2'
|
||||
});
|
||||
|
||||
// Cohere
|
||||
const cohereEmbed = createEmbedder('cohere', {
|
||||
model: 'embed-english-v3.0'
|
||||
});
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
| Operation | Dimensions | Time | Throughput |
|
||||
|-----------|------------|------|------------|
|
||||
| **HNSW Search (k=10)** | 384 | 61µs | 16,400 QPS |
|
||||
| **HNSW Search (k=100)** | 384 | 164µs | 6,100 QPS |
|
||||
| **Cosine Similarity** | 1536 | 143ns | 7M ops/sec |
|
||||
| **Dot Product** | 384 | 33ns | 30M ops/sec |
|
||||
| **Insert** | 384 | 20µs | 50,000/sec |
|
||||
| **GNN Forward** | 384→512 | 89µs | 11,200/sec |
|
||||
| **Compression (PQ8)** | 384 | 12µs | 83,000/sec |
|
||||
|
||||
Run your own benchmarks:
|
||||
```bash
|
||||
npx ruvector benchmark --dimension 384 --num-vectors 100000
|
||||
```
|
||||
|
||||
## Comparison
|
||||
|
||||
| Feature | RuVector | Pinecone | Qdrant | ChromaDB | Milvus | Weaviate |
|
||||
|---------|----------|----------|--------|----------|--------|----------|
|
||||
| **Latency (p50)** | **61µs** | ~2ms | ~1ms | ~50ms | ~5ms | ~3ms |
|
||||
| **Graph Queries** | ✅ Cypher | ❌ | ❌ | ❌ | ❌ | ✅ GraphQL |
|
||||
| **Self-Learning** | ✅ GNN | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **AI Routing** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Browser/WASM** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Compression** | 2-32x | ❌ | ✅ | ❌ | ✅ | ✅ |
|
||||
| **Hybrid Search** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
|
||||
| **Multi-tenancy** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Open Source** | ✅ MIT | ❌ | ✅ Apache | ✅ Apache | ✅ Apache | ✅ BSD |
|
||||
| **Pricing** | Free | $70+/mo | Free | Free | Free | Free |
|
||||
|
||||
## npm Packages
|
||||
|
||||
| Package | Description |
|
||||
|---------|-------------|
|
||||
| [`ruvector`](https://www.npmjs.com/package/ruvector) | **All-in-one package (recommended)** |
|
||||
| [`@ruvector/wasm`](https://www.npmjs.com/package/@ruvector/wasm) | Browser/WASM bindings |
|
||||
| [`@ruvector/graph`](https://www.npmjs.com/package/@ruvector/graph) | Graph database with Cypher |
|
||||
| [`@ruvector/gnn`](https://www.npmjs.com/package/@ruvector/gnn) | Graph Neural Network layers |
|
||||
| [`@ruvector/tiny-dancer`](https://www.npmjs.com/package/@ruvector/tiny-dancer) | AI agent routing (FastGRNN) |
|
||||
| [`@ruvector/router`](https://www.npmjs.com/package/@ruvector/router) | Semantic routing engine |
|
||||
|
||||
```bash
|
||||
# Install all-in-one (recommended)
|
||||
npm install ruvector
|
||||
|
||||
# Or install specific packages
|
||||
npm install @ruvector/graph @ruvector/gnn
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### VectorDB
|
||||
|
||||
```typescript
|
||||
class VectorDB {
|
||||
constructor(dimension: number, options?: VectorDBOptions);
|
||||
|
||||
// CRUD operations
|
||||
insert(id: string, values: number[], metadata?: object): Promise<void>;
|
||||
insertBatch(vectors: Vector[], options?: BatchOptions): Promise<void>;
|
||||
get(id: string): Promise<Vector | null>;
|
||||
update(id: string, values?: number[], metadata?: object): Promise<void>;
|
||||
delete(id: string): Promise<boolean>;
|
||||
|
||||
// Search
|
||||
search(query: number[], k?: number, filter?: Filter): Promise<SearchResult[]>;
|
||||
hybridSearch(query: number[], text: string, k?: number): Promise<SearchResult[]>;
|
||||
|
||||
// Persistence
|
||||
save(path: string): Promise<void>;
|
||||
static load(path: string): Promise<VectorDB>;
|
||||
|
||||
// Management
|
||||
stats(): Promise<IndexStats>;
|
||||
optimize(): Promise<void>;
|
||||
clear(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### GraphDB
|
||||
|
||||
```typescript
|
||||
class GraphDB {
|
||||
constructor(options?: GraphDBOptions);
|
||||
|
||||
// Cypher execution
|
||||
execute(cypher: string, params?: object): QueryResult;
|
||||
|
||||
// Direct API
|
||||
createNode(label: string, properties: object): string;
|
||||
createRelationship(from: string, to: string, type: string, props?: object): void;
|
||||
createHyperedge(nodeIds: string[], type: string, props?: object): string;
|
||||
|
||||
// Traversal
|
||||
shortestPath(from: string, to: string): Path | null;
|
||||
neighbors(nodeId: string, depth?: number): Node[];
|
||||
}
|
||||
```
|
||||
|
||||
### GNNLayer
|
||||
|
||||
```typescript
|
||||
class GNNLayer {
|
||||
constructor(inputDim: number, outputDim: number, numHeads: number);
|
||||
|
||||
// Inference
|
||||
forward(query: number[], neighbors: number[][], weights: number[]): number[];
|
||||
|
||||
// Training
|
||||
train(data: TrainingData, config?: TrainingConfig): TrainingMetrics;
|
||||
save(path: string): void;
|
||||
static load(path: string): GNNLayer;
|
||||
}
|
||||
```
|
||||
|
||||
### Router
|
||||
|
||||
```typescript
|
||||
class Router {
|
||||
constructor(config?: RouterConfig);
|
||||
|
||||
// Routing
|
||||
route(query: number[], candidates: Candidate[]): RoutingDecision;
|
||||
routeBatch(queries: number[][], candidates: Candidate[]): RoutingDecision[];
|
||||
|
||||
// Management
|
||||
reloadModel(): void;
|
||||
circuitBreakerStatus(): 'closed' | 'open' | 'half-open';
|
||||
resetCircuitBreaker(): void;
|
||||
}
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Agentic AI / Multi-Agent Systems
|
||||
|
||||
```javascript
|
||||
// Route tasks to specialized agents
|
||||
const agents = [
|
||||
{ id: 'researcher', embedding: researchEmb, capabilities: ['search', 'summarize'] },
|
||||
{ id: 'coder', embedding: codeEmb, capabilities: ['code', 'debug'] },
|
||||
{ id: 'analyst', embedding: analysisEmb, capabilities: ['data', 'visualize'] }
|
||||
];
|
||||
|
||||
const taskEmb = await embed("Write a Python script to analyze sales data");
|
||||
const decision = router.route(taskEmb, agents);
|
||||
// Routes to 'coder' agent with high confidence
|
||||
```
|
||||
|
||||
### Recommendation Systems
|
||||
|
||||
```javascript
|
||||
const recommendations = graph.execute(`
|
||||
MATCH (user:User {id: $userId})-[:VIEWED]->(item:Product)
|
||||
MATCH (item)-[:SIMILAR_TO]->(rec:Product)
|
||||
WHERE NOT (user)-[:VIEWED]->(rec)
|
||||
AND vector.similarity(rec.embedding, $userPreference) > 0.7
|
||||
RETURN rec
|
||||
ORDER BY vector.similarity(rec.embedding, $userPreference) DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
```
|
||||
|
||||
### Semantic Caching
|
||||
|
||||
```javascript
|
||||
const cache = new VectorDB(1536);
|
||||
|
||||
async function cachedLLMCall(prompt) {
|
||||
const promptEmb = await embed(prompt);
|
||||
|
||||
// Check semantic cache
|
||||
const cached = await cache.search(promptEmb, 1);
|
||||
if (cached[0]?.score > 0.95) {
|
||||
return cached[0].metadata.response; // Cache hit
|
||||
}
|
||||
|
||||
// Cache miss - call LLM
|
||||
const response = await llm.complete(prompt);
|
||||
await cache.insert(generateId(), promptEmb, { prompt, response });
|
||||
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
### Document Q&A with Sources
|
||||
|
||||
```javascript
|
||||
async function qaWithSources(question) {
|
||||
const results = await db.search(await embed(question), 5);
|
||||
|
||||
const answer = await llm.complete({
|
||||
prompt: `Answer based on these sources:\n${results.map(r =>
|
||||
`[${r.id}] ${r.metadata.content}`
|
||||
).join('\n')}\n\nQuestion: ${question}`,
|
||||
});
|
||||
|
||||
return {
|
||||
answer,
|
||||
sources: results.map(r => ({
|
||||
id: r.id,
|
||||
title: r.metadata.title,
|
||||
relevance: r.score
|
||||
}))
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ ruvector │
|
||||
│ (All-in-One npm Package) │
|
||||
├──────────────┬──────────────┬──────────────┬─────────────────┤
|
||||
│ VectorDB │ GraphDB │ GNNLayer │ Router │
|
||||
│ (Search) │ (Cypher) │ (ML) │ (AI Routing) │
|
||||
├──────────────┴──────────────┴──────────────┴─────────────────┤
|
||||
│ Rust Core Engine │
|
||||
│ • HNSW Index • Cypher Parser • Attention • FastGRNN │
|
||||
│ • SIMD Ops • Hyperedges • Training • Uncertainty │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────┼──────────────────┐
|
||||
│ │ │
|
||||
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
||||
│ Native │ │ WASM │ │ FFI │
|
||||
│(napi-rs)│ │(wasm32) │ │ (C) │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
│ │ │
|
||||
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
||||
│ Node.js │ │ Browser │ │ Python │
|
||||
│ Bun │ │ Deno │ │ Go │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Platform | Backend | Installation |
|
||||
|----------|---------|--------------|
|
||||
| **Node.js 16+** | Native (napi-rs) | `npm install ruvector` |
|
||||
| **Node.js (fallback)** | WASM | Automatic if native fails |
|
||||
| **Bun** | Native | `bun add ruvector` |
|
||||
| **Deno** | WASM | `import from "npm:ruvector"` |
|
||||
| **Browser** | WASM | `npm install @ruvector/wasm` |
|
||||
| **Cloudflare Workers** | WASM | `npm install @ruvector/wasm` |
|
||||
| **Vercel Edge** | WASM | `npm install @ruvector/wasm` |
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Getting Started Guide](https://github.com/ruvnet/ruvector/blob/main/docs/guide/GETTING_STARTED.md)
|
||||
- [Cypher Reference](https://github.com/ruvnet/ruvector/blob/main/docs/api/CYPHER_REFERENCE.md)
|
||||
- [GNN Architecture](https://github.com/ruvnet/ruvector/blob/main/docs/gnn-layer-implementation.md)
|
||||
- [Performance Tuning](https://github.com/ruvnet/ruvector/blob/main/docs/optimization/PERFORMANCE_TUNING_GUIDE.md)
|
||||
- [API Reference](https://github.com/ruvnet/ruvector/tree/main/docs/api)
|
||||
|
||||
## Contributing
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/ruvnet/ruvector.git
|
||||
cd ruvector
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Benchmarks
|
||||
npm run bench
|
||||
```
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/ruvnet/ruvector/blob/main/docs/development/CONTRIBUTING.md) for guidelines.
|
||||
|
||||
## License
|
||||
|
||||
MIT License — free for commercial and personal use.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**Built by [rUv](https://ruv.io)** • [GitHub](https://github.com/ruvnet/ruvector) • [npm](https://npmjs.com/package/ruvector)
|
||||
|
||||
*Vector search that gets smarter over time.*
|
||||
|
||||
**[⭐ Star on GitHub](https://github.com/ruvnet/ruvector)** if RuVector helps your project!
|
||||
|
||||
</div>
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,77 +0,0 @@
|
|||
/**
|
||||
* Advanced search features example
|
||||
*/
|
||||
|
||||
const { VectorIndex, Utils } = require('ruvector');
|
||||
|
||||
async function main() {
|
||||
console.log('🔍 Advanced Search Example\n');
|
||||
|
||||
// Create index
|
||||
const index = new VectorIndex({
|
||||
dimension: 128,
|
||||
metric: 'cosine',
|
||||
indexType: 'hnsw'
|
||||
});
|
||||
|
||||
// Insert vectors with rich metadata
|
||||
console.log('Inserting documents...');
|
||||
const documents = [
|
||||
{ id: 'doc1', category: 'tech', tags: ['ai', 'ml'] },
|
||||
{ id: 'doc2', category: 'tech', tags: ['web', 'javascript'] },
|
||||
{ id: 'doc3', category: 'science', tags: ['physics', 'quantum'] },
|
||||
{ id: 'doc4', category: 'science', tags: ['biology', 'dna'] },
|
||||
{ id: 'doc5', category: 'business', tags: ['finance', 'stocks'] }
|
||||
];
|
||||
|
||||
const vectors = documents.map(doc => ({
|
||||
id: doc.id,
|
||||
values: Utils.randomVector(128),
|
||||
metadata: doc
|
||||
}));
|
||||
|
||||
await index.insertBatch(vectors);
|
||||
|
||||
// Perform different types of searches
|
||||
const query = Utils.randomVector(128);
|
||||
|
||||
console.log('\n1. Basic search (top 3):');
|
||||
const basic = await index.search(query, { k: 3 });
|
||||
basic.forEach((r, i) => {
|
||||
console.log(` ${i + 1}. ${r.id} - ${r.metadata.category} (${r.score.toFixed(4)})`);
|
||||
});
|
||||
|
||||
console.log('\n2. Search with HNSW tuning (higher accuracy):');
|
||||
const accurate = await index.search(query, { k: 3, ef: 100 });
|
||||
accurate.forEach((r, i) => {
|
||||
console.log(` ${i + 1}. ${r.id} - ${r.metadata.category} (${r.score.toFixed(4)})`);
|
||||
});
|
||||
|
||||
// Calculate similarities manually
|
||||
console.log('\n3. Manual similarity calculation:');
|
||||
const vec1 = Utils.randomVector(128);
|
||||
const vec2 = Utils.randomVector(128);
|
||||
const similarity = Utils.cosineSimilarity(vec1, vec2);
|
||||
const distance = Utils.euclideanDistance(vec1, vec2);
|
||||
console.log(` Cosine similarity: ${similarity.toFixed(4)}`);
|
||||
console.log(` Euclidean distance: ${distance.toFixed(4)}`);
|
||||
|
||||
// Get specific vector
|
||||
console.log('\n4. Get vector by ID:');
|
||||
const retrieved = await index.get('doc1');
|
||||
if (retrieved) {
|
||||
console.log(` Retrieved: ${retrieved.id}`);
|
||||
console.log(` Metadata:`, retrieved.metadata);
|
||||
console.log(` Vector dimension: ${retrieved.values.length}`);
|
||||
}
|
||||
|
||||
// Delete and verify
|
||||
console.log('\n5. Delete operation:');
|
||||
const deleted = await index.delete('doc5');
|
||||
console.log(` Deleted doc5: ${deleted}`);
|
||||
|
||||
const statsAfter = await index.stats();
|
||||
console.log(` Vectors remaining: ${statsAfter.vectorCount}`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* Basic usage example for rUvector
|
||||
*/
|
||||
|
||||
const { VectorIndex, Utils, getBackendInfo } = require('ruvector');
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 rUvector Basic Usage Example\n');
|
||||
|
||||
// Show backend info
|
||||
const info = getBackendInfo();
|
||||
console.log(`Backend: ${info.type} (${info.version})`);
|
||||
console.log(`Features: ${info.features.join(', ')}\n`);
|
||||
|
||||
// Create a new index
|
||||
console.log('Creating index...');
|
||||
const index = new VectorIndex({
|
||||
dimension: 384,
|
||||
metric: 'cosine',
|
||||
indexType: 'hnsw',
|
||||
hnswConfig: {
|
||||
m: 16,
|
||||
efConstruction: 200
|
||||
}
|
||||
});
|
||||
|
||||
// Insert some vectors
|
||||
console.log('Inserting vectors...');
|
||||
const vectors = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
vectors.push({
|
||||
id: `doc_${i}`,
|
||||
values: Utils.randomVector(384),
|
||||
metadata: {
|
||||
title: `Document ${i}`,
|
||||
category: i % 5 === 0 ? 'important' : 'normal'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await index.insertBatch(vectors, {
|
||||
batchSize: 100,
|
||||
progressCallback: (progress) => {
|
||||
process.stdout.write(`\rProgress: ${(progress * 100).toFixed(1)}%`);
|
||||
}
|
||||
});
|
||||
console.log('\n');
|
||||
|
||||
// Get stats
|
||||
const stats = await index.stats();
|
||||
console.log('Index stats:', {
|
||||
vectors: stats.vectorCount,
|
||||
dimension: stats.dimension,
|
||||
type: stats.indexType
|
||||
});
|
||||
console.log();
|
||||
|
||||
// Search
|
||||
console.log('Searching...');
|
||||
const query = Utils.randomVector(384);
|
||||
const results = await index.search(query, { k: 5 });
|
||||
|
||||
console.log('\nTop 5 results:');
|
||||
results.forEach((result, i) => {
|
||||
console.log(` ${i + 1}. ${result.id} (score: ${result.score.toFixed(4)})`);
|
||||
console.log(` metadata: ${JSON.stringify(result.metadata)}`);
|
||||
});
|
||||
|
||||
// Save index
|
||||
console.log('\nSaving index...');
|
||||
await index.save('my-index.bin');
|
||||
console.log('✓ Index saved to my-index.bin');
|
||||
|
||||
// Load and verify
|
||||
console.log('\nLoading index...');
|
||||
const loadedIndex = await VectorIndex.load('my-index.bin');
|
||||
const loadedStats = await loadedIndex.stats();
|
||||
console.log('✓ Index loaded, vectors:', loadedStats.vectorCount);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
/**
|
||||
* Performance benchmark example
|
||||
*/
|
||||
|
||||
const { VectorIndex, Utils, getBackendInfo } = require('ruvector');
|
||||
|
||||
function formatNumber(num) {
|
||||
return num.toLocaleString();
|
||||
}
|
||||
|
||||
function formatDuration(ms) {
|
||||
return ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms.toFixed(2)}ms`;
|
||||
}
|
||||
|
||||
async function runBenchmark(dimension, numVectors, numQueries) {
|
||||
console.log(`\n📊 Benchmark: dim=${dimension}, vectors=${formatNumber(numVectors)}, queries=${numQueries}`);
|
||||
console.log('─'.repeat(70));
|
||||
|
||||
// Create index
|
||||
const index = new VectorIndex({
|
||||
dimension,
|
||||
metric: 'cosine',
|
||||
indexType: 'hnsw',
|
||||
hnswConfig: { m: 16, efConstruction: 200 }
|
||||
});
|
||||
|
||||
// Generate vectors
|
||||
console.log('Generating vectors...');
|
||||
const vectors = Array.from({ length: numVectors }, (_, i) => ({
|
||||
id: `vec_${i}`,
|
||||
values: Utils.randomVector(dimension),
|
||||
metadata: { index: i }
|
||||
}));
|
||||
|
||||
// Benchmark insertions
|
||||
console.log('Benchmarking insertions...');
|
||||
const insertStart = performance.now();
|
||||
await index.insertBatch(vectors, { batchSize: 1000 });
|
||||
const insertDuration = performance.now() - insertStart;
|
||||
const insertThroughput = numVectors / (insertDuration / 1000);
|
||||
|
||||
console.log(` ✓ Inserted ${formatNumber(numVectors)} vectors in ${formatDuration(insertDuration)}`);
|
||||
console.log(` ✓ Throughput: ${formatNumber(Math.round(insertThroughput))} vectors/sec`);
|
||||
|
||||
// Benchmark searches
|
||||
console.log('\nBenchmarking searches...');
|
||||
const queries = Array.from({ length: numQueries }, () => Utils.randomVector(dimension));
|
||||
|
||||
const searchStart = performance.now();
|
||||
const results = await Promise.all(
|
||||
queries.map(q => index.search(q, { k: 10 }))
|
||||
);
|
||||
const searchDuration = performance.now() - searchStart;
|
||||
const searchThroughput = numQueries / (searchDuration / 1000);
|
||||
|
||||
console.log(` ✓ Executed ${numQueries} searches in ${formatDuration(searchDuration)}`);
|
||||
console.log(` ✓ Throughput: ${formatNumber(Math.round(searchThroughput))} queries/sec`);
|
||||
console.log(` ✓ Avg latency: ${formatDuration(searchDuration / numQueries)}`);
|
||||
|
||||
// Check recall (verify we get results)
|
||||
const avgResults = results.reduce((sum, r) => sum + r.length, 0) / results.length;
|
||||
console.log(` ✓ Avg results per query: ${avgResults.toFixed(2)}`);
|
||||
|
||||
// Get memory stats
|
||||
const stats = await index.stats();
|
||||
if (stats.memoryUsage) {
|
||||
const mb = (stats.memoryUsage / 1024 / 1024).toFixed(2);
|
||||
console.log(` ✓ Memory usage: ${mb} MB`);
|
||||
}
|
||||
|
||||
return {
|
||||
dimension,
|
||||
numVectors,
|
||||
insertDuration,
|
||||
insertThroughput,
|
||||
searchDuration,
|
||||
searchThroughput,
|
||||
avgLatency: searchDuration / numQueries
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('⚡ rUvector Performance Benchmark\n');
|
||||
|
||||
const info = getBackendInfo();
|
||||
console.log(`Backend: ${info.type}`);
|
||||
console.log(`Features: ${info.features.join(', ')}`);
|
||||
|
||||
// Run benchmarks with different configurations
|
||||
const configs = [
|
||||
{ dimension: 128, vectors: 1000, queries: 100 },
|
||||
{ dimension: 384, vectors: 5000, queries: 100 },
|
||||
{ dimension: 768, vectors: 10000, queries: 100 },
|
||||
{ dimension: 1536, vectors: 5000, queries: 100 }
|
||||
];
|
||||
|
||||
const results = [];
|
||||
for (const config of configs) {
|
||||
const result = await runBenchmark(config.dimension, config.vectors, config.queries);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '═'.repeat(70));
|
||||
console.log('Summary');
|
||||
console.log('═'.repeat(70));
|
||||
console.log('\nInsert Throughput:');
|
||||
results.forEach(r => {
|
||||
console.log(` dim=${r.dimension}: ${formatNumber(Math.round(r.insertThroughput))} vectors/sec`);
|
||||
});
|
||||
|
||||
console.log('\nSearch Throughput:');
|
||||
results.forEach(r => {
|
||||
console.log(` dim=${r.dimension}: ${formatNumber(Math.round(r.searchThroughput))} queries/sec`);
|
||||
});
|
||||
|
||||
console.log('\nSearch Latency:');
|
||||
results.forEach(r => {
|
||||
console.log(` dim=${r.dimension}: ${formatDuration(r.avgLatency)}`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
{
|
||||
"name": "ruvector",
|
||||
"version": "0.1.38",
|
||||
"description": "High-performance vector database with Graph Neural Networks, Cypher queries, and AI agent routing. Build RAG apps, semantic search, recommendations, and agentic AI systems. Pinecone + Neo4j + PyTorch alternative.",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"bin": {
|
||||
"ruvector": "./bin/ruvector.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"require": "./dist/index.js",
|
||||
"import": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"bin",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
||||
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepublishOnly": "echo 'Ready to publish'"
|
||||
},
|
||||
"keywords": [
|
||||
"vector-database",
|
||||
"vector-search",
|
||||
"embeddings",
|
||||
"similarity-search",
|
||||
"semantic-search",
|
||||
"rag",
|
||||
"retrieval-augmented-generation",
|
||||
"llm",
|
||||
"langchain",
|
||||
"openai",
|
||||
"gpt",
|
||||
"claude",
|
||||
"anthropic",
|
||||
"ai",
|
||||
"artificial-intelligence",
|
||||
"machine-learning",
|
||||
"deep-learning",
|
||||
"neural-network",
|
||||
"gnn",
|
||||
"graph-neural-network",
|
||||
"knowledge-graph",
|
||||
"graph-database",
|
||||
"cypher",
|
||||
"neo4j",
|
||||
"hnsw",
|
||||
"ann",
|
||||
"approximate-nearest-neighbor",
|
||||
"knn",
|
||||
"cosine-similarity",
|
||||
"recommendation-system",
|
||||
"chatbot",
|
||||
"conversational-ai",
|
||||
"agentic-ai",
|
||||
"ai-agents",
|
||||
"multi-agent",
|
||||
"agent-routing",
|
||||
"model-router",
|
||||
"llm-router",
|
||||
"rust",
|
||||
"napi",
|
||||
"napi-rs",
|
||||
"wasm",
|
||||
"webassembly",
|
||||
"compression",
|
||||
"quantization",
|
||||
"product-quantization",
|
||||
"pinecone-alternative",
|
||||
"chromadb-alternative",
|
||||
"milvus-alternative",
|
||||
"qdrant-alternative",
|
||||
"weaviate-alternative"
|
||||
],
|
||||
"author": {
|
||||
"name": "rUv",
|
||||
"url": "https://ruv.io"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/ruvnet/ruvector#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ruvnet/ruvector.git",
|
||||
"directory": "npm/ruvector"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ruvnet/ruvector/issues"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ruvnet"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^11.1.0",
|
||||
"chalk": "^4.1.2",
|
||||
"ora": "^5.4.1",
|
||||
"cli-table3": "^0.6.3",
|
||||
"inquirer": "^8.2.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@ruvector/core": "^0.1.1",
|
||||
"@ruvector/graph-node": "^0.1.0",
|
||||
"@ruvector/graph-wasm": "^0.1.0",
|
||||
"@ruvector/gnn-node": "^0.1.0",
|
||||
"@ruvector/gnn-wasm": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/inquirer": "^8.2.10",
|
||||
"typescript": "^5.3.3",
|
||||
"tsup": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
/**
|
||||
* rUvector - High-performance vector database
|
||||
*
|
||||
* Smart loader that tries native bindings first, falls back to WASM
|
||||
*/
|
||||
|
||||
import type {
|
||||
Vector,
|
||||
SearchResult,
|
||||
IndexStats,
|
||||
CreateIndexOptions,
|
||||
SearchOptions,
|
||||
BatchInsertOptions,
|
||||
BackendInfo
|
||||
} from '../types';
|
||||
|
||||
let backend: any;
|
||||
let backendType: 'native' | 'wasm' = 'wasm';
|
||||
|
||||
/**
|
||||
* Try to load the native backend first, fall back to WASM
|
||||
*/
|
||||
function loadBackend() {
|
||||
if (backend) {
|
||||
return backend;
|
||||
}
|
||||
|
||||
// Try native bindings first
|
||||
try {
|
||||
backend = require('@ruvector/core');
|
||||
backendType = 'native';
|
||||
console.log('✓ Loaded native rUvector bindings');
|
||||
return backend;
|
||||
} catch (e) {
|
||||
// Native not available, try WASM
|
||||
try {
|
||||
backend = require('@ruvector/wasm');
|
||||
backendType = 'wasm';
|
||||
console.warn('⚠ Native bindings not available, using WASM fallback');
|
||||
console.warn(' For better performance, install @ruvector/core');
|
||||
return backend;
|
||||
} catch (wasmError) {
|
||||
throw new Error(
|
||||
'Failed to load rUvector backend. Please ensure either @ruvector/core or @ruvector/wasm is installed.\n' +
|
||||
`Native error: ${e}\n` +
|
||||
`WASM error: ${wasmError}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* VectorIndex class that wraps the backend
|
||||
*/
|
||||
export class VectorIndex {
|
||||
private index: any;
|
||||
|
||||
constructor(options: CreateIndexOptions) {
|
||||
const backend = loadBackend();
|
||||
this.index = new backend.VectorIndex(options);
|
||||
}
|
||||
|
||||
async insert(vector: Vector): Promise<void> {
|
||||
return this.index.insert(vector);
|
||||
}
|
||||
|
||||
async insertBatch(vectors: Vector[], options?: BatchInsertOptions): Promise<void> {
|
||||
if (this.index.insertBatch) {
|
||||
return this.index.insertBatch(vectors, options);
|
||||
}
|
||||
|
||||
// Fallback for backends without batch support
|
||||
const batchSize = options?.batchSize || 1000;
|
||||
const total = vectors.length;
|
||||
|
||||
for (let i = 0; i < total; i += batchSize) {
|
||||
const batch = vectors.slice(i, Math.min(i + batchSize, total));
|
||||
await Promise.all(batch.map(v => this.insert(v)));
|
||||
|
||||
if (options?.progressCallback) {
|
||||
options.progressCallback(Math.min(i + batchSize, total) / total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async search(query: number[], options?: SearchOptions): Promise<SearchResult[]> {
|
||||
return this.index.search(query, options);
|
||||
}
|
||||
|
||||
async get(id: string): Promise<Vector | null> {
|
||||
return this.index.get(id);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<boolean> {
|
||||
return this.index.delete(id);
|
||||
}
|
||||
|
||||
async stats(): Promise<IndexStats> {
|
||||
return this.index.stats();
|
||||
}
|
||||
|
||||
async save(path: string): Promise<void> {
|
||||
return this.index.save(path);
|
||||
}
|
||||
|
||||
static async load(path: string): Promise<VectorIndex> {
|
||||
const backend = loadBackend();
|
||||
const index = await backend.VectorIndex.load(path);
|
||||
const wrapper = Object.create(VectorIndex.prototype);
|
||||
wrapper.index = index;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
return this.index.clear();
|
||||
}
|
||||
|
||||
async optimize(): Promise<void> {
|
||||
if (this.index.optimize) {
|
||||
return this.index.optimize();
|
||||
}
|
||||
// No-op for backends without optimization
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions
|
||||
*/
|
||||
export const Utils = {
|
||||
cosineSimilarity(a: number[], b: number[]): number {
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Vectors must have the same dimension');
|
||||
}
|
||||
|
||||
let dotProduct = 0;
|
||||
let normA = 0;
|
||||
let normB = 0;
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
dotProduct += a[i] * b[i];
|
||||
normA += a[i] * a[i];
|
||||
normB += b[i] * b[i];
|
||||
}
|
||||
|
||||
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
||||
},
|
||||
|
||||
euclideanDistance(a: number[], b: number[]): number {
|
||||
if (a.length !== b.length) {
|
||||
throw new Error('Vectors must have the same dimension');
|
||||
}
|
||||
|
||||
let sum = 0;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const diff = a[i] - b[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
|
||||
return Math.sqrt(sum);
|
||||
},
|
||||
|
||||
normalize(vector: number[]): number[] {
|
||||
const norm = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
|
||||
return vector.map(val => val / norm);
|
||||
},
|
||||
|
||||
randomVector(dimension: number): number[] {
|
||||
const vector = new Array(dimension);
|
||||
for (let i = 0; i < dimension; i++) {
|
||||
vector[i] = Math.random() * 2 - 1;
|
||||
}
|
||||
return this.normalize(vector);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get backend information
|
||||
*/
|
||||
export function getBackendInfo(): BackendInfo {
|
||||
loadBackend();
|
||||
|
||||
const features: string[] = [];
|
||||
|
||||
if (backendType === 'native') {
|
||||
features.push('SIMD', 'Multi-threading', 'Memory-mapped I/O');
|
||||
} else {
|
||||
features.push('Browser-compatible', 'No native dependencies');
|
||||
}
|
||||
|
||||
return {
|
||||
type: backendType,
|
||||
version: require('../package.json').version,
|
||||
features
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if native bindings are available
|
||||
*/
|
||||
export function isNativeAvailable(): boolean {
|
||||
try {
|
||||
require.resolve('@ruvector/core');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Default export
|
||||
export default VectorIndex;
|
||||
|
||||
// Re-export types
|
||||
export type {
|
||||
Vector,
|
||||
SearchResult,
|
||||
IndexStats,
|
||||
CreateIndexOptions,
|
||||
SearchOptions,
|
||||
BatchInsertOptions,
|
||||
BackendInfo
|
||||
};
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
/**
|
||||
* Basic test of ruvector package with mock backend
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const Module = require('module');
|
||||
|
||||
// Mock require to return our mock backend
|
||||
const originalRequire = Module.prototype.require;
|
||||
const mockBackend = require('./test-mock-backend.js');
|
||||
|
||||
Module.prototype.require = function(id) {
|
||||
if (id === '@ruvector/core' || id === '@ruvector/wasm') {
|
||||
return mockBackend;
|
||||
}
|
||||
return originalRequire.apply(this, arguments);
|
||||
};
|
||||
|
||||
const { VectorIndex, Utils, getBackendInfo, isNativeAvailable } = require('./dist/index.js');
|
||||
|
||||
async function testBasicOperations() {
|
||||
console.log('🧪 Testing Basic Operations\n');
|
||||
|
||||
try {
|
||||
// Test backend info
|
||||
console.log('1. Backend Info:');
|
||||
const info = getBackendInfo();
|
||||
console.log(` Type: ${info.type}`);
|
||||
console.log(` Version: ${info.version}`);
|
||||
console.log(` Native Available: ${isNativeAvailable()}`);
|
||||
console.log(' ✓ Backend info works\n');
|
||||
|
||||
// Test index creation
|
||||
console.log('2. Creating Index:');
|
||||
const index = new VectorIndex({
|
||||
dimension: 128,
|
||||
metric: 'cosine',
|
||||
indexType: 'hnsw'
|
||||
});
|
||||
console.log(' ✓ Index created\n');
|
||||
|
||||
// Test single insert
|
||||
console.log('3. Single Insert:');
|
||||
await index.insert({
|
||||
id: 'vec1',
|
||||
values: Utils.randomVector(128),
|
||||
metadata: { test: true }
|
||||
});
|
||||
console.log(' ✓ Vector inserted\n');
|
||||
|
||||
// Test batch insert
|
||||
console.log('4. Batch Insert:');
|
||||
const vectors = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
vectors.push({
|
||||
id: `vec${i + 2}`,
|
||||
values: Utils.randomVector(128),
|
||||
metadata: { index: i }
|
||||
});
|
||||
}
|
||||
await index.insertBatch(vectors, { batchSize: 10 });
|
||||
console.log(' ✓ Batch inserted\n');
|
||||
|
||||
// Test stats
|
||||
console.log('5. Stats:');
|
||||
const stats = await index.stats();
|
||||
console.log(` Vectors: ${stats.vectorCount}`);
|
||||
console.log(` Dimension: ${stats.dimension}`);
|
||||
console.log(` Type: ${stats.indexType}`);
|
||||
console.log(' ✓ Stats retrieved\n');
|
||||
|
||||
// Test search
|
||||
console.log('6. Search:');
|
||||
const query = Utils.randomVector(128);
|
||||
const results = await index.search(query, { k: 5 });
|
||||
console.log(` Found ${results.length} results`);
|
||||
results.slice(0, 3).forEach((r, i) => {
|
||||
console.log(` ${i + 1}. ${r.id} (score: ${r.score.toFixed(4)})`);
|
||||
});
|
||||
console.log(' ✓ Search works\n');
|
||||
|
||||
// Test get
|
||||
console.log('7. Get by ID:');
|
||||
const retrieved = await index.get('vec1');
|
||||
console.log(` Retrieved: ${retrieved ? retrieved.id : 'null'}`);
|
||||
console.log(' ✓ Get works\n');
|
||||
|
||||
// Test delete
|
||||
console.log('8. Delete:');
|
||||
const deleted = await index.delete('vec1');
|
||||
console.log(` Deleted: ${deleted}`);
|
||||
const statsAfter = await index.stats();
|
||||
console.log(` Vectors remaining: ${statsAfter.vectorCount}`);
|
||||
console.log(' ✓ Delete works\n');
|
||||
|
||||
// Test utilities
|
||||
console.log('9. Utilities:');
|
||||
const v1 = Utils.randomVector(128);
|
||||
const v2 = Utils.randomVector(128);
|
||||
const similarity = Utils.cosineSimilarity(v1, v2);
|
||||
const distance = Utils.euclideanDistance(v1, v2);
|
||||
const normalized = Utils.normalize(v1);
|
||||
console.log(` Cosine similarity: ${similarity.toFixed(4)}`);
|
||||
console.log(` Euclidean distance: ${distance.toFixed(4)}`);
|
||||
console.log(` Normalized length: ${Math.sqrt(normalized.reduce((s, v) => s + v * v, 0)).toFixed(4)}`);
|
||||
console.log(' ✓ Utilities work\n');
|
||||
|
||||
console.log('✅ All tests passed!');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Test failed:', error.message);
|
||||
console.error(error.stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
testBasicOperations().then(success => {
|
||||
process.exit(success ? 0 : 1);
|
||||
});
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
/**
|
||||
* Test CLI commands with mock backend
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const Module = require('module');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
// Mock require
|
||||
const originalRequire = Module.prototype.require;
|
||||
const mockBackend = require('./test-mock-backend.js');
|
||||
|
||||
Module.prototype.require = function(id) {
|
||||
if (id === '@ruvector/core' || id === '@ruvector/wasm') {
|
||||
return mockBackend;
|
||||
}
|
||||
return originalRequire.apply(this, arguments);
|
||||
};
|
||||
|
||||
async function testCLI() {
|
||||
console.log('🧪 Testing CLI Commands\n');
|
||||
|
||||
try {
|
||||
// Test 1: Info command
|
||||
console.log('1. Testing info command:');
|
||||
const { getBackendInfo } = require('./dist/index.js');
|
||||
const info = getBackendInfo();
|
||||
console.log(` ✓ Backend: ${info.type}`);
|
||||
console.log(` ✓ Version: ${info.version}\n`);
|
||||
|
||||
// Test 2: Create test vectors file
|
||||
console.log('2. Creating test vectors file:');
|
||||
const testVectors = [];
|
||||
const { Utils } = require('./dist/index.js');
|
||||
for (let i = 0; i < 50; i++) {
|
||||
testVectors.push({
|
||||
id: `test_${i}`,
|
||||
values: Utils.randomVector(128),
|
||||
metadata: { index: i, category: i % 3 === 0 ? 'A' : 'B' }
|
||||
});
|
||||
}
|
||||
await fs.writeFile('/tmp/test-vectors.json', JSON.stringify(testVectors, null, 2));
|
||||
console.log(` ✓ Created /tmp/test-vectors.json with ${testVectors.length} vectors\n`);
|
||||
|
||||
// Test 3: Index initialization
|
||||
console.log('3. Testing index operations:');
|
||||
const { VectorIndex } = require('./dist/index.js');
|
||||
const index = new VectorIndex({
|
||||
dimension: 128,
|
||||
metric: 'cosine',
|
||||
indexType: 'hnsw'
|
||||
});
|
||||
console.log(' ✓ Index created\n');
|
||||
|
||||
// Test 4: Insert vectors
|
||||
console.log('4. Testing insertBatch:');
|
||||
const startInsert = Date.now();
|
||||
await index.insertBatch(testVectors, {
|
||||
batchSize: 10,
|
||||
progressCallback: (p) => {
|
||||
if (p === 1) console.log(` Progress: 100%`);
|
||||
}
|
||||
});
|
||||
const insertTime = Date.now() - startInsert;
|
||||
console.log(` ✓ Inserted ${testVectors.length} vectors in ${insertTime}ms\n`);
|
||||
|
||||
// Test 5: Search
|
||||
console.log('5. Testing search:');
|
||||
const query = Utils.randomVector(128);
|
||||
const startSearch = Date.now();
|
||||
const results = await index.search(query, { k: 5 });
|
||||
const searchTime = Date.now() - startSearch;
|
||||
console.log(` ✓ Found ${results.length} results in ${searchTime}ms`);
|
||||
results.slice(0, 3).forEach((r, i) => {
|
||||
console.log(` ${i + 1}. ${r.id} (score: ${r.score.toFixed(4)})`);
|
||||
});
|
||||
console.log();
|
||||
|
||||
// Test 6: Stats
|
||||
console.log('6. Testing stats:');
|
||||
const stats = await index.stats();
|
||||
console.log(` ✓ Vectors: ${stats.vectorCount}`);
|
||||
console.log(` ✓ Dimension: ${stats.dimension}`);
|
||||
console.log(` ✓ Type: ${stats.indexType}`);
|
||||
console.log(` ✓ Memory: ${(stats.memoryUsage / 1024).toFixed(2)} KB\n`);
|
||||
|
||||
// Test 7: Save/Load
|
||||
console.log('7. Testing save/load:');
|
||||
await index.save('/tmp/test-index.bin');
|
||||
console.log(' ✓ Saved index');
|
||||
const loaded = await VectorIndex.load('/tmp/test-index.bin');
|
||||
console.log(' ✓ Loaded index\n');
|
||||
|
||||
// Test 8: Performance
|
||||
console.log('8. Performance summary:');
|
||||
const insertThroughput = testVectors.length / (insertTime / 1000);
|
||||
const searchLatency = searchTime;
|
||||
console.log(` Insert throughput: ${insertThroughput.toFixed(0)} vectors/sec`);
|
||||
console.log(` Search latency: ${searchLatency.toFixed(2)}ms`);
|
||||
console.log();
|
||||
|
||||
console.log('✅ All CLI tests passed!');
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ CLI test failed:', error.message);
|
||||
console.error(error.stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
testCLI().then(success => {
|
||||
process.exit(success ? 0 : 1);
|
||||
});
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/**
|
||||
* Mock backend for testing the main ruvector package
|
||||
* Simulates both native and WASM backends
|
||||
*/
|
||||
|
||||
class MockVectorIndex {
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
this.vectors = new Map();
|
||||
this._stats = {
|
||||
vectorCount: 0,
|
||||
dimension: options.dimension,
|
||||
indexType: options.indexType || 'hnsw',
|
||||
memoryUsage: 0
|
||||
};
|
||||
}
|
||||
|
||||
async insert(vector) {
|
||||
if (vector.values.length !== this.options.dimension) {
|
||||
throw new Error(`Vector dimension mismatch: expected ${this.options.dimension}, got ${vector.values.length}`);
|
||||
}
|
||||
this.vectors.set(vector.id, vector);
|
||||
this._stats.vectorCount = this.vectors.size;
|
||||
this._stats.memoryUsage = this.vectors.size * this.options.dimension * 4; // Rough estimate
|
||||
}
|
||||
|
||||
async insertBatch(vectors, options = {}) {
|
||||
const batchSize = options.batchSize || 1000;
|
||||
const total = vectors.length;
|
||||
|
||||
for (let i = 0; i < total; i += batchSize) {
|
||||
const batch = vectors.slice(i, Math.min(i + batchSize, total));
|
||||
await Promise.all(batch.map(v => this.insert(v)));
|
||||
|
||||
if (options.progressCallback) {
|
||||
options.progressCallback(Math.min(i + batchSize, total) / total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async search(query, options = {}) {
|
||||
const k = options.k || 10;
|
||||
const results = [];
|
||||
|
||||
// Simple cosine similarity
|
||||
for (const [id, vector] of this.vectors.entries()) {
|
||||
const score = this._cosineSimilarity(query, vector.values);
|
||||
results.push({ id, score, metadata: vector.metadata });
|
||||
}
|
||||
|
||||
// Sort by score descending and return top k
|
||||
results.sort((a, b) => b.score - a.score);
|
||||
return results.slice(0, k);
|
||||
}
|
||||
|
||||
_cosineSimilarity(a, b) {
|
||||
let dotProduct = 0;
|
||||
let normA = 0;
|
||||
let normB = 0;
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
dotProduct += a[i] * b[i];
|
||||
normA += a[i] * a[i];
|
||||
normB += b[i] * b[i];
|
||||
}
|
||||
|
||||
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
||||
}
|
||||
|
||||
async get(id) {
|
||||
return this.vectors.get(id) || null;
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
const result = this.vectors.delete(id);
|
||||
if (result) {
|
||||
this._stats.vectorCount = this.vectors.size;
|
||||
this._stats.memoryUsage = this.vectors.size * this.options.dimension * 4;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
stats() {
|
||||
return { ...this._stats };
|
||||
}
|
||||
|
||||
async save(path) {
|
||||
// Mock save - just log
|
||||
console.log(`Mock: Saving index to ${path}`);
|
||||
}
|
||||
|
||||
static async load(path) {
|
||||
// Mock load - create empty index
|
||||
console.log(`Mock: Loading index from ${path}`);
|
||||
return new MockVectorIndex({ dimension: 384, indexType: 'hnsw' });
|
||||
}
|
||||
|
||||
async clear() {
|
||||
this.vectors.clear();
|
||||
this._stats.vectorCount = 0;
|
||||
this._stats.memoryUsage = 0;
|
||||
}
|
||||
|
||||
async optimize() {
|
||||
// Mock optimize
|
||||
console.log('Mock: Optimizing index');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { VectorIndex: MockVectorIndex };
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
153
npm/ruvector/types/index.d.ts
vendored
153
npm/ruvector/types/index.d.ts
vendored
|
|
@ -1,153 +0,0 @@
|
|||
/**
|
||||
* Vector database types compatible with both NAPI and WASM backends
|
||||
*/
|
||||
|
||||
export interface Vector {
|
||||
id: string;
|
||||
values: number[];
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
id: string;
|
||||
score: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface IndexStats {
|
||||
vectorCount: number;
|
||||
dimension: number;
|
||||
indexType: string;
|
||||
memoryUsage?: number;
|
||||
}
|
||||
|
||||
export interface CreateIndexOptions {
|
||||
dimension: number;
|
||||
metric?: 'cosine' | 'euclidean' | 'dot';
|
||||
indexType?: 'flat' | 'hnsw';
|
||||
hnswConfig?: {
|
||||
m?: number;
|
||||
efConstruction?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
k?: number;
|
||||
ef?: number;
|
||||
filter?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface BatchInsertOptions {
|
||||
batchSize?: number;
|
||||
progressCallback?: (progress: number) => void;
|
||||
}
|
||||
|
||||
export interface BenchmarkResult {
|
||||
operation: string;
|
||||
duration: number;
|
||||
throughput?: number;
|
||||
memoryUsage?: number;
|
||||
}
|
||||
|
||||
export class VectorIndex {
|
||||
constructor(options: CreateIndexOptions);
|
||||
|
||||
/**
|
||||
* Insert a single vector into the index
|
||||
*/
|
||||
insert(vector: Vector): Promise<void>;
|
||||
|
||||
/**
|
||||
* Insert multiple vectors in batches
|
||||
*/
|
||||
insertBatch(vectors: Vector[], options?: BatchInsertOptions): Promise<void>;
|
||||
|
||||
/**
|
||||
* Search for k nearest neighbors
|
||||
*/
|
||||
search(query: number[], options?: SearchOptions): Promise<SearchResult[]>;
|
||||
|
||||
/**
|
||||
* Get vector by ID
|
||||
*/
|
||||
get(id: string): Promise<Vector | null>;
|
||||
|
||||
/**
|
||||
* Delete vector by ID
|
||||
*/
|
||||
delete(id: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Get index statistics
|
||||
*/
|
||||
stats(): Promise<IndexStats>;
|
||||
|
||||
/**
|
||||
* Save index to file
|
||||
*/
|
||||
save(path: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Load index from file
|
||||
*/
|
||||
static load(path: string): Promise<VectorIndex>;
|
||||
|
||||
/**
|
||||
* Clear all vectors from index
|
||||
*/
|
||||
clear(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Optimize index (rebuild HNSW, etc.)
|
||||
*/
|
||||
optimize(): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backend information
|
||||
*/
|
||||
export interface BackendInfo {
|
||||
type: 'native' | 'wasm';
|
||||
version: string;
|
||||
features: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the active backend
|
||||
*/
|
||||
export function getBackendInfo(): BackendInfo;
|
||||
|
||||
/**
|
||||
* Check if native bindings are available
|
||||
*/
|
||||
export function isNativeAvailable(): boolean;
|
||||
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
export namespace Utils {
|
||||
/**
|
||||
* Calculate cosine similarity between two vectors
|
||||
*/
|
||||
export function cosineSimilarity(a: number[], b: number[]): number;
|
||||
|
||||
/**
|
||||
* Calculate euclidean distance between two vectors
|
||||
*/
|
||||
export function euclideanDistance(a: number[], b: number[]): number;
|
||||
|
||||
/**
|
||||
* Normalize a vector
|
||||
*/
|
||||
export function normalize(vector: number[]): number[];
|
||||
|
||||
/**
|
||||
* Generate random vector for testing
|
||||
*/
|
||||
export function randomVector(dimension: number): number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default exports
|
||||
*/
|
||||
export { VectorIndex as default };
|
||||
Loading…
Add table
Add a link
Reference in a new issue