multidimensions 0.1.0__tar.gz

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.
File without changes
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: multidimensions
3
+ Version: 0.1.0
4
+ Summary: Framework for Reasoning and Agentic base
5
+ Author: Alexander Baum
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: openai
10
+ Requires-Dist: anthropic
11
+ Dynamic: license-file
12
+
13
+ Dieses Framework bildet die Grundlage für Multistep-Reasoning und den Bau von Agenten.
14
+
15
+ Aktuell wurde ein vierstufiges Reasoning eingefügt.
16
+
17
+ 1. Analyse
18
+ 2. Planung
19
+ 3. Lösung
20
+ 4. Fertige Antwort
21
+
22
+ Die derzeitigen Prompts für die jeweiligen Schritte sind minimal gestaltet. Der aktuelle Fokus liegt auf dem sicherstellen der Funktionalität sowie dem Implementieren neuer Funktionen. Im späteren Verlauf wird das konkrete Prompting für das LLM ausgearbeitet.
23
+
24
+
25
+ Schnittstelle:
26
+ -> Über ProviderConfig werden die Attribute 'base_url', 'api_key', 'model' und 'provider' übergeben. Dabei sind alles Strings, 'provider' ist als Plaintext geschrieben 'openai' / 'anthropic'. Schließlich wird das Providerobjekt instanziert durch das Übergeben des ProviderConfig-Objektes durch Provider.from_config().
27
+ Die Providerinstanz wird als model_engine an das Raesoning übergeben.
28
+
29
+
30
+ Diese Funktion muss folgende Variablen + Datentypen empfangen:
31
+ - user_prompt (str)
32
+ - system_prompt (str)
33
+
34
+ Der User muss sicherstellen, dass der eigentliche LLM-Aufruf in dieser Funktion erfolgt und das 'return'-Statement der Funktion die Antwort des LLMs als String zurückgibt.
35
+
36
+
37
+ Der unkomplizierteste Weg liegt in der passenden Nutzung des internen 'Provider'-Moduls. Dieses ist ausdrücklich auf die Kompatibilität ausgerichtet und enthält eigene Fehler sowie Infrastruktur über eigene DTOs. Zudem bietet es sehr hohe Abstraktion bei minimalster Eingabe.
38
+
39
+ --------------------------
40
+
41
+ Die Reasoner-eigene Funktion namens "think()" erwartet die initiale Nuteranfrage (user_prompt).
42
+
43
+ Sie ruft aktuell vier mal die Funktion 'generate()' aus dem übergebenen Ojekt auf und liefert am Ende die finale Antwort als String an den Aufrufer.
44
+ Durch das Flag 'print_thinking_steps' (standardmäßig False) werden die "Thinking-Steps" ausgegeben.
@@ -0,0 +1,32 @@
1
+ Dieses Framework bildet die Grundlage für Multistep-Reasoning und den Bau von Agenten.
2
+
3
+ Aktuell wurde ein vierstufiges Reasoning eingefügt.
4
+
5
+ 1. Analyse
6
+ 2. Planung
7
+ 3. Lösung
8
+ 4. Fertige Antwort
9
+
10
+ Die derzeitigen Prompts für die jeweiligen Schritte sind minimal gestaltet. Der aktuelle Fokus liegt auf dem sicherstellen der Funktionalität sowie dem Implementieren neuer Funktionen. Im späteren Verlauf wird das konkrete Prompting für das LLM ausgearbeitet.
11
+
12
+
13
+ Schnittstelle:
14
+ -> Über ProviderConfig werden die Attribute 'base_url', 'api_key', 'model' und 'provider' übergeben. Dabei sind alles Strings, 'provider' ist als Plaintext geschrieben 'openai' / 'anthropic'. Schließlich wird das Providerobjekt instanziert durch das Übergeben des ProviderConfig-Objektes durch Provider.from_config().
15
+ Die Providerinstanz wird als model_engine an das Raesoning übergeben.
16
+
17
+
18
+ Diese Funktion muss folgende Variablen + Datentypen empfangen:
19
+ - user_prompt (str)
20
+ - system_prompt (str)
21
+
22
+ Der User muss sicherstellen, dass der eigentliche LLM-Aufruf in dieser Funktion erfolgt und das 'return'-Statement der Funktion die Antwort des LLMs als String zurückgibt.
23
+
24
+
25
+ Der unkomplizierteste Weg liegt in der passenden Nutzung des internen 'Provider'-Moduls. Dieses ist ausdrücklich auf die Kompatibilität ausgerichtet und enthält eigene Fehler sowie Infrastruktur über eigene DTOs. Zudem bietet es sehr hohe Abstraktion bei minimalster Eingabe.
26
+
27
+ --------------------------
28
+
29
+ Die Reasoner-eigene Funktion namens "think()" erwartet die initiale Nuteranfrage (user_prompt).
30
+
31
+ Sie ruft aktuell vier mal die Funktion 'generate()' aus dem übergebenen Ojekt auf und liefert am Ende die finale Antwort als String an den Aufrufer.
32
+ Durch das Flag 'print_thinking_steps' (standardmäßig False) werden die "Thinking-Steps" ausgegeben.
@@ -0,0 +1,24 @@
1
+ requires = ["setuptools>=77"]
2
+ build-backend = "setuptools.build_meta"
3
+
4
+ [project]
5
+ name = "multidimensions"
6
+ version = "0.1.0"
7
+ description = "Framework for Reasoning and Agentic base"
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+
11
+ authors = [
12
+ { name = "Alexander Baum" }
13
+ ]
14
+
15
+ dependencies = [
16
+ "openai",
17
+ "anthropic",
18
+ ]
19
+
20
+ [tool.setuptools]
21
+ package-dir = { "" = "src" }
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ from .reasoning.reasoner import Reasoner
2
+ from .providers.provider_config import ProviderConfig
3
+ from .providers.providers import Provider
4
+ from .providers.provider_errors import UnsupportedFeatureError
5
+ from .providers.infrastructure_dtos import LLMResponse
6
+
7
+ __all__ = [
8
+ "Reasoner",
9
+ "Provider",
10
+ "ProviderConfig",
11
+ "UnsupportedFeatureError",
12
+ "LLMResponse"
13
+ ]
@@ -0,0 +1,6 @@
1
+ from .provider_config import ProviderConfig
2
+ from .providers import Provider
3
+ from .provider_errors import UnsupportedFeatureError
4
+ from .infrastructure_dtos import LLMResponse
5
+
6
+ __all__ = ["ProviderConfig", "Provider", "UnsupportedFeatureError", "LLMResponse"]
@@ -0,0 +1,8 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(frozen=True, slots=True)
5
+ class LLMResponse:
6
+ answer: str
7
+ confidence_score: float | None = None
8
+ confidence_level: str = ""
@@ -0,0 +1,29 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Literal, Optional
3
+
4
+
5
+
6
+ ALLOWED_PROVIDERS = ('openai', 'anthropic') # Konstante für das Überprüfen in der __post_init__()
7
+ ProviderName = Literal['openai', 'anthropic'] # Literal als Typehinting für den User
8
+
9
+
10
+ @dataclass(frozen=True, slots=True) # Mit 'frozen' wird die dataclass unveränderlich, mit Slots wirds Tippfehlersicher und schneller, da es kein dict mehr ist.
11
+ class ProviderConfig:
12
+ # Provider ist das AI-Lab 'openai' / 'anthropic' | aktuell noch nicht: 'gemini' / 'x' / 'mistral'
13
+ provider: ProviderName
14
+
15
+ model: str
16
+ base_url: str
17
+ api_key: str = field(repr=False)
18
+ max_retries: int = 2
19
+ timeout: float = 600.0
20
+
21
+
22
+
23
+
24
+ def __post_init__(self) -> None:
25
+ if self.provider not in ALLOWED_PROVIDERS:
26
+ raise ValueError(
27
+ f"Ungültiger Provider {self.provider!r}. "
28
+ f"Erlaubt sind: {", " .join(ALLOWED_PROVIDERS)}."
29
+ )
@@ -0,0 +1,5 @@
1
+
2
+
3
+ # Klasse zur Fehlerbehandlung durch Anthropic
4
+ class UnsupportedFeatureError(Exception):
5
+ pass
@@ -0,0 +1,225 @@
1
+ from abc import ABC, abstractmethod
2
+ from .provider_config import ProviderConfig
3
+ from .provider_errors import UnsupportedFeatureError
4
+ from .infrastructure_dtos import LLMResponse
5
+
6
+ import math
7
+ from openai import OpenAI
8
+ from anthropic import Anthropic
9
+
10
+
11
+
12
+ class Provider(ABC):
13
+ def __init__(self, config: ProviderConfig):
14
+ self.config = config
15
+
16
+
17
+ def _validate_prompt(self, prompt: str) -> None:
18
+ if not prompt.strip():
19
+ raise ValueError(
20
+ "Der Prompt darf nicht leer sein."
21
+ )
22
+
23
+
24
+ # Factory-Methode, die für die öffentliche API zur Konfiguration genutzt werden kann
25
+ # Der Nutzer furft diese Funktion auf und erhält die fertige Provider-Klasse
26
+ @classmethod
27
+ def from_config(cls, provider_config: ProviderConfig) -> "Provider":
28
+ provider_classes = {
29
+ "openai": OpenAIProvider,
30
+ "anthropic": AnthropicProvider,
31
+ }
32
+
33
+ try:
34
+ provider_class = provider_classes[provider_config.provider]
35
+ except KeyError:
36
+ raise KeyError(
37
+ f"Unbekannter Provider: {provider_config.provider}"
38
+ )
39
+
40
+ return provider_class(provider_config)
41
+
42
+
43
+ @abstractmethod
44
+ def generate(self, system_prompt: str, user_prompt: str, temperature: float = 0.5) -> LLMResponse:
45
+ ...
46
+
47
+
48
+ @abstractmethod
49
+ def generate_with_logprobs(self, system_prompt: str, user_prompt, temperature: float = 0.5) -> LLMResponse:
50
+ ...
51
+
52
+
53
+ # Provider-Klasse 'OpenAI'
54
+ class OpenAIProvider(Provider):
55
+ def __init__(self, config):
56
+ super().__init__(config)
57
+
58
+ self.client = OpenAI(
59
+ base_url=config.base_url,
60
+ api_key=config.api_key,
61
+ max_retries=config.max_retries,
62
+ timeout=config.timeout
63
+ )
64
+
65
+
66
+ def generate(self, system_prompt: str, user_prompt: str, temperature: float = 0.5) -> LLMResponse:
67
+
68
+ # Für diese beiden Funktionen gibt es keinen try-block, da der Fehler weiter hoch blubbern soll.
69
+ self._validate_prompt(prompt=system_prompt)
70
+ self._validate_prompt(prompt=user_prompt)
71
+
72
+
73
+ response = self.client.responses.create(
74
+ model=self.config.model,
75
+ temperature=temperature,
76
+ input=[
77
+ {
78
+ "role": "system",
79
+ "content": [
80
+ {
81
+ "type": "input_text",
82
+ "text": system_prompt,
83
+ }
84
+ ],
85
+ },
86
+ {
87
+ "role": "user",
88
+ "content": [
89
+ {
90
+ "type": "input_text",
91
+ "text": user_prompt,
92
+ }
93
+ ],
94
+ },
95
+ ],
96
+ )
97
+
98
+
99
+ # Das Response-DTO ist wird befüllt und schließlich im return-Statement übergeben
100
+ response_dto = LLMResponse(
101
+ answer=response.output_text
102
+ )
103
+
104
+ return response_dto
105
+
106
+
107
+
108
+ # LLM-Call mit Logprobs (Tokenwahrscheinlichkeit)
109
+ def generate_with_logprobs(self, system_prompt: str, user_prompt: str, temperature: float = 0.5) -> LLMResponse:
110
+
111
+ # Für diese beiden Funktionen gibt es keinen try-block, da der Fehler weiter hoch blubbern soll.
112
+ self._validate_prompt(prompt=system_prompt)
113
+ self._validate_prompt(prompt=user_prompt)
114
+
115
+
116
+ response = self.client.chat.completions.create(
117
+ model=self.config.model,
118
+ temperature=temperature,
119
+ logprobs=True,
120
+
121
+ messages=[
122
+ {
123
+ "role": "system",
124
+ "content": system_prompt
125
+ },
126
+ {
127
+ "role": "user",
128
+ "content": user_prompt
129
+ }
130
+ ],
131
+ )
132
+
133
+ answer = response.choices[0].message.content or ""
134
+
135
+
136
+ # Fehler abfangen, falls doch keine Logprobs geliefert werden können.
137
+ if response.choices[0].logprobs is None or response.choices[0].logprobs.content is None:
138
+ raise UnsupportedFeatureError
139
+
140
+
141
+ token_probs = []
142
+ for token in response.choices[0].logprobs.content:
143
+ token_probs.append(math.exp(token))
144
+
145
+ confidence_score = sum(token_probs) / len(token_probs)
146
+
147
+
148
+ response_dto = LLMResponse(
149
+ answer=answer,
150
+ confidence_score=confidence_score
151
+ )
152
+
153
+ return response_dto
154
+
155
+
156
+
157
+
158
+ # Provider-Klasse 'Anthropic'
159
+ class AnthropicProvider(Provider):
160
+ def __init__(self, config):
161
+ super().__init__(config)
162
+
163
+ self.client = Anthropic(
164
+ base_url=self.config.base_url,
165
+ api_key=self.config.api_key,
166
+ timeout=self.config.timeout,
167
+ max_retries=self.config.max_retries
168
+ )
169
+
170
+
171
+ def generate(self, system_prompt: str, user_prompt: str, temperature: float = 0.5):
172
+
173
+ # Für diese beiden Funktionen gibt es keinen try-block, da der Fehler weiter hoch blubbern soll.
174
+ self._validate_prompt(prompt=system_prompt)
175
+ self._validate_prompt(prompt=user_prompt)
176
+
177
+
178
+ response = self.client.messages.create(
179
+ model=self.config.model,
180
+ max_tokens=2096,
181
+ system=system_prompt,
182
+ temperature=temperature,
183
+ messages=[
184
+ {
185
+ "role": "user",
186
+ "content": [
187
+ {
188
+ "type": "text",
189
+ "text": user_prompt,
190
+ }
191
+ ],
192
+ }
193
+ ],
194
+ )
195
+
196
+ text_parts = []
197
+
198
+ for block in response.content:
199
+ if block.type == "text":
200
+ text_parts.append(block.text)
201
+
202
+ if not text_parts:
203
+ raise RuntimeError(
204
+ "Anthropic hat keine Textausgabe zurückgegeben."
205
+ )
206
+
207
+ answer = "".join(text_parts)
208
+
209
+ response_dto = LLMResponse(
210
+ answer=answer
211
+ )
212
+
213
+
214
+ return response_dto
215
+
216
+
217
+ # Wahrscheinlichkeitsrückgabe (Nur Fehlerbehandlung, da Anthropic das nicht anbietet)
218
+ def generate_with_logprobs(self, system_prompt: str, user_prompt: str, temperature: float = 0.5):
219
+
220
+ raise UnsupportedFeatureError(
221
+ "Anthropic-API ermöglicht derzeitig nicht das Extrahieren von Tokenwahrscheinlichkeiten."
222
+ )
223
+
224
+
225
+
@@ -0,0 +1,3 @@
1
+ from .reasoner import Reasoner
2
+
3
+ __all__ = ["Reasoner"]
@@ -0,0 +1,77 @@
1
+ class Prompts:
2
+
3
+ @staticmethod
4
+ def get_analysis_prompt() -> str:
5
+ return (
6
+ "Analysiere ausschließlich die Nutzeranfrage.\n"
7
+ "Bestimme Ziel, Anforderungen, Einschränkungen, Kontext und etwaige Unklarheiten.\n"
8
+ "Löse die Aufgabe nicht.\n"
9
+ "Erstelle keinen Lösungsplan.\n"
10
+ "Gib keine finale Antwort an den Nutzer.\n"
11
+ "Dein Output ist nur ein interner Analysebericht.\n"
12
+ "Struktur:\n"
13
+ "1. Ziel der Anfrage\n"
14
+ "2. Anforderungen\n"
15
+ "3. Einschränkungen\n"
16
+ "4. Unklarheiten oder Annahmen\n"
17
+ "5. Relevanter Kontext\n"
18
+ )
19
+
20
+ @staticmethod
21
+ def get_planning_prompt() -> str:
22
+ return (
23
+ "Erstelle ausschließlich einen Lösungsplan auf Grundlage der Nutzeranfrage "
24
+ "und der Analyse.\n"
25
+ "Löse die Aufgabe nicht.\n"
26
+ "Formuliere keine Antwort an den Nutzer.\n"
27
+ "Der Output ist ein interner Arbeitsplan für die nächste Stufe.\n"
28
+ "Zerlege die Aufgabe in konkrete, aufeinander aufbauende Schritte.\n"
29
+ "Berücksichtige Anforderungen, Einschränkungen und mögliche Fehlerquellen.\n"
30
+ "Struktur:\n"
31
+ "1. Vorgehensstrategie\n"
32
+ "2. Benötigte Informationen oder Annahmen\n"
33
+ "3. Arbeitsschritte\n"
34
+ "4. Qualitätskriterien für die Lösung\n"
35
+ )
36
+
37
+ @staticmethod
38
+ def get_solving_prompt() -> str:
39
+ return (
40
+ "Erarbeite ausschließlich die fachliche Roh-Lösung.\n"
41
+ "Nutze dafür die Nutzeranfrage, die Analyse und den Lösungsplan.\n"
42
+ "Dies ist nicht die finale Antwort an den Nutzer.\n"
43
+ "Formuliere nicht didaktisch, nicht rhetorisch und nicht als Chat-Antwort.\n"
44
+ "Keine Einleitung, kein Fazit, keine direkte Ansprache des Nutzers.\n"
45
+ "Arbeite nur den inhaltlichen Kern aus.\n"
46
+ "Der Output soll Rohmaterial für die finale Antwort sein.\n"
47
+ "Erlaubt sind:\n"
48
+ "- zentrale Aussagen\n"
49
+ "- notwendige Schritte\n"
50
+ "- technische Details\n"
51
+ "- Beispiele im Rohformat\n"
52
+ "- wichtige Einschränkungen\n"
53
+ "- Fehlerquellen\n"
54
+ "Nicht erlaubt sind:\n"
55
+ "- fertige Antwortabsätze\n"
56
+ "- freundliche Einleitungen\n"
57
+ "- Zusammenfassungen für den Nutzer\n"
58
+ "- Meta-Kommentare über die eigene Arbeit\n"
59
+ "Struktur:\n"
60
+ "1. Kernlösung\n"
61
+ "2. Begründung\n"
62
+ "3. Wichtige Details\n"
63
+ "4. Mögliche Einschränkungen oder Risiken\n"
64
+ )
65
+
66
+ @staticmethod
67
+ def get_answer_prompt() -> str:
68
+ return (
69
+ "Formuliere ausschließlich die finale Antwort für den Nutzer.\n"
70
+ "Nutze dafür die erarbeitete Roh-Lösung.\n"
71
+ "Gib keine Analyse, keinen Plan und keine internen Arbeitsschritte wieder.\n"
72
+ "Füge keine neuen fachlichen Punkte hinzu, außer sie sind zwingend nötig, "
73
+ "um die Roh-Lösung verständlich zu machen.\n"
74
+ "Beantworte die ursprüngliche Nutzeranfrage direkt und vollständig.\n"
75
+ "Passe Detailgrad, Sprache und Struktur an die Anfrage an.\n"
76
+ "Der Output darf nur die fertige Nutzerantwort enthalten.\n"
77
+ )
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+ from typing import Protocol, TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from ..providers import LLMResponse
6
+
7
+
8
+
9
+ class LanguageModel(Protocol):
10
+ def generate(self, *, system_prompt: str, user_prompt: str) -> LLMResponse:
11
+ ... # Elipsis wird hier nur als Signaturbeschreibung verwendet! statt 'pass' "Dieser Methodenrumpf tut nichts."
12
+
13
+
14
+
15
+ def generate_with_logprobs(self, *, system_prompt: str, user_prompt: str, temperature: float = 0.5) -> LLMResponse:
16
+ ...
@@ -0,0 +1,188 @@
1
+ from .protocols import LanguageModel
2
+ from .prompts import Prompts
3
+ from ..providers import UnsupportedFeatureError, LLMResponse
4
+
5
+ from dataclasses import replace
6
+
7
+ class Reasoner:
8
+ def __init__(self, *, model_engine: LanguageModel):
9
+
10
+ self.model_engine=model_engine
11
+
12
+
13
+ def think(self, user_prompt: str, print_thinking_steps: bool = False) -> str:
14
+ """
15
+ !!Die generate-Methode in model_engine muss im return-Statement einen String zurückgeben
16
+ 1. Analyse
17
+ 2. Planung
18
+ 3. Lösung
19
+ 4. Antwort
20
+ """
21
+
22
+ while True:
23
+ analysis_response: LLMResponse = self._analyse(user_prompt=user_prompt)
24
+ analysis_response: LLMResponse = self.__validate_response(response_dto=analysis_response, reasoning_mode="Analysephase", print_thinking_steps=print_thinking_steps)
25
+
26
+
27
+ print(f"DEBUG: CONFIDENCE_LEVEL: {analysis_response.confidence_level}")
28
+
29
+ if analysis_response.confidence_level != "RETRY":
30
+ break
31
+
32
+
33
+
34
+ while True:
35
+ planning_response: LLMResponse = self._planning(user_prompt=user_prompt, analysis_response=analysis_response)
36
+ planning_response: LLMResponse = self.__validate_response(response_dto=planning_response, reasoning_mode="Planungsphase", print_thinking_steps=print_thinking_steps)
37
+
38
+ print(f"DEBUG: CONFIDENCE_LEVEL: {planning_response.confidence_level}")
39
+
40
+ if planning_response.confidence_level != "RETRY":
41
+ break
42
+
43
+ while True:
44
+ solution_response: LLMResponse = self._solve(user_prompt=user_prompt, analysis_response=analysis_response, planning_response=planning_response)
45
+ solution_response: LLMResponse = self.__validate_response(response_dto=solution_response, reasoning_mode="Lösungsphase", print_thinking_steps=print_thinking_steps)
46
+
47
+ print(f"DEBUG: CONFIDENCE_LEVEL: {solution_response.confidence_level}")
48
+
49
+ if solution_response.confidence_level != "RETRY":
50
+ break
51
+
52
+
53
+
54
+ final_answer_response: LLMResponse = self._write_answer(user_prompt=user_prompt, solution_response=solution_response)
55
+
56
+
57
+ # TODO
58
+ # Hier kommt die Logik rein für die Abfrage mit / ohne finale Antwort
59
+
60
+
61
+ # Rückgabe der finalen Antwort
62
+ return final_answer_response.answer
63
+
64
+
65
+
66
+
67
+ # Analyse-Funktion
68
+ def _analyse(self, user_prompt: str) -> LLMResponse:
69
+ # Wir holen uns aus der Prompt-Klasse den Analyse-Prompt
70
+ system_prompt = Prompts.get_analysis_prompt()
71
+
72
+ # Eigentlicher LLM-Call -> läuft über den LLM-Provider
73
+ try:
74
+ return self.model_engine.generate_with_logprobs(system_prompt=system_prompt, user_prompt=user_prompt)
75
+
76
+ # Falls der Provider keine Logprobs ermöglicht, wird die simple generate-methode genommen.
77
+ except UnsupportedFeatureError as e:
78
+ return self.model_engine.generate(system_prompt=system_prompt, user_prompt=user_prompt)
79
+
80
+
81
+
82
+ # Planungs-Funktion
83
+ def _planning(self, user_prompt: str, analysis_response: LLMResponse) -> LLMResponse:
84
+ # Wir holen uns aus de Prompt-Klasse den Planungs-Prompt
85
+ system_prompt = Prompts.get_planning_prompt()
86
+
87
+
88
+ # Hier bauen wir uns den finalen user_prompt
89
+ user_prompt = f"Initiale Useranfrage: {user_prompt} | Analyse des Falls: {analysis_response.answer} | {analysis_response.confidence_level}"
90
+
91
+ # Eigentlicher LLM-Call -> läuft über den LLM-Provider
92
+ try:
93
+ return self.model_engine.generate_with_logprobs(system_prompt=system_prompt, user_prompt=user_prompt)
94
+
95
+ # Falls der Provider keine Logprobs ermöglicht, wird die simple generate-methode genommen.
96
+ except UnsupportedFeatureError as e:
97
+ return self.model_engine.generate(system_prompt=system_prompt, user_prompt=user_prompt)
98
+
99
+
100
+
101
+ # Lösungs-Funktion
102
+ def _solve(self, user_prompt: str, analysis_response: LLMResponse, planning_response: LLMResponse) -> LLMResponse:
103
+ # Wir holen uns aus der Prompt-Klasse den Planungs-Prompt
104
+ system_prompt = Prompts.get_solving_prompt()
105
+
106
+ # Hier bauen wir uns den finalen user_prompt
107
+ user_prompt = f"Initiale Useranfrage: {user_prompt} | Analyse des Falls: {analysis_response.answer} | {analysis_response.confidence_level} | Planung des Falls: {planning_response.answer} | {planning_response.confidence_level}"
108
+
109
+ # Eigentlicher LLM-Call -> läuft über den LLM-Provider
110
+ try:
111
+ return self.model_engine.generate_with_logprobs(system_prompt=system_prompt, user_prompt=user_prompt)
112
+
113
+ except UnsupportedFeatureError as e:
114
+ return self.model_engine.generate(system_prompt=system_prompt, user_prompt=user_prompt)
115
+
116
+
117
+
118
+ # Antwortfunktion (Ausformulierung), wobei nur die Lösung übergeben wird, nicht die anderen Reasoningsteps.
119
+ def _write_answer(self, user_prompt: str, solution_response: LLMResponse, ) -> LLMResponse:
120
+ # Wir holen uns aus der Prompt-Klasse den Planungs-Prompt
121
+ system_prompt = Prompts.get_answer_prompt()
122
+
123
+ # Hier bauen wir uns den finalen user_prompt
124
+ user_prompt = f"Initiale Useranfrage: {user_prompt} | Die konkrete Lösung: {solution_response.answer} | {solution_response.confidence_level}"
125
+
126
+ # Eigentlicher LLM-Call -> läuft über den LLM-Provider
127
+ try:
128
+ return self.model_engine.generate_with_logprobs(system_prompt=system_prompt, user_prompt=user_prompt)
129
+
130
+ except UnsupportedFeatureError as e:
131
+ return self.model_engine.generate(system_prompt=system_prompt, user_prompt=user_prompt)
132
+
133
+
134
+
135
+
136
+ # Validierung. Aktuell der Confidence und Umwandlung in ein LLM-Lesbares Format.
137
+ def __validate_response(self, print_thinking_steps, response_dto: LLMResponse, reasoning_mode: str, minimum_confidence: float = 0.55):
138
+
139
+ # Mapping des Confidencelevels auf natürliche Sprache, aktuell hat minimum_confidence noch keinen direkten Einfluss!
140
+ # 'reasoning_mode' wird später Einfluss haben, da jeweilige Modi aufgrund der confidence anders bewetet werden!
141
+
142
+ mapping = [
143
+ (0.85, "Das Modell ist sich seiner Generierung sehr sicher, hier ist der Confidence Score: "),
144
+ (0.7, "Das Modell hat beim Confidence-Thresholding einen Normalen Wert erreicht."),
145
+ (0.55, "Vosicht: Das Modell war sich bei seiner Generierung unsicher."),
146
+
147
+ # Für zukünftige Operationen, könnte eine vierte Stufe sinnvoll sein, auch wenn der Wert von 0.55 direkt zu einem Retry führt.
148
+ (0.0, "Problematisch: Das Modell hat bei der Generierung der letzen Stufe des mehrstufigen Reasonings eine kritische Confidence.")
149
+ ]
150
+
151
+ # Check, ob überhaupt confidence_score vorhanden ist. Dann erfolgt der Abgleich mit der Map
152
+ if response_dto.confidence_score is not None:
153
+
154
+ # Wenn die Confidence unter die Erlaubte fällt, gibt es ein Update und das DTO wird zurückgegeben.
155
+ if response_dto.confidence_score <= minimum_confidence:
156
+ level = "RETRY"
157
+ response_dto = replace(response_dto, confidence_level=level)
158
+ return response_dto
159
+
160
+ # Wenn die Confidence nicht unter die Erlaubte fällt, wird eines der anderen Level ausgewählt.
161
+ for threshold, label in mapping:
162
+ if response_dto.confidence_score >= threshold:
163
+ level = label
164
+
165
+ # Zusaztaussage, damit das Modell beim Reasoning dem Verlauf folegen kann.
166
+ # Standardwert von LLMResponse.confidence_level wurde auf "" gesetzt.
167
+ confidence_level = (
168
+ f"Confidence Level der {reasoning_mode}: {level}"
169
+ )
170
+
171
+ response_dto = replace(response_dto, confidence_level=confidence_level)
172
+
173
+ # Check, falls User die Thinking-Steps ausgedruckt haben möchte.
174
+ if print_thinking_steps:
175
+ print(response_dto.answer)
176
+
177
+ return response_dto
178
+
179
+
180
+ # Wenn 'confidence_score' leer ist.
181
+ else:
182
+ # Check, falls User die Thinking-Steps ausgedruckt haben möchte.
183
+ if print_thinking_steps:
184
+ print(response_dto.answer)
185
+
186
+ return response_dto
187
+
188
+
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: multidimensions
3
+ Version: 0.1.0
4
+ Summary: Framework for Reasoning and Agentic base
5
+ Author: Alexander Baum
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: openai
10
+ Requires-Dist: anthropic
11
+ Dynamic: license-file
12
+
13
+ Dieses Framework bildet die Grundlage für Multistep-Reasoning und den Bau von Agenten.
14
+
15
+ Aktuell wurde ein vierstufiges Reasoning eingefügt.
16
+
17
+ 1. Analyse
18
+ 2. Planung
19
+ 3. Lösung
20
+ 4. Fertige Antwort
21
+
22
+ Die derzeitigen Prompts für die jeweiligen Schritte sind minimal gestaltet. Der aktuelle Fokus liegt auf dem sicherstellen der Funktionalität sowie dem Implementieren neuer Funktionen. Im späteren Verlauf wird das konkrete Prompting für das LLM ausgearbeitet.
23
+
24
+
25
+ Schnittstelle:
26
+ -> Über ProviderConfig werden die Attribute 'base_url', 'api_key', 'model' und 'provider' übergeben. Dabei sind alles Strings, 'provider' ist als Plaintext geschrieben 'openai' / 'anthropic'. Schließlich wird das Providerobjekt instanziert durch das Übergeben des ProviderConfig-Objektes durch Provider.from_config().
27
+ Die Providerinstanz wird als model_engine an das Raesoning übergeben.
28
+
29
+
30
+ Diese Funktion muss folgende Variablen + Datentypen empfangen:
31
+ - user_prompt (str)
32
+ - system_prompt (str)
33
+
34
+ Der User muss sicherstellen, dass der eigentliche LLM-Aufruf in dieser Funktion erfolgt und das 'return'-Statement der Funktion die Antwort des LLMs als String zurückgibt.
35
+
36
+
37
+ Der unkomplizierteste Weg liegt in der passenden Nutzung des internen 'Provider'-Moduls. Dieses ist ausdrücklich auf die Kompatibilität ausgerichtet und enthält eigene Fehler sowie Infrastruktur über eigene DTOs. Zudem bietet es sehr hohe Abstraktion bei minimalster Eingabe.
38
+
39
+ --------------------------
40
+
41
+ Die Reasoner-eigene Funktion namens "think()" erwartet die initiale Nuteranfrage (user_prompt).
42
+
43
+ Sie ruft aktuell vier mal die Funktion 'generate()' aus dem übergebenen Ojekt auf und liefert am Ende die finale Antwort als String an den Aufrufer.
44
+ Durch das Flag 'print_thinking_steps' (standardmäßig False) werden die "Thinking-Steps" ausgegeben.
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/multidimensions/__init__.py
5
+ src/multidimensions.egg-info/PKG-INFO
6
+ src/multidimensions.egg-info/SOURCES.txt
7
+ src/multidimensions.egg-info/dependency_links.txt
8
+ src/multidimensions.egg-info/requires.txt
9
+ src/multidimensions.egg-info/top_level.txt
10
+ src/multidimensions/providers/__init__.py
11
+ src/multidimensions/providers/infrastructure_dtos.py
12
+ src/multidimensions/providers/provider_config.py
13
+ src/multidimensions/providers/provider_errors.py
14
+ src/multidimensions/providers/providers.py
15
+ src/multidimensions/reasoning/__init__.py
16
+ src/multidimensions/reasoning/prompts.py
17
+ src/multidimensions/reasoning/protocols.py
18
+ src/multidimensions/reasoning/reasoner.py
@@ -0,0 +1,2 @@
1
+ openai
2
+ anthropic
@@ -0,0 +1 @@
1
+ multidimensions