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.
- multidimensions-0.1.0/LICENSE +0 -0
- multidimensions-0.1.0/PKG-INFO +44 -0
- multidimensions-0.1.0/README.md +32 -0
- multidimensions-0.1.0/pyproject.toml +24 -0
- multidimensions-0.1.0/setup.cfg +4 -0
- multidimensions-0.1.0/src/multidimensions/__init__.py +13 -0
- multidimensions-0.1.0/src/multidimensions/providers/__init__.py +6 -0
- multidimensions-0.1.0/src/multidimensions/providers/infrastructure_dtos.py +8 -0
- multidimensions-0.1.0/src/multidimensions/providers/provider_config.py +29 -0
- multidimensions-0.1.0/src/multidimensions/providers/provider_errors.py +5 -0
- multidimensions-0.1.0/src/multidimensions/providers/providers.py +225 -0
- multidimensions-0.1.0/src/multidimensions/reasoning/__init__.py +3 -0
- multidimensions-0.1.0/src/multidimensions/reasoning/prompts.py +77 -0
- multidimensions-0.1.0/src/multidimensions/reasoning/protocols.py +16 -0
- multidimensions-0.1.0/src/multidimensions/reasoning/reasoner.py +188 -0
- multidimensions-0.1.0/src/multidimensions.egg-info/PKG-INFO +44 -0
- multidimensions-0.1.0/src/multidimensions.egg-info/SOURCES.txt +18 -0
- multidimensions-0.1.0/src/multidimensions.egg-info/dependency_links.txt +1 -0
- multidimensions-0.1.0/src/multidimensions.egg-info/requires.txt +2 -0
- multidimensions-0.1.0/src/multidimensions.egg-info/top_level.txt +1 -0
|
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,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,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,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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
multidimensions
|