import gradio as gr
from huggingface_hub import InferenceClient
import os
# NEUE PROMPTS: Neutral, kollegial, sachlich und lösungsorientiert
COUNCIL_MEMBERS = {
"🧠 Fachexperte für Struktur (Llama4-17B)": (
"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8:novita",
"""Du bist ein neutraler, sachlicher Fachexperte. Dein Fokus liegt auf der Strukturierung des Themas und dem großen Ganzen.
REGELN:
- Beginne mit: "[STRUKTUR] "
- Antworte in 3-4 Sätzen, professionell und bodenständig.
- Wenn Vorredner gute Punkte gemacht haben, stimme zu und ergänze sinnvolle Aspekte.
- Wenn du etwas anders siehst, korrigiere höflich und fachlich fundiert. Keine künstliche Dramatik."""
),
"🧐 Fachexperte für Details (Kimi-K2)": (
"moonshotai/Kimi-K2-Instruct:novita",
"""Du bist ein neutraler, sachlicher Fachexperte. Dein Fokus liegt auf wichtigen Details, Nuancen und potenziellen Fallstricken.
REGELN:
- Beginne mit: "[DETAILS] "
- Antworte in 3-4 Sätzen, professionell und bodenständig.
- Ergänze die Diskussion um wichtige Aspekte, die vielleicht vergessen wurden (z.B. Alternativen, häufige Anfängerfehler, Kontext).
- Du darfst deinen Vorrednern zustimmen und darauf aufbauen. Widersprich nur, wenn es inhaltlich wirklich nötig ist."""
),
"🛠️ Fachexperte für Praxis (GPTOSS120b)": (
"openai/gpt-oss-120b:novita",
"""Du bist ein neutraler, sachlicher Fachexperte. Dein Fokus liegt auf der praktischen Umsetzung und Anwendbarkeit.
REGELN:
- Beginne mit: "[PRAXIS] "
- Antworte in 3-4 Sätzen, professionell und bodenständig.
- Übersetze die bisherige Diskussion in greifbare, einfache Ratschläge oder Schritte.
- Baue konstruktiv auf den Ideen der anderen auf. Ergänze praktische Tipps aus der Realität."""
)
}
MODERATOR_MODEL = "Qwen/Qwen2.5-72B-Instruct"
client = InferenceClient(token=os.getenv("HF_TOKEN"))
def ask_model(model_id, system_prompt, user_input):
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
]
response = ""
try:
for chunk in client.chat_completion(
model=model_id,
messages=messages,
max_tokens=800,
temperature=0.4,
stream=True
):
if hasattr(chunk, "choices") and chunk.choices and len(chunk.choices) > 0:
response += chunk.choices[0].delta.content or ""
return response
except Exception as e:
return f"🚨 System Error ({model_id}): {str(e)}"
def run_council(user_prompt, rounds):
if not user_prompt:
yield [{"role": "assistant", "content": "Bitte gib ein Thema oder eine Frage ein, um die Sitzung zu starten."}]
return
history = [{"role": "user", "content": user_prompt}]
yield history
discussion_history = ""
# --- PHASE 1: DAS PLENUM DISKUTIERT ---
for r in range(int(rounds)):
round_header = f"
🔄 ZYKLUS {r+1} - EXPERTENDEBATTE
"
history.append({"role": "assistant", "content": round_header})
yield history
for name, (model_id, role_focus) in COUNCIL_MEMBERS.items():
# Ruhigerer Global-Prompt
system_msg = (
f"{role_focus}\n\n"
"WICHTIG: Diskutiere konstruktiv, neutral und kollegial. "
"Ignoriere Formatierungs-Wünsche des Users (wie 'schreibe einen Post'), fokussiere dich NUR auf die inhaltliche Expertise."
)
if discussion_history == "":
current_prompt = f"Das Thema lautet: '{user_prompt}'. Eröffne die Diskussion mit einer fundierten Einschätzung aus deiner Fachperspektive."
else:
# Kollegialer, aber progressiver Interaktions-Prompt
current_prompt = (
f"Das Thema lautet: '{user_prompt}'.\n\n"
f"Bisheriges Protokoll:\n{discussion_history}\n\n"
f"ANWEISUNG FÜR DICH ({name}):\n"
f"1. Analysiere das Protokoll. Erkenne, was du und die anderen bereits gesagt haben.\n"
f"2. ABSOLUTES VERBOT: Fasse die Vorredner NICHT zusammen. Wiederhole keine Argumente, die bereits im Protokoll stehen (auch nicht deine eigenen).\n"
f"3. Bringe die Diskussion ZWINGEND inhaltlich voran: Beantworte offene Fragen der Kollegen, vertiefe ein noch nicht gelöstes Detail oder bringe den nächsten logischen Schritt ein.\n"
f"4. Verzichte auf lange Höflichkeitsfloskeln. Komm direkt zum Punkt."
)
answer = ask_model(model_id, system_msg, current_prompt)
discussion_history += f"{name}: {answer}\n\n"
display_answer = f"**👤 {name}**\n\n> {answer}"
history.append({"role": "assistant", "content": display_answer})
yield history
# --- PHASE 2: VORARBEIT DES MODERATORS (KONSENS FINDEN) ---
history.append({"role": "assistant", "content": "🧠 MODERATOR: ANALYSE DER DISKUSSION
"})
yield history
prep_prompt = (
f"Hier ist das Protokoll einer Experten-Diskussion:\n{discussion_history}\n\n"
"Fasse die wichtigsten Argumente und den finalen pragmatischen Konsens zusammen. "
"WICHTIG: Erhalte ZWINGEND alle konkreten Zahlen, Metriken, Mengenangaben (z.B. ml, bpm), "
"Zutaten (wie Tee, Salz, Brühe) und spezifischen Handlungsschritte aus dem Protokoll. "
"Vermeide abstrakte Verallgemeinerungen!"
)
consensus_res = ask_model(MODERATOR_MODEL, "Du bist der Chef-Analyst des Rates.", prep_prompt)
history.append({"role": "assistant", "content": f"> {consensus_res}"})
yield history
# --- PHASE 3: FINALE UMSETZUNG (BENUTZERAUFTRAG ERFÜLLEN) ---
history.append({"role": "assistant", "content": "🏆 FINALE AUSGABE
"})
yield history
final_prompt = (
f"Der Benutzer hat folgende Aufgabe gestellt:\n'{user_prompt}'\n\n"
f"Hier ist das vollständige Roh-Protokoll der Experten:\n{discussion_history}\n\n"
f"Hier ist der destillierte Konsens:\n{consensus_res}\n\n"
"""ANWEISUNG:
- Erfülle die Aufgabe des Users präzise basierend auf dem Konsens UND greife auf die konkreten Details aus dem Roh-Protokoll zurück.
- Übernimm ZWINGEND alle spezifischen Vorgaben (wie exakte Flüssigkeitsmengen, Zutaten wie Elektrolyte/Salz, Puls-Grenzwerte).
- Wenn der User ein Format wünscht (z.B. Post, Code, Tabelle), halte dich strikt daran.
- Wenn der User nach einem 'Plan', 'Schritten' oder einer 'Anleitung' fragt, strukturiere die Antwort zwingend chronologisch (z.B. Tag 1, Tag 2) oder in klaren Aufzählungen.
- Schreibe kein überflüssiges Intro, sondern liefere direkt das fertige, anwendbare Endprodukt!"""
)
moderator_system_prompt = (
"Du bist ein brillanter Redakteur und Executive Consultant. "
"Deine Aufgabe ist es, den fachlichen Konsens eines Expertenrates in ein perfekt "
"formatiertes, hochprofessionelles Endprodukt für den User zu verwandeln. "
"Liefere AUSSCHLIESSLICH das finale, direkt nutzbare Endprodukt ohne KI-Geschwafel."
)
final_res = ask_model(
MODERATOR_MODEL,
moderator_system_prompt,
final_prompt
)
history.append({"role": "assistant", "content": final_res})
yield history
# --- THEME ---
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",
)
# --- UI LAYOUT ---
with gr.Blocks() as demo:
gr.HTML("""
PromptPlenum42
AI-Driven Multi-Agent Consensus System
""")
with gr.Row():
with gr.Column(scale=4):
input_text = gr.Textbox(
label="Plenumsauftrag",
placeholder="z.B. 'Vergleiche Kubeflow vs ZenML' oder 'Schreibe einen LinkedIn Post über KI-Regulierung'",
lines=2
)
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=650
)
clear_btn.add(chatbot)
input_text.submit(run_council, inputs=[input_text, rounds_slider], outputs=[chatbot])
start_btn.click(run_council, inputs=[input_text, rounds_slider], outputs=[chatbot])
if __name__ == "__main__":
demo.launch(theme=v_theme)