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.
Files changed (84) hide show
  1. dialectical_framework/__init__.py +43 -0
  2. dialectical_framework/ai_dto/__init__.py +0 -0
  3. dialectical_framework/ai_dto/causal_cycle_assessment_dto.py +16 -0
  4. dialectical_framework/ai_dto/causal_cycle_dto.py +16 -0
  5. dialectical_framework/ai_dto/causal_cycles_deck_dto.py +11 -0
  6. dialectical_framework/ai_dto/dialectical_component_dto.py +16 -0
  7. dialectical_framework/ai_dto/dialectical_components_deck_dto.py +12 -0
  8. dialectical_framework/ai_dto/dto_mapper.py +103 -0
  9. dialectical_framework/ai_dto/reciprocal_solution_dto.py +24 -0
  10. dialectical_framework/analyst/__init__.py +1 -0
  11. dialectical_framework/analyst/audit/__init__.py +0 -0
  12. dialectical_framework/analyst/consultant.py +30 -0
  13. dialectical_framework/analyst/decorator_action_reflection.py +28 -0
  14. dialectical_framework/analyst/decorator_discrete_spiral.py +30 -0
  15. dialectical_framework/analyst/domain/__init__.py +0 -0
  16. dialectical_framework/analyst/domain/assessable_cycle.py +128 -0
  17. dialectical_framework/analyst/domain/cycle.py +133 -0
  18. dialectical_framework/analyst/domain/interpretation.py +16 -0
  19. dialectical_framework/analyst/domain/rationale.py +116 -0
  20. dialectical_framework/analyst/domain/spiral.py +47 -0
  21. dialectical_framework/analyst/domain/transformation.py +24 -0
  22. dialectical_framework/analyst/domain/transition.py +160 -0
  23. dialectical_framework/analyst/domain/transition_cell_to_cell.py +29 -0
  24. dialectical_framework/analyst/domain/transition_segment_to_segment.py +16 -0
  25. dialectical_framework/analyst/strategic_consultant.py +32 -0
  26. dialectical_framework/analyst/think_action_reflection.py +217 -0
  27. dialectical_framework/analyst/think_constructive_convergence.py +87 -0
  28. dialectical_framework/analyst/wheel_builder_transition_calculator.py +157 -0
  29. dialectical_framework/brain.py +81 -0
  30. dialectical_framework/dialectical_analysis.py +14 -0
  31. dialectical_framework/dialectical_component.py +111 -0
  32. dialectical_framework/dialectical_components_deck.py +48 -0
  33. dialectical_framework/dialectical_reasoning.py +105 -0
  34. dialectical_framework/directed_graph.py +419 -0
  35. dialectical_framework/enums/__init__.py +0 -0
  36. dialectical_framework/enums/causality_type.py +10 -0
  37. dialectical_framework/enums/di.py +11 -0
  38. dialectical_framework/enums/dialectical_reasoning_mode.py +9 -0
  39. dialectical_framework/enums/predicate.py +9 -0
  40. dialectical_framework/protocols/__init__.py +0 -0
  41. dialectical_framework/protocols/assessable.py +141 -0
  42. dialectical_framework/protocols/causality_sequencer.py +111 -0
  43. dialectical_framework/protocols/content_fidelity_evaluator.py +16 -0
  44. dialectical_framework/protocols/has_brain.py +18 -0
  45. dialectical_framework/protocols/has_config.py +18 -0
  46. dialectical_framework/protocols/ratable.py +79 -0
  47. dialectical_framework/protocols/reloadable.py +7 -0
  48. dialectical_framework/protocols/thesis_extractor.py +15 -0
  49. dialectical_framework/settings.py +77 -0
  50. dialectical_framework/synthesis.py +7 -0
  51. dialectical_framework/synthesist/__init__.py +1 -0
  52. dialectical_framework/synthesist/causality/__init__.py +0 -0
  53. dialectical_framework/synthesist/causality/causality_sequencer_balanced.py +398 -0
  54. dialectical_framework/synthesist/causality/causality_sequencer_desirable.py +55 -0
  55. dialectical_framework/synthesist/causality/causality_sequencer_feasible.py +55 -0
  56. dialectical_framework/synthesist/causality/causality_sequencer_realistic.py +56 -0
  57. dialectical_framework/synthesist/concepts/__init__.py +0 -0
  58. dialectical_framework/synthesist/concepts/thesis_extractor_basic.py +135 -0
  59. dialectical_framework/synthesist/polarity/__init__.py +0 -0
  60. dialectical_framework/synthesist/polarity/polarity_reasoner.py +700 -0
  61. dialectical_framework/synthesist/polarity/reason_blind.py +62 -0
  62. dialectical_framework/synthesist/polarity/reason_conversational.py +91 -0
  63. dialectical_framework/synthesist/polarity/reason_fast.py +177 -0
  64. dialectical_framework/synthesist/polarity/reason_fast_and_simple.py +52 -0
  65. dialectical_framework/synthesist/polarity/reason_fast_polarized_conflict.py +55 -0
  66. dialectical_framework/synthesist/reverse_engineer.py +401 -0
  67. dialectical_framework/synthesist/wheel_builder.py +337 -0
  68. dialectical_framework/utils/__init__.py +1 -0
  69. dialectical_framework/utils/dc_replace.py +42 -0
  70. dialectical_framework/utils/decompose_probability_uniformly.py +15 -0
  71. dialectical_framework/utils/extend_tpl.py +12 -0
  72. dialectical_framework/utils/gm.py +12 -0
  73. dialectical_framework/utils/is_async.py +13 -0
  74. dialectical_framework/utils/use_brain.py +80 -0
  75. dialectical_framework/validator/__init__.py +1 -0
  76. dialectical_framework/validator/basic_checks.py +75 -0
  77. dialectical_framework/validator/check.py +12 -0
  78. dialectical_framework/wheel.py +395 -0
  79. dialectical_framework/wheel_segment.py +203 -0
  80. dialectical_framework/wisdom_unit.py +185 -0
  81. dialectical_framework-0.4.4.dist-info/LICENSE +21 -0
  82. dialectical_framework-0.4.4.dist-info/METADATA +123 -0
  83. dialectical_framework-0.4.4.dist-info/RECORD +84 -0
  84. 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
+ )