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,87 @@
|
|
1
|
+
from mirascope import Messages, prompt_template
|
2
|
+
from mirascope.integrations.langfuse import with_langfuse
|
3
|
+
|
4
|
+
from dialectical_framework.analyst.domain.rationale import Rationale
|
5
|
+
from dialectical_framework.analyst.domain.transition_segment_to_segment import \
|
6
|
+
TransitionSegmentToSegment
|
7
|
+
from dialectical_framework.analyst.strategic_consultant import \
|
8
|
+
StrategicConsultant
|
9
|
+
from dialectical_framework.enums.predicate import Predicate
|
10
|
+
from dialectical_framework.synthesist.reverse_engineer import ReverseEngineer
|
11
|
+
from dialectical_framework.utils.use_brain import use_brain
|
12
|
+
from dialectical_framework.wheel_segment import WheelSegment
|
13
|
+
|
14
|
+
|
15
|
+
class ThinkConstructiveConvergence(StrategicConsultant):
|
16
|
+
@prompt_template(
|
17
|
+
"""
|
18
|
+
MESSAGES:
|
19
|
+
{wheel_construction}
|
20
|
+
|
21
|
+
USER:
|
22
|
+
<instructions>
|
23
|
+
Identify the most actionable intermediate transition step that transforms the negative/exaggerated side of {from_alias}, i.e. {from_minus_alias}, to the positive/constructive side of the {to_alias}, i.e. {to_plus_alias}:
|
24
|
+
|
25
|
+
This step should be:
|
26
|
+
- Concrete and immediately implementable
|
27
|
+
- Bridge the gap between opposing or contrasting elements
|
28
|
+
- Create momentum toward synthesis and balance
|
29
|
+
- Address the root tension that causes the negative aspect
|
30
|
+
|
31
|
+
1. Start with the negative (-) or neutral state of {from_alias}, i.e. {from_minus_alias} or {from_alias}
|
32
|
+
2. To reach {to_plus_alias} identify
|
33
|
+
- **Action**: What specific step to take (1-2 sentences)
|
34
|
+
- **Mechanism**: How this step transforms the negative into positive (1 sentence)
|
35
|
+
- **Timing**: When this transition is most effective (1 phrase)
|
36
|
+
|
37
|
+
<examples>
|
38
|
+
1) T1- (Tyranny) → T2+ (Balance):
|
39
|
+
**Action**: Implement transparent priority matrices with employee input
|
40
|
+
**Mechanism**: Converts rigid control into collaborative structure
|
41
|
+
**Timing**: During planning cycles
|
42
|
+
</examples>
|
43
|
+
</instructions>
|
44
|
+
|
45
|
+
<formatting>
|
46
|
+
Output the transition step as a fluent practical, implementable action plan (summarized but not mentioning derived Action, Mechanism, and Timing) that someone could take immediately to facilitate the transformation. Don't mention any special denotations such as "T", "T+", "A-", "Ac", "Re", etc.
|
47
|
+
</formatting>
|
48
|
+
"""
|
49
|
+
)
|
50
|
+
def prompt(
|
51
|
+
self, text: str, focus: WheelSegment, next_ws: WheelSegment
|
52
|
+
) -> Messages.Type:
|
53
|
+
# TODO: do we want to include transitions that are already present in the wheel?
|
54
|
+
return {
|
55
|
+
"computed_fields": {
|
56
|
+
"wheel_construction": ReverseEngineer.wheel(
|
57
|
+
text=text, wheel=self._wheel
|
58
|
+
),
|
59
|
+
"from_alias": focus.t.alias,
|
60
|
+
"from_minus_alias": focus.t_minus.alias,
|
61
|
+
"to_alias": next_ws.t.alias,
|
62
|
+
"to_plus_alias": next_ws.t_plus.alias,
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
@with_langfuse()
|
67
|
+
@use_brain(response_model=str)
|
68
|
+
async def constructive_convergence(
|
69
|
+
self, focus: WheelSegment, next_ws: WheelSegment
|
70
|
+
):
|
71
|
+
return self.prompt(self._text, focus=focus, next_ws=next_ws)
|
72
|
+
|
73
|
+
async def think(self, focus: WheelSegment) -> TransitionSegmentToSegment:
|
74
|
+
current_index = self._wheel.index_of(focus)
|
75
|
+
next_index = (current_index + 1) % self._wheel.degree
|
76
|
+
next_ws = self._wheel.wheel_segment_at(next_index)
|
77
|
+
|
78
|
+
return TransitionSegmentToSegment(
|
79
|
+
predicate=Predicate.CONSTRUCTIVELY_CONVERGES_TO,
|
80
|
+
source_aliases=[focus.t_minus.alias, focus.t.alias],
|
81
|
+
target_aliases=[next_ws.t_plus.alias],
|
82
|
+
source=focus,
|
83
|
+
target=next_ws,
|
84
|
+
rationales=[Rationale(
|
85
|
+
text=await self.constructive_convergence(focus=focus, next_ws=next_ws)
|
86
|
+
)],
|
87
|
+
)
|
@@ -0,0 +1,157 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from typing import Dict, Union
|
3
|
+
|
4
|
+
from dialectical_framework.analyst.domain.cycle import Cycle
|
5
|
+
from dialectical_framework.analyst.domain.transition import Transition
|
6
|
+
from dialectical_framework.enums.predicate import Predicate
|
7
|
+
from dialectical_framework.settings import Settings
|
8
|
+
from dialectical_framework.synthesist.polarity.polarity_reasoner import \
|
9
|
+
PolarityReasoner
|
10
|
+
from dialectical_framework.synthesist.wheel_builder import WheelBuilder
|
11
|
+
from dialectical_framework.wheel import Wheel, WheelSegmentReference
|
12
|
+
from dialectical_framework.wheel_segment import WheelSegment
|
13
|
+
|
14
|
+
|
15
|
+
class WheelBuilderTransitionCalculator(WheelBuilder, ABC):
|
16
|
+
def __init__(self, builder: WheelBuilder):
|
17
|
+
super().__init__(text=builder.text)
|
18
|
+
self.__decorated_builder = builder
|
19
|
+
|
20
|
+
@property
|
21
|
+
def decorated_builder(self) -> WheelBuilder:
|
22
|
+
return self.__decorated_builder
|
23
|
+
|
24
|
+
@property
|
25
|
+
def reasoner(self) -> PolarityReasoner:
|
26
|
+
return self.__decorated_builder.reasoner
|
27
|
+
|
28
|
+
@property
|
29
|
+
def wheel_permutations(self) -> list[Wheel]:
|
30
|
+
return self.__decorated_builder.wheel_permutations
|
31
|
+
|
32
|
+
@property
|
33
|
+
def text(self) -> str | None:
|
34
|
+
return self.__decorated_builder.text
|
35
|
+
|
36
|
+
@property
|
37
|
+
def settings(self) -> Settings:
|
38
|
+
return self.__decorated_builder.settings
|
39
|
+
|
40
|
+
async def build_wheel_permutations(
|
41
|
+
self, *, theses: list[Union[str, None]] = None, t_cycle: Cycle = None
|
42
|
+
) -> list[Wheel]:
|
43
|
+
await self.__decorated_builder.build_wheel_permutations(
|
44
|
+
theses=theses, t_cycle=t_cycle
|
45
|
+
)
|
46
|
+
return self.wheel_permutations
|
47
|
+
|
48
|
+
async def redefine(
|
49
|
+
self, modified_statement_per_alias: Dict[str, str]
|
50
|
+
) -> list[Wheel]:
|
51
|
+
await self.__decorated_builder.redefine(
|
52
|
+
modified_statement_per_alias=modified_statement_per_alias
|
53
|
+
)
|
54
|
+
return self.wheel_permutations
|
55
|
+
|
56
|
+
async def calculate_syntheses(
|
57
|
+
self,
|
58
|
+
wheel: Wheel,
|
59
|
+
at: WheelSegmentReference | list[WheelSegmentReference] = None,
|
60
|
+
):
|
61
|
+
await self.__decorated_builder.calculate_syntheses(wheel=wheel, at=at)
|
62
|
+
|
63
|
+
async def calculate_transitions(
|
64
|
+
self,
|
65
|
+
wheel: Wheel,
|
66
|
+
at: WheelSegmentReference | list[WheelSegmentReference] = None,
|
67
|
+
):
|
68
|
+
if wheel not in self.wheel_permutations:
|
69
|
+
raise ValueError(f"Wheel permutation {wheel} not found in available wheels")
|
70
|
+
|
71
|
+
if at is None:
|
72
|
+
# Calculate for each
|
73
|
+
if hasattr(self.decorated_builder, "calculate_transitions"):
|
74
|
+
await self.decorated_builder.calculate_transitions(wheel=wheel, at=None)
|
75
|
+
# This is for subclasses to implement
|
76
|
+
trs = await self._do_calculate_transitions_all(wheel=wheel)
|
77
|
+
for tr in trs:
|
78
|
+
self._take_transition(wheel=wheel, transition=tr)
|
79
|
+
elif isinstance(at, list):
|
80
|
+
# Calculate for some
|
81
|
+
for ref in at:
|
82
|
+
if hasattr(self.decorated_builder, "calculate_transitions"):
|
83
|
+
await self.decorated_builder.calculate_transitions(
|
84
|
+
wheel=wheel, at=ref
|
85
|
+
)
|
86
|
+
# This is for subclasses to implement
|
87
|
+
trs_i = await self._do_calculate_transitions(wheel=wheel, at=ref)
|
88
|
+
for tr in trs_i:
|
89
|
+
self._take_transition(wheel=wheel, transition=tr)
|
90
|
+
else:
|
91
|
+
# Calculate for one
|
92
|
+
if hasattr(self.decorated_builder, "calculate_transitions"):
|
93
|
+
await self.decorated_builder.calculate_transitions(wheel=wheel, at=at)
|
94
|
+
# This is for subclasses to implement
|
95
|
+
trs = await self._do_calculate_transitions(wheel=wheel, at=at)
|
96
|
+
for tr in trs:
|
97
|
+
self._take_transition(wheel=wheel, transition=tr)
|
98
|
+
|
99
|
+
wheel.calculate_score()
|
100
|
+
|
101
|
+
@abstractmethod
|
102
|
+
async def _do_calculate_transitions(
|
103
|
+
self, wheel: Wheel, at: WheelSegment
|
104
|
+
) -> list[Transition]:
|
105
|
+
"""Subclasses implement the actual transition calculation logic here."""
|
106
|
+
|
107
|
+
@abstractmethod
|
108
|
+
async def _do_calculate_transitions_all(self, wheel: Wheel) -> list[Transition]:
|
109
|
+
"""Subclasses implement the actual transition calculation logic here."""
|
110
|
+
|
111
|
+
@staticmethod
|
112
|
+
def _take_transition(wheel: Wheel, transition: Transition) -> None:
|
113
|
+
"""
|
114
|
+
The decorator might be enriching the existing transition, so we need to merge, not just add
|
115
|
+
"""
|
116
|
+
new_transition = transition
|
117
|
+
|
118
|
+
if transition.predicate == Predicate.TRANSFORMS_TO:
|
119
|
+
# this is only valid for wisdom units!
|
120
|
+
wu = wheel.wisdom_unit_at(transition.source)
|
121
|
+
old_transition = wu.transformation.graph.get_transition(
|
122
|
+
transition.source_aliases, transition.target_aliases
|
123
|
+
)
|
124
|
+
if old_transition is not None:
|
125
|
+
new_transition = old_transition.new_with(transition)
|
126
|
+
wu.transformation.graph.add_transition(new_transition)
|
127
|
+
elif transition.predicate == Predicate.CONSTRUCTIVELY_CONVERGES_TO:
|
128
|
+
old_transition = wheel.spiral.graph.get_transition(
|
129
|
+
transition.source_aliases, transition.target_aliases
|
130
|
+
)
|
131
|
+
if old_transition is not None:
|
132
|
+
new_transition = old_transition.new_with(transition)
|
133
|
+
wheel.spiral.graph.add_transition(new_transition)
|
134
|
+
elif transition.predicate == Predicate.CAUSES:
|
135
|
+
# Cycle graphs must be present in the wheel upfront, so we only enrich the transitions
|
136
|
+
# TODO: I'm not 100% confident, that given a transition we should update it in both cycles. They might have different rationales... in each cycle.
|
137
|
+
graph = None
|
138
|
+
old_transition = wheel.t_cycle.graph.get_transition(
|
139
|
+
transition.source_aliases, transition.target_aliases
|
140
|
+
)
|
141
|
+
if old_transition is not None:
|
142
|
+
graph = wheel.t_cycle.graph
|
143
|
+
if graph:
|
144
|
+
if old_transition is not None:
|
145
|
+
new_transition = old_transition.new_with(transition)
|
146
|
+
graph.add_transition(new_transition)
|
147
|
+
graph = None
|
148
|
+
|
149
|
+
old_transition = wheel.cycle.graph.get_transition(
|
150
|
+
transition.source_aliases, transition.target_aliases
|
151
|
+
)
|
152
|
+
if old_transition is not None:
|
153
|
+
graph = wheel.cycle.graph
|
154
|
+
if graph:
|
155
|
+
if old_transition is not None:
|
156
|
+
new_transition = old_transition.new_with(transition)
|
157
|
+
graph.add_transition(new_transition)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class Brain:
|
2
|
+
def __init__(self, *, ai_model: str, ai_provider: str | None):
|
3
|
+
if not ai_provider:
|
4
|
+
if not "/" in ai_model:
|
5
|
+
raise ValueError(
|
6
|
+
"ai_model must be in the form of 'provider/model' if ai_provider is not specified."
|
7
|
+
)
|
8
|
+
else:
|
9
|
+
derived_ai_provider, derived_ai_model = ai_model.split("/", 1)
|
10
|
+
self._ai_provider, self._ai_model = (
|
11
|
+
derived_ai_provider,
|
12
|
+
derived_ai_model,
|
13
|
+
)
|
14
|
+
else:
|
15
|
+
if not "/" in ai_model:
|
16
|
+
self._ai_provider, self._ai_model = ai_provider, ai_model
|
17
|
+
else:
|
18
|
+
derived_ai_provider, derived_ai_model = ai_model.split("/", 1)
|
19
|
+
# Special case for litellm which can handle models with provider/ prefix
|
20
|
+
if ai_provider == "litellm":
|
21
|
+
self._ai_provider, self._ai_model = ai_provider, ai_model
|
22
|
+
elif derived_ai_provider != ai_provider:
|
23
|
+
raise ValueError(
|
24
|
+
f"ai_provider '{ai_provider}' does not match ai_model '{ai_model}'"
|
25
|
+
)
|
26
|
+
else:
|
27
|
+
self._ai_provider, self._ai_model = (
|
28
|
+
derived_ai_provider,
|
29
|
+
derived_ai_model,
|
30
|
+
)
|
31
|
+
|
32
|
+
def __str__(self):
|
33
|
+
provider, model = self.specification()
|
34
|
+
return f"Brain(provider='{provider}', model='{model}')"
|
35
|
+
|
36
|
+
def specification(self) -> tuple[str, str]:
|
37
|
+
return self._ai_provider, self._ai_model
|
38
|
+
|
39
|
+
def modified_specification(
|
40
|
+
self, *, ai_provider: str | None = None, ai_model: str | None = None
|
41
|
+
) -> tuple[str, str]:
|
42
|
+
"""
|
43
|
+
This doesn't mutate the current instance.
|
44
|
+
"""
|
45
|
+
current_provider, current_model = self.specification()
|
46
|
+
|
47
|
+
if ai_provider == "litellm":
|
48
|
+
if not ai_model:
|
49
|
+
if not current_provider and not current_model:
|
50
|
+
raise ValueError("ai_model not provided.")
|
51
|
+
else:
|
52
|
+
return ai_provider, f"{current_provider}/{current_model}"
|
53
|
+
else:
|
54
|
+
if not "/" in ai_model:
|
55
|
+
if not current_provider:
|
56
|
+
raise ValueError(
|
57
|
+
"ai_model must be in the form of 'provider/model' for litellm."
|
58
|
+
)
|
59
|
+
else:
|
60
|
+
return ai_provider, f"{current_provider}/{ai_model}"
|
61
|
+
else:
|
62
|
+
return ai_provider, ai_model
|
63
|
+
|
64
|
+
if not ai_provider:
|
65
|
+
if not "/" in ai_model:
|
66
|
+
raise ValueError(
|
67
|
+
"ai_model must be in the form of 'provider/model' if ai_provider is not specified."
|
68
|
+
)
|
69
|
+
else:
|
70
|
+
derived_ai_provider, derived_ai_model = ai_model.split("/", 1)
|
71
|
+
return derived_ai_provider, derived_ai_model
|
72
|
+
else:
|
73
|
+
if not "/" in ai_model:
|
74
|
+
return ai_provider, ai_model
|
75
|
+
else:
|
76
|
+
derived_ai_provider, derived_ai_model = ai_model.split("/", 1)
|
77
|
+
if derived_ai_provider != ai_provider:
|
78
|
+
raise ValueError(
|
79
|
+
f"ai_provider '{ai_provider}' does not match ai_model '{ai_model}'"
|
80
|
+
)
|
81
|
+
return derived_ai_provider, derived_ai_model
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
3
|
+
|
4
|
+
from dialectical_framework.wisdom_unit import WisdomUnit
|
5
|
+
|
6
|
+
|
7
|
+
class DialecticalAnalysis(BaseModel):
|
8
|
+
model_config = ConfigDict(
|
9
|
+
extra="forbid",
|
10
|
+
)
|
11
|
+
|
12
|
+
# TODO: This should rather be documents, references, images, texts...
|
13
|
+
corpus: str = None
|
14
|
+
perspectives: list[WisdomUnit] = []
|
@@ -0,0 +1,111 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import re
|
4
|
+
|
5
|
+
from pydantic import Field
|
6
|
+
|
7
|
+
from dialectical_framework.protocols.ratable import Ratable
|
8
|
+
|
9
|
+
|
10
|
+
class DialecticalComponent(Ratable):
|
11
|
+
alias: str = Field(
|
12
|
+
...,
|
13
|
+
description="The user friendly name of the dialectical component such as T, A, T+, A+, etc.",
|
14
|
+
)
|
15
|
+
statement: str = Field(
|
16
|
+
...,
|
17
|
+
description="The dialectical component value that is provided after analysis.",
|
18
|
+
)
|
19
|
+
|
20
|
+
def is_same(self, other: DialecticalComponent) -> bool:
|
21
|
+
"""
|
22
|
+
Determines if the current object is equal to another object based on their attributes.
|
23
|
+
|
24
|
+
This method compares the `alias` and `statement` attributes of the current object
|
25
|
+
with those of another object to check if they are identical.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
other: The object to compare against the current object.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
bool: True if both `alias` and `statement` attributes of the objects are
|
32
|
+
the same, otherwise False.
|
33
|
+
"""
|
34
|
+
return (
|
35
|
+
self == other
|
36
|
+
or self.alias == other.alias
|
37
|
+
and self.statement == other.statement
|
38
|
+
)
|
39
|
+
|
40
|
+
def get_human_friendly_index(self) -> int:
|
41
|
+
"""
|
42
|
+
Converts the alias of an object into a human-friendly integer index.
|
43
|
+
|
44
|
+
This method processes an alias string provided by the object and identifies
|
45
|
+
the last sequence of digits as the human-readable integer index. If no index
|
46
|
+
can be identified, the method defaults to returning zero.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
int: The extracted integer index if present; otherwise, returns 0.
|
50
|
+
"""
|
51
|
+
# Find the last sequence of digits in the alias
|
52
|
+
match = re.search(r"(\d+)(?!.*\d)", self.alias)
|
53
|
+
return int(match.group(1)) if match else 0
|
54
|
+
|
55
|
+
def set_human_friendly_index(self, human_friendly_index: int):
|
56
|
+
"""
|
57
|
+
Updates the alias of the object by replacing the last sequence of digits with the
|
58
|
+
provided human-friendly index. If the index is 0, removes any existing digits entirely.
|
59
|
+
If no digits exist and index > 0, inserts the index before any trailing signs.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
human_friendly_index: The integer index to replace the last sequence of digits
|
63
|
+
in the alias with. If 0, removes existing digits.
|
64
|
+
"""
|
65
|
+
if human_friendly_index == 0:
|
66
|
+
# Remove the last sequence of digits entirely
|
67
|
+
self.alias = re.sub(r"(\d+)(?!.*\d)", "", self.alias)
|
68
|
+
else:
|
69
|
+
# Try to replace existing digits first
|
70
|
+
if re.search(r"\d", self.alias):
|
71
|
+
# Replace the last sequence of digits with the new index
|
72
|
+
self.alias = re.sub(
|
73
|
+
r"(\d+)(?!.*\d)", str(human_friendly_index), self.alias
|
74
|
+
)
|
75
|
+
else:
|
76
|
+
# No digits exist, insert before any trailing signs
|
77
|
+
match = re.search(r"([+-]+)$", self.alias)
|
78
|
+
if match:
|
79
|
+
# Has trailing signs, insert index before them
|
80
|
+
base = self.alias[: match.start()]
|
81
|
+
signs = match.group(1)
|
82
|
+
self.alias = f"{base}{human_friendly_index}{signs}"
|
83
|
+
else:
|
84
|
+
# No trailing signs, just append the index
|
85
|
+
self.alias = f"{self.alias}{human_friendly_index}"
|
86
|
+
|
87
|
+
def calculate_probability(self, *, mutate: bool = True) -> float | None:
|
88
|
+
"""
|
89
|
+
Fallback to 1.0 for leaves.
|
90
|
+
"""
|
91
|
+
|
92
|
+
# A statement is a fact, so probability is always 1.0
|
93
|
+
probability = self.probability if self.probability is not None else 1.0
|
94
|
+
|
95
|
+
if mutate:
|
96
|
+
self.probability = probability
|
97
|
+
|
98
|
+
return probability
|
99
|
+
|
100
|
+
def pretty(
|
101
|
+
self, dialectical_component_label: str | None = None, *, skip_explanation=False
|
102
|
+
) -> str:
|
103
|
+
if not dialectical_component_label:
|
104
|
+
dialectical_component_label = self.alias
|
105
|
+
result = f"{dialectical_component_label} = {self.statement}"
|
106
|
+
if self.best_rationale and not skip_explanation:
|
107
|
+
result = f"{result}\nExplanation: {self.best_rationale.text if self.best_rationale else ''}"
|
108
|
+
return result
|
109
|
+
|
110
|
+
def __str__(self):
|
111
|
+
return self.pretty()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
from pydantic import BaseModel, Field
|
3
|
+
|
4
|
+
from dialectical_framework.dialectical_component import DialecticalComponent
|
5
|
+
|
6
|
+
|
7
|
+
class DialecticalComponentsDeck(BaseModel):
|
8
|
+
dialectical_components: list[DialecticalComponent] = Field(
|
9
|
+
...,
|
10
|
+
description="A list of dialectical components. It can be empty when no dialectical components are found. It might also be filled with only one dialectical component if only one is to be found.",
|
11
|
+
)
|
12
|
+
|
13
|
+
def get_aliases_as_cycle_str(self) -> str:
|
14
|
+
aliases = self.get_aliases()
|
15
|
+
|
16
|
+
if len(aliases) < 2:
|
17
|
+
return ""
|
18
|
+
else:
|
19
|
+
# Create a simple cycle: first → second → third → ... → first
|
20
|
+
cycle_parts = aliases + [aliases[0]] # Add first element at the end
|
21
|
+
return " → ".join(cycle_parts) + "..."
|
22
|
+
|
23
|
+
def get_aliases(self) -> list[str]:
|
24
|
+
return [dc.alias for dc in self.dialectical_components]
|
25
|
+
|
26
|
+
def get_by_alias(self, alias: str) -> DialecticalComponent:
|
27
|
+
return next(filter(lambda d: d.alias == alias, self.dialectical_components))
|
28
|
+
|
29
|
+
def rearrange_by_aliases(
|
30
|
+
self, ordered_aliases: list[str], mutate: bool = False
|
31
|
+
) -> list[DialecticalComponent]:
|
32
|
+
# Use dict to maintain first occurrence order while removing duplicates
|
33
|
+
unique_aliases = dict.fromkeys(ordered_aliases)
|
34
|
+
|
35
|
+
sorted_components = []
|
36
|
+
for alias in unique_aliases:
|
37
|
+
component = next(
|
38
|
+
(c for c in self.dialectical_components if c.alias == alias), None
|
39
|
+
)
|
40
|
+
if component:
|
41
|
+
sorted_components.append(component)
|
42
|
+
|
43
|
+
if mutate:
|
44
|
+
# mutate the existing list in place instead of rebinding the attribute
|
45
|
+
self.dialectical_components[:] = sorted_components
|
46
|
+
return self.dialectical_components
|
47
|
+
else:
|
48
|
+
return sorted_components
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import importlib
|
2
|
+
import pkgutil
|
3
|
+
|
4
|
+
from dependency_injector import containers, providers
|
5
|
+
|
6
|
+
from dialectical_framework.brain import Brain
|
7
|
+
from dialectical_framework.enums.causality_type import CausalityType
|
8
|
+
from dialectical_framework.protocols.causality_sequencer import \
|
9
|
+
CausalitySequencer
|
10
|
+
from dialectical_framework.protocols.thesis_extractor import ThesisExtractor
|
11
|
+
from dialectical_framework.settings import Settings
|
12
|
+
from dialectical_framework.synthesist.causality.causality_sequencer_balanced import \
|
13
|
+
CausalitySequencerBalanced
|
14
|
+
from dialectical_framework.synthesist.causality.causality_sequencer_desirable import \
|
15
|
+
CausalitySequencerDesirable
|
16
|
+
from dialectical_framework.synthesist.causality.causality_sequencer_feasible import \
|
17
|
+
CausalitySequencerFeasible
|
18
|
+
from dialectical_framework.synthesist.causality.causality_sequencer_realistic import \
|
19
|
+
CausalitySequencerRealistic
|
20
|
+
from dialectical_framework.synthesist.concepts.thesis_extractor_basic import \
|
21
|
+
ThesisExtractorBasic
|
22
|
+
from dialectical_framework.synthesist.polarity.polarity_reasoner import \
|
23
|
+
PolarityReasoner
|
24
|
+
from dialectical_framework.synthesist.polarity.reason_fast_and_simple import \
|
25
|
+
ReasonFastAndSimple
|
26
|
+
from dialectical_framework.synthesist.wheel_builder import WheelBuilder
|
27
|
+
|
28
|
+
|
29
|
+
class DialecticalReasoning(containers.DeclarativeContainer):
|
30
|
+
"""
|
31
|
+
Main DI container for the Dialectical Reasoning Framework.
|
32
|
+
|
33
|
+
Provides injectable services for building wheels and calculating transitions.
|
34
|
+
|
35
|
+
|
36
|
+
IMPORTANT:
|
37
|
+
When renaming the fields, make sure to also change it in di.py, as IDE refactoring will not do it automatically.
|
38
|
+
"""
|
39
|
+
|
40
|
+
@classmethod
|
41
|
+
def setup(cls, settings: Settings) -> 'DialecticalReasoning':
|
42
|
+
"""Create a new container instance with user-specific settings."""
|
43
|
+
container = cls()
|
44
|
+
container.settings.override(settings)
|
45
|
+
return container
|
46
|
+
|
47
|
+
# It will be the same settings for all services in the container
|
48
|
+
settings = providers.Dependency(instance_of=Settings)
|
49
|
+
|
50
|
+
brain: providers.Factory[Brain] = providers.Factory(
|
51
|
+
lambda settings: Brain(ai_model=settings.ai_model, ai_provider=settings.ai_provider),
|
52
|
+
settings=settings
|
53
|
+
)
|
54
|
+
|
55
|
+
polarity_reasoner: providers.Factory[PolarityReasoner] = providers.Factory(
|
56
|
+
ReasonFastAndSimple,
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
@staticmethod
|
61
|
+
def _create_causality_sequencer(settings: Settings) -> CausalitySequencer:
|
62
|
+
"""Factory method to create the appropriate causality sequencer based on config"""
|
63
|
+
causality_type = settings.causality_type
|
64
|
+
|
65
|
+
if causality_type == CausalityType.DESIRABLE:
|
66
|
+
return CausalitySequencerDesirable()
|
67
|
+
elif causality_type == CausalityType.FEASIBLE:
|
68
|
+
return CausalitySequencerFeasible()
|
69
|
+
elif causality_type == CausalityType.REALISTIC:
|
70
|
+
return CausalitySequencerRealistic()
|
71
|
+
else:
|
72
|
+
return CausalitySequencerBalanced()
|
73
|
+
|
74
|
+
causality_sequencer: providers.Factory[CausalitySequencer] = providers.Factory(
|
75
|
+
_create_causality_sequencer,
|
76
|
+
settings=settings,
|
77
|
+
)
|
78
|
+
|
79
|
+
thesis_extractor: providers.Factory[ThesisExtractor] = providers.Factory(
|
80
|
+
ThesisExtractorBasic,
|
81
|
+
)
|
82
|
+
|
83
|
+
wheel_builder: providers.Factory[WheelBuilder] = providers.Factory(WheelBuilder)
|
84
|
+
|
85
|
+
# -- Wiring --
|
86
|
+
|
87
|
+
@staticmethod
|
88
|
+
def _discover_modules() -> list[str]:
|
89
|
+
try:
|
90
|
+
package = importlib.import_module("dialectical_framework")
|
91
|
+
modules = []
|
92
|
+
|
93
|
+
for _, module_name, _ in pkgutil.walk_packages(
|
94
|
+
package.__path__, package.__name__ + "."
|
95
|
+
):
|
96
|
+
modules.append(module_name)
|
97
|
+
|
98
|
+
return modules
|
99
|
+
except (ImportError, AttributeError):
|
100
|
+
# Fallback to empty list if package can't be imported
|
101
|
+
return []
|
102
|
+
|
103
|
+
wiring_config = containers.WiringConfiguration(
|
104
|
+
modules=_discover_modules(),
|
105
|
+
)
|