Spaces:
Running
Running
File size: 12,126 Bytes
7c3f0ce | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | """
Synthesis Engine - Combines all agent perspectives into a unified multi-perspective response.
Takes the concept, all agent analyses, and critic feedback, then produces
a synthesized explanation that highlights how different perspectives complement
each other. Includes a Final Integrated Understanding section.
"""
import random
import re
class SynthesisEngine:
"""Combines multi-agent analyses into coherent synthesized responses."""
# Opening templates that set up the multi-perspective frame
_opening_templates = [
(
"To understand '{concept}' with genuine depth, we must examine it through "
"multiple lenses, each revealing structure that the others miss."
),
(
"'{concept}' resists single-framework analysis. Its full meaning emerges "
"only at the intersection of several distinct modes of reasoning."
),
(
"A comprehensive understanding of '{concept}' requires weaving together "
"insights from fundamentally different ways of thinking."
),
(
"No single perspective captures '{concept}' adequately. What follows is "
"an integrated analysis drawing on physics, philosophy, ethics, creativity, "
"and human experience."
),
(
"The richness of '{concept}' becomes apparent only when we hold multiple "
"analytical frameworks simultaneously and let them inform each other."
),
]
# Bridge templates connecting one perspective to another
_bridge_templates = [
"Where {agent_a} reveals {insight_a}, {agent_b} adds the crucial dimension of {insight_b}.",
"The {agent_a} analysis and the {agent_b} analysis converge on a shared insight: {shared}.",
"What appears as {aspect_a} from the {agent_a} perspective is revealed as {aspect_b} when viewed through {agent_b}.",
"The tension between {agent_a}'s emphasis on {focus_a} and {agent_b}'s emphasis on {focus_b} is productive, not contradictory.",
"{agent_a} identifies the mechanism; {agent_b} identifies the meaning.",
"Combining {agent_a}'s structural analysis with {agent_b}'s human-centered analysis yields a fuller picture.",
]
# Closing templates for the Final Integrated Understanding
_closing_templates = [
(
"**Final Integrated Understanding:** {concept} is simultaneously a "
"{physical_desc}, a {philosophical_desc}, a {ethical_desc}, a "
"{creative_desc}, and a {human_desc}. These are not competing descriptions "
"but complementary facets of a single complex reality. The most robust "
"understanding holds all five in view, using each to compensate for the "
"blind spots of the others."
),
(
"**Final Integrated Understanding:** The multi-perspective analysis reveals "
"that {concept} cannot be reduced to any single framework without distortion. "
"The physical analysis provides causal grounding, the philosophical analysis "
"excavates hidden assumptions, the ethical analysis maps the stakes, the "
"creative analysis opens new solution spaces, and the empathic analysis "
"anchors everything in lived human experience. Together they constitute "
"not a list of separate views but an integrated understanding richer than "
"any view alone."
),
(
"**Final Integrated Understanding:** What emerges from this multi-lens "
"examination of {concept} is not a single 'correct' interpretation but a "
"structured understanding of how different valid interpretations relate to "
"each other. The causal structure identified by physics, the meaning "
"structure identified by philosophy, the value structure identified by "
"ethics, the possibility structure identified by creative reasoning, and "
"the experience structure identified by empathy are all real and all "
"essential. Wisdom lies in knowing which lens to apply in which context "
"and how to translate insights between them."
),
]
def synthesize(
self,
concept: str,
analyses: dict[str, str],
critique: dict,
) -> str:
"""Produce a synthesized multi-perspective response.
Args:
concept: The original concept.
analyses: Dict mapping agent_name -> analysis_text.
critique: Output from CriticAgent.evaluate_ensemble().
Returns:
A synthesized text of 200-400 words.
"""
sections = []
# 1. Opening
opening = random.choice(self._opening_templates).replace("{concept}", concept)
sections.append(opening)
# 2. Per-perspective summaries (compressed)
perspective_summaries = self._extract_perspective_summaries(analyses)
for agent_name, summary in perspective_summaries.items():
sections.append(f"**{agent_name} perspective:** {summary}")
# 3. Cross-perspective bridges (pick 2-3)
bridges = self._generate_bridges(analyses, perspective_summaries)
if bridges:
sections.append("") # blank line for readability
for bridge in bridges[:2]:
sections.append(bridge)
# 4. Incorporate critic insights
critic_section = self._incorporate_critique(critique)
if critic_section:
sections.append("")
sections.append(critic_section)
# 5. Final Integrated Understanding
closing = self._generate_closing(concept, perspective_summaries)
sections.append("")
sections.append(closing)
raw_synthesis = "\n\n".join(sections)
# Trim to 200-400 words if needed
return self._trim_to_target(raw_synthesis, min_words=200, max_words=400)
def _extract_perspective_summaries(
self, analyses: dict[str, str]
) -> dict[str, str]:
"""Extract a 1-2 sentence summary from each agent's analysis."""
summaries = {}
for agent_name, text in analyses.items():
sentences = [s.strip() for s in re.split(r'(?<=[.!?])\s+', text) if s.strip()]
if len(sentences) >= 3:
# Take the 2nd and 3rd sentences (skip the opening framing)
summary = " ".join(sentences[1:3])
elif len(sentences) >= 1:
summary = sentences[0]
else:
summary = text[:200]
# Trim to ~40 words
words = summary.split()
if len(words) > 45:
summary = " ".join(words[:40]) + "..."
summaries[agent_name] = summary
return summaries
def _generate_bridges(
self,
analyses: dict[str, str],
summaries: dict[str, str],
) -> list[str]:
"""Generate cross-perspective bridge statements."""
bridges = []
agent_names = list(analyses.keys())
# Define perspective focus areas for bridge generation
focus_map = {
"Newton": "causal mechanisms and measurable dynamics",
"Quantum": "uncertainty, probability, and the limits of definite knowledge",
"Ethics": "moral stakes, fairness, and human impact",
"Philosophy": "foundational assumptions and the structure of meaning",
"DaVinci": "creative possibilities and cross-domain innovation",
"Empathy": "emotional reality and lived human experience",
}
# Generate a few meaningful bridges
if len(agent_names) >= 2:
pairs = []
for i in range(len(agent_names)):
for j in range(i + 1, len(agent_names)):
pairs.append((agent_names[i], agent_names[j]))
random.shuffle(pairs)
for name_a, name_b in pairs[:3]:
focus_a = focus_map.get(name_a, "its analytical focus")
focus_b = focus_map.get(name_b, "its analytical focus")
template = random.choice(self._bridge_templates)
bridge = template.format(
agent_a=name_a,
agent_b=name_b,
insight_a=focus_a,
insight_b=focus_b,
shared="the importance of understanding the full system rather than isolated parts",
aspect_a="a structural feature",
aspect_b="a deeply human concern",
focus_a=focus_a,
focus_b=focus_b,
)
bridges.append(bridge)
return bridges
def _incorporate_critique(self, critique: dict) -> str:
"""Turn critic feedback into a synthesis-relevant observation."""
parts = []
if critique.get("missing_perspectives"):
gap = critique["missing_perspectives"][0]
# Extract just the perspective name
parts.append(
f"A notable gap in the analysis is the limited attention to "
f"{gap.split('lacks a ')[1].split(' perspective')[0] if 'lacks a ' in gap else 'additional'} "
f"dimensions, which future analysis should address."
)
if critique.get("improvement_suggestions"):
suggestion = critique["improvement_suggestions"][0]
# Compress the suggestion
words = suggestion.split()
if len(words) > 25:
suggestion = " ".join(words[:25]) + "..."
parts.append(f"The critic notes: {suggestion}")
overall = critique.get("overall_quality", 0)
if overall >= 0.75:
parts.append(
"Overall, the multi-perspective ensemble achieves strong analytical "
"coverage with good complementarity between viewpoints."
)
elif overall >= 0.5:
parts.append(
"The ensemble provides reasonable coverage but would benefit from "
"deeper engagement between perspectives."
)
return " ".join(parts) if parts else ""
def _generate_closing(
self, concept: str, summaries: dict[str, str]
) -> str:
"""Generate the Final Integrated Understanding section."""
template = random.choice(self._closing_templates)
# Build descriptors from available perspectives
descriptors = {
"physical_desc": "system governed by causal dynamics and conservation principles",
"philosophical_desc": "concept whose meaning depends on the framework from which it is examined",
"ethical_desc": "domain of genuine moral stakes affecting real people",
"creative_desc": "space of untapped possibilities waiting for cross-domain insight",
"human_desc": "lived experience with emotional texture that abstract analysis alone cannot capture",
}
result = template
result = result.replace("{concept}", concept)
for key, value in descriptors.items():
result = result.replace("{" + key + "}", value)
return result
def _trim_to_target(
self, text: str, min_words: int = 200, max_words: int = 400
) -> str:
"""Trim or pad text to fall within the target word range."""
words = text.split()
if len(words) > max_words:
# Trim from the middle sections, preserving opening and closing
lines = text.split("\n\n")
while len(" ".join(lines).split()) > max_words and len(lines) > 3:
# Remove the longest middle section
middle_indices = list(range(1, len(lines) - 1))
if not middle_indices:
break
longest_idx = max(middle_indices, key=lambda i: len(lines[i].split()))
lines.pop(longest_idx)
return "\n\n".join(lines)
return text
|