UNPKG

arela

Version:

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

201 lines 6.7 kB
import { RelevanceScorer } from "./scorer.js"; import { SemanticDeduplicator } from "./dedup.js"; /** * ResultMerger - Merges results from multiple memory layers * * Pipeline: * 1. Collect items from all layers * 2. Score by relevance * 3. Deduplicate similar items * 4. Sort by score * 5. Filter by minimum score * 6. Truncate to token limit */ export class ResultMerger { scorer; deduplicator; constructor() { this.scorer = new RelevanceScorer(); this.deduplicator = new SemanticDeduplicator(); } /** * Merge results from multiple layers into a single ranked list */ merge(routingResult, options) { const startTime = Date.now(); // Apply deduplication threshold if provided if (options.deduplicationThreshold !== undefined) { this.deduplicator.setThreshold(options.deduplicationThreshold); } // 1. Collect all items from all layers const allItems = this.collectItems(routingResult); // 2. Score items by relevance const scored = this.scorer.score(routingResult.query, allItems); // 3. Deduplicate similar items (keeps highest scores) const deduplicated = this.deduplicator.deduplicate(scored); // 4. Sort by score (descending) const sorted = deduplicated.sort((a, b) => b.score - a.score); // 5. Filter by minimum score const minScore = options.minScore ?? 0.3; const filtered = sorted.filter((item) => item.score >= minScore); // 6. Truncate to token limit const maxTokens = options.maxTokens ?? 10000; const truncated = this.truncateToTokens(filtered, maxTokens); const fusionTime = Date.now() - startTime; return { items: truncated, stats: { totalItems: allItems.length, deduplicatedItems: deduplicated.length, finalItems: truncated.length, estimatedTokens: this.estimateTokens(truncated), fusionTime, }, }; } /** * Collect all items from routing result */ collectItems(routingResult) { const allItems = []; for (const layerResult of routingResult.results) { // Skip layers with errors if (layerResult.error || !layerResult.items) { continue; } // Extract weight for this layer const layerWeight = routingResult.classification.weights[layerResult.layer] ?? 1.0; // Handle different item formats const items = this.normalizeItems(layerResult.items, layerResult.layer, layerWeight); allItems.push(...items); } return allItems; } /** * Normalize items to consistent MemoryItem format */ normalizeItems(items, layer, layerWeight) { if (!items) return []; // Handle array of items if (Array.isArray(items)) { return items .map((item) => this.normalizeItem(item, layer, layerWeight)) .filter((item) => item !== null); } // Handle single item const normalized = this.normalizeItem(items, layer, layerWeight); return normalized ? [normalized] : []; } /** * Normalize a single item */ normalizeItem(item, layer, layerWeight) { if (!item) return null; // If already a MemoryItem if (item.content !== undefined) { return { content: item.content, timestamp: item.timestamp, layer: item.layer || layer, layerWeight: item.layerWeight || layerWeight, metadata: item.metadata || {}, }; } // If it's a string if (typeof item === "string") { return { content: item, layer: layer, layerWeight, metadata: {}, }; } // If it's an object with text/message field if (typeof item === "object") { const content = item.text || item.message || item.data || item.value || JSON.stringify(item); return { content, timestamp: item.timestamp || item.created_at || item.createdAt, layer: layer, layerWeight, metadata: item, }; } return null; } /** * Truncate items to fit within token limit */ truncateToTokens(items, maxTokens) { const result = []; let tokens = 0; for (const item of items) { const itemTokens = this.estimateItemTokens(item); if (tokens + itemTokens > maxTokens) { break; } result.push({ content: this.getContent(item), score: item.score, layer: item.layer, metadata: item.metadata || {}, }); tokens += itemTokens; } return result; } /** * Estimate tokens for a single item */ estimateItemTokens(item) { const content = this.getContent(item); // Rough estimate: ~4 chars per token return Math.ceil(content.length / 4); } /** * Estimate total tokens in items */ estimateTokens(items) { return items.reduce((total, item) => { return total + this.estimateItemTokens(item); }, 0); } /** * Extract content from item */ getContent(item) { if (typeof item.content === "string") { return item.content; } if (item.content && typeof item.content === "object") { return JSON.stringify(item.content); } return ""; } /** * Apply diversity boosting (prioritize items from different layers) */ applyDiversityBoost(items, diversityWeight) { if (diversityWeight === 0) return items; const layerCounts = {}; return items.map((item) => { const layer = item.layer || "unknown"; layerCounts[layer] = (layerCounts[layer] || 0) + 1; // Reduce score for items from over-represented layers const penalty = Math.log(layerCounts[layer] + 1) * diversityWeight; const adjustedScore = Math.max(0, item.score - penalty); return { ...item, score: adjustedScore, }; }); } } //# sourceMappingURL=merger.js.map