hegelion 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. hegelion/__init__.py +45 -0
  2. hegelion/core/__init__.py +29 -0
  3. hegelion/core/agent.py +166 -0
  4. hegelion/core/autocoding_state.py +293 -0
  5. hegelion/core/backends.py +442 -0
  6. hegelion/core/cache.py +92 -0
  7. hegelion/core/config.py +276 -0
  8. hegelion/core/core.py +649 -0
  9. hegelion/core/engine.py +865 -0
  10. hegelion/core/logging_utils.py +67 -0
  11. hegelion/core/models.py +293 -0
  12. hegelion/core/parsing.py +271 -0
  13. hegelion/core/personas.py +81 -0
  14. hegelion/core/prompt_autocoding.py +353 -0
  15. hegelion/core/prompt_dialectic.py +414 -0
  16. hegelion/core/prompts.py +127 -0
  17. hegelion/core/schema.py +67 -0
  18. hegelion/core/validation.py +68 -0
  19. hegelion/council.py +254 -0
  20. hegelion/examples_data/__init__.py +6 -0
  21. hegelion/examples_data/glm4_6_examples.jsonl +2 -0
  22. hegelion/judge.py +230 -0
  23. hegelion/mcp/__init__.py +3 -0
  24. hegelion/mcp/server.py +918 -0
  25. hegelion/scripts/hegelion_agent_cli.py +90 -0
  26. hegelion/scripts/hegelion_bench.py +117 -0
  27. hegelion/scripts/hegelion_cli.py +497 -0
  28. hegelion/scripts/hegelion_dataset.py +99 -0
  29. hegelion/scripts/hegelion_eval.py +137 -0
  30. hegelion/scripts/mcp_setup.py +150 -0
  31. hegelion/search_providers.py +151 -0
  32. hegelion/training/__init__.py +7 -0
  33. hegelion/training/datasets.py +123 -0
  34. hegelion/training/generator.py +232 -0
  35. hegelion/training/mlx_scu_trainer.py +379 -0
  36. hegelion/training/mlx_trainer.py +181 -0
  37. hegelion/training/unsloth_trainer.py +136 -0
  38. hegelion-0.4.0.dist-info/METADATA +295 -0
  39. hegelion-0.4.0.dist-info/RECORD +43 -0
  40. hegelion-0.4.0.dist-info/WHEEL +5 -0
  41. hegelion-0.4.0.dist-info/entry_points.txt +8 -0
  42. hegelion-0.4.0.dist-info/licenses/LICENSE +21 -0
  43. hegelion-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,414 @@
1
+ """Prompt-driven dialectical reasoning for any LLM via MCP.
2
+
3
+ This module provides a way to run dialectical reasoning without making external API calls.
4
+ Instead, it returns structured prompts that guide the calling LLM through the process.
5
+ Perfect for Cursor, Claude Desktop, or any MCP-compatible environment.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Dict, List, Optional, Any
11
+ from dataclasses import dataclass
12
+
13
+
14
+ @dataclass
15
+ class DialecticalPrompt:
16
+ """A structured prompt for dialectical reasoning."""
17
+
18
+ phase: str
19
+ prompt: str
20
+ instructions: str
21
+ expected_format: str
22
+
23
+
24
+ class PromptDrivenDialectic:
25
+ """Orchestrates dialectical reasoning using prompts instead of API calls."""
26
+
27
+ def __init__(self):
28
+ self.conversation_state = {}
29
+
30
+ def generate_thesis_prompt(self, query: str) -> DialecticalPrompt:
31
+ """Generate a prompt for the thesis phase."""
32
+ return DialecticalPrompt(
33
+ phase="thesis",
34
+ prompt=f"""You are in the THESIS phase of Hegelian dialectical reasoning.
35
+
36
+ QUERY: {query}
37
+
38
+ Your task:
39
+ 1. Provide a comprehensive, well-reasoned initial position on this query
40
+ 2. Consider multiple perspectives and build a strong foundational argument
41
+ 3. Be thorough but clear in your reasoning
42
+ 4. Acknowledge uncertainty where appropriate
43
+ 5. Think step by step, then present a polished thesis
44
+
45
+ Generate your THESIS response now.""",
46
+ instructions="Respond with a clear, well-structured thesis that establishes your initial position on the query.",
47
+ expected_format="Free-form text thesis response",
48
+ )
49
+
50
+ def generate_antithesis_prompt(
51
+ self, query: str, thesis: str, use_search_context: bool = False
52
+ ) -> DialecticalPrompt:
53
+ """Generate a prompt for the antithesis phase."""
54
+
55
+ search_instruction = ""
56
+ if use_search_context:
57
+ search_instruction = """
58
+ IMPORTANT: Before critiquing, use available search tools to find current information about this topic. Ground your critique in real-world evidence and recent developments."""
59
+
60
+ return DialecticalPrompt(
61
+ phase="antithesis",
62
+ prompt=f"""You are in the ANTITHESIS phase of Hegelian dialectical reasoning.
63
+
64
+ ORIGINAL QUERY: {query}
65
+
66
+ THESIS TO CRITIQUE: {thesis}
67
+ {search_instruction}
68
+
69
+ Your task as the critical voice:
70
+ 1. Find contradictions, inconsistencies, or logical gaps in the thesis
71
+ 2. Identify unexamined assumptions and hidden premises
72
+ 3. Propose alternative framings that challenge the thesis
73
+ 4. Find edge cases or scenarios where the thesis breaks down
74
+ 5. Be adversarial but intellectually honest
75
+
76
+ For each significant problem you identify, use this EXACT format:
77
+ CONTRADICTION: [brief description]
78
+ EVIDENCE: [detailed explanation of why this is problematic]
79
+
80
+ Generate your ANTITHESIS critique now.""",
81
+ instructions="Respond with a rigorous critique that identifies specific contradictions and weaknesses in the thesis.",
82
+ expected_format="Text with embedded CONTRADICTION: and EVIDENCE: sections",
83
+ )
84
+
85
+ def generate_council_prompts(self, query: str, thesis: str) -> List[DialecticalPrompt]:
86
+ """Generate prompts for multi-perspective council critique."""
87
+
88
+ council_members = [
89
+ {
90
+ "name": "The Logician",
91
+ "expertise": "Logical consistency and formal reasoning",
92
+ "focus": "logical fallacies, internal contradictions, invalid inferences, missing premises",
93
+ },
94
+ {
95
+ "name": "The Empiricist",
96
+ "expertise": "Evidence, facts, and empirical grounding",
97
+ "focus": "factual errors, unsupported claims, missing evidence, contradictions with established science",
98
+ },
99
+ {
100
+ "name": "The Ethicist",
101
+ "expertise": "Ethical implications and societal impact",
102
+ "focus": "potential harm, ethical blind spots, fairness issues, unintended consequences",
103
+ },
104
+ ]
105
+
106
+ prompts = []
107
+ for member in council_members:
108
+ prompt = DialecticalPrompt(
109
+ phase=f"council_{member['name'].lower().replace(' ', '_')}",
110
+ prompt=f"""You are {member['name'].upper()}, an expert in {member['expertise']}.
111
+
112
+ ORIGINAL QUERY: {query}
113
+
114
+ THESIS TO CRITIQUE: {thesis}
115
+
116
+ Your expertise: {member['expertise']}
117
+ Focus specifically on: {member['focus']}
118
+
119
+ Examine the thesis from your specialized perspective and identify problems within your domain.
120
+
121
+ For each issue you identify, use this format:
122
+ CONTRADICTION: [brief description]
123
+ EVIDENCE: [detailed explanation from your expert perspective]
124
+
125
+ Generate your specialized critique now.""",
126
+ instructions=f"Respond as {member['name']} with critiques specific to {member['expertise']}",
127
+ expected_format="Text with embedded CONTRADICTION: and EVIDENCE: sections",
128
+ )
129
+ prompts.append(prompt)
130
+
131
+ return prompts
132
+
133
+ def generate_synthesis_prompt(
134
+ self,
135
+ query: str,
136
+ thesis: str,
137
+ antithesis: str,
138
+ contradictions: Optional[List[str]] = None,
139
+ ) -> DialecticalPrompt:
140
+ """Generate a prompt for the synthesis phase."""
141
+
142
+ contradictions_text = ""
143
+ if contradictions:
144
+ contradictions_text = f"""
145
+
146
+ IDENTIFIED CONTRADICTIONS:
147
+ {chr(10).join(f"- {contradiction}" for contradiction in contradictions)}"""
148
+
149
+ return DialecticalPrompt(
150
+ phase="synthesis",
151
+ prompt=f"""You are in the SYNTHESIS phase of Hegelian dialectical reasoning.
152
+
153
+ ORIGINAL QUERY: {query}
154
+
155
+ THESIS: {thesis}
156
+
157
+ ANTITHESIS (critique): {antithesis}
158
+ {contradictions_text}
159
+
160
+ Your task:
161
+ 1. Generate a SYNTHESIS that TRANSCENDS both thesis and antithesis
162
+ 2. Resolve or reframe the contradictions by finding a higher-level perspective
163
+ 3. Make predictions that NEITHER thesis nor antithesis would make alone
164
+ 4. Ensure your synthesis is testable or falsifiable when possible
165
+ 5. Propose research directions or experiments if appropriate
166
+
167
+ Requirements for a valid SYNTHESIS:
168
+ - Must not simply say "the thesis is right" or "the antithesis is right"
169
+ - Must not just say "both have merit"
170
+ - Must offer a genuinely novel perspective
171
+ - Should be more sophisticated than either original position
172
+
173
+ If the synthesis suggests new research, use this format:
174
+ RESEARCH_PROPOSAL: [brief description]
175
+ TESTABLE_PREDICTION: [specific falsifiable claim]
176
+
177
+ Generate your SYNTHESIS now.""",
178
+ instructions="Respond with a synthesis that transcends both positions and offers novel insights",
179
+ expected_format="Text with optional RESEARCH_PROPOSAL: and TESTABLE_PREDICTION: sections",
180
+ )
181
+
182
+ def generate_judge_prompt(
183
+ self, query: str, thesis: str, antithesis: str, synthesis: str
184
+ ) -> DialecticalPrompt:
185
+ """Generate a prompt for quality evaluation."""
186
+
187
+ return DialecticalPrompt(
188
+ phase="judge",
189
+ prompt=f"""You are the Iron Judge, evaluating dialectical reasoning quality.
190
+
191
+ ORIGINAL QUERY: {query}
192
+ THESIS: {thesis}
193
+ ANTITHESIS: {antithesis}
194
+ SYNTHESIS: {synthesis}
195
+
196
+ Evaluate this dialectical process on:
197
+
198
+ 1. **Thesis Quality** (0-2 points): Is the initial position well-reasoned?
199
+ 2. **Antithesis Rigor** (0-3 points): Does the critique identify genuine problems?
200
+ 3. **Synthesis Innovation** (0-3 points): Does the synthesis transcend both positions?
201
+ 4. **Critique Validity** (0-2 points): Were critiques actually addressed?
202
+
203
+ Score criteria:
204
+ - 0-3: Poor quality, major logical flaws
205
+ - 4-5: Below average, some good elements but significant issues
206
+ - 6-7: Good quality, solid reasoning with minor gaps
207
+ - 8-9: Excellent, sophisticated analysis with minimal flaws
208
+ - 10: Outstanding, exemplary dialectical reasoning
209
+
210
+ Respond with EXACTLY this format:
211
+ SCORE: [integer 0-10]
212
+ CRITIQUE_VALIDITY: [true/false]
213
+ REASONING: [detailed explanation]
214
+ STRENGTHS: [specific areas of excellence]
215
+ IMPROVEMENTS: [specific areas needing work]""",
216
+ instructions="Evaluate the dialectical quality and provide structured feedback",
217
+ expected_format="Structured response with SCORE:, CRITIQUE_VALIDITY:, REASONING:, STRENGTHS:, IMPROVEMENTS:",
218
+ )
219
+
220
+
221
+ def create_dialectical_workflow(
222
+ query: str,
223
+ use_search: bool = False,
224
+ use_council: bool = False,
225
+ use_judge: bool = False,
226
+ ) -> Dict[str, Any]:
227
+ """Create a complete dialectical workflow as structured prompts.
228
+
229
+ Returns a workflow that can be executed by any LLM via MCP.
230
+ """
231
+
232
+ dialectic = PromptDrivenDialectic()
233
+ workflow = {"query": query, "workflow_type": "prompt_driven_dialectic", "steps": []}
234
+
235
+ # Step 1: Thesis
236
+ workflow["steps"].append(
237
+ {
238
+ "step": 1,
239
+ "name": "Generate Thesis",
240
+ "prompt": dialectic.generate_thesis_prompt(query).__dict__,
241
+ }
242
+ )
243
+
244
+ # Step 2: Antithesis (standard or council-based)
245
+ if use_council:
246
+ council_prompts = dialectic.generate_council_prompts(query, "{{thesis_from_step_1}}")
247
+ antithesis_refs = []
248
+ for i, council_prompt in enumerate(council_prompts):
249
+ step_num = 2 + i
250
+ workflow["steps"].append(
251
+ {
252
+ "step": step_num,
253
+ "name": f"Council Critique: {council_prompt.phase}",
254
+ "prompt": council_prompt.__dict__,
255
+ }
256
+ )
257
+ antithesis_refs.append(
258
+ f"### {council_prompt.phase.replace('_', ' ').title()}\n{{{{{council_prompt.phase}_from_step_{step_num}}}}}"
259
+ )
260
+
261
+ antithesis_step = 2 + len(council_prompts)
262
+ antithesis_output_ref = "\n\n".join(antithesis_refs)
263
+ else:
264
+ workflow["steps"].append(
265
+ {
266
+ "step": 2,
267
+ "name": "Generate Antithesis",
268
+ "prompt": dialectic.generate_antithesis_prompt(
269
+ query, "{{thesis_from_step_1}}", use_search
270
+ ).__dict__,
271
+ }
272
+ )
273
+ antithesis_step = 3
274
+ antithesis_output_ref = "{{antithesis_from_step_2}}"
275
+
276
+ # Step 3: Synthesis
277
+ workflow["steps"].append(
278
+ {
279
+ "step": antithesis_step,
280
+ "name": "Generate Synthesis",
281
+ "prompt": dialectic.generate_synthesis_prompt(
282
+ query, "{{thesis_from_step_1}}", antithesis_output_ref
283
+ ).__dict__,
284
+ }
285
+ )
286
+
287
+ # Step 4: Judge (optional)
288
+ if use_judge:
289
+ workflow["steps"].append(
290
+ {
291
+ "step": antithesis_step + 1,
292
+ "name": "Evaluate Quality",
293
+ "prompt": dialectic.generate_judge_prompt(
294
+ query,
295
+ "{{thesis_from_step_1}}",
296
+ antithesis_output_ref,
297
+ f"{{synthesis_from_step_{antithesis_step}}}",
298
+ ).__dict__,
299
+ }
300
+ )
301
+
302
+ workflow["instructions"] = {
303
+ "execution_mode": "sequential",
304
+ "description": "Execute each step in order, using outputs from previous steps as inputs to later steps",
305
+ "variable_substitution": "Replace {{variable_name}} with actual outputs from previous steps",
306
+ "final_output": "Combine all outputs into a structured HegelionResult",
307
+ }
308
+
309
+ return workflow
310
+
311
+
312
+ def create_single_shot_dialectic_prompt(
313
+ query: str,
314
+ use_search: bool = False,
315
+ use_council: bool = False,
316
+ response_style: str = "sections",
317
+ ) -> str:
318
+ """Create a single comprehensive prompt for dialectical reasoning.
319
+
320
+ This is for models that can handle complex multi-step reasoning in one go.
321
+ """
322
+
323
+ search_instruction = ""
324
+ if use_search:
325
+ search_instruction = """
326
+ Before beginning, use available search tools to gather current information about this topic."""
327
+
328
+ council_instruction = ""
329
+ if use_council:
330
+ council_instruction = """
331
+ For the ANTITHESIS phase, adopt three distinct critical perspectives:
332
+ - THE LOGICIAN: Focus on logical consistency and formal reasoning
333
+ - THE EMPIRICIST: Focus on evidence, facts, and empirical grounding
334
+ - THE ETHICIST: Focus on ethical implications and societal impact
335
+
336
+ Generate critiques from each perspective, then synthesize them."""
337
+ if response_style == "json":
338
+ output_instructions = f"""Return ONLY a JSON object with this shape:
339
+ {{
340
+ "query": "{query}",
341
+ "thesis": "...",
342
+ "antithesis": "...",
343
+ "synthesis": "...",
344
+ "contradictions": [
345
+ {{"description": "...", "evidence": "..."}}
346
+ ],
347
+ "research_proposals": [
348
+ {{"proposal": "...", "testable_prediction": "..."}}
349
+ ]
350
+ }}
351
+ No markdown, no commentary outside the JSON."""
352
+ elif response_style == "synthesis_only":
353
+ output_instructions = """Return ONLY the SYNTHESIS as 2-3 tight paragraphs. Do not include thesis, antithesis, headings, or lists."""
354
+ elif response_style == "conversational":
355
+ output_instructions = """Adopt a natural, conversational tone. Present the dialectical analysis as if you are a thoughtful colleague explaining your reasoning.
356
+
357
+ Structure:
358
+ 1. Start with your initial thoughts (Thesis)
359
+ 2. Then, "but on the other hand..." (Antithesis)
360
+ 3. Finally, "so perhaps the best way forward is..." (Synthesis)
361
+
362
+ Avoid rigid headings like ## THESIS. Use natural transitions."""
363
+ elif response_style == "bullet_points":
364
+ output_instructions = """Format the response as a concise set of bullet points.
365
+
366
+ * **Thesis**: [Key point]
367
+ * **Antithesis**: [Key counter-point]
368
+ * **Synthesis**: [Resolution]
369
+
370
+ Keep it brief and scannable."""
371
+ else:
372
+ output_instructions = f"""Structure your complete response as:
373
+
374
+ # DIALECTICAL ANALYSIS: {query}
375
+
376
+ ## THESIS
377
+ [Your initial position]
378
+
379
+ ## ANTITHESIS
380
+ [Your critical examination]
381
+
382
+ ## SYNTHESIS
383
+ [Your transcendent resolution]
384
+
385
+ ## CONTRADICTIONS IDENTIFIED
386
+ 1. [Contradiction 1]: [Evidence]
387
+ 2. [Contradiction 2]: [Evidence]
388
+
389
+ ## RESEARCH PROPOSALS
390
+ 1. [Proposal 1]: [Testable prediction]"""
391
+
392
+ return f"""You will now perform Hegelian dialectical reasoning on the following query using a three-phase process: THESIS → ANTITHESIS → SYNTHESIS.
393
+ {search_instruction}
394
+
395
+ QUERY: {query}
396
+
397
+ Execute the following phases:
398
+
399
+ **PHASE 1 - THESIS:**
400
+ Generate a comprehensive initial position on the query. Be thorough, well-reasoned, and consider multiple perspectives while establishing your foundational argument.
401
+
402
+ **PHASE 2 - ANTITHESIS:**{council_instruction}
403
+ Critically examine your thesis. Find contradictions, logical gaps, unexamined assumptions, and alternative framings. For each problem, use:
404
+ CONTRADICTION: [description]
405
+ EVIDENCE: [explanation]
406
+
407
+ **PHASE 3 - SYNTHESIS:**
408
+ Transcend both thesis and antithesis with a novel perspective that resolves the contradictions. Make predictions neither position would make alone. Include research proposals if appropriate:
409
+ RESEARCH_PROPOSAL: [description]
410
+ TESTABLE_PREDICTION: [falsifiable claim]
411
+
412
+ {output_instructions}
413
+
414
+ Begin your dialectical analysis now."""
@@ -0,0 +1,127 @@
1
+ """Prompt templates for the Hegelion dialectical phases."""
2
+
3
+ THESIS_PROMPT = """You are in the THESIS phase of Hegelian dialectical reasoning.
4
+
5
+ Original question:
6
+ {query}
7
+
8
+ Your task:
9
+ 1. Provide a comprehensive, well-reasoned answer.
10
+ 2. Consider multiple perspectives.
11
+ 3. Be thorough but clear.
12
+ 4. Acknowledge uncertainty where appropriate.
13
+ 5. Think step by step, then present a polished answer.
14
+
15
+ Now produce your THESIS answer.
16
+ """
17
+
18
+ ANTITHESIS_PROMPT = """You are in the ANTITHESIS phase of Hegelian dialectical reasoning.
19
+
20
+ Original question:
21
+ {query}
22
+
23
+ Thesis answer:
24
+ {thesis}
25
+ {search_instruction}
26
+
27
+ Your task:
28
+ 1. Find contradictions, inconsistencies, or logical gaps in the thesis.
29
+ 2. Identify unexamined assumptions.
30
+ 3. Propose alternative framings that challenge the thesis.
31
+ 4. Find edge cases or scenarios where the thesis breaks down.
32
+ 5. Be adversarial but intellectually honest.
33
+
34
+ For each contradiction, use this format exactly:
35
+ CONTRADICTION: [brief description]
36
+ EVIDENCE: [why this is problematic]
37
+
38
+ Now produce your ANTITHESIS critique.
39
+ """
40
+
41
+ PERSONA_ANTITHESIS_PROMPT = """You are {persona_name}.
42
+ {persona_description}
43
+
44
+ Original question:
45
+ {query}
46
+
47
+ Thesis answer:
48
+ {thesis}
49
+ {search_instruction}
50
+
51
+ Your expertise: {persona_focus}
52
+ Your instructions: {persona_instructions}
53
+
54
+ Examine the thesis from your specialized perspective. Identify problems within your domain.
55
+
56
+ For each issue you identify, use this format:
57
+ CONTRADICTION: [brief description]
58
+ EVIDENCE: [detailed explanation from your expert perspective]
59
+
60
+ Generate your specialized critique now.
61
+ """
62
+
63
+ SYNTHESIS_PROMPT = """You are in the SYNTHESIS phase of Hegelian dialectical reasoning.
64
+
65
+ Original question:
66
+ {query}
67
+
68
+ Thesis:
69
+ {thesis}
70
+
71
+ Antithesis (critique):
72
+ {antithesis}
73
+
74
+ Identified contradictions:
75
+ {contradictions}
76
+
77
+ Your task:
78
+ 1. Generate a SYNTHESIS that TRANSCENDS both thesis and antithesis.
79
+ 2. Resolve or reframe the contradictions by finding a higher-level perspective.
80
+ 3. Make predictions that NEITHER thesis nor antithesis would make alone.
81
+ 4. Ensure your synthesis is testable or falsifiable when possible.
82
+ 5. If appropriate, propose a research direction or experiment.
83
+
84
+ Requirements for a valid SYNTHESIS:
85
+ - Must not simply say "the thesis is right" or "the antithesis is right".
86
+ - Must not just say "both have merit".
87
+ - Must offer a genuinely novel perspective.
88
+ - Should be more sophisticated than either original position.
89
+
90
+ If the synthesis suggests new research, use this format:
91
+ RESEARCH_PROPOSAL: [brief description]
92
+ TESTABLE_PREDICTION: [specific falsifiable claim]
93
+
94
+ Now produce your SYNTHESIS.
95
+ """
96
+
97
+ MULTI_PERSPECTIVE_SYNTHESIS_PROMPT = """You are in the SYNTHESIS phase of Hegelian dialectical reasoning.
98
+
99
+ Original question:
100
+ {query}
101
+
102
+ Thesis:
103
+ {thesis}
104
+
105
+ CRITIQUES (Multiple Perspectives):
106
+ {antithesis}
107
+
108
+ Identified contradictions:
109
+ {contradictions}
110
+
111
+ Your task:
112
+ 1. Synthesize the thesis with ALL the critiques provided above.
113
+ 2. Resolve the tensions between the initial position and the various critical perspectives.
114
+ 3. Find a higher-level perspective that satisfies the valid points raised by all critics.
115
+ 4. Make predictions that transcend the initial debate.
116
+
117
+ Requirements for a valid SYNTHESIS:
118
+ - Must not simply say "everyone is right".
119
+ - Must offer a genuinely novel perspective that integrates these diverse viewpoints.
120
+ - Should be more sophisticated than any single position.
121
+
122
+ If the synthesis suggests new research, use this format:
123
+ RESEARCH_PROPOSAL: [brief description]
124
+ TESTABLE_PREDICTION: [specific falsifiable claim]
125
+
126
+ Now produce your SYNTHESIS.
127
+ """
@@ -0,0 +1,67 @@
1
+ """JSON Schemas for Hegelion outputs."""
2
+
3
+ HEGELION_RESULT_SCHEMA = {
4
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
5
+ "title": "HegelionResult",
6
+ "type": "object",
7
+ "required": [
8
+ "query",
9
+ "thesis",
10
+ "antithesis",
11
+ "synthesis",
12
+ ],
13
+ "properties": {
14
+ "query": {"type": "string"},
15
+ "mode": {"type": "string"},
16
+ "thesis": {"type": "string"},
17
+ "antithesis": {"type": "string"},
18
+ "synthesis": {"type": "string"},
19
+ "timestamp": {"type": ["string", "null"]},
20
+ "validation_score": {"type": ["number", "null"]},
21
+ "contradictions": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": "object",
25
+ "required": ["description"],
26
+ "properties": {
27
+ "description": {"type": "string"},
28
+ "evidence": {"type": "string"},
29
+ },
30
+ "additionalProperties": False,
31
+ },
32
+ },
33
+ "research_proposals": {
34
+ "type": "array",
35
+ "items": {
36
+ "type": "object",
37
+ "required": ["description"],
38
+ "properties": {
39
+ "description": {"type": "string"},
40
+ "testable_prediction": {"type": "string"},
41
+ },
42
+ "additionalProperties": False,
43
+ },
44
+ },
45
+ "metadata": {
46
+ "type": ["object", "null"],
47
+ "required": [
48
+ "thesis_time_ms",
49
+ "antithesis_time_ms",
50
+ "synthesis_time_ms",
51
+ "total_time_ms",
52
+ ],
53
+ "properties": {
54
+ "thesis_time_ms": {"type": "number"},
55
+ "antithesis_time_ms": {"type": "number"},
56
+ "synthesis_time_ms": {"type": ["number", "null"]},
57
+ "total_time_ms": {"type": "number"},
58
+ "backend_provider": {"type": ["string", "null"]},
59
+ "backend_model": {"type": ["string", "null"]},
60
+ "debug": {"type": "object"},
61
+ "errors": {"type": "array"},
62
+ },
63
+ },
64
+ "trace": {"type": ["object", "null"]},
65
+ },
66
+ "additionalProperties": False,
67
+ }
@@ -0,0 +1,68 @@
1
+ """Runtime validation helpers for Hegelion outputs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Union
6
+
7
+ from jsonschema import validate
8
+ from jsonschema.exceptions import ValidationError as JsonSchemaValidationError
9
+
10
+ from .models import HegelionResult, ValidationError
11
+ from .schema import HEGELION_RESULT_SCHEMA
12
+
13
+
14
+ class ResultValidationError(ValidationError, RuntimeError):
15
+ """Raised when a result fails schema validation."""
16
+
17
+ def __init__(self, message: str, original: JsonSchemaValidationError) -> None:
18
+ self.original = original
19
+ super().__init__(message)
20
+
21
+
22
+ def validate_hegelion_result(result: Union[HegelionResult, Dict[str, Any]]) -> None:
23
+ """Validate a HegelionResult against the public schema."""
24
+ if hasattr(result, "to_dict"):
25
+ payload: Dict[str, Any] = result.to_dict()
26
+ else:
27
+ payload = result
28
+
29
+ try:
30
+ validate(instance=payload, schema=HEGELION_RESULT_SCHEMA)
31
+ except JsonSchemaValidationError as exc: # pragma: no cover - defensive
32
+ msg = exc.message
33
+ field = ""
34
+ if exc.path:
35
+ field = f"Field '{exc.path[-1]}': "
36
+
37
+ if "required property" in msg:
38
+ msg = f"Missing required field(s): {msg}"
39
+ elif "not of type" in msg:
40
+ msg = f"{field}Invalid field type: {msg}"
41
+ raise ResultValidationError(f"Result failed schema validation: {msg}", exc) from exc
42
+
43
+
44
+ def validate_prompt_workflow(workflow: Any) -> None:
45
+ """Validate a PromptWorkflow."""
46
+ if not isinstance(workflow, dict):
47
+ raise ValidationError("Workflow must be a dictionary")
48
+
49
+ required = ["query", "thesis", "antithesis", "synthesis"]
50
+ missing = [field for field in required if field not in workflow]
51
+ if missing:
52
+ raise ValidationError(f"Missing required field(s): {', '.join(missing)}")
53
+
54
+
55
+ def validate_hegelion_result_list(results: List[Dict[str, Any]]) -> None:
56
+ """Validate a list of HegelionResults."""
57
+ for i, item in enumerate(results):
58
+ try:
59
+ validate_hegelion_result(item)
60
+ except Exception as exc:
61
+ raise ValidationError(f"Item at index {i} is invalid: {exc}")
62
+
63
+
64
+ def validate_dialectic_output(output: Any) -> None:
65
+ """Validate a DialecticOutput."""
66
+ if isinstance(output, dict) and "query" not in output:
67
+ raise ValidationError("Missing required field(s): query")
68
+ validate_hegelion_result(output)