git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
395 lines
11 KiB
Markdown
395 lines
11 KiB
Markdown
# Self-Learning / ReasoningBank Integration Plan
|
|
|
|
## Overview
|
|
|
|
Integrate adaptive learning capabilities into ruvector-postgres, enabling the database to learn from query patterns, optimize search strategies, and improve recall/precision over time.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ PostgreSQL Extension │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
|
│ │ Trajectory │ │ Verdict │ │ Memory Distillation│ │
|
|
│ │ Tracker │ │ Judgment │ │ Engine │ │
|
|
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
|
|
│ │ │ │ │
|
|
│ └────────────────┼─────────────────────┘ │
|
|
│ ▼ │
|
|
│ ┌───────────────────────┐ │
|
|
│ │ ReasoningBank │ │
|
|
│ │ (Pattern Storage) │ │
|
|
│ └───────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Module Structure
|
|
|
|
```
|
|
src/
|
|
├── learning/
|
|
│ ├── mod.rs # Module exports
|
|
│ ├── trajectory.rs # Query trajectory tracking
|
|
│ ├── verdict.rs # Success/failure judgment
|
|
│ ├── distillation.rs # Pattern extraction
|
|
│ ├── reasoning_bank.rs # Pattern storage & retrieval
|
|
│ └── optimizer.rs # Search parameter optimization
|
|
```
|
|
|
|
## SQL Interface
|
|
|
|
### Configuration
|
|
|
|
```sql
|
|
-- Enable self-learning for a table
|
|
SELECT ruvector_enable_learning('embeddings',
|
|
trajectory_window := 1000,
|
|
learning_rate := 0.01,
|
|
min_samples := 100
|
|
);
|
|
|
|
-- View learning statistics
|
|
SELECT * FROM ruvector_learning_stats('embeddings');
|
|
|
|
-- Export learned patterns
|
|
SELECT ruvector_export_patterns('embeddings') AS patterns_json;
|
|
|
|
-- Import patterns from another instance
|
|
SELECT ruvector_import_patterns('embeddings', patterns_json);
|
|
```
|
|
|
|
### Automatic Optimization
|
|
|
|
```sql
|
|
-- Auto-tune HNSW parameters based on query patterns
|
|
SELECT ruvector_auto_tune('embeddings_idx',
|
|
optimize_for := 'recall', -- or 'latency', 'balanced'
|
|
sample_queries := 1000
|
|
);
|
|
|
|
-- Get recommended index parameters
|
|
SELECT * FROM ruvector_recommend_params('embeddings');
|
|
```
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1: Trajectory Tracking (Week 1-2)
|
|
|
|
```rust
|
|
// src/learning/trajectory.rs
|
|
|
|
pub struct QueryTrajectory {
|
|
pub query_id: Uuid,
|
|
pub query_vector: Vec<f32>,
|
|
pub timestamp: DateTime<Utc>,
|
|
pub index_params: IndexParams,
|
|
pub results: Vec<SearchResult>,
|
|
pub latency_ms: f64,
|
|
pub recall_estimate: Option<f32>,
|
|
}
|
|
|
|
pub struct TrajectoryTracker {
|
|
buffer: RingBuffer<QueryTrajectory>,
|
|
storage: TrajectoryStorage,
|
|
}
|
|
|
|
impl TrajectoryTracker {
|
|
pub fn record(&mut self, trajectory: QueryTrajectory);
|
|
pub fn get_recent(&self, n: usize) -> Vec<&QueryTrajectory>;
|
|
pub fn analyze_patterns(&self) -> PatternAnalysis;
|
|
}
|
|
```
|
|
|
|
**SQL Functions:**
|
|
```sql
|
|
-- Record query feedback (user indicates relevance)
|
|
SELECT ruvector_record_feedback(
|
|
query_id := 'abc123',
|
|
relevant_ids := ARRAY[1, 5, 7],
|
|
irrelevant_ids := ARRAY[2, 3]
|
|
);
|
|
```
|
|
|
|
### Phase 2: Verdict Judgment (Week 3-4)
|
|
|
|
```rust
|
|
// src/learning/verdict.rs
|
|
|
|
pub struct VerdictEngine {
|
|
success_threshold: f32,
|
|
metrics: VerdictMetrics,
|
|
}
|
|
|
|
impl VerdictEngine {
|
|
/// Judge if a search was successful based on multiple signals
|
|
pub fn judge(&self, trajectory: &QueryTrajectory) -> Verdict {
|
|
let signals = vec![
|
|
self.latency_score(trajectory),
|
|
self.recall_score(trajectory),
|
|
self.diversity_score(trajectory),
|
|
self.user_feedback_score(trajectory),
|
|
];
|
|
|
|
Verdict {
|
|
success: signals.iter().sum::<f32>() / signals.len() as f32 > self.success_threshold,
|
|
confidence: self.compute_confidence(&signals),
|
|
recommendations: self.generate_recommendations(&signals),
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Phase 3: Memory Distillation (Week 5-6)
|
|
|
|
```rust
|
|
// src/learning/distillation.rs
|
|
|
|
pub struct DistillationEngine {
|
|
pattern_extractor: PatternExtractor,
|
|
compressor: PatternCompressor,
|
|
}
|
|
|
|
impl DistillationEngine {
|
|
/// Extract reusable patterns from trajectories
|
|
pub fn distill(&self, trajectories: &[QueryTrajectory]) -> Vec<LearnedPattern> {
|
|
let raw_patterns = self.pattern_extractor.extract(trajectories);
|
|
let compressed = self.compressor.compress(raw_patterns);
|
|
compressed
|
|
}
|
|
}
|
|
|
|
pub struct LearnedPattern {
|
|
pub query_cluster_centroid: Vec<f32>,
|
|
pub optimal_ef_search: u32,
|
|
pub optimal_probes: u32,
|
|
pub expected_recall: f32,
|
|
pub confidence: f32,
|
|
}
|
|
```
|
|
|
|
### Phase 4: ReasoningBank Storage (Week 7-8)
|
|
|
|
```rust
|
|
// src/learning/reasoning_bank.rs
|
|
|
|
pub struct ReasoningBank {
|
|
patterns: HnswIndex<LearnedPattern>,
|
|
metadata: HashMap<PatternId, PatternMetadata>,
|
|
}
|
|
|
|
impl ReasoningBank {
|
|
/// Find applicable patterns for a query
|
|
pub fn lookup(&self, query: &[f32], k: usize) -> Vec<&LearnedPattern> {
|
|
self.patterns.search(query, k)
|
|
}
|
|
|
|
/// Store a new pattern
|
|
pub fn store(&mut self, pattern: LearnedPattern) -> PatternId;
|
|
|
|
/// Merge similar patterns to prevent bloat
|
|
pub fn consolidate(&mut self);
|
|
|
|
/// Prune low-value patterns
|
|
pub fn prune(&mut self, min_usage: u32, min_confidence: f32);
|
|
}
|
|
```
|
|
|
|
### Phase 5: Search Optimizer (Week 9-10)
|
|
|
|
```rust
|
|
// src/learning/optimizer.rs
|
|
|
|
pub struct SearchOptimizer {
|
|
reasoning_bank: Arc<ReasoningBank>,
|
|
default_params: SearchParams,
|
|
}
|
|
|
|
impl SearchOptimizer {
|
|
/// Get optimized parameters for a query
|
|
pub fn optimize(&self, query: &[f32]) -> SearchParams {
|
|
match self.reasoning_bank.lookup(query, 3) {
|
|
patterns if !patterns.is_empty() => {
|
|
self.interpolate_params(query, patterns)
|
|
}
|
|
_ => self.default_params.clone()
|
|
}
|
|
}
|
|
|
|
fn interpolate_params(&self, query: &[f32], patterns: &[&LearnedPattern]) -> SearchParams {
|
|
// Weight patterns by similarity to query
|
|
let weights: Vec<f32> = patterns.iter()
|
|
.map(|p| cosine_similarity(query, &p.query_cluster_centroid))
|
|
.collect();
|
|
|
|
SearchParams {
|
|
ef_search: weighted_average(
|
|
patterns.iter().map(|p| p.optimal_ef_search as f32),
|
|
&weights
|
|
) as u32,
|
|
// ...
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## PostgreSQL Integration
|
|
|
|
### Background Worker
|
|
|
|
```rust
|
|
// src/learning/bgworker.rs
|
|
|
|
#[pg_guard]
|
|
pub extern "C" fn learning_bgworker_main(_arg: pg_sys::Datum) {
|
|
BackgroundWorker::attach_signal_handlers(SignalWakeFlags::SIGHUP | SignalWakeFlags::SIGTERM);
|
|
|
|
loop {
|
|
// Process trajectory buffer
|
|
let trajectories = TRAJECTORY_BUFFER.drain();
|
|
|
|
if trajectories.len() >= MIN_BATCH_SIZE {
|
|
// Distill patterns
|
|
let patterns = DISTILLATION_ENGINE.distill(&trajectories);
|
|
|
|
// Store in reasoning bank
|
|
for pattern in patterns {
|
|
REASONING_BANK.store(pattern);
|
|
}
|
|
|
|
// Periodic consolidation
|
|
if should_consolidate() {
|
|
REASONING_BANK.consolidate();
|
|
}
|
|
}
|
|
|
|
// Sleep until next batch
|
|
BackgroundWorker::wait_latch(LEARNING_INTERVAL_MS);
|
|
}
|
|
}
|
|
```
|
|
|
|
### GUC Configuration
|
|
|
|
```rust
|
|
static LEARNING_ENABLED: GucSetting<bool> = GucSetting::new(false);
|
|
static LEARNING_RATE: GucSetting<f64> = GucSetting::new(0.01);
|
|
static TRAJECTORY_BUFFER_SIZE: GucSetting<i32> = GucSetting::new(10000);
|
|
static PATTERN_CONSOLIDATION_INTERVAL: GucSetting<i32> = GucSetting::new(3600);
|
|
```
|
|
|
|
## Optimization Strategies
|
|
|
|
### 1. Adaptive ef_search
|
|
|
|
```sql
|
|
-- Before: Static ef_search
|
|
SET ruvector.ef_search = 40;
|
|
SELECT * FROM items ORDER BY embedding <-> query_vec LIMIT 10;
|
|
|
|
-- After: Adaptive ef_search based on learned patterns
|
|
SELECT * FROM items
|
|
ORDER BY embedding <-> query_vec
|
|
LIMIT 10
|
|
WITH (adaptive_search := true);
|
|
```
|
|
|
|
### 2. Query-Aware Probing
|
|
|
|
For IVFFlat, learn optimal probe counts per query cluster:
|
|
|
|
```rust
|
|
pub fn adaptive_probes(&self, query: &[f32]) -> u32 {
|
|
let cluster_id = self.assign_cluster(query);
|
|
self.learned_probes.get(&cluster_id).unwrap_or(&self.default_probes)
|
|
}
|
|
```
|
|
|
|
### 3. Index Selection
|
|
|
|
Learn when to use HNSW vs IVFFlat:
|
|
|
|
```rust
|
|
pub fn select_index(&self, query: &[f32], k: usize) -> IndexType {
|
|
let features = QueryFeatures::extract(query, k);
|
|
self.index_selector.predict(&features)
|
|
}
|
|
```
|
|
|
|
## Benchmarks
|
|
|
|
### Metrics to Track
|
|
|
|
| Metric | Baseline | Target | Measurement |
|
|
|--------|----------|--------|-------------|
|
|
| Recall@10 | 0.95 | 0.98 | After 10K queries |
|
|
| p99 Latency | 5ms | 3ms | After learning |
|
|
| Memory Overhead | 0 | <100MB | Pattern storage |
|
|
| Learning Time | N/A | <1s/1K queries | Background processing |
|
|
|
|
### Benchmark Queries
|
|
|
|
```sql
|
|
-- Measure recall improvement
|
|
SELECT ruvector_benchmark_recall(
|
|
table_name := 'embeddings',
|
|
ground_truth_table := 'embeddings_ground_truth',
|
|
num_queries := 1000,
|
|
k := 10
|
|
);
|
|
|
|
-- Measure latency improvement
|
|
SELECT ruvector_benchmark_latency(
|
|
table_name := 'embeddings',
|
|
num_queries := 10000,
|
|
k := 10,
|
|
percentiles := ARRAY[50, 90, 99]
|
|
);
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
```toml
|
|
[dependencies]
|
|
# Existing ruvector crates (optional integration)
|
|
# ruvector-core = { path = "../ruvector-core", optional = true }
|
|
|
|
# Pattern storage
|
|
dashmap = "6.0"
|
|
parking_lot = "0.12"
|
|
|
|
# Statistics
|
|
statrs = "0.16"
|
|
|
|
# Clustering for pattern extraction
|
|
linfa = "0.7"
|
|
linfa-clustering = "0.7"
|
|
|
|
# Serialization for pattern export/import
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
serde_json = "1.0"
|
|
```
|
|
|
|
## Feature Flags
|
|
|
|
```toml
|
|
[features]
|
|
learning = []
|
|
learning-advanced = ["learning", "linfa", "linfa-clustering"]
|
|
learning-distributed = ["learning", "ruvector-replication"]
|
|
```
|
|
|
|
## Migration Path
|
|
|
|
1. **v0.2.0**: Basic trajectory tracking, manual feedback
|
|
2. **v0.3.0**: Verdict judgment, automatic pattern extraction
|
|
3. **v0.4.0**: Full ReasoningBank, adaptive search
|
|
4. **v0.5.0**: Distributed learning across replicas
|
|
|
|
## Security Considerations
|
|
|
|
- Pattern data is stored locally, no external transmission
|
|
- Trajectory data can be anonymized (hash query vectors)
|
|
- Learning can be disabled per-table for sensitive data
|
|
- Export/import requires superuser privileges
|