Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
34
crates/ruvector-tiny-dancer-wasm/Cargo.toml
Normal file
34
crates/ruvector-tiny-dancer-wasm/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "ruvector-tiny-dancer-wasm"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "README.md"
|
||||
description = "WASM bindings for Tiny Dancer neural routing"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
ruvector-tiny-dancer-core = { version = "2.0", path = "../ruvector-tiny-dancer-core" }
|
||||
|
||||
# WASM dependencies
|
||||
wasm-bindgen = { workspace = true }
|
||||
wasm-bindgen-futures = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
web-sys = { version = "0.3", features = ["console"] }
|
||||
|
||||
# Workspace dependencies
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
246
crates/ruvector-tiny-dancer-wasm/README.md
Normal file
246
crates/ruvector-tiny-dancer-wasm/README.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Ruvector Tiny Dancer WASM
|
||||
|
||||
[](https://www.npmjs.com/package/@ruvector/tiny-dancer)
|
||||
[](https://crates.io/crates/ruvector-tiny-dancer-wasm)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
**WebAssembly bindings for Tiny Dancer neural routing.**
|
||||
|
||||
`ruvector-tiny-dancer-wasm` brings production-grade AI agent routing to the browser with WebAssembly. Run FastGRNN neural inference for intelligent request routing directly in client-side applications. Part of the [Ruvector](https://github.com/ruvnet/ruvector) ecosystem.
|
||||
|
||||
## Why Tiny Dancer WASM?
|
||||
|
||||
- **Browser Native**: Run neural routing in any browser
|
||||
- **Low Latency**: Sub-millisecond inference times
|
||||
- **Small Bundle**: Optimized WASM binary (~100KB gzipped)
|
||||
- **Offline Capable**: No server required for inference
|
||||
- **Privacy First**: Route decisions stay client-side
|
||||
|
||||
## Features
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
- **Neural Inference**: FastGRNN model execution
|
||||
- **Feature Engineering**: Request feature extraction
|
||||
- **Multi-Agent Routing**: Score and rank agent candidates
|
||||
- **Model Loading**: Load pre-trained models
|
||||
- **Batch Inference**: Process multiple requests
|
||||
|
||||
### Advanced Features
|
||||
|
||||
- **Web Workers**: Background inference threads
|
||||
- **Streaming**: Process streaming requests
|
||||
- **Model Caching**: IndexedDB model persistence
|
||||
- **Quantization**: INT8 models for smaller size
|
||||
- **SIMD**: Hardware acceleration when available
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @ruvector/tiny-dancer-wasm
|
||||
# or
|
||||
yarn add @ruvector/tiny-dancer-wasm
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import init, { TinyDancer, RouteRequest } from '@ruvector/tiny-dancer-wasm';
|
||||
|
||||
// Initialize WASM module
|
||||
await init();
|
||||
|
||||
// Create router instance
|
||||
const router = new TinyDancer();
|
||||
|
||||
// Load pre-trained model
|
||||
await router.loadModel('/models/router-v1.bin');
|
||||
|
||||
// Create routing request
|
||||
const request: RouteRequest = {
|
||||
query: "What is the weather like today?",
|
||||
context: {
|
||||
userId: "user-123",
|
||||
sessionLength: 5,
|
||||
previousAgent: "general",
|
||||
},
|
||||
agents: ["weather", "general", "calendar", "search"],
|
||||
};
|
||||
|
||||
// Get routing decision
|
||||
const result = await router.route(request);
|
||||
console.log(`Route to: ${result.agent} (confidence: ${result.confidence})`);
|
||||
```
|
||||
|
||||
### With Web Workers
|
||||
|
||||
```typescript
|
||||
import { TinyDancerWorker } from '@ruvector/tiny-dancer-wasm/worker';
|
||||
|
||||
// Create worker-based router (non-blocking)
|
||||
const router = new TinyDancerWorker();
|
||||
|
||||
// Initialize in background
|
||||
await router.init();
|
||||
await router.loadModel('/models/router-v1.bin');
|
||||
|
||||
// Route without blocking main thread
|
||||
const result = await router.route(request);
|
||||
```
|
||||
|
||||
### Feature Engineering
|
||||
|
||||
```typescript
|
||||
import { FeatureExtractor } from '@ruvector/tiny-dancer-wasm';
|
||||
|
||||
const extractor = new FeatureExtractor();
|
||||
|
||||
// Extract features from request
|
||||
const features = extractor.extract({
|
||||
query: "Book a flight to Paris",
|
||||
tokens: 6,
|
||||
language: "en",
|
||||
sentiment: 0.7,
|
||||
entities: ["Paris"],
|
||||
});
|
||||
|
||||
console.log(`Feature vector: ${features.length} dimensions`);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### TinyDancer Class
|
||||
|
||||
```typescript
|
||||
class TinyDancer {
|
||||
constructor();
|
||||
|
||||
// Model management
|
||||
loadModel(url: string): Promise<void>;
|
||||
loadModelFromBuffer(buffer: Uint8Array): void;
|
||||
|
||||
// Routing
|
||||
route(request: RouteRequest): Promise<RouteResult>;
|
||||
routeBatch(requests: RouteRequest[]): Promise<RouteResult[]>;
|
||||
|
||||
// Scoring
|
||||
scoreAgents(request: RouteRequest): Promise<AgentScore[]>;
|
||||
|
||||
// Info
|
||||
getModelInfo(): ModelInfo;
|
||||
isReady(): boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
```typescript
|
||||
interface RouteRequest {
|
||||
query: string;
|
||||
context?: Record<string, any>;
|
||||
agents: string[];
|
||||
constraints?: RouteConstraints;
|
||||
}
|
||||
|
||||
interface RouteResult {
|
||||
agent: string;
|
||||
confidence: number;
|
||||
scores: Record<string, number>;
|
||||
latencyMs: number;
|
||||
}
|
||||
|
||||
interface AgentScore {
|
||||
agent: string;
|
||||
score: number;
|
||||
features: number[];
|
||||
}
|
||||
|
||||
interface RouteConstraints {
|
||||
excludeAgents?: string[];
|
||||
minConfidence?: number;
|
||||
timeout?: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Bundle Optimization
|
||||
|
||||
### Tree Shaking
|
||||
|
||||
```typescript
|
||||
// Import only what you need
|
||||
import { TinyDancer } from '@ruvector/tiny-dancer-wasm/core';
|
||||
import { FeatureExtractor } from '@ruvector/tiny-dancer-wasm/features';
|
||||
```
|
||||
|
||||
### CDN Usage
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import init, { TinyDancer } from 'https://unpkg.com/@ruvector/tiny-dancer-wasm';
|
||||
|
||||
await init();
|
||||
const router = new TinyDancer();
|
||||
</script>
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Benchmarks (Chrome 120, M1 Mac)
|
||||
|
||||
```
|
||||
Operation Latency (p50)
|
||||
────────────────────────────────
|
||||
Model load ~50ms
|
||||
Single inference ~0.5ms
|
||||
Batch (10) ~2ms
|
||||
Feature extraction ~0.1ms
|
||||
```
|
||||
|
||||
### Bundle Size
|
||||
|
||||
```
|
||||
Format Size
|
||||
────────────────────────
|
||||
WASM binary ~100KB gzipped
|
||||
JS glue ~5KB gzipped
|
||||
Total ~105KB gzipped
|
||||
```
|
||||
|
||||
## Browser Support
|
||||
|
||||
| Browser | Version | SIMD |
|
||||
|---------|---------|------|
|
||||
| Chrome | 89+ | ✅ |
|
||||
| Firefox | 89+ | ✅ |
|
||||
| Safari | 15+ | ✅ |
|
||||
| Edge | 89+ | ✅ |
|
||||
|
||||
## Related Packages
|
||||
|
||||
- **[ruvector-tiny-dancer-core](../ruvector-tiny-dancer-core/)** - Core Rust implementation
|
||||
- **[ruvector-tiny-dancer-node](../ruvector-tiny-dancer-node/)** - Node.js bindings
|
||||
- **[ruvector-core](../ruvector-core/)** - Core vector database
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[Main README](../../README.md)** - Complete project overview
|
||||
- **[API Documentation](https://docs.rs/ruvector-tiny-dancer-wasm)** - Full API reference
|
||||
- **[GitHub Repository](https://github.com/ruvnet/ruvector)** - Source code
|
||||
|
||||
## License
|
||||
|
||||
**MIT License** - see [LICENSE](../../LICENSE) for details.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**Part of [Ruvector](https://github.com/ruvnet/ruvector) - Built by [rUv](https://ruv.io)**
|
||||
|
||||
[](https://github.com/ruvnet/ruvector)
|
||||
|
||||
[Documentation](https://docs.rs/ruvector-tiny-dancer-wasm) | [npm](https://www.npmjs.com/package/@ruvector/tiny-dancer) | [GitHub](https://github.com/ruvnet/ruvector)
|
||||
|
||||
</div>
|
||||
35
crates/ruvector-tiny-dancer-wasm/package.json
Normal file
35
crates/ruvector-tiny-dancer-wasm/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@ruvector/tiny-dancer-wasm",
|
||||
"version": "0.1.0",
|
||||
"description": "WebAssembly bindings for Tiny Dancer - FastGRNN neural inference for AI routing in browsers",
|
||||
"main": "pkg/ruvector_tiny_dancer_wasm.js",
|
||||
"types": "pkg/ruvector_tiny_dancer_wasm.d.ts",
|
||||
"files": [
|
||||
"pkg/"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "wasm-pack build --target web --out-dir pkg",
|
||||
"build:node": "wasm-pack build --target nodejs --out-dir pkg-node",
|
||||
"build:bundler": "wasm-pack build --target bundler --out-dir pkg-bundler",
|
||||
"test": "wasm-pack test --headless --firefox"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ruvnet/ruvector"
|
||||
},
|
||||
"keywords": [
|
||||
"wasm",
|
||||
"webassembly",
|
||||
"ai-routing",
|
||||
"fastgrnn",
|
||||
"neural-network",
|
||||
"inference",
|
||||
"browser"
|
||||
],
|
||||
"author": "rUv",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ruvnet/ruvector/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ruvnet/ruvector"
|
||||
}
|
||||
274
crates/ruvector-tiny-dancer-wasm/src/lib.rs
Normal file
274
crates/ruvector-tiny-dancer-wasm/src/lib.rs
Normal file
@@ -0,0 +1,274 @@
|
||||
//! WASM bindings for Tiny Dancer neural routing
|
||||
|
||||
use ruvector_tiny_dancer_core::{
|
||||
types::{
|
||||
Candidate as CoreCandidate, RouterConfig as CoreRouterConfig,
|
||||
RoutingRequest as CoreRoutingRequest, RoutingResponse as CoreRoutingResponse,
|
||||
},
|
||||
Router as CoreRouter,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Initialize panic hook for better error messages in WASM
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn init() {
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
/// Router configuration for WASM
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct RouterConfig {
|
||||
model_path: String,
|
||||
confidence_threshold: f32,
|
||||
max_uncertainty: f32,
|
||||
enable_circuit_breaker: bool,
|
||||
circuit_breaker_threshold: u32,
|
||||
enable_quantization: bool,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RouterConfig {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
model_path: "./models/fastgrnn.safetensors".to_string(),
|
||||
confidence_threshold: 0.85,
|
||||
max_uncertainty: 0.15,
|
||||
enable_circuit_breaker: true,
|
||||
circuit_breaker_threshold: 5,
|
||||
enable_quantization: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_model_path(&mut self, path: String) {
|
||||
self.model_path = path;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_confidence_threshold(&mut self, threshold: f32) {
|
||||
self.confidence_threshold = threshold;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_max_uncertainty(&mut self, uncertainty: f32) {
|
||||
self.max_uncertainty = uncertainty;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RouterConfig> for CoreRouterConfig {
|
||||
fn from(config: RouterConfig) -> Self {
|
||||
CoreRouterConfig {
|
||||
model_path: config.model_path,
|
||||
confidence_threshold: config.confidence_threshold,
|
||||
max_uncertainty: config.max_uncertainty,
|
||||
enable_circuit_breaker: config.enable_circuit_breaker,
|
||||
circuit_breaker_threshold: config.circuit_breaker_threshold,
|
||||
enable_quantization: config.enable_quantization,
|
||||
database_path: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Candidate for routing
|
||||
#[wasm_bindgen]
|
||||
pub struct Candidate {
|
||||
id: String,
|
||||
embedding: Vec<f32>,
|
||||
metadata: String,
|
||||
created_at: i64,
|
||||
access_count: u64,
|
||||
success_rate: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Candidate {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
id: String,
|
||||
embedding: Vec<f32>,
|
||||
metadata: String,
|
||||
created_at: i64,
|
||||
access_count: u64,
|
||||
success_rate: f32,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
embedding,
|
||||
metadata,
|
||||
created_at,
|
||||
access_count,
|
||||
success_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Candidate> for CoreCandidate {
|
||||
type Error = JsValue;
|
||||
|
||||
fn try_from(candidate: Candidate) -> Result<Self, Self::Error> {
|
||||
let metadata: HashMap<String, serde_json::Value> =
|
||||
serde_json::from_str(&candidate.metadata)
|
||||
.map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
|
||||
|
||||
Ok(CoreCandidate {
|
||||
id: candidate.id,
|
||||
embedding: candidate.embedding,
|
||||
metadata,
|
||||
created_at: candidate.created_at,
|
||||
access_count: candidate.access_count,
|
||||
success_rate: candidate.success_rate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Routing request
|
||||
#[wasm_bindgen]
|
||||
pub struct RoutingRequest {
|
||||
query_embedding: Vec<f32>,
|
||||
candidates: Vec<Candidate>,
|
||||
metadata: Option<String>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoutingRequest {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(query_embedding: Vec<f32>, candidates: Vec<Candidate>) -> Self {
|
||||
Self {
|
||||
query_embedding,
|
||||
candidates,
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(setter)]
|
||||
pub fn set_metadata(&mut self, metadata: String) {
|
||||
self.metadata = Some(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RoutingRequest> for CoreRoutingRequest {
|
||||
type Error = JsValue;
|
||||
|
||||
fn try_from(request: RoutingRequest) -> Result<Self, Self::Error> {
|
||||
let candidates: Result<Vec<CoreCandidate>, JsValue> = request
|
||||
.candidates
|
||||
.into_iter()
|
||||
.map(|c| c.try_into())
|
||||
.collect();
|
||||
|
||||
let metadata = if let Some(meta_str) = request.metadata {
|
||||
Some(
|
||||
serde_json::from_str(&meta_str)
|
||||
.map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(CoreRoutingRequest {
|
||||
query_embedding: request.query_embedding,
|
||||
candidates: candidates?,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Routing response
|
||||
#[wasm_bindgen]
|
||||
pub struct RoutingResponse {
|
||||
decisions_json: String,
|
||||
inference_time_us: u64,
|
||||
candidates_processed: usize,
|
||||
feature_time_us: u64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RoutingResponse {
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn decisions_json(&self) -> String {
|
||||
self.decisions_json.clone()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn inference_time_us(&self) -> u64 {
|
||||
self.inference_time_us
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn candidates_processed(&self) -> usize {
|
||||
self.candidates_processed
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn feature_time_us(&self) -> u64 {
|
||||
self.feature_time_us
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreRoutingResponse> for RoutingResponse {
|
||||
fn from(response: CoreRoutingResponse) -> Self {
|
||||
let decisions_json = serde_json::to_string(&response.decisions).unwrap_or_default();
|
||||
|
||||
Self {
|
||||
decisions_json,
|
||||
inference_time_us: response.inference_time_us,
|
||||
candidates_processed: response.candidates_processed,
|
||||
feature_time_us: response.feature_time_us,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tiny Dancer router for WASM
|
||||
#[wasm_bindgen]
|
||||
pub struct Router {
|
||||
inner: CoreRouter,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Router {
|
||||
/// Create a new router with configuration
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(config: RouterConfig) -> Result<Router, JsValue> {
|
||||
let core_config: CoreRouterConfig = config.into();
|
||||
let router = CoreRouter::new(core_config)
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to create router: {}", e)))?;
|
||||
|
||||
Ok(Router { inner: router })
|
||||
}
|
||||
|
||||
/// Route a request
|
||||
pub fn route(&self, request: RoutingRequest) -> Result<RoutingResponse, JsValue> {
|
||||
let core_request: CoreRoutingRequest = request.try_into()?;
|
||||
let core_response = self
|
||||
.inner
|
||||
.route(core_request)
|
||||
.map_err(|e| JsValue::from_str(&format!("Routing failed: {}", e)))?;
|
||||
|
||||
Ok(core_response.into())
|
||||
}
|
||||
|
||||
/// Check circuit breaker status
|
||||
pub fn circuit_breaker_status(&self) -> Option<bool> {
|
||||
self.inner.circuit_breaker_status()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get library version
|
||||
#[wasm_bindgen]
|
||||
pub fn version() -> String {
|
||||
env!("CARGO_PKG_VERSION").to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_version() {
|
||||
assert!(!version().is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user