aidn commited on
Commit
743c52b
Β·
verified Β·
1 Parent(s): 908de16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +337 -200
app.py CHANGED
@@ -1,321 +1,458 @@
1
  import gradio as gr
2
  from openai import OpenAI
3
  import os
4
- import mlflow
5
- import dagshub
6
- import uuid
7
-
8
- # DagsHub Verbindung
9
- dagshub.init(repo_owner='homuhe', repo_name='PromptPlenum-Tracking', mlflow=True)
10
-
11
- # Das magische Autologging fΓΌr alle OpenAI-kompatiblen Aufrufe!
12
- mlflow.openai.autolog()
13
 
14
  # ==========================================
15
- # 1. KONFIGURATION & PROMPTS
16
  # ==========================================
17
 
18
  MODERATOR_MODEL = "meta-llama/Llama-3.3-70B-Instruct"
19
 
20
- COUNCIL_MEMBERS = {
21
- "🧠 Fachexperte für Struktur (GPT-OSS-120b)": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  "model": "openai/gpt-oss-120b",
23
- "role": """Du bist ein neutraler Fachexperte fΓΌr Struktur und Architektur.
24
- REGELN:
25
- - Beginne mit: "[STRUKTUR] "
26
- - Liefere das logische GerΓΌst fΓΌr die LΓΆsung der Aufgabe.
27
- - Antworte in 2-4 SΓ€tzen, professionell und bodenstΓ€ndig."""
28
  },
29
- "🧐 Fachexperte für Details (DeepSeek-V3)": {
 
30
  "model": "deepseek-ai/DeepSeek-V3.2",
31
- "role": """Du bist ein neutraler Fachexperte fΓΌr tiefe Details, Edge-Cases und QualitΓ€tssicherung.
32
- REGELN:
33
- - Beginne mit: "[DETAILS] "
34
- - Finde LΓΌcken im aktuellen Entwurf, korrigiere Fehler (technisch oder logisch) und fΓΌge fehlende Tiefe hinzu.
35
- - Antworte in 2-4 SΓ€tzen, professionell und bodenstΓ€ndig."""
 
36
  },
37
- "πŸ› οΈ Fachexperte fΓΌr Praxis (Llama-4-Maverick)": {
 
38
  "model": "meta-llama/Llama-4-Maverick-17B-128E-Instruct",
39
- "role": """Du bist der Fachexperte fΓΌr die finale Umsetzung.
40
- REGELN:
41
- - Beginne mit: "[PRAXIS] "
42
- - Gieße die Erkenntnisse der anderen in greifbare Artefakte (z.B. konkrete Textentwürfe, Code-Snippets, Tabellen).
43
- - Antworte in 2-4 SΓ€tzen, professionell und bodenstΓ€ndig."""
44
- }
45
- }
 
 
 
 
 
 
46
 
47
  class PromptManager:
48
- """Verwaltet alle dynamischen Prompts (Universelles Meta-Prompting)"""
49
-
50
  @staticmethod
51
- def get_moderator_kickoff_sys():
52
  return (
53
- "Du bist der Lead-Moderator eines Expertenrates. "
54
- "Analysiere die User-Anfrage. Definiere in 2-3 SΓ€tzen das Ziel und leite ab, "
55
- "welches Format und welche TonalitΓ€t am Ende erwartet werden (z.B. IT-Code, Marketing-Text, strategischer Plan). "
56
- "Briefe dein Team. WICHTIG: Schreibe direkt und professionell. ABSOLUTES VERBOT von Brief-Floskeln (kein 'Hallo Team', kein 'Mit freundlichen Grüßen')."
 
57
  )
58
-
 
 
 
 
59
  @staticmethod
60
- def get_moderator_mid_sys():
61
  return (
62
- "Du bist der Lead-Moderator. Bewerte den Zwischenstand in einem Satz. "
63
- "Gib danach exakt EINEN klaren Arbeitsauftrag für die nÀchste Runde, um Lücken zu schließen, die Lesbarkeit zu verbessern oder das Format zu schÀrfen."
 
 
 
 
 
64
  )
65
 
66
  @staticmethod
67
- def get_expert_sys(role_focus):
 
68
  return (
69
- f"{role_focus}\n\n"
70
- "WICHTIG: Passt euren fachlichen Stil und euer Output-Format AUTOMATISCH an die Natur der Aufgabe an. "
71
- "Schreibt keine Platzhalter fΓΌr Bilder oder Grafiken in den Text. Arbeitet iterativ am Entwurf und setzt die Vorgaben des Moderators um."
72
  )
73
 
74
  @staticmethod
75
- def get_expert_user_prompt(user_prompt, discussion_history, name):
76
- if not discussion_history:
77
- return f"Auftrag: '{user_prompt}'"
78
  return (
79
- f"Auftrag: '{user_prompt}'.\n\n"
80
- f"Bisheriges Protokoll:\n{discussion_history}\n\n"
81
- f"ANWEISUNG FÜR DICH ({name}):\n"
82
- "1. Richte dich nach der letzten Anweisung des Moderators.\n"
83
- "2. Bringe das Endprodukt inhaltlich voran. Keine Zusammenfassungen der Vorredner.\n"
84
- "3. Komm direkt zum Punkt."
85
  )
86
 
87
  @staticmethod
88
- def get_analysis_sys():
89
- return "Du bist der Chef-Analyst. Extrahiere alle Fakten, Code-BlΓΆcke, Metriken und Strukturvorgaben verlustfrei aus dem Protokoll."
 
 
 
 
 
90
 
91
  @staticmethod
92
- def get_analysis_user(discussion_history):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  return (
94
- f"Protokoll:\n{discussion_history}\n\n"
95
- "Fasse den Konsens zusammen. Erhalte zwingend alle technischen Artefakte (Code, Tabellen, exakte Formulierungen). "
96
- "Vermeide abstrakte Verallgemeinerungen!"
 
97
  )
98
 
99
  @staticmethod
100
- def get_final_sys():
101
  return (
102
- "Du bist ein hochadaptiver Output-Generator. Du lieferst perfekte, industriestandard-konforme Endprodukte. "
103
- "Liefere AUSSCHLIESSLICH das finale, sofort nutzbare Endprodukt ohne KI-Geschwafel."
 
 
104
  )
105
 
106
  @staticmethod
107
- def get_final_user(user_prompt, consensus_res):
108
  return (
109
- f"Auftrag:\n'{user_prompt}'\n\nKonsens:\n{consensus_res}\n\n"
110
- "ANWEISUNG:\n"
111
- "1. Analysiere den ursprΓΌnglichen Auftrag und wΓ€hle AUTOMATISCH die perfekte TonalitΓ€t und das richtige Format.\n"
112
- "2. Wenn es ein Text/Social-Media-Post ist: Nutze moderne Formatierung (kurze AbsΓ€tze fΓΌr mobile Leser, sinnvolle Emojis, knackige AufzΓ€hlungen).\n"
113
- "3. Wenn es Code ist: Liefere sauberen Code ohne Marketing-Sprech.\n"
114
- "4. ABSOLUTES VERBOT: Generiere NIEMALS Bild-Platzhalter (wie '[Bild von...]') in den Text. Der Text muss 1:1 copy-paste-fertig sein.\n"
115
- "5. Verarbeite alle Fakten aus dem Konsens."
116
-
117
  )
118
 
 
119
  # ==========================================
120
- # 2. CORE SERVICES
121
  # ==========================================
 
122
  class LLMService:
123
- """Kapselt die API-Kommunikation ΓΌber das OpenAI SDK (verbunden mit HF Router)"""
124
  def __init__(self):
125
- # Wir nutzen den OpenAI Client, leiten ihn aber an Hugging Face um!
126
  self.client = OpenAI(
127
- base_url="https://router.huggingface.co/v1",
128
- api_key=os.getenv("HF_TOKEN")
129
  )
130
 
131
- @mlflow.trace(name="Plenum_Turn", span_type="LLM")
132
- def ask(self, model_id, system_prompt, user_input, session_id, role_name):
133
-
134
- # FΓΌgt Meta-Daten zum Trace hinzu, damit du in DagsHub filtern kannst
135
- mlflow.update_current_trace(tags={
136
- "session_id": session_id,
137
- "expert_role": role_name,
138
- "model_name": model_id
139
- })
140
-
141
  messages = [
142
  {"role": "system", "content": system_prompt},
143
- {"role": "user", "content": user_input}
144
  ]
145
- response = ""
146
  try:
147
- # Der Aufruf im OpenAI SDK Format (stream=True fΓΌr die UI)
148
- stream = self.client.chat.completions.create(
149
- model=model_id,
150
- messages=messages,
151
- max_tokens=4000,
152
- temperature=0.4,
153
- stream=True
154
  )
155
- for chunk in stream:
156
- if chunk.choices[0].delta.content is not None:
157
- response += chunk.choices[0].delta.content
158
- return response
159
  except Exception as e:
160
- return f"🚨 System Error ({model_id}): {str(e)}"
 
 
161
  class UIHelper:
162
- """Formatiert Ausgaben fΓΌr die Gradio Chatbot-UI."""
163
  @staticmethod
164
  def header(title, color="#FF5A4D"):
165
- return f"<h2 style='color: {color}; border-bottom: 2px solid #FFEBE8; padding-bottom: 5px; margin-top: 20px;'>{title}</h2>"
 
 
 
 
 
 
 
166
 
167
  @staticmethod
168
- def message(name, content, color="#4241A6"):
169
- return f"**<span style='color: {color}; font-size: 1.1em;'>πŸ‘€ {name}</span>**\n\n> {content}"
 
170
 
171
  # ==========================================
172
- # 3. DER ORCHESTRATOR
173
  # ==========================================
 
174
  class PlenumOrchestrator:
175
- """Steuert den gesamten Diskussions- und Generierungsprozess."""
176
  def __init__(self):
177
  self.llm = LLMService()
178
- self.prompts = PromptManager()
179
  self.ui = UIHelper()
180
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  def run(self, user_prompt, rounds):
182
- if not user_prompt:
183
- yield [{"role": "assistant", "content": "Bitte gib ein Thema oder eine Frage ein."}]
184
  return
185
-
186
- # Generiert die Session ID fΓΌr das spΓ€tere BΓΌndeln in MLflow!
187
- current_session_id = str(uuid.uuid4())
188
 
189
  history = [{"role": "user", "content": user_prompt}]
190
  yield history
191
-
192
- discussion_history = ""
193
-
194
- # --- KICK-OFF ---
195
- history.append({"role": "assistant", "content": self.ui.header("🎀 MODERATOR ((Llama-3.3-70B)): SITZUNGSERΓ–FFNUNG")})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  yield history
197
-
198
- kickoff_res = self.llm.ask(
199
- MODERATOR_MODEL,
200
- self.prompts.get_moderator_kickoff_sys(),
201
- f"User-Anfrage: '{user_prompt}'\nBriefe das Team.",
202
- current_session_id, # <-- NEU hinzugefΓΌgt
203
- "Moderator_Kickoff" # <-- NEU hinzugefΓΌgt
204
  )
205
- discussion_history += f"Moderator - Kick-off (Llama-3.3-70B): {kickoff_res}\n\n"
206
- history.append({"role": "assistant", "content": self.ui.message("🎀 Moderator", kickoff_res, "#FF5A4D")})
207
  yield history
208
 
209
- # --- ZYKLEN ---
210
  for r in range(int(rounds)):
211
- history.append({"role": "assistant", "content": self.ui.header(f"πŸ”„ ZYKLUS {r+1} - EXPERTENDEBATTE", "#4241A6")})
 
 
 
212
  yield history
213
-
214
- # Moderator Steuerung (ab Runde 2)
215
- if r > 0:
216
- mid_res = self.llm.ask(
217
- MODERATOR_MODEL,
218
- self.prompts.get_moderator_mid_sys(),
219
- f"Protokoll:\n{discussion_history}\n\nGib die Anweisung.",
220
- current_session_id, # <-- NEU hinzugefΓΌgt
221
- "Moderator_Steuerung" # <-- NEU hinzugefΓΌgt
222
  )
223
- discussion_history += f"Moderator - Steuerung (Llama-3.3-70B): {mid_res}\n\n"
224
- history.append({"role": "assistant", "content": self.ui.message("🎀 Moderator (Steuerung)", mid_res, "#FF5A4D")})
 
 
225
  yield history
226
 
227
- # Experten antworten
228
- for name, config in COUNCIL_MEMBERS.items():
229
- sys_msg = self.prompts.get_expert_sys(config["role"])
230
- user_msg = self.prompts.get_expert_user_prompt(user_prompt, discussion_history, name)
231
-
232
- answer = self.llm.ask(
233
- config["model"],
234
- sys_msg,
235
- user_msg,
236
- current_session_id,
237
- name
238
- )
239
- discussion_history += f"{name}: {answer}\n\n"
240
-
241
- history.append({"role": "assistant", "content": self.ui.message(name, answer)})
242
- yield history
243
 
244
- # --- ANALYSE ---
245
- history.append({"role": "assistant", "content": self.ui.header("🧠 MODERATOR: ANALYSE DER DISKUSSION (Llama-3.3-70B)")})
246
- yield history
247
-
248
- consensus_res = self.llm.ask(
249
- MODERATOR_MODEL,
250
- self.prompts.get_analysis_sys(),
251
- self.prompts.get_analysis_user(discussion_history),
252
- current_session_id, # <-- NEU hinzugefΓΌgt
253
- "Moderator_Analyse" # <-- NEU hinzugefΓΌgt
254
- )
255
- history.append({"role": "assistant", "content": f"> {consensus_res}"})
256
- yield history
 
 
 
 
 
 
 
 
 
257
 
258
- # --- FINALE AUSGABE ---
259
  history.append({"role": "assistant", "content": self.ui.header("πŸ† FINALE AUSGABE")})
260
  yield history
261
-
262
- final_res = self.llm.ask(
263
- MODERATOR_MODEL,
264
- self.prompts.get_final_sys(),
265
- self.prompts.get_final_user(user_prompt, consensus_res),
266
- current_session_id, # <-- NEU hinzugefΓΌgt
267
- "Moderator_Finale" # <-- NEU hinzugefΓΌgt
268
  )
269
- history.append({"role": "assistant", "content": final_res})
270
  yield history
271
 
272
- # Instanziiere den Orchestrator
 
273
  orchestrator = PlenumOrchestrator()
274
 
 
275
  # ==========================================
276
- # 4. GRADIO UI
277
  # ==========================================
 
278
  v_theme = gr.themes.Soft(
279
- primary_hue="indigo",
280
- font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
281
  ).set(
282
- button_primary_background_fill="#4241A6",
283
  button_primary_background_fill_hover="#2D2C73",
284
  button_primary_text_color="white",
285
- block_title_text_color="#FF5A4D",
286
  block_label_text_color="#4241A6",
287
  body_text_color="#1F2937",
288
- color_accent_soft="#FFEBE8",
289
  )
290
 
291
  with gr.Blocks(theme=v_theme) as demo:
292
  gr.HTML("""
293
- <div style="text-align: center; margin-bottom: 2rem; margin-top: 1rem;">
294
- <h1 style="color: #FF5A4D; font-weight: 900; font-size: 2.8rem; margin-bottom: 0.2rem; font-family: 'Inter', sans-serif; letter-spacing: -0.02em;">PromptPlenum42</h1>
295
- <p style="color: #4B5563; font-size: 1.1rem; font-family: 'Inter', sans-serif;">AI-Driven Multi-Agent Consensus System</p>
 
 
 
296
  </div>
297
  """)
298
 
299
  with gr.Row():
300
  with gr.Column(scale=4):
301
  input_text = gr.Textbox(
302
- label="Plenumsauftrag",
303
- placeholder="z.B. 'Refaktorier dieses Python-Skript' oder 'Schreibe einen LinkedIn Post ΓΌber KI'",
304
- lines=2
 
 
 
 
305
  )
306
  with gr.Column(scale=1):
307
- rounds_slider = gr.Slider(minimum=1, maximum=5, value=1, step=1, label="Diskussionszyklen")
308
-
 
 
309
  with gr.Row():
310
  start_btn = gr.Button("Sitzung starten", variant="primary", size="lg")
311
- clear_btn = gr.ClearButton(components=[input_text], value="Protokoll leeren", size="lg")
312
-
313
- chatbot = gr.Chatbot(label="Sitzungsprotokoll", height=650)
 
 
 
 
 
 
 
314
  clear_btn.add(chatbot)
315
 
316
- # UI Events an den Orchestrator binden
317
- input_text.submit(orchestrator.run, inputs=[input_text, rounds_slider], outputs=[chatbot])
318
- start_btn.click(orchestrator.run, inputs=[input_text, rounds_slider], outputs=[chatbot])
 
 
 
319
 
320
  if __name__ == "__main__":
321
  demo.launch()
 
1
  import gradio as gr
2
  from openai import OpenAI
3
  import os
4
+ import json
 
 
 
 
 
 
 
 
5
 
6
  # ==========================================
7
+ # 1. KONFIGURATION & AUFGABEN-PROFILE
8
  # ==========================================
9
 
10
  MODERATOR_MODEL = "meta-llama/Llama-3.3-70B-Instruct"
11
 
12
+ # Jedes Profil definiert, wie Experten denken und was am Ende rauskommt
13
+ TASK_PROFILES = {
14
+ "CODE": {
15
+ "label": "πŸ’» Code / Technisch",
16
+ "expert_focuses": [
17
+ "Architektur, Algorithmus & Gesamtstruktur des Codes",
18
+ "Edge-Cases, Fehlerbehandlung & Sicherheit",
19
+ "Saubere, idiomatische Implementierung & Lesbarkeit",
20
+ ],
21
+ "final_instruction": (
22
+ "Liefere AUSSCHLIESSLICH den fertigen, lauffΓ€higen Code. "
23
+ "Inline-Kommentare nur wo nâtig. Kein Fließtext drumherum. "
24
+ "Korrekter Code-Block fΓΌr die jeweilige Sprache."
25
+ ),
26
+ "draft_label": "Aktueller Code-Entwurf",
27
+ },
28
+ "TEXT": {
29
+ "label": "✍️ Text / Content",
30
+ "expert_focuses": [
31
+ "Argumentationsstruktur, roter Faden & Aufbau",
32
+ "Ton, Zielgruppe, Fakten & fehlende Tiefe",
33
+ "Formulierung, Wirkung & plattformgerechtes Format",
34
+ ],
35
+ "final_instruction": (
36
+ "Liefere den fertigen, copy-paste-fΓ€higen Text. "
37
+ "Passe LΓ€nge, Ton und Format EXAKT an die Plattform an "
38
+ "(z.B. LinkedIn: kurze AbsΓ€tze, mobile-freundlich, sinnvolle Emojis). "
39
+ "Keine Platzhalter, keine KI-Floskeln, kein Meta-Kommentar."
40
+ ),
41
+ "draft_label": "Aktueller Text-Entwurf",
42
+ },
43
+ "PLAN": {
44
+ "label": "πŸ“‹ Strategie / Plan",
45
+ "expert_focuses": [
46
+ "Gesamtstrategie, Phasen & logische Abfolge",
47
+ "Risiken, AbhΓ€ngigkeiten, KPIs & blinde Flecken",
48
+ "Konkrete Maßnahmen, Verantwortlichkeiten & Zeitplan",
49
+ ],
50
+ "final_instruction": (
51
+ "Liefere einen klaren, strukturierten Aktionsplan. "
52
+ "Phasen, Meilensteine, Maßnahmen. Tabellen wo sinnvoll. "
53
+ "Direkt umsetzbar, keine abstrakten WorthΓΌlsen."
54
+ ),
55
+ "draft_label": "Aktueller Plan-Entwurf",
56
+ },
57
+ "ANALYSIS": {
58
+ "label": "πŸ” Analyse / Konzept",
59
+ "expert_focuses": [
60
+ "Problemstruktur, Hypothesen & Analyserahmen",
61
+ "Daten, Belege, Gegenargumente & LΓΌcken",
62
+ "Schlussfolgerungen, Empfehlungen & Priorisierung",
63
+ ],
64
+ "final_instruction": (
65
+ "Liefere eine strukturierte Analyse: Befunde β†’ Bewertung β†’ Empfehlung. "
66
+ "Faktenbasiert, prΓ€zise, keine leeren Phrasen."
67
+ ),
68
+ "draft_label": "Aktuelle Analyse",
69
+ },
70
+ }
71
+
72
+ COUNCIL_MEMBERS = [
73
+ {
74
+ "name": "🧠 Experte I",
75
  "model": "openai/gpt-oss-120b",
76
+ "tag": "STRUKTUR",
77
+ "color": "#1a6b3c",
78
+ "role_hint": "Erstelle oder verbessere die Grundstruktur und das logische GerΓΌst.",
 
 
79
  },
80
+ {
81
+ "name": "🧐 Experte II",
82
  "model": "deepseek-ai/DeepSeek-V3.2",
83
+ "tag": "KRITIK",
84
+ "color": "#7c3aed",
85
+ "role_hint": (
86
+ "PrΓΌfe den Entwurf von [STRUKTUR] kritisch. "
87
+ "Finde konkrete Fehler, LΓΌcken und SchwΓ€chen β€” und behebe sie direkt im Entwurf."
88
+ ),
89
  },
90
+ {
91
+ "name": "πŸ› οΈ Experte III",
92
  "model": "meta-llama/Llama-4-Maverick-17B-128E-Instruct",
93
+ "tag": "UMSETZUNG",
94
+ "color": "#b45309",
95
+ "role_hint": (
96
+ "Nimm den von [KRITIK] ΓΌberarbeiteten Entwurf und bringe ihn zur Serienreife. "
97
+ "SchΓ€rfe die Formulierungen, vervollstΓ€ndige fehlende Teile, sorge fΓΌr Konsistenz."
98
+ ),
99
+ },
100
+ ]
101
+
102
+
103
+ # ==========================================
104
+ # 2. PROMPT MANAGER
105
+ # ==========================================
106
 
107
  class PromptManager:
108
+
 
109
  @staticmethod
110
+ def task_detection_sys():
111
  return (
112
+ "Du bist ein prΓ€ziser Aufgaben-Klassifikator. Analysiere die Anfrage und antworte NUR mit einem JSON-Objekt.\n"
113
+ "GΓΌltige task_type-Werte: CODE, TEXT, PLAN, ANALYSIS\n"
114
+ "Format (keine weiteren Zeichen außerhalb):\n"
115
+ '{"task_type": "...", "core_goal": "Ein-Satz-Beschreibung des Ziels", '
116
+ '"key_constraints": ["Constraint 1", "Constraint 2"]}'
117
  )
118
+
119
+ @staticmethod
120
+ def task_detection_user(user_prompt):
121
+ return f"Aufgabe: {user_prompt}"
122
+
123
  @staticmethod
124
+ def moderator_kickoff_sys(task_profile):
125
  return (
126
+ f"Du bist Lead-Moderator eines Expertenrats. Aufgabentyp: {task_profile['label']}.\n"
127
+ "Brief das Team in genau 4 Punkten (je 1 Satz):\n"
128
+ "1. Konkretes Ziel\n"
129
+ "2. Wichtigste QualitΓ€tskriterien fΓΌr das Endprodukt\n"
130
+ "3. Grâßte Risiken / hÀufigste Fehler bei diesem Aufgabentyp\n"
131
+ "4. Erwartetes Format des Endprodukts\n"
132
+ "Kein Smalltalk. Direkt. Ohne Anrede."
133
  )
134
 
135
  @staticmethod
136
+ def moderator_kickoff_user(user_prompt, task_info):
137
+ constraints = ", ".join(task_info.get("key_constraints", [])) or "keine"
138
  return (
139
+ f"Auftrag: '{user_prompt}'\n"
140
+ f"Kernziel: {task_info.get('core_goal', '')}\n"
141
+ f"Constraints: {constraints}"
142
  )
143
 
144
  @staticmethod
145
+ def moderator_steering_sys():
 
 
146
  return (
147
+ "Du bist Lead-Moderator. Bewerte den aktuellen Entwurf knapp und gib dann "
148
+ "EINEN einzigen, konkreten Arbeitsauftrag fΓΌr die nΓ€chste Runde.\n"
149
+ "Format:\n"
150
+ "STAND: [Was gut ist β€” 1 Satz]\n"
151
+ "AUFTRAG: [Was als nΓ€chstes konkret zu tun ist β€” 1-2 SΓ€tze, so spezifisch wie mΓΆglich]"
 
152
  )
153
 
154
  @staticmethod
155
+ def moderator_steering_user(current_draft, round_num):
156
+ return (
157
+ f"Aktueller Entwurf nach Runde {round_num}:\n\n{current_draft}\n\n"
158
+ "Gib Steuerungsanweisung fΓΌr Runde {next_round}.".replace(
159
+ "{next_round}", str(round_num + 1)
160
+ )
161
+ )
162
 
163
  @staticmethod
164
+ def expert_sys(expert, task_profile, focus_area, round_num):
165
+ tag = expert["tag"]
166
+ return (
167
+ f"Du bist Experte [{tag}] in einem iterativen Expertenrat.\n\n"
168
+ f"DEIN FOKUS in dieser Runde: {focus_area}\n"
169
+ f"DEINE ROLLE [{tag}]: {expert['role_hint']}\n\n"
170
+ f"REGELN:\n"
171
+ f"- Beginne mit '[{tag}] '\n"
172
+ f"- Du lieferst den {task_profile['draft_label']} β€” nicht einen Kommentar darΓΌber.\n"
173
+ f"- Verbessere den Entwurf direkt. Kein 'Ich wΓΌrde vorschlagen...'\n"
174
+ f"- Keine Wiederholung des Auftrags, kein Meta-Kommentar am Ende.\n"
175
+ f"- Runde {round_num}: {'Erstelle die erste Version.' if round_num == 1 and tag == 'STRUKTUR' else 'Baue auf dem bestehenden Entwurf auf.'}"
176
+ )
177
+
178
+ @staticmethod
179
+ def expert_user(user_prompt, current_draft, steering_instruction, expert_tag):
180
+ draft_block = (
181
+ f"\n\nAKTUELLER ENTWURF (verbessere diesen direkt):\n---\n{current_draft}\n---"
182
+ if current_draft
183
+ else "\n\nEs gibt noch keinen Entwurf. Erstelle die erste Version."
184
+ )
185
+ steering_block = (
186
+ f"\n\nMODERATOR-ANWEISUNG: {steering_instruction}"
187
+ if steering_instruction
188
+ else ""
189
+ )
190
  return (
191
+ f"Auftrag: '{user_prompt}'"
192
+ f"{draft_block}"
193
+ f"{steering_block}\n\n"
194
+ f"Liefere jetzt den verbesserten {expert_tag}-Entwurf:"
195
  )
196
 
197
  @staticmethod
198
+ def final_sys(task_profile):
199
  return (
200
+ f"Du bist ein Output-Finisher fΓΌr {task_profile['label']}-Aufgaben.\n\n"
201
+ f"ANWEISUNG:\n{task_profile['final_instruction']}\n\n"
202
+ "Nutze den vorliegenden Entwurf als Basis und liefere das polierte Endprodukt. "
203
+ "Kein einleitender Satz, kein abschließender Kommentar β€” nur das Produkt."
204
  )
205
 
206
  @staticmethod
207
+ def final_user(user_prompt, best_draft, task_info):
208
  return (
209
+ f"UrsprΓΌnglicher Auftrag: '{user_prompt}'\n"
210
+ f"Kernziel: {task_info.get('core_goal', '')}\n\n"
211
+ f"Bester Entwurf aus der Diskussion:\n---\n{best_draft}\n---\n\n"
212
+ "Erstelle das finale, sofort nutzbare Endprodukt:"
 
 
 
 
213
  )
214
 
215
+
216
  # ==========================================
217
+ # 3. LLM SERVICE & UI HELPER
218
  # ==========================================
219
+
220
  class LLMService:
 
221
  def __init__(self):
 
222
  self.client = OpenAI(
223
+ base_url="https://router.huggingface.co/v1",
224
+ api_key=os.getenv("HF_TOKEN"),
225
  )
226
 
227
+ def ask(self, model_id, system_prompt, user_input):
 
 
 
 
 
 
 
 
 
228
  messages = [
229
  {"role": "system", "content": system_prompt},
230
+ {"role": "user", "content": user_input},
231
  ]
 
232
  try:
233
+ response = self.client.chat.completions.create(
234
+ model=model_id,
235
+ messages=messages,
236
+ max_tokens=4000,
237
+ temperature=0.4,
238
+ stream=False,
 
239
  )
240
+ return response.choices[0].message.content or ""
 
 
 
241
  except Exception as e:
242
+ return f"🚨 Fehler ({model_id}): {str(e)}"
243
+
244
+
245
  class UIHelper:
 
246
  @staticmethod
247
  def header(title, color="#FF5A4D"):
248
+ return (
249
+ f"<h2 style='color:{color}; border-bottom:2px solid #FFEBE8; "
250
+ f"padding-bottom:5px; margin-top:20px;'>{title}</h2>"
251
+ )
252
+
253
+ @staticmethod
254
+ def message(label, content, color="#4241A6"):
255
+ return f"**<span style='color:{color}; font-size:1.05em;'>{label}</span>**\n\n{content}"
256
 
257
  @staticmethod
258
+ def info(content):
259
+ return f"> ℹ️ {content}"
260
+
261
 
262
  # ==========================================
263
+ # 4. ORCHESTRATOR
264
  # ==========================================
265
+
266
  class PlenumOrchestrator:
 
267
  def __init__(self):
268
  self.llm = LLMService()
269
+ self.pm = PromptManager()
270
  self.ui = UIHelper()
271
 
272
+ def _detect_task(self, user_prompt):
273
+ raw = self.llm.ask(
274
+ MODERATOR_MODEL,
275
+ self.pm.task_detection_sys(),
276
+ self.pm.task_detection_user(user_prompt),
277
+ )
278
+ try:
279
+ clean = raw.strip().removeprefix("```json").removeprefix("```").removesuffix("```").strip()
280
+ return json.loads(clean)
281
+ except Exception:
282
+ return {"task_type": "TEXT", "core_goal": user_prompt, "key_constraints": []}
283
+
284
  def run(self, user_prompt, rounds):
285
+ if not user_prompt.strip():
286
+ yield [{"role": "assistant", "content": "Bitte gib einen Auftrag ein."}]
287
  return
 
 
 
288
 
289
  history = [{"role": "user", "content": user_prompt}]
290
  yield history
291
+
292
+ # Zustand: Das ist das HerzstΓΌck des neuen Ansatzes
293
+ current_draft = ""
294
+ steering_instruction = ""
295
+
296
+ # ── SCHRITT 0: AUFGABE ERKENNEN ──────────────────────────────
297
+ history.append({"role": "assistant", "content": self.ui.header("πŸ” AUFGABENANALYSE", "#6b7280")})
298
+ yield history
299
+
300
+ task_info = self._detect_task(user_prompt)
301
+ task_type = task_info.get("task_type", "TEXT")
302
+ task_profile = TASK_PROFILES.get(task_type, TASK_PROFILES["TEXT"])
303
+
304
+ detection_text = (
305
+ f"**Erkannter Typ:** {task_profile['label']} \n"
306
+ f"**Kernziel:** {task_info.get('core_goal', 'β€”')} \n"
307
+ f"**Constraints:** {', '.join(task_info.get('key_constraints', ['β€”'])) or 'β€”'}"
308
+ )
309
+ history.append({"role": "assistant", "content": detection_text})
310
+ yield history
311
+
312
+ # ── SCHRITT 1: KICK-OFF ───────────────────────────────────────
313
+ history.append({"role": "assistant", "content": self.ui.header("🎀 MODERATOR: SITZUNGSERΓ–FFNUNG")})
314
  yield history
315
+
316
+ kickoff = self.llm.ask(
317
+ MODERATOR_MODEL,
318
+ self.pm.moderator_kickoff_sys(task_profile),
319
+ self.pm.moderator_kickoff_user(user_prompt, task_info),
 
 
320
  )
321
+ history.append({"role": "assistant", "content": self.ui.message("🎀 Moderator", kickoff, "#FF5A4D")})
 
322
  yield history
323
 
324
+ # ── SCHRITT 2: ZYKLEN ─────────────────────────────────────────
325
  for r in range(int(rounds)):
326
+ history.append({
327
+ "role": "assistant",
328
+ "content": self.ui.header(f"πŸ”„ ZYKLUS {r + 1} β€” EXPERTENDEBATTE", "#4241A6"),
329
+ })
330
  yield history
331
+
332
+ # Moderator-Steuerung (ab Runde 2, basierend auf aktuellem Entwurf)
333
+ if r > 0 and current_draft:
334
+ steering_instruction = self.llm.ask(
335
+ MODERATOR_MODEL,
336
+ self.pm.moderator_steering_sys(),
337
+ self.pm.moderator_steering_user(current_draft, r),
 
 
338
  )
339
+ history.append({
340
+ "role": "assistant",
341
+ "content": self.ui.message("🎀 Moderator (Steuerung)", steering_instruction, "#FF5A4D"),
342
+ })
343
  yield history
344
 
345
+ # Experten arbeiten SEQUENZIELL:
346
+ # Experte I sieht aktuellen Entwurf β†’ produziert Draft A
347
+ # Experte II sieht Draft A β†’ produziert Draft B (mit Korrekturen)
348
+ # Experte III sieht Draft B β†’ produziert Draft C (serienreif)
349
+ # β†’ Draft C wird zum current_draft fΓΌr die nΓ€chste Runde
350
+ round_draft = current_draft
 
 
 
 
 
 
 
 
 
 
351
 
352
+ for idx, expert in enumerate(COUNCIL_MEMBERS):
353
+ focus = task_profile["expert_focuses"][idx]
354
+
355
+ sys_msg = self.pm.expert_sys(expert, task_profile, focus, r + 1)
356
+ usr_msg = self.pm.expert_user(user_prompt, round_draft, steering_instruction if r > 0 else "", expert["tag"])
357
+
358
+ answer = self.llm.ask(expert["model"], sys_msg, usr_msg)
359
+
360
+ # Dieser Experte liefert den Entwurf fΓΌr den NΓ€chsten
361
+ round_draft = answer
362
+
363
+ label = f"{expert['name']} [{expert['tag']}] β€” {focus}"
364
+ history.append({"role": "assistant", "content": self.ui.message(label, answer, expert["color"])})
365
+ yield history
366
+
367
+ # Bester Stand dieser Runde = Output von Experte III
368
+ current_draft = round_draft
369
+ history.append({
370
+ "role": "assistant",
371
+ "content": self.ui.info(f"Zyklus {r + 1} abgeschlossen. Entwurf gesichert β†’ Basis fΓΌr nΓ€chste Runde."),
372
+ })
373
+ yield history
374
 
375
+ # ── SCHRITT 3: FINALE AUSGABE ─────────────────────────────────
376
  history.append({"role": "assistant", "content": self.ui.header("πŸ† FINALE AUSGABE")})
377
  yield history
378
+
379
+ final = self.llm.ask(
380
+ MODERATOR_MODEL,
381
+ self.pm.final_sys(task_profile),
382
+ self.pm.final_user(user_prompt, current_draft, task_info),
 
 
383
  )
384
+ history.append({"role": "assistant", "content": final})
385
  yield history
386
 
387
+
388
+ # Orchestrator-Instanz
389
  orchestrator = PlenumOrchestrator()
390
 
391
+
392
  # ==========================================
393
+ # 5. GRADIO UI
394
  # ==========================================
395
+
396
  v_theme = gr.themes.Soft(
397
+ primary_hue="indigo",
398
+ font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
399
  ).set(
400
+ button_primary_background_fill="#4241A6",
401
  button_primary_background_fill_hover="#2D2C73",
402
  button_primary_text_color="white",
403
+ block_title_text_color="#FF5A4D",
404
  block_label_text_color="#4241A6",
405
  body_text_color="#1F2937",
406
+ color_accent_soft="#FFEBE8",
407
  )
408
 
409
  with gr.Blocks(theme=v_theme) as demo:
410
  gr.HTML("""
411
+ <div style="text-align:center; margin-bottom:2rem; margin-top:1rem;">
412
+ <h1 style="color:#FF5A4D; font-weight:900; font-size:2.8rem; margin-bottom:0.2rem;
413
+ font-family:'Inter',sans-serif; letter-spacing:-0.02em;">PromptPlenum42</h1>
414
+ <p style="color:#4B5563; font-size:1.1rem; font-family:'Inter',sans-serif;">
415
+ AI-Driven Multi-Agent Consensus System
416
+ </p>
417
  </div>
418
  """)
419
 
420
  with gr.Row():
421
  with gr.Column(scale=4):
422
  input_text = gr.Textbox(
423
+ label="Plenumsauftrag",
424
+ placeholder=(
425
+ "z.B. 'Refaktoriere dieses Python-Skript' Β· "
426
+ "'Schreibe einen LinkedIn-Post ΓΌber KI' Β· "
427
+ "'Erstelle einen Go-to-Market-Plan fΓΌr ein SaaS-Produkt'"
428
+ ),
429
+ lines=3,
430
  )
431
  with gr.Column(scale=1):
432
+ rounds_slider = gr.Slider(
433
+ minimum=1, maximum=5, value=1, step=1, label="Diskussionszyklen"
434
+ )
435
+
436
  with gr.Row():
437
  start_btn = gr.Button("Sitzung starten", variant="primary", size="lg")
438
+ clear_btn = gr.ClearButton(
439
+ components=[input_text], value="Protokoll leeren", size="lg"
440
+ )
441
+
442
+ chatbot = gr.Chatbot(
443
+ label="Sitzungsprotokoll",
444
+ height=700,
445
+ type="messages",
446
+ render_markdown=True,
447
+ )
448
  clear_btn.add(chatbot)
449
 
450
+ input_text.submit(
451
+ orchestrator.run, inputs=[input_text, rounds_slider], outputs=[chatbot]
452
+ )
453
+ start_btn.click(
454
+ orchestrator.run, inputs=[input_text, rounds_slider], outputs=[chatbot]
455
+ )
456
 
457
  if __name__ == "__main__":
458
  demo.launch()