dialectical-framework 0.4.4__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.
- dialectical_framework/__init__.py +43 -0
- dialectical_framework/ai_dto/__init__.py +0 -0
- dialectical_framework/ai_dto/causal_cycle_assessment_dto.py +16 -0
- dialectical_framework/ai_dto/causal_cycle_dto.py +16 -0
- dialectical_framework/ai_dto/causal_cycles_deck_dto.py +11 -0
- dialectical_framework/ai_dto/dialectical_component_dto.py +16 -0
- dialectical_framework/ai_dto/dialectical_components_deck_dto.py +12 -0
- dialectical_framework/ai_dto/dto_mapper.py +103 -0
- dialectical_framework/ai_dto/reciprocal_solution_dto.py +24 -0
- dialectical_framework/analyst/__init__.py +1 -0
- dialectical_framework/analyst/audit/__init__.py +0 -0
- dialectical_framework/analyst/consultant.py +30 -0
- dialectical_framework/analyst/decorator_action_reflection.py +28 -0
- dialectical_framework/analyst/decorator_discrete_spiral.py +30 -0
- dialectical_framework/analyst/domain/__init__.py +0 -0
- dialectical_framework/analyst/domain/assessable_cycle.py +128 -0
- dialectical_framework/analyst/domain/cycle.py +133 -0
- dialectical_framework/analyst/domain/interpretation.py +16 -0
- dialectical_framework/analyst/domain/rationale.py +116 -0
- dialectical_framework/analyst/domain/spiral.py +47 -0
- dialectical_framework/analyst/domain/transformation.py +24 -0
- dialectical_framework/analyst/domain/transition.py +160 -0
- dialectical_framework/analyst/domain/transition_cell_to_cell.py +29 -0
- dialectical_framework/analyst/domain/transition_segment_to_segment.py +16 -0
- dialectical_framework/analyst/strategic_consultant.py +32 -0
- dialectical_framework/analyst/think_action_reflection.py +217 -0
- dialectical_framework/analyst/think_constructive_convergence.py +87 -0
- dialectical_framework/analyst/wheel_builder_transition_calculator.py +157 -0
- dialectical_framework/brain.py +81 -0
- dialectical_framework/dialectical_analysis.py +14 -0
- dialectical_framework/dialectical_component.py +111 -0
- dialectical_framework/dialectical_components_deck.py +48 -0
- dialectical_framework/dialectical_reasoning.py +105 -0
- dialectical_framework/directed_graph.py +419 -0
- dialectical_framework/enums/__init__.py +0 -0
- dialectical_framework/enums/causality_type.py +10 -0
- dialectical_framework/enums/di.py +11 -0
- dialectical_framework/enums/dialectical_reasoning_mode.py +9 -0
- dialectical_framework/enums/predicate.py +9 -0
- dialectical_framework/protocols/__init__.py +0 -0
- dialectical_framework/protocols/assessable.py +141 -0
- dialectical_framework/protocols/causality_sequencer.py +111 -0
- dialectical_framework/protocols/content_fidelity_evaluator.py +16 -0
- dialectical_framework/protocols/has_brain.py +18 -0
- dialectical_framework/protocols/has_config.py +18 -0
- dialectical_framework/protocols/ratable.py +79 -0
- dialectical_framework/protocols/reloadable.py +7 -0
- dialectical_framework/protocols/thesis_extractor.py +15 -0
- dialectical_framework/settings.py +77 -0
- dialectical_framework/synthesis.py +7 -0
- dialectical_framework/synthesist/__init__.py +1 -0
- dialectical_framework/synthesist/causality/__init__.py +0 -0
- dialectical_framework/synthesist/causality/causality_sequencer_balanced.py +398 -0
- dialectical_framework/synthesist/causality/causality_sequencer_desirable.py +55 -0
- dialectical_framework/synthesist/causality/causality_sequencer_feasible.py +55 -0
- dialectical_framework/synthesist/causality/causality_sequencer_realistic.py +56 -0
- dialectical_framework/synthesist/concepts/__init__.py +0 -0
- dialectical_framework/synthesist/concepts/thesis_extractor_basic.py +135 -0
- dialectical_framework/synthesist/polarity/__init__.py +0 -0
- dialectical_framework/synthesist/polarity/polarity_reasoner.py +700 -0
- dialectical_framework/synthesist/polarity/reason_blind.py +62 -0
- dialectical_framework/synthesist/polarity/reason_conversational.py +91 -0
- dialectical_framework/synthesist/polarity/reason_fast.py +177 -0
- dialectical_framework/synthesist/polarity/reason_fast_and_simple.py +52 -0
- dialectical_framework/synthesist/polarity/reason_fast_polarized_conflict.py +55 -0
- dialectical_framework/synthesist/reverse_engineer.py +401 -0
- dialectical_framework/synthesist/wheel_builder.py +337 -0
- dialectical_framework/utils/__init__.py +1 -0
- dialectical_framework/utils/dc_replace.py +42 -0
- dialectical_framework/utils/decompose_probability_uniformly.py +15 -0
- dialectical_framework/utils/extend_tpl.py +12 -0
- dialectical_framework/utils/gm.py +12 -0
- dialectical_framework/utils/is_async.py +13 -0
- dialectical_framework/utils/use_brain.py +80 -0
- dialectical_framework/validator/__init__.py +1 -0
- dialectical_framework/validator/basic_checks.py +75 -0
- dialectical_framework/validator/check.py +12 -0
- dialectical_framework/wheel.py +395 -0
- dialectical_framework/wheel_segment.py +203 -0
- dialectical_framework/wisdom_unit.py +185 -0
- dialectical_framework-0.4.4.dist-info/LICENSE +21 -0
- dialectical_framework-0.4.4.dist-info/METADATA +123 -0
- dialectical_framework-0.4.4.dist-info/RECORD +84 -0
- dialectical_framework-0.4.4.dist-info/WHEEL +4 -0
@@ -0,0 +1,700 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import inspect
|
4
|
+
from abc import abstractmethod
|
5
|
+
from typing import Self
|
6
|
+
|
7
|
+
from dependency_injector.wiring import Provide
|
8
|
+
from mirascope import BaseMessageParam, Messages, prompt_template
|
9
|
+
from mirascope.integrations.langfuse import with_langfuse
|
10
|
+
|
11
|
+
from dialectical_framework import Rationale
|
12
|
+
from dialectical_framework.ai_dto.dialectical_component_dto import \
|
13
|
+
DialecticalComponentDto
|
14
|
+
from dialectical_framework.ai_dto.dialectical_components_deck_dto import \
|
15
|
+
DialecticalComponentsDeckDto
|
16
|
+
from dialectical_framework.ai_dto.dto_mapper import (map_from_dto,
|
17
|
+
map_list_from_dto)
|
18
|
+
from dialectical_framework.dialectical_analysis import DialecticalAnalysis
|
19
|
+
from dialectical_framework.dialectical_component import DialecticalComponent
|
20
|
+
from dialectical_framework.dialectical_components_deck import \
|
21
|
+
DialecticalComponentsDeck
|
22
|
+
from dialectical_framework.enums.di import DI
|
23
|
+
from dialectical_framework.enums.dialectical_reasoning_mode import \
|
24
|
+
DialecticalReasoningMode
|
25
|
+
from dialectical_framework.protocols.has_brain import HasBrain
|
26
|
+
from dialectical_framework.protocols.reloadable import Reloadable
|
27
|
+
from dialectical_framework.settings import Settings
|
28
|
+
from dialectical_framework.synthesist.reverse_engineer import ReverseEngineer
|
29
|
+
from dialectical_framework.utils.dc_replace import dc_safe_replace
|
30
|
+
from dialectical_framework.utils.extend_tpl import extend_tpl
|
31
|
+
from dialectical_framework.utils.use_brain import use_brain
|
32
|
+
from dialectical_framework.validator.basic_checks import (check,
|
33
|
+
is_negative_side,
|
34
|
+
is_positive_side,
|
35
|
+
is_strict_opposition,
|
36
|
+
is_valid_opposition)
|
37
|
+
from dialectical_framework.wheel_segment import (ALIAS_T, ALIAS_T_MINUS,
|
38
|
+
ALIAS_T_PLUS)
|
39
|
+
from dialectical_framework.wisdom_unit import (ALIAS_A, ALIAS_A_MINUS,
|
40
|
+
ALIAS_A_PLUS, WisdomUnit)
|
41
|
+
|
42
|
+
|
43
|
+
class PolarityReasoner(HasBrain, Reloadable):
|
44
|
+
def __init__(
|
45
|
+
self,
|
46
|
+
*,
|
47
|
+
text: str = "",
|
48
|
+
):
|
49
|
+
self._text = text
|
50
|
+
self._wisdom_unit = None
|
51
|
+
|
52
|
+
self._mode: DialecticalReasoningMode = DialecticalReasoningMode.GENERAL_CONCEPTS
|
53
|
+
|
54
|
+
self._analysis = DialecticalAnalysis(corpus=self._text)
|
55
|
+
|
56
|
+
@property
|
57
|
+
def text(self) -> str:
|
58
|
+
return self._text
|
59
|
+
|
60
|
+
def reload(
|
61
|
+
self, *, text: str, perspectives: WisdomUnit | list[WisdomUnit] = None
|
62
|
+
) -> Self:
|
63
|
+
self._text = text
|
64
|
+
if not perspectives:
|
65
|
+
perspectives = []
|
66
|
+
if isinstance(perspectives, WisdomUnit):
|
67
|
+
perspectives = [perspectives]
|
68
|
+
|
69
|
+
self._analysis = DialecticalAnalysis(
|
70
|
+
corpus=text,
|
71
|
+
perspectives=perspectives,
|
72
|
+
)
|
73
|
+
|
74
|
+
if perspectives:
|
75
|
+
# Take first perspective as active
|
76
|
+
self._wisdom_unit = perspectives[-1]
|
77
|
+
else:
|
78
|
+
self._wisdom_unit = None
|
79
|
+
return self
|
80
|
+
|
81
|
+
@prompt_template(
|
82
|
+
"""
|
83
|
+
USER:
|
84
|
+
{start}
|
85
|
+
|
86
|
+
USER:
|
87
|
+
Extract the central idea or the primary thesis (denote it as T) of the context with minimal distortion. If already concise (single word/phrase/clear thesis), keep it intact; only condense verbose messages while preserving original meaning.
|
88
|
+
|
89
|
+
Output the dialectical component T within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T" in the explanation.
|
90
|
+
"""
|
91
|
+
)
|
92
|
+
def prompt_thesis(
|
93
|
+
self, text: str = None, config: Settings = Provide[DI.settings]
|
94
|
+
) -> Messages.Type:
|
95
|
+
return {
|
96
|
+
"computed_fields": {
|
97
|
+
# Sometimes we don't want the whole user input again, as we're calculating the thesis within a longer analysis
|
98
|
+
"start": (
|
99
|
+
"<context>" + inspect.cleandoc(text) + "</context>"
|
100
|
+
if text
|
101
|
+
else "Ok."
|
102
|
+
),
|
103
|
+
"component_length": config.component_length,
|
104
|
+
},
|
105
|
+
}
|
106
|
+
|
107
|
+
@prompt_template(
|
108
|
+
"""
|
109
|
+
A dialectical opposition presents the conceptual or functional antithesis of the original statement that creates direct opposition, while potentially still allowing their mutual coexistence. For instance, Love vs. Hate or Indifference; Science vs. Superstition, Faith/Belief; Human-caused Global Warming vs. Natural Cycles.
|
110
|
+
|
111
|
+
Generate a dialectical opposition (A) of the thesis "{thesis}" (T). Be detailed enough to show deep understanding, yet concise enough to maintain clarity. Generalize all of them using up to 6 words.
|
112
|
+
|
113
|
+
Output the dialectical component A within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T" or "A" in the explanation.
|
114
|
+
"""
|
115
|
+
)
|
116
|
+
def prompt_antithesis(
|
117
|
+
self, thesis: str | DialecticalComponent, config: Settings = Provide[DI.settings]
|
118
|
+
) -> Messages.Type:
|
119
|
+
if isinstance(thesis, DialecticalComponent):
|
120
|
+
thesis = thesis.statement
|
121
|
+
return {
|
122
|
+
"computed_fields": {
|
123
|
+
"thesis": thesis,
|
124
|
+
"component_length": config.component_length,
|
125
|
+
},
|
126
|
+
}
|
127
|
+
|
128
|
+
@prompt_template(
|
129
|
+
"""
|
130
|
+
Generate a negative side (T-) of a thesis "{thesis}" (T), representing its strict semantic exaggeration and overdevelopment, as if the author of T lost his inner control. Make sure that T- is not the same as: "{not_like_this}".
|
131
|
+
|
132
|
+
For instance, if T = Courage, then T- = Foolhardiness. If T = Love, then T- = Obsession, Fixation, Loss of Mindfulness. If T = Fear, then T- = Paranoia. If T = Hate and Indifference then T- = Malevolence and Apathy.
|
133
|
+
|
134
|
+
If more than one T- exists, provide a generalized representation that encompasses their essence. Be detailed enough to show deep understanding, yet concise enough to maintain clarity. For instance, T- = "Obsession, Fixation, Loss of Mindfulness" can be generalized into T- = Mental Preoccupation
|
135
|
+
|
136
|
+
Output the dialectical component T- within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T", "T-" or "A-" in the explanation.
|
137
|
+
"""
|
138
|
+
)
|
139
|
+
def prompt_thesis_negative_side(
|
140
|
+
self,
|
141
|
+
thesis: str | DialecticalComponent,
|
142
|
+
not_like_this: str | DialecticalComponent = "",
|
143
|
+
config: Settings = Provide[DI.settings],
|
144
|
+
) -> Messages.Type:
|
145
|
+
if isinstance(thesis, DialecticalComponent):
|
146
|
+
thesis = thesis.statement
|
147
|
+
if isinstance(not_like_this, DialecticalComponent):
|
148
|
+
not_like_this = not_like_this.statement
|
149
|
+
return {
|
150
|
+
"computed_fields": {
|
151
|
+
"thesis": thesis,
|
152
|
+
"not_like_this": not_like_this,
|
153
|
+
"component_length": config.component_length,
|
154
|
+
},
|
155
|
+
}
|
156
|
+
|
157
|
+
@prompt_template()
|
158
|
+
def prompt_antithesis_negative_side(
|
159
|
+
self,
|
160
|
+
antithesis: str | DialecticalComponent,
|
161
|
+
not_like_this: str | DialecticalComponent = "",
|
162
|
+
) -> Messages.Type:
|
163
|
+
tpl: list[BaseMessageParam] = self.prompt_thesis_negative_side(
|
164
|
+
antithesis, not_like_this
|
165
|
+
)
|
166
|
+
# Replace the technical terms in the prompt, so that it makes sense when passed in the history
|
167
|
+
for i in range(len(tpl)):
|
168
|
+
if tpl[i].content:
|
169
|
+
tpl[i].content = dc_safe_replace(
|
170
|
+
tpl[i].content,
|
171
|
+
{
|
172
|
+
ALIAS_T: ALIAS_A,
|
173
|
+
ALIAS_T_MINUS: ALIAS_A_MINUS,
|
174
|
+
ALIAS_A_MINUS: ALIAS_T_MINUS,
|
175
|
+
},
|
176
|
+
)
|
177
|
+
return tpl
|
178
|
+
|
179
|
+
@prompt_template(
|
180
|
+
"""
|
181
|
+
A contradictory/semantic opposition presents a direct semantic opposition and/or contradiction to the original statement that excludes their mutual coexistence. For instance, Happiness vs. Unhappiness; Truthfulness vs. Lie/Deceptiveness; Dependence vs. Independence.
|
182
|
+
|
183
|
+
Generate a positive side or outcome (T+) of a thesis "{thesis}" (T), representing its constructive (balanced) form/side, that is also the contradictory/semantic opposition of "{antithesis_negative}" (A-).
|
184
|
+
|
185
|
+
Make sure that T+ is truly connected to the semantic T, representing its positive and constructive side or outcome that is also highly perceptive, nuanced, gentle, evolving, and instrumental in solving problems and creating friendships. For instance, T+ = Trust can be seen as the constructive side of T = Courage. T+ = Kindness and Empathy are natural constructive outcomes of T = Love.
|
186
|
+
|
187
|
+
If more than one T+ exists, provide a generalized representation that encompasses their essence. Be detailed enough to show deep understanding, yet concise enough to maintain clarity.
|
188
|
+
|
189
|
+
Output the dialectical component T+ within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T", "T+" or "A-" in the explanation.
|
190
|
+
"""
|
191
|
+
)
|
192
|
+
def prompt_thesis_positive_side(
|
193
|
+
self,
|
194
|
+
thesis: str | DialecticalComponent,
|
195
|
+
antithesis_negative: str | DialecticalComponent,
|
196
|
+
config: Settings = Provide[DI.settings],
|
197
|
+
) -> Messages.Type:
|
198
|
+
if isinstance(thesis, DialecticalComponent):
|
199
|
+
thesis = thesis.statement
|
200
|
+
if isinstance(antithesis_negative, DialecticalComponent):
|
201
|
+
antithesis_negative = antithesis_negative.statement
|
202
|
+
return {
|
203
|
+
"computed_fields": {
|
204
|
+
"thesis": thesis,
|
205
|
+
"antithesis_negative": antithesis_negative,
|
206
|
+
"component_length": config.component_length,
|
207
|
+
},
|
208
|
+
}
|
209
|
+
|
210
|
+
@prompt_template()
|
211
|
+
def prompt_antithesis_positive_side(
|
212
|
+
self,
|
213
|
+
antithesis: str | DialecticalComponent,
|
214
|
+
thesis_negative: str | DialecticalComponent,
|
215
|
+
) -> Messages.Type:
|
216
|
+
tpl: list[BaseMessageParam] = self.prompt_thesis_positive_side(
|
217
|
+
antithesis, thesis_negative
|
218
|
+
)
|
219
|
+
# Replace the technical terms in the prompt, so that it makes sense when passed in the history
|
220
|
+
for i in range(len(tpl)):
|
221
|
+
if tpl[i].content:
|
222
|
+
tpl[i].content = dc_safe_replace(
|
223
|
+
tpl[i].content,
|
224
|
+
{
|
225
|
+
ALIAS_T: ALIAS_A,
|
226
|
+
ALIAS_T_PLUS: ALIAS_A_PLUS,
|
227
|
+
ALIAS_A_MINUS: ALIAS_T_MINUS,
|
228
|
+
},
|
229
|
+
)
|
230
|
+
return tpl
|
231
|
+
|
232
|
+
@prompt_template(
|
233
|
+
"""
|
234
|
+
MESSAGES:
|
235
|
+
{wu_construction}
|
236
|
+
|
237
|
+
USER:
|
238
|
+
Identifying Positive and Negative Syntheses
|
239
|
+
|
240
|
+
USER:
|
241
|
+
Consider the dialectical components identified in previous analysis:
|
242
|
+
{wu_dcs:list}
|
243
|
+
|
244
|
+
For the pair of thesis and antithesis, identify both positive synthesis (S+) and negative synthesis (S-):
|
245
|
+
|
246
|
+
S+ (Positive Synthesis): The emergent quality that arises when the positive/constructive aspects (T+ and A+) are combined in complementary harmony. This represents a new dimension where 1+1>2, expanding possibilities while preserving the unique value of each component. Consider the birth of a child from two parents, or binocular vision producing depth perception.
|
247
|
+
|
248
|
+
S- (Negative Synthesis): The uniformity that results when negative/exaggerated aspects (T- and A-) reinforce each other. This represents reduction of dimensionality where 1+1<2, increasing intensity along limited axes at the expense of diversity. Examples include pendulums aligning their rhythms, or centralized rules that suppress variation.
|
249
|
+
|
250
|
+
Output the dialectical components S+ and S-. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T", "T+" or "A-" in the explanation. To the explanation add a concrete real life example.
|
251
|
+
"""
|
252
|
+
)
|
253
|
+
def prompt_synthesis(self, wisdom_unit: WisdomUnit) -> Messages.Type:
|
254
|
+
tpl = ReverseEngineer.till_wisdom_units(
|
255
|
+
wisdom_units=[wisdom_unit], text=self._analysis.corpus
|
256
|
+
)
|
257
|
+
wu_dcs = []
|
258
|
+
for field, alias in wisdom_unit.field_to_alias.items():
|
259
|
+
dc = wisdom_unit.get(alias)
|
260
|
+
if not dc:
|
261
|
+
continue
|
262
|
+
wu_dcs.append(f"{alias} = {dc.statement}")
|
263
|
+
return {
|
264
|
+
"computed_fields": {
|
265
|
+
"wu_construction": tpl,
|
266
|
+
"wu_dcs": wu_dcs,
|
267
|
+
},
|
268
|
+
}
|
269
|
+
|
270
|
+
@prompt_template()
|
271
|
+
@abstractmethod
|
272
|
+
def prompt_next(self, wu_so_far: WisdomUnit) -> Messages.Type: ...
|
273
|
+
|
274
|
+
@with_langfuse()
|
275
|
+
@use_brain(response_model=DialecticalComponentDto)
|
276
|
+
async def find_thesis(self) -> DialecticalComponentDto:
|
277
|
+
return self.prompt_thesis(self._text)
|
278
|
+
|
279
|
+
@with_langfuse()
|
280
|
+
@use_brain(response_model=DialecticalComponentDto)
|
281
|
+
async def find_antithesis(
|
282
|
+
self,
|
283
|
+
thesis: str,
|
284
|
+
) -> DialecticalComponentDto:
|
285
|
+
return self.prompt_antithesis(thesis)
|
286
|
+
|
287
|
+
@with_langfuse()
|
288
|
+
@use_brain(response_model=DialecticalComponentDto)
|
289
|
+
async def find_thesis_negative_side(
|
290
|
+
self,
|
291
|
+
thesis: str,
|
292
|
+
not_like_this: str = "",
|
293
|
+
) -> DialecticalComponentDto:
|
294
|
+
return self.prompt_thesis_negative_side(thesis, not_like_this)
|
295
|
+
|
296
|
+
@with_langfuse()
|
297
|
+
@use_brain(response_model=DialecticalComponentDto)
|
298
|
+
async def find_antithesis_negative_side(
|
299
|
+
self,
|
300
|
+
thesis: str,
|
301
|
+
not_like_this: str = "",
|
302
|
+
) -> DialecticalComponentDto:
|
303
|
+
return self.prompt_antithesis_negative_side(thesis, not_like_this)
|
304
|
+
|
305
|
+
@with_langfuse()
|
306
|
+
@use_brain(response_model=DialecticalComponentDto)
|
307
|
+
async def find_thesis_positive_side(
|
308
|
+
self,
|
309
|
+
thesis: str,
|
310
|
+
antithesis_negative: str,
|
311
|
+
) -> DialecticalComponentDto:
|
312
|
+
return self.prompt_thesis_positive_side(thesis, antithesis_negative)
|
313
|
+
|
314
|
+
@with_langfuse()
|
315
|
+
@use_brain(response_model=DialecticalComponentDto)
|
316
|
+
async def find_antithesis_positive_side(
|
317
|
+
self,
|
318
|
+
thesis: str,
|
319
|
+
antithesis_negative: str,
|
320
|
+
) -> DialecticalComponentDto:
|
321
|
+
return self.prompt_antithesis_positive_side(thesis, antithesis_negative)
|
322
|
+
|
323
|
+
@with_langfuse()
|
324
|
+
@use_brain(response_model=DialecticalComponentsDeckDto)
|
325
|
+
async def find_next(
|
326
|
+
self,
|
327
|
+
wu_so_far: WisdomUnit,
|
328
|
+
) -> DialecticalComponentsDeckDto:
|
329
|
+
"""
|
330
|
+
Raises:
|
331
|
+
StopIteration: if nothing needs to be found anymore
|
332
|
+
"""
|
333
|
+
prompt = self.prompt_next(wu_so_far)
|
334
|
+
if self._analysis.perspectives:
|
335
|
+
tpl = ReverseEngineer.till_wisdom_units(
|
336
|
+
wisdom_units=self._analysis.perspectives, text=self._analysis.corpus
|
337
|
+
)
|
338
|
+
else:
|
339
|
+
tpl = ReverseEngineer().prompt_input_text(text=self.text)
|
340
|
+
|
341
|
+
return extend_tpl(tpl, prompt)
|
342
|
+
|
343
|
+
@with_langfuse()
|
344
|
+
@use_brain(response_model=DialecticalComponentsDeckDto)
|
345
|
+
async def find_synthesis(
|
346
|
+
self,
|
347
|
+
wu: WisdomUnit,
|
348
|
+
) -> DialecticalComponentsDeckDto:
|
349
|
+
return self.prompt_synthesis(wu)
|
350
|
+
|
351
|
+
async def think(self, thesis: str | DialecticalComponent = None) -> WisdomUnit:
|
352
|
+
# TODO: when thesis is a simple str, we need to actualize it with AI, where it comes from
|
353
|
+
wu = WisdomUnit(reasoning_mode=self._mode)
|
354
|
+
|
355
|
+
if thesis is not None:
|
356
|
+
if isinstance(thesis, DialecticalComponent):
|
357
|
+
if thesis.alias != ALIAS_T:
|
358
|
+
raise ValueError(
|
359
|
+
f"The thesis cannot be a dialectical component with alias '{thesis.alias}'"
|
360
|
+
)
|
361
|
+
wu.t = thesis
|
362
|
+
else:
|
363
|
+
wu.t = DialecticalComponent(
|
364
|
+
alias=ALIAS_T, statement=thesis,
|
365
|
+
# explanation="Provided as string"
|
366
|
+
)
|
367
|
+
else:
|
368
|
+
wu.t = map_from_dto(await self.find_thesis(), DialecticalComponent)
|
369
|
+
|
370
|
+
self._wisdom_unit = await self._fill_with_reason(wu)
|
371
|
+
self._analysis.perspectives.append(self._wisdom_unit)
|
372
|
+
return self._wisdom_unit
|
373
|
+
|
374
|
+
async def _fill_with_reason(self, wu: WisdomUnit) -> WisdomUnit:
|
375
|
+
empty_count = len(wu.alias_to_field)
|
376
|
+
for alias in wu.alias_to_field:
|
377
|
+
if wu.is_set(alias):
|
378
|
+
empty_count -= 1
|
379
|
+
|
380
|
+
try:
|
381
|
+
ci = 0
|
382
|
+
while ci < empty_count:
|
383
|
+
if wu.is_complete():
|
384
|
+
break
|
385
|
+
"""
|
386
|
+
We assume here, that with every iteration we will find a new dialectical component(s).
|
387
|
+
If we keep finding the same ones (or not find at all), we will still avoid the infinite loop - that's good.
|
388
|
+
"""
|
389
|
+
dc_deck_dto = await self.find_next(wu)
|
390
|
+
dc_deck: DialecticalComponentsDeck = DialecticalComponentsDeck(dialectical_components=map_list_from_dto(dc_deck_dto.dialectical_components, DialecticalComponent))
|
391
|
+
for dc in dc_deck.dialectical_components:
|
392
|
+
alias = dc.alias
|
393
|
+
if wu.get(alias):
|
394
|
+
# Don't override if we already have it
|
395
|
+
continue
|
396
|
+
else:
|
397
|
+
setattr(wu, alias, dc)
|
398
|
+
ci += 1
|
399
|
+
except StopIteration:
|
400
|
+
pass
|
401
|
+
|
402
|
+
return wu
|
403
|
+
|
404
|
+
async def redefine(
|
405
|
+
self,
|
406
|
+
*, # ← everything after * is keyword-only
|
407
|
+
original: WisdomUnit | None = None,
|
408
|
+
**modified_dialectical_components,
|
409
|
+
) -> WisdomUnit:
|
410
|
+
"""
|
411
|
+
This method doesn't mutate the original WisdomUnit. It returns a fresh instance.
|
412
|
+
"""
|
413
|
+
|
414
|
+
warnings: dict[str, list[str]] = {}
|
415
|
+
|
416
|
+
if original is None:
|
417
|
+
original = self._wisdom_unit
|
418
|
+
|
419
|
+
if original is None:
|
420
|
+
raise ValueError("Wisdom unit is not generated yet.")
|
421
|
+
|
422
|
+
# Replace it in case the parameter "original" was given
|
423
|
+
self._wisdom_unit = original
|
424
|
+
|
425
|
+
changed: dict[str, str] = {
|
426
|
+
k: str(v)
|
427
|
+
for k, v in modified_dialectical_components.items()
|
428
|
+
if k in WisdomUnit.__pydantic_fields__
|
429
|
+
}
|
430
|
+
|
431
|
+
new_wu: WisdomUnit = WisdomUnit(reasoning_mode=original.reasoning_mode)
|
432
|
+
|
433
|
+
# ==
|
434
|
+
# Redefine opposition
|
435
|
+
# ==
|
436
|
+
base = "t"
|
437
|
+
other = "a" if base == "t" else "t"
|
438
|
+
|
439
|
+
for dialectical_component in [base, other]:
|
440
|
+
if changed.get(dialectical_component):
|
441
|
+
setattr(
|
442
|
+
new_wu,
|
443
|
+
dialectical_component,
|
444
|
+
DialecticalComponent(
|
445
|
+
alias=new_wu.__pydantic_fields__.get(
|
446
|
+
dialectical_component
|
447
|
+
).alias,
|
448
|
+
statement=changed.get(dialectical_component),
|
449
|
+
rationales=[
|
450
|
+
Rationale(
|
451
|
+
text=f"{new_wu.__pydantic_fields__.get(dialectical_component).alias} redefined."
|
452
|
+
)
|
453
|
+
]
|
454
|
+
),
|
455
|
+
)
|
456
|
+
else:
|
457
|
+
new_wu.set_dialectical_component_as_copy_from_another_segment(original, dialectical_component)
|
458
|
+
|
459
|
+
alias_base = "T" if base == "t" else "A"
|
460
|
+
alias_other = "A" if base == "t" else "T"
|
461
|
+
|
462
|
+
if changed.get(base) or changed.get(other):
|
463
|
+
check1 = check(
|
464
|
+
is_valid_opposition,
|
465
|
+
self,
|
466
|
+
getattr(new_wu, base).statement,
|
467
|
+
getattr(new_wu, other).statement,
|
468
|
+
)
|
469
|
+
|
470
|
+
if not check1.valid:
|
471
|
+
if changed.get(base) and not changed.get(other):
|
472
|
+
# base side changed
|
473
|
+
o = map_from_dto(await self.find_antithesis(getattr(new_wu, base).statement), DialecticalComponent)
|
474
|
+
assert isinstance(o, DialecticalComponent)
|
475
|
+
if o.best_rationale and o.best_rationale.text:
|
476
|
+
o.best_rationale.text = f"REGENERATED. {o.best_rationale.text}"
|
477
|
+
setattr(new_wu, other, o)
|
478
|
+
changed[other] = o.statement
|
479
|
+
check1.valid = 1
|
480
|
+
check1.explanation = "Regenerated, therefore must be valid."
|
481
|
+
elif changed.get(other) and not changed.get(base):
|
482
|
+
# other side changed
|
483
|
+
bm = map_from_dto(await self.find_antithesis(getattr(new_wu, other).statement), DialecticalComponent)
|
484
|
+
assert isinstance(bm, DialecticalComponent)
|
485
|
+
if bm.best_rationale and bm.best_rationale.text:
|
486
|
+
bm.best_rationale.text = f"REGENERATED. {bm.best_rationale.text}"
|
487
|
+
setattr(new_wu, base, bm)
|
488
|
+
changed[base] = bm.statement
|
489
|
+
check1.valid = 1
|
490
|
+
check1.explanation = "Regenerated, therefore must be valid."
|
491
|
+
|
492
|
+
if not check1.valid:
|
493
|
+
getattr(new_wu, base).statement = (
|
494
|
+
f"ERROR: {getattr(new_wu, base).statement}"
|
495
|
+
)
|
496
|
+
getattr(new_wu, other).statement = (
|
497
|
+
f"ERROR: {getattr(new_wu, other).statement}"
|
498
|
+
)
|
499
|
+
warnings.setdefault(alias_base, []).append(check1.explanation)
|
500
|
+
warnings.setdefault(alias_other, []).append(check1.explanation)
|
501
|
+
raise AssertionError(f"{alias_base}, {alias_other}", warnings, new_wu)
|
502
|
+
|
503
|
+
else:
|
504
|
+
# Keep originals
|
505
|
+
pass
|
506
|
+
|
507
|
+
# NOTE: At this point we are sure that T and A are present and valid in the new wheel
|
508
|
+
|
509
|
+
# ==
|
510
|
+
# Redefine diagonal relations
|
511
|
+
# ==
|
512
|
+
for side in ["t", "a"]:
|
513
|
+
base = side
|
514
|
+
other = "a" if base == "t" else "t"
|
515
|
+
|
516
|
+
base_negative_side_fn = (
|
517
|
+
self.find_thesis_negative_side
|
518
|
+
if side == "t"
|
519
|
+
else self.find_antithesis_negative_side
|
520
|
+
)
|
521
|
+
other_positive_side_fn = (
|
522
|
+
self.find_antithesis_positive_side
|
523
|
+
if side == "t"
|
524
|
+
else self.find_thesis_positive_side
|
525
|
+
)
|
526
|
+
|
527
|
+
base_minus = "t_minus" if side == "t" else "a_minus"
|
528
|
+
base_plus = "t_plus" if side == "t" else "a_plus"
|
529
|
+
other_plus = "a_plus" if side == "t" else "t_plus"
|
530
|
+
other_minus = "a_minus" if side == "t" else "t_minus"
|
531
|
+
|
532
|
+
alias_base_minus = "T-" if side == "t" else "A-"
|
533
|
+
alias_other_plus = "A+" if side == "t" else "T+"
|
534
|
+
|
535
|
+
for dialectical_component in [base_minus, other_plus]:
|
536
|
+
if changed.get(dialectical_component):
|
537
|
+
setattr(
|
538
|
+
new_wu,
|
539
|
+
dialectical_component,
|
540
|
+
DialecticalComponent(
|
541
|
+
alias=new_wu.__pydantic_fields__.get(
|
542
|
+
dialectical_component
|
543
|
+
).alias,
|
544
|
+
statement=changed.get(dialectical_component),
|
545
|
+
rationales=[
|
546
|
+
Rationale(
|
547
|
+
text=f"{new_wu.__pydantic_fields__.get(dialectical_component).alias} redefined."
|
548
|
+
)
|
549
|
+
]
|
550
|
+
),
|
551
|
+
)
|
552
|
+
else:
|
553
|
+
new_wu.set_dialectical_component_as_copy_from_another_segment(
|
554
|
+
original, dialectical_component
|
555
|
+
)
|
556
|
+
|
557
|
+
if (changed.get(base) or changed.get(base_minus)) or (
|
558
|
+
changed.get(other) or changed.get(other_plus)
|
559
|
+
):
|
560
|
+
if changed.get(base_minus) or changed.get(base):
|
561
|
+
check2 = check(
|
562
|
+
is_negative_side,
|
563
|
+
self,
|
564
|
+
getattr(new_wu, base_minus).statement,
|
565
|
+
getattr(new_wu, base).statement,
|
566
|
+
)
|
567
|
+
|
568
|
+
if not check2.valid:
|
569
|
+
if changed.get(base) and not changed.get(base_minus):
|
570
|
+
not_like_other_minus = ""
|
571
|
+
if hasattr(new_wu, other_minus):
|
572
|
+
if getattr(new_wu, other_minus):
|
573
|
+
not_like_other_minus = getattr(
|
574
|
+
new_wu, other_minus
|
575
|
+
).statement
|
576
|
+
bm = map_from_dto(await base_negative_side_fn(
|
577
|
+
getattr(new_wu, base).statement, not_like_other_minus
|
578
|
+
), DialecticalComponent)
|
579
|
+
assert isinstance(bm, DialecticalComponent)
|
580
|
+
if bm.best_rationale and bm.best_rationale.text:
|
581
|
+
bm.best_rationale.text = f"REGENERATED. {bm.best_rationale.text}"
|
582
|
+
setattr(new_wu, base_minus, bm)
|
583
|
+
changed[base_minus] = bm.statement
|
584
|
+
check2.valid = True
|
585
|
+
check2.explanation = "Regenerated, therefore must be valid."
|
586
|
+
|
587
|
+
if not check2.valid:
|
588
|
+
getattr(new_wu, base_minus).statement = (
|
589
|
+
f"ERROR: {getattr(new_wu, base_minus).statement}"
|
590
|
+
)
|
591
|
+
warnings.setdefault(alias_base_minus, []).append(
|
592
|
+
check2.explanation
|
593
|
+
)
|
594
|
+
raise AssertionError(f"{alias_base_minus}", warnings, new_wu)
|
595
|
+
|
596
|
+
# NOTE: At this point we are sure that BASE and BASE- are present and valid between themselves in the new wheel
|
597
|
+
|
598
|
+
other_plus_regenerated = False
|
599
|
+
if changed.get(other_plus) or changed.get(other):
|
600
|
+
check3 = check(
|
601
|
+
is_positive_side,
|
602
|
+
self,
|
603
|
+
getattr(new_wu, other_plus).statement,
|
604
|
+
getattr(new_wu, other).statement,
|
605
|
+
)
|
606
|
+
|
607
|
+
if not check3.valid:
|
608
|
+
if changed.get(other) and not changed.get(other_plus):
|
609
|
+
op = map_from_dto(await other_positive_side_fn(
|
610
|
+
getattr(new_wu, other).statement,
|
611
|
+
getattr(new_wu, base_minus).statement,
|
612
|
+
), DialecticalComponent)
|
613
|
+
assert isinstance(op, DialecticalComponent)
|
614
|
+
if op.best_rationale and op.best_rationale.text:
|
615
|
+
op.best_rationale.text = f"REGENERATED. {op.best_rationale.text}"
|
616
|
+
setattr(new_wu, other_plus, op)
|
617
|
+
changed[other_plus] = op.statement
|
618
|
+
check3.valid = True
|
619
|
+
check3.explanation = "Regenerated, therefore must be valid."
|
620
|
+
other_plus_regenerated = True
|
621
|
+
|
622
|
+
if not check3.valid:
|
623
|
+
getattr(new_wu, other_plus).statement = (
|
624
|
+
f"ERROR: {getattr(new_wu, other_plus).statement}"
|
625
|
+
)
|
626
|
+
warnings.setdefault(alias_other_plus, []).append(
|
627
|
+
check3.explanation
|
628
|
+
)
|
629
|
+
raise AssertionError(f"{alias_other_plus}", warnings, new_wu)
|
630
|
+
|
631
|
+
# NOTE: At this point we are sure that OTHER and OTHER- are present and valid between themselves in the new wheel
|
632
|
+
|
633
|
+
additional_diagonal_check_skip = other_plus_regenerated or (
|
634
|
+
not changed.get(base_minus) and not changed.get(other_plus)
|
635
|
+
)
|
636
|
+
|
637
|
+
if not additional_diagonal_check_skip:
|
638
|
+
check4 = check(
|
639
|
+
is_strict_opposition,
|
640
|
+
self,
|
641
|
+
getattr(new_wu, base_minus).statement,
|
642
|
+
getattr(new_wu, other_plus).statement,
|
643
|
+
)
|
644
|
+
if not check4.valid:
|
645
|
+
if changed.get(base_minus) and not changed.get(other_plus):
|
646
|
+
# base side changed
|
647
|
+
op = map_from_dto(await other_positive_side_fn(
|
648
|
+
getattr(new_wu, other).statement,
|
649
|
+
getattr(new_wu, base_minus).statement,
|
650
|
+
), DialecticalComponent)
|
651
|
+
assert isinstance(op, DialecticalComponent)
|
652
|
+
if op.best_rationale and op.best_rationale.text:
|
653
|
+
op.best_rationale.text = f"REGENERATED. {op.best_rationale.text}"
|
654
|
+
setattr(new_wu, other_plus, op)
|
655
|
+
changed[other_plus] = op.statement
|
656
|
+
check4.valid = True
|
657
|
+
check4.explanation = "Regenerated, therefore must be valid."
|
658
|
+
elif changed.get(other_plus) and not changed.get(base_minus):
|
659
|
+
# other side changed
|
660
|
+
not_like_other_minus = ""
|
661
|
+
if hasattr(new_wu, other_minus):
|
662
|
+
if getattr(new_wu, other_minus):
|
663
|
+
not_like_other_minus = getattr(
|
664
|
+
new_wu, other_minus
|
665
|
+
).statement
|
666
|
+
bm = map_from_dto(await base_negative_side_fn(
|
667
|
+
getattr(new_wu, base).statement, not_like_other_minus
|
668
|
+
), DialecticalComponent)
|
669
|
+
assert isinstance(bm, DialecticalComponent)
|
670
|
+
if bm.best_rationale and bm.best_rationale.text:
|
671
|
+
bm.best_rationale.text = f"REGENERATED. {bm.best_rationale.text}"
|
672
|
+
setattr(new_wu, base_minus, bm)
|
673
|
+
changed[base_minus] = bm.statement
|
674
|
+
check4.valid = True
|
675
|
+
check4.explanation = "Regenerated, therefore must be valid."
|
676
|
+
|
677
|
+
if not check4.valid:
|
678
|
+
getattr(new_wu, base_minus).statement = (
|
679
|
+
f"ERROR: {getattr(new_wu, base_minus).statement}"
|
680
|
+
)
|
681
|
+
getattr(new_wu, other_plus).statement = (
|
682
|
+
f"ERROR: {getattr(new_wu, other_plus).statement}"
|
683
|
+
)
|
684
|
+
warnings.setdefault(alias_base_minus, []).append(
|
685
|
+
check4.explanation
|
686
|
+
)
|
687
|
+
warnings.setdefault(alias_other_plus, []).append(
|
688
|
+
check4.explanation
|
689
|
+
)
|
690
|
+
raise AssertionError(
|
691
|
+
f"{alias_base_minus}, {alias_other_plus}", warnings, new_wu
|
692
|
+
)
|
693
|
+
|
694
|
+
# NOTE: At this point we are sure that diagonals are present and valid in the new wheel
|
695
|
+
|
696
|
+
else:
|
697
|
+
# Keep originals
|
698
|
+
pass
|
699
|
+
|
700
|
+
return new_wu
|