Skip to main content
Glama
synthesis-engine.ts14.6 kB
/** * Result Synthesis Engine * * Integrates outputs from all four parallel reasoning streams into a coherent * result with attributed insights, ranked recommendations, preserved conflicts, * and quality assessment. */ import { ConflictResolutionEngine } from "./conflict-resolution-engine"; import { ConflictSeverity, type AttributedInsight, type Conflict, type Insight, type QualityAssessment, type Recommendation, type StreamResult, type SynthesizedResult, } from "./types"; /** * Result Synthesis Engine * * Synthesizes results from parallel reasoning streams into a unified, * coherent output with insight attribution, recommendation ranking, * conflict preservation, and quality assessment. */ export class ResultSynthesisEngine { private readonly conflictEngine: ConflictResolutionEngine; constructor() { this.conflictEngine = new ConflictResolutionEngine(); } /** * Synthesize results from multiple reasoning streams * * @param results - Array of stream results to synthesize * @returns Synthesized result with integrated insights and recommendations */ synthesizeResults(results: StreamResult[]): SynthesizedResult { const startTime = performance.now(); // Handle empty results if (results.length === 0) { return this.createEmptySynthesis("Unable to synthesize results: no streams provided"); } // Filter successful streams const successfulResults = results.filter((r) => r.status === "completed"); // Handle no successful streams if (successfulResults.length === 0) { return this.createEmptySynthesis("Unable to synthesize results: no successful streams"); } // Detect conflicts BEFORE synthesis using ConflictResolutionEngine const conflicts = this.conflictEngine.detectConflicts(successfulResults); // Extract all insights from successful streams const allInsights: Insight[] = []; for (const result of successfulResults) { allInsights.push(...result.insights); } // Attribute and rank insights const attributedInsights = this.attributeInsights(allInsights, successfulResults); // Extract recommendations from insights and conclusions const recommendations = this.extractRecommendations(successfulResults); const rankedRecommendations = this.rankRecommendations(recommendations); // Generate integrated conclusion (guided by resolution frameworks) const conclusion = this.generateConclusion(successfulResults, conflicts); // Calculate overall confidence const confidence = this.calculateOverallConfidence(successfulResults); // Create synthesis result const synthesis: SynthesizedResult = { conclusion, insights: attributedInsights, recommendations: rankedRecommendations, conflicts, confidence, quality: { overallScore: 0, coherence: 0, completeness: 0, consistency: 0, insightQuality: 0, recommendationQuality: 0, }, metadata: { streamsUsed: successfulResults.map((r) => r.streamType), synthesisTime: Math.max(1, Math.round(performance.now() - startTime)), timestamp: new Date(), }, }; // Assess quality synthesis.quality = this.assessSynthesisQuality(synthesis); return synthesis; } /** * Attribute insights to source streams * * @param insights - Array of insights to attribute * @param results - Array of stream results for context * @returns Array of attributed insights */ attributeInsights(insights: Insight[], results: StreamResult[]): AttributedInsight[] { // Group similar insights const insightGroups = new Map<string, Insight[]>(); for (const insight of insights) { const normalized = insight.content.toLowerCase().trim(); const existing = insightGroups.get(normalized); if (existing) { existing.push(insight); } else { insightGroups.set(normalized, [insight]); } } // Create attributed insights const attributed: AttributedInsight[] = []; for (const [, group] of insightGroups.entries()) { // Collect unique sources const sources = Array.from(new Set(group.map((i) => i.source))); // Calculate average confidence and importance const avgConfidence = group.reduce((sum, i) => sum + i.confidence, 0) / group.length; const avgImportance = group.reduce((sum, i) => sum + i.importance, 0) / group.length; // Extract evidence from stream reasoning const evidence: string[] = []; for (const source of sources) { const result = results.find((r) => r.streamType === source); if (result) { evidence.push(...result.reasoning.slice(0, 2)); // Take first 2 reasoning steps } } attributed.push({ content: group[0].content, // Use original content (not normalized) sources, confidence: avgConfidence, importance: avgImportance, evidence, }); } // Rank by importance * confidence attributed.sort((a, b) => { const scoreA = a.importance * a.confidence; const scoreB = b.importance * b.confidence; return scoreB - scoreA; }); // Filter low-importance insights (threshold: 0.4) after ranking const filtered = attributed.filter((i) => i.importance > 0.4); return filtered; } /** * Rank recommendations by priority and confidence * * @param recommendations - Array of recommendations to rank * @returns Array of ranked recommendations */ rankRecommendations(recommendations: Recommendation[]): Recommendation[] { // Group similar recommendations const recGroups = new Map<string, Recommendation[]>(); for (const rec of recommendations) { const normalized = rec.description.toLowerCase().trim(); const existing = recGroups.get(normalized); if (existing) { existing.push(rec); } else { recGroups.set(normalized, [rec]); } } // Merge similar recommendations const merged: Recommendation[] = []; for (const [, group] of recGroups.entries()) { // Collect unique sources const sources = Array.from(new Set(group.flatMap((r) => r.sources))); // Calculate average priority and confidence const avgPriority = group.reduce((sum, r) => sum + r.priority, 0) / group.length; const avgConfidence = group.reduce((sum, r) => sum + r.confidence, 0) / group.length; // Collect all rationale and concerns const rationale = Array.from(new Set(group.flatMap((r) => r.rationale))); const concerns = Array.from(new Set(group.flatMap((r) => r.concerns))); merged.push({ description: group[0].description, sources, priority: avgPriority, confidence: avgConfidence, rationale, concerns, }); } // Rank by priority, then confidence merged.sort((a, b) => { if (Math.abs(a.priority - b.priority) > 0.01) { return b.priority - a.priority; } return b.confidence - a.confidence; }); return merged; } /** * Assess synthesis quality * * @param synthesis - Synthesized result to assess * @returns Quality assessment */ assessSynthesisQuality(synthesis: SynthesizedResult): QualityAssessment { // Assess completeness (coverage of all 4 streams) const completeness = synthesis.metadata.streamsUsed.length / 4; // Assess consistency (inverse of conflict severity) const consistency = this.assessConsistency(synthesis.conflicts); // Assess coherence (based on conclusion quality) const coherence = this.assessCoherence(synthesis.conclusion, synthesis.insights); // Assess insight quality const insightQuality = this.assessInsightQuality(synthesis.insights); // Assess recommendation quality const recommendationQuality = this.assessRecommendationQuality(synthesis.recommendations); // Calculate overall score (weighted average) const overallScore = completeness * 0.2 + consistency * 0.2 + coherence * 0.2 + insightQuality * 0.2 + recommendationQuality * 0.2; return { overallScore, coherence, completeness, consistency, insightQuality, recommendationQuality, }; } // Private helper methods private createEmptySynthesis(message: string): SynthesizedResult { return { conclusion: message, insights: [], recommendations: [], conflicts: [], confidence: 0, quality: { overallScore: 0, coherence: 0, completeness: 0, consistency: 0, insightQuality: 0, recommendationQuality: 0, }, metadata: { streamsUsed: [], synthesisTime: 0, timestamp: new Date(), }, }; } private extractRecommendations(results: StreamResult[]): Recommendation[] { const recommendations: Recommendation[] = []; for (const result of results) { // Extract recommendations from conclusions // Simple heuristic: look for action-oriented language if ( result.conclusion.toLowerCase().includes("should") || result.conclusion.toLowerCase().includes("recommend") || result.conclusion.toLowerCase().includes("suggest") ) { recommendations.push({ description: result.conclusion, sources: [result.streamType], priority: result.confidence, confidence: result.confidence, rationale: result.reasoning, concerns: [], }); } } return recommendations; } private generateConclusion(results: StreamResult[], conflicts: Conflict[]): string { if (results.length === 0) { return "No conclusion available"; } // If no conflicts, synthesize conclusions if (conflicts.length === 0) { const conclusions = results.map((r) => r.conclusion).filter((c) => c.length > 0); return `Integrated conclusion: ${conclusions.join(". ")}`; } // Categorize conflicts by severity const criticalConflicts = conflicts.filter((c) => c.severity === ConflictSeverity.CRITICAL); const highConflicts = conflicts.filter((c) => c.severity === ConflictSeverity.HIGH); const otherConflicts = conflicts.filter( (c) => c.severity !== ConflictSeverity.CRITICAL && c.severity !== ConflictSeverity.HIGH ); const conclusions = results.map((r) => r.conclusion).filter((c) => c.length > 0); let conclusionText = `Multiple perspectives identified: ${conclusions.join(". ")}`; // Highlight critical conflicts prominently if (criticalConflicts.length > 0) { const criticalDescriptions = criticalConflicts .map((c) => this.formatConflictWithResolution(c)) .join(" "); conclusionText += ` CRITICAL CONFLICTS REQUIRING IMMEDIATE ATTENTION: ${criticalDescriptions}`; } // Highlight high-severity conflicts if (highConflicts.length > 0) { const highDescriptions = highConflicts .map((c) => this.formatConflictWithResolution(c)) .join(" "); conclusionText += ` HIGH-PRIORITY CONFLICTS: ${highDescriptions}`; } // Mention other conflicts if (otherConflicts.length > 0) { conclusionText += ` (${otherConflicts.length} additional conflicts detected)`; } return conclusionText; } /** * Format a conflict with its resolution suggestion */ private formatConflictWithResolution(conflict: Conflict): string { let formatted = conflict.description; // Add resolution suggestion if available if (conflict.resolutionFramework) { formatted += ` Resolution: ${conflict.resolutionFramework.recommendedAction}`; } return formatted; } private calculateOverallConfidence(results: StreamResult[]): number { if (results.length === 0) { return 0; } const totalConfidence = results.reduce((sum, r) => sum + r.confidence, 0); return totalConfidence / results.length; } private assessConsistency(conflicts: Conflict[]): number { if (conflicts.length === 0) { return 1.0; } // Calculate average conflict severity (map enum to numeric score) const severityScores = conflicts.map((c) => this.severityToScore(c.severity)); const avgSeverity = severityScores.reduce((sum, score) => sum + score, 0) / severityScores.length; // Consistency is inverse of severity return 1.0 - avgSeverity; } private severityToScore(severity: ConflictSeverity): number { switch (severity) { case ConflictSeverity.LOW: return 0.25; case ConflictSeverity.MEDIUM: return 0.5; case ConflictSeverity.HIGH: return 0.75; case ConflictSeverity.CRITICAL: return 1.0; default: return 0.5; } } private assessCoherence(conclusion: string, insights: AttributedInsight[]): number { // Simple heuristic: longer conclusions with more insights are more coherent if (conclusion.length === 0) { return 0; } const lengthScore = Math.min(conclusion.length / 100, 1.0); const insightScore = Math.min(insights.length / 5, 1.0); return (lengthScore + insightScore) / 2; } private assessInsightQuality(insights: AttributedInsight[]): number { if (insights.length === 0) { return 0; } // Quality based on average confidence, importance, and evidence const avgConfidence = insights.reduce((sum, i) => sum + i.confidence, 0) / insights.length; const avgImportance = insights.reduce((sum, i) => sum + i.importance, 0) / insights.length; const avgEvidence = insights.reduce((sum, i) => sum + i.evidence.length, 0) / insights.length; const evidenceScore = Math.min(avgEvidence / 3, 1.0); return (avgConfidence + avgImportance + evidenceScore) / 3; } private assessRecommendationQuality(recommendations: Recommendation[]): number { if (!recommendations || recommendations.length === 0) { return 0; } // Quality based on average priority, confidence, and rationale const avgPriority = recommendations.reduce((sum, r) => sum + r.priority, 0) / recommendations.length; const avgConfidence = recommendations.reduce((sum, r) => sum + r.confidence, 0) / recommendations.length; const avgRationale = recommendations.reduce((sum, r) => sum + r.rationale.length, 0) / recommendations.length; const rationaleScore = Math.min(avgRationale / 3, 1.0); return (avgPriority + avgConfidence + rationaleScore) / 3; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server