UNPKG

arela

Version:

AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.

234 lines 8.44 kB
import { ResultCombiner } from "./combiner.js"; /** * MultiHopRouter - Executes sub-queries and combines results * * Execution strategies: * - Sequential: Execute hops one by one (A → B → C) * - Parallel: Execute all hops concurrently (A, B, C) * - Hybrid: Mix of sequential and parallel */ export class MultiHopRouter { contextRouter; combiner; options; constructor(contextRouter, options = {}) { this.contextRouter = contextRouter; this.combiner = new ResultCombiner(); this.options = { maxConcurrentHops: options.maxConcurrentHops ?? 3, hopTimeout: options.hopTimeout ?? 10000, }; } /** * Route a decomposed query through multiple hops */ async route(decomposition) { const startTime = Date.now(); const decompositionTime = 0; // Already done by decomposer // Execute hops based on strategy const startExecution = Date.now(); let hops; switch (decomposition.strategy) { case "sequential": hops = await this.executeSequential(decomposition.subQueries); break; case "parallel": hops = await this.executeParallel(decomposition.subQueries); break; case "hybrid": hops = await this.executeHybrid(decomposition.subQueries); break; default: hops = await this.executeSequential(decomposition.subQueries); } const executionTime = Date.now() - startExecution; // Combine results const startCombination = Date.now(); const originalHops = [...hops]; // Keep for dedup calc const combinedContext = this.combiner.combine(hops); const combinationTime = Date.now() - startCombination; // Calculate stats const totalTime = Date.now() - startTime; const deduplicationRate = this.combiner.calculateDeduplicationRate(originalHops, combinedContext); return { originalQuery: decomposition.originalQuery, decomposition, hops, combinedContext, stats: { totalHops: hops.length, totalTime, decompositionTime, executionTime, combinationTime, resultsPerHop: hops.length > 0 ? hops.reduce((sum, h) => sum + h.context.length, 0) / hops.length : 0, deduplicationRate, tokensEstimated: this.estimateTokens(combinedContext), }, }; } /** * Execute sub-queries sequentially */ async executeSequential(subQueries) { const results = []; // Sort by priority (higher first) const sorted = [...subQueries].sort((a, b) => b.priority - a.priority); for (const subQuery of sorted) { // Wait for dependencies to complete await this.waitForDependencies(subQuery.dependencies, results); // Execute hop const hop = await this.executeHop(subQuery); results.push(hop); } return results; } /** * Execute sub-queries in parallel */ async executeParallel(subQueries) { // Execute all hops concurrently const promises = subQueries.map((subQuery) => this.executeHop(subQuery)); return Promise.all(promises); } /** * Execute sub-queries using hybrid strategy */ async executeHybrid(subQueries) { const results = []; const executed = new Set(); // Group sub-queries by dependency level const levels = this.groupByDependencyLevel(subQueries); // Execute each level for (const level of levels) { // Within each level, execute in parallel (limited concurrency) const batches = this.createBatches(level, this.options.maxConcurrentHops); for (const batch of batches) { const promises = batch.map((subQuery) => this.executeHop(subQuery)); const batchResults = await Promise.all(promises); results.push(...batchResults); batch.forEach((sq) => executed.add(sq.id)); } } return results; } /** * Execute a single hop (sub-query) */ async executeHop(subQuery) { const startTime = Date.now(); try { // Use context router to get results const response = await Promise.race([ this.contextRouter.route({ query: subQuery.query }), this.timeout(this.options.hopTimeout), ]); const executionTime = Date.now() - startTime; // Calculate relevance score const relevanceScore = this.calculateRelevance(response.context); return { subQueryId: subQuery.id, subQuery: subQuery.query, classification: response.classification, context: response.context, relevanceScore, executionTime, }; } catch (error) { // Return empty result on error return { subQueryId: subQuery.id, subQuery: subQuery.query, classification: { query: subQuery.query, type: "general", confidence: 0, layers: [], weights: { session: 0, project: 0, user: 0, vector: 0, graph: 0, governance: 0, }, reasoning: "Error executing hop", }, context: [], relevanceScore: 0, executionTime: Date.now() - startTime, }; } } /** * Wait for dependencies to complete */ async waitForDependencies(dependencies, results) { // Check if all dependencies are satisfied const completed = new Set(results.map((r) => r.subQueryId)); for (const depId of dependencies) { if (!completed.has(depId)) { // Wait a bit and check again (polling) await new Promise((resolve) => setTimeout(resolve, 100)); return this.waitForDependencies(dependencies, results); } } } /** * Group sub-queries by dependency level * Level 0: No dependencies * Level 1: Depends on level 0 * Level 2: Depends on level 1, etc. */ groupByDependencyLevel(subQueries) { const levels = []; const processed = new Set(); let currentLevel = subQueries.filter((sq) => sq.dependencies.length === 0); while (currentLevel.length > 0) { levels.push(currentLevel); currentLevel.forEach((sq) => processed.add(sq.id)); // Find next level (queries whose dependencies are all processed) currentLevel = subQueries.filter((sq) => !processed.has(sq.id) && sq.dependencies.every((dep) => processed.has(dep))); } return levels; } /** * Create batches for limited concurrency */ createBatches(items, batchSize) { const batches = []; for (let i = 0; i < items.length; i += batchSize) { batches.push(items.slice(i, i + batchSize)); } return batches; } /** * Calculate relevance score for context items */ calculateRelevance(context) { if (context.length === 0) return 0; // Average of individual scores const totalScore = context.reduce((sum, item) => sum + (item.score || 0.5), 0); return totalScore / context.length; } /** * Estimate token count */ estimateTokens(context) { // Rough estimate: 4 characters = 1 token const totalChars = context.reduce((sum, item) => sum + (item.content?.length || 0), 0); return Math.ceil(totalChars / 4); } /** * Timeout helper */ timeout(ms) { return new Promise((_, reject) => { setTimeout(() => reject(new Error("Hop timeout")), ms); }); } } //# sourceMappingURL=multi-hop-router.js.map