Spaces:
Running
Running
| import gradio as gr | |
| from openai import OpenAI | |
| import os | |
| import json | |
| # ========================================== | |
| # 1. KONFIGURATION & AUFGABEN-PROFILE | |
| # ========================================== | |
| MODERATOR_MODEL = "openai/gpt-oss-120b" | |
| MODERATOR_PROVIDER = "groq" # 435 tok/s β wird oft aufgerufen, Geschwindigkeit summiert sich | |
| # Jedes Profil definiert, wie Experten denken und was am Ende rauskommt | |
| TASK_PROFILES = { | |
| "CODE": { | |
| "label": "π» Code / Technisch", | |
| "expert_focuses": [ | |
| "Architektur, Algorithmus & Gesamtstruktur des Codes", | |
| "Edge-Cases, Fehlerbehandlung & Sicherheit", | |
| "Saubere, idiomatische Implementierung & Lesbarkeit", | |
| ], | |
| "final_instruction": ( | |
| "Liefere AUSSCHLIESSLICH den fertigen, lauffΓ€higen Code. " | |
| "Inline-Kommentare nur wo nΓΆtig. Kein FlieΓtext drumherum. " | |
| "Korrekter Code-Block fΓΌr die jeweilige Sprache." | |
| ), | |
| "draft_label": "Aktueller Code-Entwurf", | |
| }, | |
| "TEXT": { | |
| "label": "βοΈ Text / Content", | |
| "expert_focuses": [ | |
| "Argumentationsstruktur, roter Faden & Aufbau", | |
| "Ton, Zielgruppe, Fakten & fehlende Tiefe", | |
| "Formulierung, Wirkung & plattformgerechtes Format", | |
| ], | |
| "final_instruction": ( | |
| "Liefere den fertigen, copy-paste-fΓ€higen Text. " | |
| "Passe LΓ€nge, Ton und Format EXAKT an die Plattform an " | |
| "(z.B. LinkedIn: kurze AbsΓ€tze, mobile-freundlich, sinnvolle Emojis). " | |
| "Keine Platzhalter, keine KI-Floskeln, kein Meta-Kommentar." | |
| ), | |
| "draft_label": "Aktueller Text-Entwurf", | |
| }, | |
| "PLAN": { | |
| "label": "π Strategie / Plan", | |
| "expert_focuses": [ | |
| "Gesamtstrategie, Phasen & logische Abfolge", | |
| "Risiken, AbhΓ€ngigkeiten, KPIs & blinde Flecken", | |
| "Konkrete MaΓnahmen, Verantwortlichkeiten & Zeitplan", | |
| ], | |
| "final_instruction": ( | |
| "Liefere einen klaren, strukturierten Aktionsplan. " | |
| "Phasen, Meilensteine, MaΓnahmen. Tabellen wo sinnvoll. " | |
| "Direkt umsetzbar, keine abstrakten WorthΓΌlsen." | |
| ), | |
| "draft_label": "Aktueller Plan-Entwurf", | |
| }, | |
| "ANALYSIS": { | |
| "label": "π Analyse / Konzept", | |
| "expert_focuses": [ | |
| "Problemstruktur, Hypothesen & Analyserahmen", | |
| "Daten, Belege, Gegenargumente & LΓΌcken", | |
| "Schlussfolgerungen, Empfehlungen & Priorisierung", | |
| ], | |
| "final_instruction": ( | |
| "Liefere eine strukturierte Analyse: Befunde β Bewertung β Empfehlung. " | |
| "Faktenbasiert, prΓ€zise, keine leeren Phrasen." | |
| ), | |
| "draft_label": "Aktuelle Analyse", | |
| }, | |
| } | |
| COUNCIL_MEMBERS = [ | |
| { | |
| "name": "π§ Experte I", | |
| "model": "openai/gpt-oss-120b", | |
| "tag": "STRUKTUR", | |
| "color": "#1a6b3c", | |
| "role_hint": "Erstelle oder verbessere die Grundstruktur und das logische GerΓΌst.", | |
| }, | |
| { | |
| "name": "π§ Experte II", | |
| "model": "deepseek-ai/DeepSeek-R1", # Reasoning-Modell β ideal fΓΌr Kritik & Fehlersuche | |
| "tag": "KRITIK", | |
| "color": "#7c3aed", | |
| "role_hint": ( | |
| "PrΓΌfe den Entwurf von [STRUKTUR] kritisch. " | |
| "Finde konkrete Fehler, LΓΌcken und SchwΓ€chen β und behebe sie direkt im Entwurf." | |
| ), | |
| }, | |
| { | |
| "name": "π οΈ Experte III", | |
| "model": "moonshotai/Kimi-K2.5", # andere Trainings-Lineage β echte DiversitΓ€t im Council | |
| "code_model": "Qwen/Qwen3-Coder-480B-A35B-Instruct", # Swap fΓΌr CODE-Tasks | |
| "tag": "UMSETZUNG", | |
| "color": "#b45309", | |
| "role_hint": ( | |
| "Nimm den von [KRITIK] ΓΌberarbeiteten Entwurf und bringe ihn zur Serienreife. " | |
| "SchΓ€rfe die Formulierungen, vervollstΓ€ndige fehlende Teile, sorge fΓΌr Konsistenz." | |
| ), | |
| }, | |
| ] | |
| # ========================================== | |
| # 2. PROMPT MANAGER | |
| # ========================================== | |
| class PromptManager: | |
| def task_detection_sys(): | |
| return ( | |
| "Du bist ein prΓ€ziser Aufgaben-Klassifikator. Analysiere die Anfrage und antworte NUR mit einem JSON-Objekt.\n" | |
| "GΓΌltige task_type-Werte: CODE, TEXT, PLAN, ANALYSIS\n" | |
| "Format (keine weiteren Zeichen auΓerhalb):\n" | |
| '{"task_type": "...", "core_goal": "Ein-Satz-Beschreibung des Ziels", ' | |
| '"key_constraints": ["Constraint 1", "Constraint 2"]}' | |
| ) | |
| def task_detection_user(user_prompt): | |
| return f"Aufgabe: {user_prompt}" | |
| def moderator_kickoff_sys(task_profile): | |
| return ( | |
| f"Du bist Lead-Moderator eines Expertenrats. Aufgabentyp: {task_profile['label']}.\n" | |
| "Brief das Team in genau 4 Punkten (je 1 Satz):\n" | |
| "1. Konkretes Ziel\n" | |
| "2. Wichtigste QualitΓ€tskriterien fΓΌr das Endprodukt\n" | |
| "3. GrΓΆΓte Risiken / hΓ€ufigste Fehler bei diesem Aufgabentyp\n" | |
| "4. Erwartetes Format des Endprodukts\n" | |
| "Kein Smalltalk. Direkt. Ohne Anrede." | |
| ) | |
| def moderator_kickoff_user(user_prompt, task_info): | |
| constraints = ", ".join(task_info.get("key_constraints", [])) or "keine" | |
| return ( | |
| f"Auftrag: '{user_prompt}'\n" | |
| f"Kernziel: {task_info.get('core_goal', '')}\n" | |
| f"Constraints: {constraints}" | |
| ) | |
| def moderator_steering_sys(): | |
| return ( | |
| "Du bist Lead-Moderator. Bewerte den aktuellen Entwurf knapp und gib dann " | |
| "EINEN einzigen, konkreten Arbeitsauftrag fΓΌr die nΓ€chste Runde.\n" | |
| "Format:\n" | |
| "STAND: [Was gut ist β 1 Satz]\n" | |
| "AUFTRAG: [Was als nΓ€chstes konkret zu tun ist β 1-2 SΓ€tze, so spezifisch wie mΓΆglich]" | |
| ) | |
| def moderator_steering_user(current_draft, round_num): | |
| return ( | |
| f"Aktueller Entwurf nach Runde {round_num}:\n\n{current_draft}\n\n" | |
| "Gib Steuerungsanweisung fΓΌr Runde {next_round}.".replace( | |
| "{next_round}", str(round_num + 1) | |
| ) | |
| ) | |
| def expert_sys(expert, task_profile, focus_area, round_num): | |
| tag = expert["tag"] | |
| return ( | |
| f"Du bist Experte [{tag}] in einem iterativen Expertenrat.\n\n" | |
| f"DEIN FOKUS in dieser Runde: {focus_area}\n" | |
| f"DEINE ROLLE [{tag}]: {expert['role_hint']}\n\n" | |
| f"REGELN:\n" | |
| f"- Beginne mit '[{tag}] '\n" | |
| f"- Du lieferst den {task_profile['draft_label']} β nicht einen Kommentar darΓΌber.\n" | |
| f"- Verbessere den Entwurf direkt. Kein 'Ich wΓΌrde vorschlagen...'\n" | |
| f"- Keine Wiederholung des Auftrags, kein Meta-Kommentar am Ende.\n" | |
| f"- Runde {round_num}: {'Erstelle die erste Version.' if round_num == 1 and tag == 'STRUKTUR' else 'Baue auf dem bestehenden Entwurf auf.'}" | |
| ) | |
| def expert_user(user_prompt, current_draft, steering_instruction, expert_tag): | |
| draft_block = ( | |
| f"\n\nAKTUELLER ENTWURF (verbessere diesen direkt):\n---\n{current_draft}\n---" | |
| if current_draft | |
| else "\n\nEs gibt noch keinen Entwurf. Erstelle die erste Version." | |
| ) | |
| steering_block = ( | |
| f"\n\nMODERATOR-ANWEISUNG: {steering_instruction}" | |
| if steering_instruction | |
| else "" | |
| ) | |
| return ( | |
| f"Auftrag: '{user_prompt}'" | |
| f"{draft_block}" | |
| f"{steering_block}\n\n" | |
| f"Liefere jetzt den verbesserten {expert_tag}-Entwurf:" | |
| ) | |
| def final_sys(task_profile): | |
| return ( | |
| f"Du bist ein Output-Finisher fΓΌr {task_profile['label']}-Aufgaben.\n\n" | |
| f"ANWEISUNG:\n{task_profile['final_instruction']}\n\n" | |
| "Nutze den vorliegenden Entwurf als Basis und liefere das polierte Endprodukt. " | |
| "Kein einleitender Satz, kein abschlieΓender Kommentar β nur das Produkt." | |
| ) | |
| def final_user(user_prompt, best_draft, task_info): | |
| return ( | |
| f"UrsprΓΌnglicher Auftrag: '{user_prompt}'\n" | |
| f"Kernziel: {task_info.get('core_goal', '')}\n\n" | |
| f"Bester Entwurf aus der Diskussion:\n---\n{best_draft}\n---\n\n" | |
| "Erstelle das finale, sofort nutzbare Endprodukt:" | |
| ) | |
| # ========================================== | |
| # 3. LLM SERVICE & UI HELPER | |
| # ========================================== | |
| class LLMService: | |
| def __init__(self): | |
| self.client = OpenAI( | |
| base_url="https://router.huggingface.co/v1", | |
| api_key=os.getenv("HF_TOKEN"), | |
| ) | |
| def _strip_thinking(text: str) -> str: | |
| """Entfernt <think>...</think> BlΓΆcke (z.B. von DeepSeek-R1) aus dem Output.""" | |
| import re | |
| return re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL).strip() | |
| def ask(self, model_id, system_prompt, user_input, provider=None): | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_input}, | |
| ] | |
| extra_headers = {"x-provider": provider} if provider else {} | |
| try: | |
| response = self.client.chat.completions.create( | |
| model=model_id, | |
| messages=messages, | |
| max_tokens=4000, | |
| temperature=0.4, | |
| stream=False, | |
| extra_headers=extra_headers, | |
| ) | |
| raw = response.choices[0].message.content or "" | |
| return self._strip_thinking(raw) | |
| except Exception as e: | |
| return f"π¨ Fehler ({model_id}): {str(e)}" | |
| class UIHelper: | |
| def header(title, color="#FF5A4D"): | |
| return ( | |
| f"<h2 style='color:{color}; border-bottom:2px solid #FFEBE8; " | |
| f"padding-bottom:5px; margin-top:20px;'>{title}</h2>" | |
| ) | |
| def message(label, content, color="#4241A6"): | |
| return f"**<span style='color:{color}; font-size:1.05em;'>{label}</span>**\n\n{content}" | |
| def info(content): | |
| return f"> βΉοΈ {content}" | |
| # ========================================== | |
| # 4. ORCHESTRATOR | |
| # ========================================== | |
| class PlenumOrchestrator: | |
| def __init__(self): | |
| self.llm = LLMService() | |
| self.pm = PromptManager() | |
| self.ui = UIHelper() | |
| def _detect_task(self, user_prompt): | |
| raw = self.llm.ask( | |
| MODERATOR_MODEL, | |
| self.pm.task_detection_sys(), | |
| self.pm.task_detection_user(user_prompt), | |
| provider=MODERATOR_PROVIDER, | |
| ) | |
| try: | |
| clean = raw.strip().removeprefix("```json").removeprefix("```").removesuffix("```").strip() | |
| return json.loads(clean) | |
| except Exception: | |
| return {"task_type": "TEXT", "core_goal": user_prompt, "key_constraints": []} | |
| def run(self, user_prompt, rounds): | |
| if not user_prompt.strip(): | |
| yield [{"role": "assistant", "content": "Bitte gib einen Auftrag ein."}] | |
| return | |
| history = [{"role": "user", "content": user_prompt}] | |
| yield history | |
| # Zustand: Das ist das HerzstΓΌck des neuen Ansatzes | |
| current_draft = "" | |
| steering_instruction = "" | |
| # ββ SCHRITT 0: AUFGABE ERKENNEN ββββββββββββββββββββββββββββββ | |
| history.append({"role": "assistant", "content": self.ui.header("π AUFGABENANALYSE", "#6b7280")}) | |
| yield history | |
| task_info = self._detect_task(user_prompt) | |
| task_type = task_info.get("task_type", "TEXT") | |
| task_profile = TASK_PROFILES.get(task_type, TASK_PROFILES["TEXT"]) | |
| detection_text = ( | |
| f"**Erkannter Typ:** {task_profile['label']} \n" | |
| f"**Kernziel:** {task_info.get('core_goal', 'β')} \n" | |
| f"**Constraints:** {', '.join(task_info.get('key_constraints', ['β'])) or 'β'}" | |
| ) | |
| history.append({"role": "assistant", "content": detection_text}) | |
| yield history | |
| # ββ SCHRITT 1: KICK-OFF βββββββββββββββββββββββββββββββββββββββ | |
| history.append({"role": "assistant", "content": self.ui.header("π€ MODERATOR: SITZUNGSERΓFFNUNG")}) | |
| yield history | |
| kickoff = self.llm.ask( | |
| MODERATOR_MODEL, | |
| self.pm.moderator_kickoff_sys(task_profile), | |
| self.pm.moderator_kickoff_user(user_prompt, task_info), | |
| provider=MODERATOR_PROVIDER, | |
| ) | |
| history.append({"role": "assistant", "content": self.ui.message("π€ Moderator", kickoff, "#FF5A4D")}) | |
| yield history | |
| # ββ SCHRITT 2: ZYKLEN βββββββββββββββββββββββββββββββββββββββββ | |
| for r in range(int(rounds)): | |
| history.append({ | |
| "role": "assistant", | |
| "content": self.ui.header(f"π ZYKLUS {r + 1} β EXPERTENDEBATTE", "#4241A6"), | |
| }) | |
| yield history | |
| # Moderator-Steuerung (ab Runde 2, basierend auf aktuellem Entwurf) | |
| if r > 0 and current_draft: | |
| steering_instruction = self.llm.ask( | |
| MODERATOR_MODEL, | |
| self.pm.moderator_steering_sys(), | |
| self.pm.moderator_steering_user(current_draft, r), | |
| provider=MODERATOR_PROVIDER, | |
| ) | |
| history.append({ | |
| "role": "assistant", | |
| "content": self.ui.message("π€ Moderator (Steuerung)", steering_instruction, "#FF5A4D"), | |
| }) | |
| yield history | |
| # Experten arbeiten SEQUENZIELL: | |
| # Experte I sieht aktuellen Entwurf β produziert Draft A | |
| # Experte II sieht Draft A β produziert Draft B (mit Korrekturen) | |
| # Experte III sieht Draft B β produziert Draft C (serienreif) | |
| # β Draft C wird zum current_draft fΓΌr die nΓ€chste Runde | |
| round_draft = current_draft | |
| for idx, expert in enumerate(COUNCIL_MEMBERS): | |
| focus = task_profile["expert_focuses"][idx] | |
| # Task-aware Model Swap: Experte III nutzt Coder-Modell bei CODE-Tasks | |
| model_id = ( | |
| expert.get("code_model", expert["model"]) | |
| if task_type == "CODE" and expert["tag"] == "UMSETZUNG" | |
| else expert["model"] | |
| ) | |
| sys_msg = self.pm.expert_sys(expert, task_profile, focus, r + 1) | |
| usr_msg = self.pm.expert_user(user_prompt, round_draft, steering_instruction if r > 0 else "", expert["tag"]) | |
| answer = self.llm.ask(model_id, sys_msg, usr_msg, provider=expert.get("provider")) | |
| # Dieser Experte liefert den Entwurf fΓΌr den NΓ€chsten | |
| round_draft = answer | |
| label = f"{expert['name']} [{expert['tag']}] β {focus}" | |
| history.append({"role": "assistant", "content": self.ui.message(label, answer, expert["color"])}) | |
| yield history | |
| # Bester Stand dieser Runde = Output von Experte III | |
| current_draft = round_draft | |
| history.append({ | |
| "role": "assistant", | |
| "content": self.ui.info(f"Zyklus {r + 1} abgeschlossen. Entwurf gesichert β Basis fΓΌr nΓ€chste Runde."), | |
| }) | |
| yield history | |
| # ββ SCHRITT 3: FINALE AUSGABE βββββββββββββββββββββββββββββββββ | |
| history.append({"role": "assistant", "content": self.ui.header("π FINALE AUSGABE")}) | |
| yield history | |
| final = self.llm.ask( | |
| MODERATOR_MODEL, | |
| self.pm.final_sys(task_profile), | |
| self.pm.final_user(user_prompt, current_draft, task_info), | |
| provider=MODERATOR_PROVIDER, | |
| ) | |
| history.append({"role": "assistant", "content": final}) | |
| yield history | |
| # Orchestrator-Instanz | |
| orchestrator = PlenumOrchestrator() | |
| # ========================================== | |
| # 5. GRADIO UI | |
| # ========================================== | |
| v_theme = gr.themes.Soft( | |
| primary_hue="indigo", | |
| font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"], | |
| ).set( | |
| button_primary_background_fill="#4241A6", | |
| button_primary_background_fill_hover="#2D2C73", | |
| button_primary_text_color="white", | |
| block_title_text_color="#FF5A4D", | |
| block_label_text_color="#4241A6", | |
| body_text_color="#1F2937", | |
| color_accent_soft="#FFEBE8", | |
| ) | |
| with gr.Blocks() as demo: | |
| gr.HTML(""" | |
| <div style="text-align:center; margin-bottom:2rem; margin-top:1rem;"> | |
| <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> | |
| <p style="color:#4B5563; font-size:1.1rem; font-family:'Inter',sans-serif;"> | |
| AI-Driven Multi-Agent Consensus System | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=4): | |
| input_text = gr.Textbox( | |
| label="Plenumsauftrag", | |
| placeholder=( | |
| "z.B. 'Refaktoriere dieses Python-Skript' Β· " | |
| "'Schreibe einen LinkedIn-Post ΓΌber KI' Β· " | |
| "'Erstelle einen Go-to-Market-Plan fΓΌr ein SaaS-Produkt'" | |
| ), | |
| lines=3, | |
| ) | |
| with gr.Column(scale=1): | |
| rounds_slider = gr.Slider( | |
| minimum=1, maximum=5, value=1, step=1, label="Diskussionszyklen" | |
| ) | |
| with gr.Row(): | |
| start_btn = gr.Button("Sitzung starten", variant="primary", size="lg") | |
| clear_btn = gr.ClearButton( | |
| components=[input_text], value="Protokoll leeren", size="lg" | |
| ) | |
| chatbot = gr.Chatbot( | |
| label="Sitzungsprotokoll", | |
| height=700, | |
| sanitize_html=False, | |
| ) | |
| clear_btn.add(chatbot) | |
| input_text.submit( | |
| orchestrator.run, inputs=[input_text, rounds_slider], outputs=[chatbot] | |
| ) | |
| start_btn.click( | |
| orchestrator.run, inputs=[input_text, rounds_slider], outputs=[chatbot] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(theme=v_theme) |