const fs = require('fs'); const path = require('path'); const { performance } = require('perf_hooks'); class MockToolExecutor { static async searchKnowledge({ query, response_mode }, context) { await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 200)); const mockResults = { '小红产品有什么功效': { hit: true, content: 'FitLine小红Activize Oxyplus的核心功效是提升细胞能量。它含有维生素B族、C和辅酶Q10等成分。主要作用:1.提升精力和体能。2.促进细胞氧气利用。3.增强免疫力。' }, '大白产品怎么吃': { hit: true, content: '德国PM大白Basics是基础营养素,早上空腹服用,1平勺兑200-300ml温水。' }, '德国PM公司介绍': { hit: true, content: '德国PM-International是1993年创立的国际营养品直销企业。核心品牌FitLine专注细胞营养,拥有独家NTC营养保送系统技术。' }, 'NTC营养保送系统原理': { hit: true, content: 'NTC营养保送系统是PM产品的核心技术优势。它能确保营养素在体内精准保送到细胞层面。' }, '基础三合一怎么吃': { hit: true, hot_answer: true, content: '基础三合一这样吃:1.大白Basics:早上空腹,1平勺兑200-300ml温水。2.小红Activize:大白喝完15-30分钟后,兑100-150ml温水。3.小白Restorate:睡前空腹,1平勺兑200ml温水。' }, '今天天气怎么样': { hit: false, content: '知识库中暂未找到相关信息。' } }; const result = mockResults[query] || { hit: false, content: '未找到相关信息' }; return { query, results: [{ title: '模拟知识库结果', content: result.content }], hit: result.hit, source: result.hot_answer ? 'hot_answer_cache' : 'mock_knowledge', hot_answer: result.hot_answer }; } } class VikingRetrievalPerformanceTester { constructor(options = {}) { this.results = []; this.outputDir = options.outputDir || path.join(__dirname, 'test_results'); this.verbose = options.verbose !== false; this.warmupRuns = options.warmupRuns || 2; this.mockMode = options.mockMode !== false; if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } } log(msg) { if (this.verbose) { console.log(`[VikingTest] ${msg}`); } } async warmup(queries) { this.log('Warming up...'); const ToolExecutor = this.mockMode ? MockToolExecutor : require('../services/toolExecutor'); for (let i = 0; i < this.warmupRuns; i++) { for (const query of queries) { try { await ToolExecutor.searchKnowledge({ query, response_mode: 'answer' }, []); } catch (e) { } } } this.log('Warmup complete'); } async testLatency(testQueries, iterations = 10) { this.log(`Starting latency test with ${iterations} iterations...`); const ToolExecutor = this.mockMode ? MockToolExecutor : require('../services/toolExecutor'); const results = {}; for (const { name, query } of testQueries) { this.log(`Testing query: ${name}`); const latencies = []; const hits = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); let result; try { result = await ToolExecutor.searchKnowledge({ query, response_mode: 'answer' }, []); const latency = performance.now() - start; latencies.push(latency); hits.push(!!result.hit); this.log(` Iteration ${i + 1}: ${latency.toFixed(2)}ms, hit=${result.hit}`); } catch (e) { this.log(` Iteration ${i + 1} error: ${e.message}`); } } results[name] = { query, latencies, hits, avgLatency: latencies.length ? latencies.reduce((a, b) => a + b, 0) / latencies.length : 0, minLatency: latencies.length ? Math.min(...latencies) : 0, maxLatency: latencies.length ? Math.max(...latencies) : 0, p50Latency: this.percentile(latencies, 50), p95Latency: this.percentile(latencies, 95), p99Latency: this.percentile(latencies, 99), hitRate: hits.length ? hits.filter(h => h).length / hits.length : 0 }; } this.results.push({ type: 'latency', timestamp: new Date().toISOString(), iterations, mockMode: this.mockMode, results }); return results; } async testCacheEfficiency(queries, cacheHitsIterations = 5) { this.log('Testing cache efficiency...'); const ToolExecutor = this.mockMode ? MockToolExecutor : require('../services/toolExecutor'); const results = []; for (const { name, query } of queries) { this.log(`Testing cache for query: ${name}`); const firstStart = performance.now(); const firstResult = await ToolExecutor.searchKnowledge({ query, response_mode: 'answer' }, []); const firstLatency = performance.now() - firstStart; const cacheLatencies = []; for (let i = 0; i < cacheHitsIterations; i++) { const start = performance.now(); const result = await ToolExecutor.searchKnowledge({ query, response_mode: 'answer' }, []); const latency = performance.now() - start; cacheLatencies.push(latency); this.log(` Cache hit ${i + 1}: ${latency.toFixed(2)}ms`); } const avgCacheLatency = cacheLatencies.reduce((a, b) => a + b, 0) / cacheLatencies.length; results.push({ name, query, firstHitLatency: firstLatency, cacheHitLatencies: cacheLatencies, avgCacheLatency, speedup: firstLatency / avgCacheLatency, firstHit: firstResult }); } this.results.push({ type: 'cache', timestamp: new Date().toISOString(), cacheHitsIterations, mockMode: this.mockMode, results }); return results; } async testConcurrency(queries, concurrencyLevels = [1, 5, 10, 20]) { this.log('Testing concurrency...'); const ToolExecutor = this.mockMode ? MockToolExecutor : require('../services/toolExecutor'); const results = {}; for (const concurrency of concurrencyLevels) { this.log(`Testing concurrency level: ${concurrency}`); const startTime = performance.now(); const promises = []; for (let i = 0; i < concurrency; i++) { const queryObj = queries[i % queries.length]; promises.push( ToolExecutor.searchKnowledge({ query: queryObj.query, response_mode: 'answer' }, []) ); } const allResults = await Promise.all(promises); const totalTime = performance.now() - startTime; const successCount = allResults.filter(r => r && !r.error).length; results[concurrency] = { concurrency, totalTime, throughput: concurrency / (totalTime / 1000), successRate: successCount / concurrency, results: allResults }; this.log(` Throughput: ${results[concurrency].throughput.toFixed(2)} req/s`); this.log(` Success rate: ${(results[concurrency].successRate * 100).toFixed(1)}%`); } this.results.push({ type: 'concurrency', timestamp: new Date().toISOString(), concurrencyLevels, mockMode: this.mockMode, results }); return results; } percentile(arr, p) { if (arr.length === 0) return 0; const sorted = [...arr].sort((a, b) => a - b); const index = Math.ceil((p / 100) * sorted.length) - 1; return sorted[Math.max(0, index)]; } generateReport() { const report = { generatedAt: new Date().toISOString(), mockMode: this.mockMode, summary: {}, tests: this.results }; for (const test of this.results) { if (test.type === 'latency') { report.summary.latency = Object.fromEntries( Object.entries(test.results).map(([name, data]) => [ name, { avg: data.avgLatency.toFixed(2), p95: data.p95Latency.toFixed(2), hitRate: (data.hitRate * 100).toFixed(1) + '%' } ]) ); } else if (test.type === 'cache') { report.summary.cache = test.results.map(r => ({ name: r.name, speedup: r.speedup.toFixed(2) + 'x' })); } else if (test.type === 'concurrency') { report.summary.concurrency = Object.fromEntries( Object.entries(test.results).map(([level, data]) => [ level, { throughput: data.throughput.toFixed(2) + ' req/s', successRate: (data.successRate * 100).toFixed(1) + '%' } ]) ); } } return report; } saveReport(filename = null) { const report = this.generateReport(); const filepath = path.join( this.outputDir, filename || `viking_performance_${Date.now()}.json` ); fs.writeFileSync(filepath, JSON.stringify(report, null, 2)); this.log(`Report saved to ${filepath}`); return filepath; } printSummary() { console.log('\n' + '='.repeat(80)); console.log('VIKING RETRIEVAL PERFORMANCE TEST SUMMARY'); if (this.mockMode) { console.log('(Mock Mode - For Framework Validation)'); } console.log('='.repeat(80)); const report = this.generateReport(); if (report.summary.latency) { console.log('\n--- Latency Test ---'); for (const [name, data] of Object.entries(report.summary.latency)) { console.log(` ${name}:`); console.log(` Avg: ${data.avg}ms, P95: ${data.p95}ms, Hit Rate: ${data.hitRate}`); } } if (report.summary.cache) { console.log('\n--- Cache Efficiency ---'); for (const r of report.summary.cache) { console.log(` ${r.name}: Speedup ${r.speedup}`); } } if (report.summary.concurrency) { console.log('\n--- Concurrency Test ---'); for (const [level, data] of Object.entries(report.summary.concurrency)) { console.log(` ${level} concurrent:`); console.log(` Throughput: ${data.throughput}, Success: ${data.successRate}`); } } console.log('\n' + '='.repeat(80)); } async runFullSuite() { this.log('Starting full Viking retrieval performance test suite...'); const testQueries = [ { name: 'Product Query - Xiaohong', query: '小红产品有什么功效' }, { name: 'Product Query - Dabai', query: '大白产品怎么吃' }, { name: 'Company Info', query: '德国PM公司介绍' }, { name: 'NTC Technology', query: 'NTC营养保送系统原理' }, { name: 'Hot Answer', query: '基础三合一怎么吃' }, { name: 'No Hit Query', query: '今天天气怎么样' } ]; await this.warmup(testQueries.map(q => q.query)); await this.testLatency(testQueries, 10); await this.testCacheEfficiency(testQueries.slice(0, 3), 5); await this.testConcurrency(testQueries.slice(0, 3), [1, 3, 5]); this.printSummary(); this.saveReport(); return this.results; } } module.exports = VikingRetrievalPerformanceTester; if (require.main === module) { (async () => { const args = process.argv.slice(2); const mockMode = args[0] !== 'real'; const tester = new VikingRetrievalPerformanceTester({ mockMode }); await tester.runFullSuite(); })(); }