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