mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
98e58c3c27
- Integrate 8 previously unregistered MCP handlers: - Chaos engineering: chaos_inject_latency, chaos_inject_failure, chaos_resilience_test - Integration testing: integration_dependency_check, integration_test_orchestrate - Token-optimized: test_execute_filtered, performance_test_filtered, quality_assess_filtered - Create NewDomainToolsHandler.ts to route new domain tools Dead code removal (~22k lines): - Delete 4 overlapping filtered handlers (coverage, flaky, security, contract) - Remove unused directories: src/alerting/, src/reporting/, src/transport/ - Remove 4 unregistered CLI command dirs: fleet/, test/, quality/, monitor/ (42 files) - Archive 10 one-time verification scripts to scripts/archive/ - Remove 2 CLI backup files (index-spec.ts, index-working.ts) Test organization: - Move HNSWPatternStore.test.ts from src/ to tests/unit/memory/ - Delete OutputFormatter duplicate test from src/ - Consolidate duplicate RuVector and learning-persistence tests - Rename RuVector.SelfLearning.test.ts for consistent naming Dependencies: - Remove unused packages: jose, @types/chrome, gpt-tokenizer Build config: - Exclude src/edge/ subdirectories from main tsconfig (separate build) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
702 lines
23 KiB
TypeScript
702 lines
23 KiB
TypeScript
/**
|
|
* RuVector Self-Learning Validation Tests
|
|
*
|
|
* Tests GNN self-learning capabilities and validates GOAP metrics:
|
|
* - Search quality improves by 10%+ over 100 queries
|
|
* - EWC++ maintains 98%+ pattern retention after adding new patterns
|
|
* - LoRA adapters stay under 300MB
|
|
* - Search latency remains <1ms for 95th percentile
|
|
*
|
|
* @group integration
|
|
* @group ruvector
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
|
|
import {
|
|
RuVectorClient,
|
|
createRuVectorClient,
|
|
RuVectorConfig,
|
|
QueryResult,
|
|
SearchResult,
|
|
LearningMetrics,
|
|
HealthCheckResponse
|
|
} from '../../src/providers/RuVectorClient';
|
|
import { createSeededRandom } from '../../src/utils/SeededRandom';
|
|
|
|
// Mock configuration for when Docker is not available
|
|
const MOCK_MODE = process.env.RUVECTOR_MOCK === 'true';
|
|
const SKIP_INTEGRATION = process.env.SKIP_RUVECTOR_TESTS === 'true';
|
|
|
|
// Test configuration
|
|
const TEST_CONFIG: RuVectorConfig = {
|
|
baseUrl: process.env.RUVECTOR_URL || 'http://localhost:8080',
|
|
learningEnabled: true,
|
|
cacheThreshold: 0.8,
|
|
loraRank: 8,
|
|
ewcEnabled: true,
|
|
timeout: 30000,
|
|
maxRetries: 3,
|
|
retryDelay: 1000,
|
|
debug: process.env.DEBUG === 'true'
|
|
};
|
|
|
|
// Seeded RNG for deterministic test data
|
|
const rng = createSeededRandom(18200);
|
|
|
|
// Helper to generate test embeddings (768-dim)
|
|
function generateEmbedding(seed: number = rng.random()): number[] {
|
|
const dim = 768;
|
|
const embedding: number[] = [];
|
|
|
|
for (let i = 0; i < dim; i++) {
|
|
// Use seed to make embeddings deterministic but slightly varied
|
|
embedding.push(Math.sin(seed * i) * 0.1 + Math.cos(seed * (i + 1)) * 0.1);
|
|
}
|
|
|
|
// Normalize
|
|
const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
|
|
return embedding.map(val => val / magnitude);
|
|
}
|
|
|
|
// Helper to generate similar embeddings
|
|
function generateSimilarEmbedding(baseEmbedding: number[], variance: number = 0.1): number[] {
|
|
const similar = baseEmbedding.map(val => val + (rng.random() - 0.5) * variance);
|
|
|
|
// Normalize
|
|
const magnitude = Math.sqrt(similar.reduce((sum, val) => sum + val * val, 0));
|
|
return similar.map(val => val / magnitude);
|
|
}
|
|
|
|
// Helper to calculate percentile
|
|
function percentile(values: number[], p: number): number {
|
|
const sorted = [...values].sort((a, b) => a - b);
|
|
const index = Math.ceil((p / 100) * sorted.length) - 1;
|
|
return sorted[Math.max(0, index)];
|
|
}
|
|
|
|
// Helper to simulate LLM responses
|
|
function createLLMFallback(response: string): () => Promise<string> {
|
|
return async () => {
|
|
// Simulate LLM latency
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
return response;
|
|
};
|
|
}
|
|
|
|
// Mock client for when Docker is not available
|
|
class MockRuVectorClient {
|
|
private patterns: Map<string, { embedding: number[]; content: string; confidence: number }> = new Map();
|
|
private queryCount = 0;
|
|
private cacheHits = 0;
|
|
|
|
async search(embedding: number[], k: number = 10): Promise<SearchResult[]> {
|
|
const results: SearchResult[] = [];
|
|
|
|
// Convert Map to Array to avoid iteration issues
|
|
const patternEntries = Array.from(this.patterns.entries());
|
|
|
|
for (const [id, pattern] of patternEntries) {
|
|
// Calculate cosine similarity
|
|
const dotProduct = embedding.reduce((sum, val, i) => sum + val * pattern.embedding[i], 0);
|
|
const confidence = Math.max(0, dotProduct); // Cosine similarity in [0, 1]
|
|
|
|
results.push({
|
|
id,
|
|
content: pattern.content,
|
|
embedding: pattern.embedding,
|
|
confidence,
|
|
metadata: {}
|
|
});
|
|
}
|
|
|
|
// Sort by confidence and return top k
|
|
return results.sort((a, b) => b.confidence - a.confidence).slice(0, k);
|
|
}
|
|
|
|
async store(pattern: { embedding: number[]; content: string }): Promise<void> {
|
|
const id = `pattern-${this.patterns.size}`;
|
|
this.patterns.set(id, { ...pattern, confidence: 0.9 });
|
|
}
|
|
|
|
async queryWithLearning(
|
|
query: string,
|
|
embedding: number[],
|
|
llmFallback: () => Promise<string>
|
|
): Promise<QueryResult> {
|
|
this.queryCount++;
|
|
|
|
const results = await this.search(embedding, 5);
|
|
const topResult = results[0];
|
|
|
|
if (topResult && topResult.confidence > 0.8) {
|
|
this.cacheHits++;
|
|
return {
|
|
content: topResult.content,
|
|
source: 'cache',
|
|
confidence: topResult.confidence,
|
|
latency: 1
|
|
};
|
|
}
|
|
|
|
const llmResponse = await llmFallback();
|
|
await this.store({ embedding, content: llmResponse });
|
|
|
|
return {
|
|
content: llmResponse,
|
|
source: 'llm',
|
|
confidence: 1.0,
|
|
latency: 50
|
|
};
|
|
}
|
|
|
|
async getMetrics(): Promise<LearningMetrics> {
|
|
return {
|
|
cacheHitRate: this.queryCount > 0 ? this.cacheHits / this.queryCount : 0,
|
|
totalQueries: this.queryCount,
|
|
loraUpdates: Math.floor(this.patterns.size / 10),
|
|
averageLatency: 1,
|
|
patternCount: this.patterns.size,
|
|
memoryUsageMB: this.patterns.size * 0.1,
|
|
gnnMetrics: {
|
|
precision: 0.95,
|
|
recall: 0.93,
|
|
f1Score: 0.94
|
|
}
|
|
};
|
|
}
|
|
|
|
async healthCheck(): Promise<HealthCheckResponse> {
|
|
return {
|
|
status: 'healthy',
|
|
version: 'mock-1.0.0',
|
|
uptime: 1000,
|
|
gnnStatus: 'active',
|
|
loraStatus: 'active',
|
|
vectorCount: this.patterns.size
|
|
};
|
|
}
|
|
|
|
async forceLearn(): Promise<{ success: boolean; updatedParameters: number; duration: number }> {
|
|
return {
|
|
success: true,
|
|
updatedParameters: this.patterns.size,
|
|
duration: 100
|
|
};
|
|
}
|
|
|
|
resetMetrics(): void {
|
|
this.queryCount = 0;
|
|
this.cacheHits = 0;
|
|
}
|
|
}
|
|
|
|
describe('RuVector Self-Learning Validation', () => {
|
|
let client: RuVectorClient | MockRuVectorClient;
|
|
let isRealClient: boolean;
|
|
|
|
beforeAll(async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping RuVector integration tests (SKIP_RUVECTOR_TESTS=true)');
|
|
return;
|
|
}
|
|
|
|
if (MOCK_MODE) {
|
|
console.log('🎭 Using mock RuVectorClient (RUVECTOR_MOCK=true)');
|
|
client = new MockRuVectorClient() as any;
|
|
isRealClient = false;
|
|
} else {
|
|
console.log('🐳 Using real RuVectorClient (attempting Docker connection)');
|
|
client = createRuVectorClient(TEST_CONFIG);
|
|
isRealClient = true;
|
|
|
|
try {
|
|
// Test connection
|
|
await client.healthCheck();
|
|
console.log('✅ RuVector Docker service is healthy');
|
|
} catch (error) {
|
|
console.warn('⚠️ RuVector Docker not available, falling back to mock mode');
|
|
console.warn(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
client = new MockRuVectorClient() as any;
|
|
isRealClient = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
beforeEach(() => {
|
|
if (!SKIP_INTEGRATION && client) {
|
|
client.resetMetrics();
|
|
}
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (client && isRealClient) {
|
|
console.log('🧹 Cleaning up RuVector client');
|
|
}
|
|
});
|
|
|
|
describe('Health Check', () => {
|
|
it('should verify RuVector service is healthy', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
const health = await client.healthCheck();
|
|
|
|
expect(health.status).toBe('healthy');
|
|
expect(health.gnnStatus).toBe('active');
|
|
expect(health.loraStatus).toBe('active');
|
|
expect(health.version).toBeTruthy();
|
|
expect(health.uptime).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Search Quality Improvement', () => {
|
|
it('should improve search quality by 10%+ over 100 queries', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
// Phase 1: Baseline quality (first 20 queries)
|
|
const baselineQueries = 20;
|
|
const baselineConfidences: number[] = [];
|
|
|
|
for (let i = 0; i < baselineQueries; i++) {
|
|
const queryEmbedding = generateEmbedding(i / 100);
|
|
const llmResponse = `Response for query ${i}`;
|
|
|
|
const result = await client.queryWithLearning(
|
|
`Test query ${i}`,
|
|
queryEmbedding,
|
|
createLLMFallback(llmResponse)
|
|
);
|
|
|
|
baselineConfidences.push(result.confidence);
|
|
}
|
|
|
|
const baselineAvgConfidence = baselineConfidences.reduce((a, b) => a + b, 0) / baselineConfidences.length;
|
|
|
|
// Phase 2: Learning phase (next 50 queries with similar patterns)
|
|
const learningQueries = 50;
|
|
|
|
for (let i = 0; i < learningQueries; i++) {
|
|
// Generate similar queries to existing patterns
|
|
const baseIndex = i % baselineQueries;
|
|
const baseEmbedding = generateEmbedding(baseIndex / 100);
|
|
const similarEmbedding = generateSimilarEmbedding(baseEmbedding, 0.05);
|
|
|
|
await client.queryWithLearning(
|
|
`Similar query ${i}`,
|
|
similarEmbedding,
|
|
createLLMFallback(`Response for query ${baseIndex}`)
|
|
);
|
|
}
|
|
|
|
// Phase 3: Evaluation quality (next 30 queries)
|
|
const evalQueries = 30;
|
|
const evalConfidences: number[] = [];
|
|
|
|
for (let i = 0; i < evalQueries; i++) {
|
|
const baseIndex = i % baselineQueries;
|
|
const baseEmbedding = generateEmbedding(baseIndex / 100);
|
|
const similarEmbedding = generateSimilarEmbedding(baseEmbedding, 0.05);
|
|
|
|
const result = await client.queryWithLearning(
|
|
`Eval query ${i}`,
|
|
similarEmbedding,
|
|
createLLMFallback(`Response for query ${baseIndex}`)
|
|
);
|
|
|
|
evalConfidences.push(result.confidence);
|
|
}
|
|
|
|
const evalAvgConfidence = evalConfidences.reduce((a, b) => a + b, 0) / evalConfidences.length;
|
|
|
|
// Calculate improvement
|
|
const improvement = ((evalAvgConfidence - baselineAvgConfidence) / baselineAvgConfidence) * 100;
|
|
|
|
console.log(`📊 Search Quality Metrics:`);
|
|
console.log(` Baseline confidence: ${baselineAvgConfidence.toFixed(3)}`);
|
|
console.log(` Evaluation confidence: ${evalAvgConfidence.toFixed(3)}`);
|
|
console.log(` Improvement: ${improvement.toFixed(2)}%`);
|
|
|
|
// GOAP target: 10%+ improvement
|
|
expect(improvement).toBeGreaterThanOrEqual(10);
|
|
|
|
// Verify learning is happening
|
|
const metrics = await client.getMetrics();
|
|
expect(metrics.totalQueries).toBe(baselineQueries + learningQueries + evalQueries);
|
|
expect(metrics.cacheHitRate).toBeGreaterThan(0);
|
|
|
|
console.log(` Cache hit rate: ${(metrics.cacheHitRate * 100).toFixed(2)}%`);
|
|
console.log(` Total patterns: ${metrics.patternCount}`);
|
|
}, 120000); // 2 minute timeout for 100 queries
|
|
|
|
it('should demonstrate increasing cache hit rate over time', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
const queryBatches = 5;
|
|
const queriesPerBatch = 20;
|
|
const cacheHitRates: number[] = [];
|
|
|
|
for (let batch = 0; batch < queryBatches; batch++) {
|
|
const batchStart = batch * queriesPerBatch;
|
|
|
|
for (let i = 0; i < queriesPerBatch; i++) {
|
|
const queryIndex = batchStart + i;
|
|
const baseEmbedding = generateEmbedding(queryIndex % 10 / 10); // Reuse patterns
|
|
const similarEmbedding = generateSimilarEmbedding(baseEmbedding, 0.03);
|
|
|
|
await client.queryWithLearning(
|
|
`Batch ${batch} query ${i}`,
|
|
similarEmbedding,
|
|
createLLMFallback(`Response ${queryIndex % 10}`)
|
|
);
|
|
}
|
|
|
|
const metrics = await client.getMetrics();
|
|
cacheHitRates.push(metrics.cacheHitRate);
|
|
|
|
console.log(` Batch ${batch + 1}: Cache hit rate = ${(metrics.cacheHitRate * 100).toFixed(2)}%`);
|
|
}
|
|
|
|
// Verify cache hit rate is increasing (with some tolerance)
|
|
const firstBatchRate = cacheHitRates[0];
|
|
const lastBatchRate = cacheHitRates[cacheHitRates.length - 1];
|
|
|
|
expect(lastBatchRate).toBeGreaterThan(firstBatchRate);
|
|
|
|
console.log(`📈 Cache Hit Rate Growth:`);
|
|
console.log(` Initial: ${(firstBatchRate * 100).toFixed(2)}%`);
|
|
console.log(` Final: ${(lastBatchRate * 100).toFixed(2)}%`);
|
|
console.log(` Growth: ${((lastBatchRate - firstBatchRate) * 100).toFixed(2)}%`);
|
|
}, 90000); // 90 second timeout
|
|
});
|
|
|
|
describe('EWC++ Pattern Retention', () => {
|
|
it('should retain 98%+ of patterns after adding new ones', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
// Phase 1: Store initial patterns and verify they work
|
|
const initialPatterns = 100;
|
|
const initialEmbeddings: number[][] = [];
|
|
|
|
console.log(`📝 Storing ${initialPatterns} initial patterns...`);
|
|
|
|
for (let i = 0; i < initialPatterns; i++) {
|
|
const embedding = generateEmbedding(i / 1000);
|
|
initialEmbeddings.push(embedding);
|
|
|
|
await client.queryWithLearning(
|
|
`Initial pattern ${i}`,
|
|
embedding,
|
|
createLLMFallback(`Initial response ${i}`)
|
|
);
|
|
}
|
|
|
|
// Verify initial patterns are retrievable
|
|
const initialRecalls: number[] = [];
|
|
|
|
for (let i = 0; i < initialPatterns; i++) {
|
|
const similarEmbedding = generateSimilarEmbedding(initialEmbeddings[i], 0.02);
|
|
const result = await client.queryWithLearning(
|
|
`Verify initial ${i}`,
|
|
similarEmbedding,
|
|
createLLMFallback(`Should not be called`)
|
|
);
|
|
|
|
initialRecalls.push(result.source === 'cache' ? 1 : 0);
|
|
}
|
|
|
|
const initialRecallRate = initialRecalls.reduce((a, b) => a + b, 0) / initialRecalls.length;
|
|
console.log(` Initial recall rate: ${(initialRecallRate * 100).toFixed(2)}%`);
|
|
|
|
// Phase 2: Add many new patterns (potential forgetting trigger)
|
|
const newPatterns = 1000;
|
|
|
|
console.log(`📝 Adding ${newPatterns} new patterns (EWC++ preventing forgetting)...`);
|
|
|
|
for (let i = 0; i < newPatterns; i++) {
|
|
const embedding = generateEmbedding((initialPatterns + i) / 1000);
|
|
|
|
await client.queryWithLearning(
|
|
`New pattern ${i}`,
|
|
embedding,
|
|
createLLMFallback(`New response ${i}`)
|
|
);
|
|
}
|
|
|
|
// Phase 3: Verify initial patterns are still retrievable
|
|
const finalRecalls: number[] = [];
|
|
|
|
console.log(`🔍 Verifying initial patterns are retained...`);
|
|
|
|
for (let i = 0; i < initialPatterns; i++) {
|
|
const similarEmbedding = generateSimilarEmbedding(initialEmbeddings[i], 0.02);
|
|
const result = await client.queryWithLearning(
|
|
`Verify after ${i}`,
|
|
similarEmbedding,
|
|
createLLMFallback(`Should not be called`)
|
|
);
|
|
|
|
finalRecalls.push(result.source === 'cache' ? 1 : 0);
|
|
}
|
|
|
|
const finalRecallRate = finalRecalls.reduce((a, b) => a + b, 0) / finalRecalls.length;
|
|
|
|
console.log(`📊 EWC++ Pattern Retention Metrics:`);
|
|
console.log(` Initial patterns: ${initialPatterns}`);
|
|
console.log(` New patterns added: ${newPatterns}`);
|
|
console.log(` Initial recall rate: ${(initialRecallRate * 100).toFixed(2)}%`);
|
|
console.log(` Final recall rate: ${(finalRecallRate * 100).toFixed(2)}%`);
|
|
console.log(` Retention: ${(finalRecallRate * 100).toFixed(2)}%`);
|
|
|
|
// GOAP target: 98%+ retention
|
|
expect(finalRecallRate).toBeGreaterThanOrEqual(0.98);
|
|
|
|
// Verify EWC is actually enabled and working
|
|
const metrics = await client.getMetrics();
|
|
expect(metrics.patternCount).toBeGreaterThan(initialPatterns);
|
|
console.log(` Total patterns stored: ${metrics.patternCount}`);
|
|
}, 180000); // 3 minute timeout for 1100 patterns
|
|
});
|
|
|
|
describe('Performance Constraints', () => {
|
|
it('should maintain <1ms p95 search latency', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
// Warm up with some patterns
|
|
const warmupPatterns = 100;
|
|
|
|
console.log(`🔥 Warming up with ${warmupPatterns} patterns...`);
|
|
|
|
for (let i = 0; i < warmupPatterns; i++) {
|
|
const embedding = generateEmbedding(i / 100);
|
|
await client.queryWithLearning(
|
|
`Warmup ${i}`,
|
|
embedding,
|
|
createLLMFallback(`Warmup response ${i}`)
|
|
);
|
|
}
|
|
|
|
// Measure search latency for 1000 searches
|
|
const searches = 1000;
|
|
const latencies: number[] = [];
|
|
|
|
console.log(`⏱️ Measuring latency for ${searches} searches...`);
|
|
|
|
for (let i = 0; i < searches; i++) {
|
|
const embedding = generateEmbedding(i / 100);
|
|
|
|
const startTime = Date.now();
|
|
await (client as RuVectorClient).search(embedding, 10);
|
|
const latency = Date.now() - startTime;
|
|
|
|
latencies.push(latency);
|
|
}
|
|
|
|
// Calculate percentiles
|
|
const p50 = percentile(latencies, 50);
|
|
const p95 = percentile(latencies, 95);
|
|
const p99 = percentile(latencies, 99);
|
|
const avg = latencies.reduce((a, b) => a + b, 0) / latencies.length;
|
|
|
|
console.log(`📊 Search Latency Metrics:`);
|
|
console.log(` Searches: ${searches}`);
|
|
console.log(` Average: ${avg.toFixed(3)}ms`);
|
|
console.log(` p50: ${p50.toFixed(3)}ms`);
|
|
console.log(` p95: ${p95.toFixed(3)}ms`);
|
|
console.log(` p99: ${p99.toFixed(3)}ms`);
|
|
|
|
// GOAP target: <1ms p95
|
|
expect(p95).toBeLessThan(1);
|
|
|
|
// Additional sanity checks
|
|
expect(avg).toBeLessThan(1);
|
|
expect(p99).toBeLessThan(5);
|
|
}, 90000); // 90 second timeout
|
|
|
|
it('should keep LoRA adapters under 300MB', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
// Store enough patterns to trigger LoRA learning
|
|
const patterns = 500;
|
|
|
|
console.log(`📝 Storing ${patterns} patterns to trigger LoRA learning...`);
|
|
|
|
for (let i = 0; i < patterns; i++) {
|
|
const embedding = generateEmbedding(i / 100);
|
|
await client.queryWithLearning(
|
|
`Pattern ${i}`,
|
|
embedding,
|
|
createLLMFallback(`Response ${i}`)
|
|
);
|
|
|
|
if ((i + 1) % 100 === 0) {
|
|
console.log(` Stored ${i + 1}/${patterns} patterns...`);
|
|
}
|
|
}
|
|
|
|
// Force consolidation to update LoRA adapters
|
|
console.log(`🔄 Forcing LoRA consolidation...`);
|
|
await client.forceLearn();
|
|
|
|
// Check memory usage
|
|
const metrics = await client.getMetrics();
|
|
|
|
console.log(`📊 LoRA Memory Metrics:`);
|
|
console.log(` Patterns stored: ${metrics.patternCount}`);
|
|
console.log(` LoRA updates: ${metrics.loraUpdates}`);
|
|
console.log(` Memory usage: ${metrics.memoryUsageMB?.toFixed(2) || 'N/A'} MB`);
|
|
|
|
if (metrics.memoryUsageMB !== undefined) {
|
|
// GOAP target: <300MB
|
|
expect(metrics.memoryUsageMB).toBeLessThan(300);
|
|
|
|
// Verify memory is reasonable relative to pattern count
|
|
const memoryPerPattern = metrics.memoryUsageMB / metrics.patternCount;
|
|
console.log(` Memory per pattern: ${memoryPerPattern.toFixed(3)} MB`);
|
|
expect(memoryPerPattern).toBeLessThan(1); // Should be well under 1MB per pattern
|
|
} else {
|
|
console.warn('⚠️ Memory usage not reported by server');
|
|
}
|
|
}, 120000); // 2 minute timeout
|
|
});
|
|
|
|
describe('GNN Metrics', () => {
|
|
it('should provide GNN quality metrics', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
// Store some patterns to train GNN
|
|
const patterns = 100;
|
|
|
|
for (let i = 0; i < patterns; i++) {
|
|
const embedding = generateEmbedding(i / 100);
|
|
await client.queryWithLearning(
|
|
`GNN pattern ${i}`,
|
|
embedding,
|
|
createLLMFallback(`GNN response ${i}`)
|
|
);
|
|
}
|
|
|
|
// Force learning to update GNN
|
|
await client.forceLearn();
|
|
|
|
const metrics = await client.getMetrics();
|
|
|
|
console.log(`📊 GNN Quality Metrics:`);
|
|
|
|
if (metrics.gnnMetrics) {
|
|
console.log(` Precision: ${(metrics.gnnMetrics.precision * 100).toFixed(2)}%`);
|
|
console.log(` Recall: ${(metrics.gnnMetrics.recall * 100).toFixed(2)}%`);
|
|
console.log(` F1 Score: ${(metrics.gnnMetrics.f1Score * 100).toFixed(2)}%`);
|
|
|
|
// GNN should maintain high quality
|
|
expect(metrics.gnnMetrics.precision).toBeGreaterThan(0.8);
|
|
expect(metrics.gnnMetrics.recall).toBeGreaterThan(0.8);
|
|
expect(metrics.gnnMetrics.f1Score).toBeGreaterThan(0.8);
|
|
} else {
|
|
console.warn('⚠️ GNN metrics not reported by server');
|
|
}
|
|
}, 60000);
|
|
});
|
|
|
|
describe('End-to-End Learning Workflow', () => {
|
|
it('should demonstrate complete learning cycle', async () => {
|
|
if (SKIP_INTEGRATION) {
|
|
console.log('⏭️ Skipping: SKIP_RUVECTOR_TESTS=true');
|
|
return;
|
|
}
|
|
|
|
console.log(`🔄 Running complete learning cycle...`);
|
|
|
|
// 1. Initial state - no patterns
|
|
let metrics = await client.getMetrics();
|
|
const initialPatternCount = metrics.patternCount;
|
|
|
|
console.log(` Initial state: ${initialPatternCount} patterns`);
|
|
|
|
// 2. Store diverse patterns
|
|
const categories = ['testing', 'debugging', 'refactoring', 'performance', 'security'];
|
|
const patternsPerCategory = 20;
|
|
|
|
for (let categoryIndex = 0; categoryIndex < categories.length; categoryIndex++) {
|
|
const category = categories[categoryIndex];
|
|
|
|
for (let i = 0; i < patternsPerCategory; i++) {
|
|
const embedding = generateEmbedding((categoryIndex * patternsPerCategory + i) / 1000);
|
|
|
|
await client.queryWithLearning(
|
|
`${category} pattern ${i}`,
|
|
embedding,
|
|
createLLMFallback(`${category} response ${i}`)
|
|
);
|
|
}
|
|
|
|
console.log(` Stored ${category} patterns (${patternsPerCategory})`);
|
|
}
|
|
|
|
// 3. Force learning consolidation
|
|
console.log(` Forcing learning consolidation...`);
|
|
const learnResult = await client.forceLearn();
|
|
|
|
expect(learnResult.success).toBe(true);
|
|
console.log(` Updated ${learnResult.updatedParameters} parameters in ${learnResult.duration}ms`);
|
|
|
|
// 4. Verify patterns are retrievable with high confidence
|
|
let highConfidenceHits = 0;
|
|
|
|
for (let categoryIndex = 0; categoryIndex < categories.length; categoryIndex++) {
|
|
const baseEmbedding = generateEmbedding(categoryIndex * patternsPerCategory / 1000);
|
|
const similarEmbedding = generateSimilarEmbedding(baseEmbedding, 0.05);
|
|
|
|
const result = await client.queryWithLearning(
|
|
`Verify ${categories[categoryIndex]}`,
|
|
similarEmbedding,
|
|
createLLMFallback('Should not be called')
|
|
);
|
|
|
|
if (result.source === 'cache' && result.confidence > 0.8) {
|
|
highConfidenceHits++;
|
|
}
|
|
}
|
|
|
|
const retrievalRate = highConfidenceHits / categories.length;
|
|
|
|
console.log(`📊 Learning Cycle Results:`);
|
|
console.log(` Patterns stored: ${patternsPerCategory * categories.length}`);
|
|
console.log(` High-confidence retrievals: ${highConfidenceHits}/${categories.length}`);
|
|
console.log(` Retrieval rate: ${(retrievalRate * 100).toFixed(2)}%`);
|
|
|
|
// Should retrieve most patterns with high confidence
|
|
expect(retrievalRate).toBeGreaterThan(0.6);
|
|
|
|
// 5. Final metrics check
|
|
metrics = await client.getMetrics();
|
|
|
|
console.log(` Final cache hit rate: ${(metrics.cacheHitRate * 100).toFixed(2)}%`);
|
|
console.log(` Total queries: ${metrics.totalQueries}`);
|
|
console.log(` LoRA updates: ${metrics.loraUpdates}`);
|
|
|
|
expect(metrics.patternCount).toBeGreaterThan(initialPatternCount);
|
|
expect(metrics.loraUpdates).toBeGreaterThan(0);
|
|
}, 120000); // 2 minute timeout
|
|
});
|
|
});
|