""" 🇨🇭 Apertus Dialekt-Konsil mit Roundtable Ein AI-Konsil mit Schweizerdeutsch, Bayrisch und Schwäbisch Sprechern Mit visuellem Roundtable und Moderator - Angepasst an Consilium Format """ import gradio as gr import os from huggingface_hub import InferenceClient import json import time from datetime import datetime import uuid from gradio_consilium_roundtable import consilium_roundtable # Configuration HF_TOKEN = os.environ.get('HF_TOKEN', '') # Initialize Apertus client with publicai provider client = None if HF_TOKEN: client = InferenceClient( provider="publicai", api_key=HF_TOKEN ) # Avatar URLs für die Dialekt-Sprecher - Wappen und Flaggen avatar_images = { "Ueli": "https://upload.wikimedia.org/wikipedia/commons/7/7d/Wappen_Basel-Stadt_matt.svg", # Basel Wappen "Sepp": "https://upload.wikimedia.org/wikipedia/commons/d/d2/Bayern_Wappen.svg", # Bayern Wappen "Karl": "https://upload.wikimedia.org/wikipedia/commons/0/0f/Lesser_coat_of_arms_of_Baden-W%C3%BCrttemberg.svg", # Baden-Württemberg Wappen "Dr. Müller": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Flag_of_Switzerland_%28Pantone%29.svg/100px-Flag_of_Switzerland_%28Pantone%29.svg.png" # Schweiz Flagge für neutralen Moderator } # Dialekt-Persönlichkeiten COUNCIL_MEMBERS = { "Ueli": { "full_name": "Ueli (Baseldytsch)", "emoji": "🇨🇭", "dialect": "Baseldytsch", "location": "Basel", "personality": "kultiviert, humorvoll, fasnachts-begeistert", "thinking_message": "dänggt nooch...", # Baseldytsch für "denkt nach" "system_prompt": """Du bist Ueli aus Basel und sprichst Baseldytsch. Du bist kultiviert, humorvoll und liebst die Basler Fasnacht. Verwende typische Basler Ausdrücke und Redewendungen. Beispiele: 'Sali zämme', 'Das isch e gueti Sach, gäll', 'Mir Baasler wisse was guet isch', 'Das brucht e bitzeli Köpfli'""" }, "Sepp": { "full_name": "Sepp (Bayrisch)", "emoji": "🥨", "dialect": "Bayrisch", "location": "München", "personality": "gesellig, direkt, traditionsbewusst", "thinking_message": "überlegt si...", # Bayrisch für "überlegt" "system_prompt": """Du bist Sepp aus München und sprichst AUSSCHLIESSLICH BAYRISCH! Du bist gesellig, direkt und traditionsbewusst. ABSOLUT WICHTIG: Verwende NUR bayrische Ausdrücke, NIEMALS Schweizerdeutsch oder Schwäbisch! Typisch bayrisch: 'Servus mitanand', 'Des is fei scho recht', 'I moan', 'Des passt scho', 'Mia san mia', 'Des is hoid so', 'Ja mei' VERBOTEN: Schweizer Wörter wie 'isch', 'gäll', 'zämme' oder schwäbische wie 'schaffe', 'koscht' NIEMALS 'isch' verwenden - immer 'is' oder 'ist'!""" }, "Karl": { "full_name": "Karl (Schwäbisch)", "emoji": "🏰", "dialect": "Schwäbisch", "location": "Stuttgart", "personality": "sparsam, fleißig, erfinderisch", "thinking_message": "grübelt grad...", # Schwäbisch für "grübelt gerade" "system_prompt": """Du bist Karl aus Stuttgart und sprichst SCHWÄBISCH (nicht bayrisch!). Du bist sparsam, fleißig und erfinderisch. WICHTIG: Verwende NUR schwäbische Ausdrücke, NICHT bayrische oder andere Dialekte! Typisch schwäbisch: 'Grüß Gott mitanand', 'Des isch halt so', 'Mir müsset schaffe', 'Des koscht Geld', 'Schwätz net so viel', 'No ja, des goht scho' NIEMALS verwenden: bayrische Wörter wie 'fei', 'miassn', 'hoid', 'mia'""" } } # Moderator MODERATOR = { "name": "Dr. Müller", "full_name": "Dr. Müller (Moderator)", "emoji": "🎓", "system_prompt": """Du bist Dr. Müller, ein neutraler Moderator für das Dialekt-Konsil. Deine Aufgaben: 1. Fasse jede Diskussionsrunde zusammen 2. Bewerte den Konsensgrad auf einer Skala von 1-10 3. Identifiziere Gemeinsamkeiten und Unterschiede 4. Schlage Kompromisse vor wenn nötig Sei neutral, professionell und präzise in deinen Bewertungen.""" } # Session storage für isolierte Diskussionen sessions = {} class DialektKonsil: def __init__(self, session_id=None): self.session_id = session_id or str(uuid.uuid4()) self.conversation_history = [] self.current_topic = "" self.consensus_score = 0 self.round_number = 0 # Roundtable state im Consilium Format self.roundtable_state = { "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"], "messages": [], "currentSpeaker": None, "thinking": [], "showBubbles": [], "avatarImages": avatar_images } def generate_response(self, member_name, topic, previous_responses): """Generate response for a council member""" member = COUNCIL_MEMBERS[member_name] # Build context but isolate dialects better context = f"Thema der Diskussion: {topic}\n\n" if previous_responses: context += "Bisherige Beiträge anderer Sprecher:\n" for resp in previous_responses: # Kürze die Responses drastisch um Kopieren zu vermeiden context += f"- {resp['speaker']}: {resp['text'][:100]}...\n" # Verstärke die Dialekt-Isolation prompt = f"""{member['system_prompt']} {context} ABSOLUT WICHTIG: Du bist {member_name} aus {member['location']} und sprichst {member['dialect']}! AUFTRAG: Gib deine EIGENE, EINZIGARTIGE Meinung zum Thema. - Verwende AUSSCHLIESSLICH {member['dialect']} Dialekt - Maximal 2 kurze Sätze - KOPIERE NIEMALS andere Sprecher - Entwickle DEINE EIGENE Perspektive - Verwende {member_name}'s typische Ausdrücke Antworte jetzt als {member_name} in {member['dialect']}:""" try: # Generate response using Apertus via publicai completion = client.chat.completions.create( model="swiss-ai/Apertus-8B-Instruct-2509", messages=[ {"role": "system", "content": member['system_prompt']}, {"role": "user", "content": prompt} ], max_tokens=80, # Noch kürzer um Abschneiden zu vermeiden temperature=0.9, # Höhere Variabilität für unterschiedliche Antworten top_p=0.95, frequency_penalty=0.5, # Reduziere Wiederholungen presence_penalty=0.3 # Ermutige neue Inhalte ) response = completion.choices[0].message.content.strip() # Aggressive Response-Cleanup um Kopieren zu verhindern # Entferne Namen-Präfixe aller Sprecher for name in ["Ueli", "Sepp", "Karl", "Dr.", "Müller"]: if response.startswith(f"{name}:"): response = response[len(name)+1:].strip() if response.startswith(f"{name} "): response = response[len(name)+1:].strip() # Entferne Prompt-Wiederholungen cleanup_phrases = ["Deine Antwort", "Antworte", "Als " + member_name, "Ich bin " + member_name, member['dialect']] for phrase in cleanup_phrases: if phrase in response: response = response.split(phrase)[0].strip() # Stelle sicher dass Antwort kurz bleibt und vollständig ist sentences = response.split('. ') if len(sentences) > 2: response = '. '.join(sentences[:2]) if not response.endswith('.'): response += '.' # Entferne unvollständige Sätze am Ende if response.endswith(', will\'s') or response.endswith(', dass'): words = response.split() response = ' '.join(words[:-2]) + '.' return response except Exception as e: # Fallback-Antworten je nach Sprecher fallbacks = { "Ueli": "Jo, das isch e schwierigi Frog, gäll. Mir müesse do guet überlegge.", "Sepp": "Mei, des is fei a schwierige Sach. Da muas ma gscheid drüber nachdenka.", "Karl": "Ha, des isch halt so a Sach. Do muss mr gründlich überlege." } return fallbacks.get(member_name, f"*hüstel* ({str(e)[:50]}...)") def generate_moderator_summary(self, round_responses): """Generate moderator summary and consensus score - mit besserem Modell""" context = "Diskussionsrunde abgeschlossen. Folgende Beiträge:\n" for resp in round_responses: context += f"- {resp['speaker']}: {resp['text']}\n" prompt = f"""{MODERATOR['system_prompt']} {context} Aufgabe als neutraler Moderator: 1. Fasse die Hauptpunkte präzise zusammen (2-3 Sätze) 2. Bewerte den Konsensgrad objektiv von 1-10 (1=völlig uneinig, 10=vollständige Einigung) 3. Identifiziere klar die Gemeinsamkeiten 4. Nenne konkrete Unterschiede falls vorhanden Format (GENAU befolgen): ZUSAMMENFASSUNG: [Präzise Zusammenfassung der Diskussion] KONSENSGRAD: [Zahl 1-10] GEMEINSAMKEITEN: [Was alle Sprecher teilen] UNTERSCHIEDE: [Konkrete Meinungsunterschiede]""" try: # Verwende das stärkere Apertus 70B Modell für bessere Moderation completion = client.chat.completions.create( model="swiss-ai/Apertus-70B-Instruct-2509", # Apertus 70B für bessere Moderation messages=[ {"role": "system", "content": MODERATOR['system_prompt']}, {"role": "user", "content": prompt} ], max_tokens=300, temperature=0.5 # Niedrigere Temperatur für konsistentere Analyse ) response = completion.choices[0].message.content.strip() # Parse consensus score mit besserer Logik consensus_score = 5 # Default if "KONSENSGRAD:" in response: try: score_line = [l for l in response.split('\n') if 'KONSENSGRAD:' in l][0] # Extrahiere alle Zahlen und nimm die erste zwischen 1-10 import re scores = re.findall(r'\b([1-9]|10)\b', score_line) if scores: consensus_score = int(scores[0]) consensus_score = max(1, min(10, consensus_score)) except Exception as parse_error: print(f"⚠️ Konsensgrad-Parsing Fehler: {parse_error}") return response, consensus_score except Exception as e: print(f"❌ Moderator-Fehler: {e}") # Fallback: Verwende Apertus falls Llama nicht verfügbar try: fallback_completion = client.chat.completions.create( model="swiss-ai/Apertus-8B-Instruct-2509", messages=[ {"role": "system", "content": MODERATOR['system_prompt']}, {"role": "user", "content": prompt} ], max_tokens=250, temperature=0.7 ) response = fallback_completion.choices[0].message.content.strip() consensus_score = 5 if "KONSENSGRAD:" in response: try: score_line = [l for l in response.split('\n') if 'KONSENSGRAD:' in l][0] consensus_score = int(''.join(filter(str.isdigit, score_line))) consensus_score = max(1, min(10, consensus_score)) except: pass return response, consensus_score except Exception as fallback_error: return f"Runde abgeschlossen. Konsens wird noch analysiert. ({str(fallback_error)[:50]}...)", 5 def update_visual_state(self, **kwargs): """Update the visual roundtable state""" self.roundtable_state.update(kwargs) return json.dumps(self.roundtable_state) def run_discussion_round(self): """Run one round of discussion with visual updates""" self.round_number += 1 responses = [] # Reset messages for new round but keep participants self.roundtable_state["messages"] = [] # Each member speaks once per round for member_name in COUNCIL_MEMBERS.keys(): member = COUNCIL_MEMBERS[member_name] thinking_msg = member.get("thinking_message", "denkt nach...") # Show thinking state with visual indicator self.roundtable_state["thinking"] = [member_name] self.roundtable_state["currentSpeaker"] = None # Also add dialect-specific thinking message self.roundtable_state["messages"].append({ "speaker": member_name, "text": f"{member['emoji']} *{thinking_msg}*" }) yield self.update_visual_state() time.sleep(1) # Generate response response = self.generate_response(member_name, self.current_topic, responses) # Clear thinking, set as current speaker, update message self.roundtable_state["thinking"] = [] self.roundtable_state["currentSpeaker"] = member_name self.roundtable_state["messages"][-1] = { "speaker": member_name, "text": f"{member['emoji']} {response}" } responses.append({ "speaker": member_name, "text": response, "emoji": COUNCIL_MEMBERS[member_name]["emoji"], "timestamp": datetime.now().strftime("%H:%M:%S"), "round": self.round_number }) # Show speech bubble if member_name not in self.roundtable_state["showBubbles"]: self.roundtable_state["showBubbles"].append(member_name) yield self.update_visual_state() # Add to conversation history self.conversation_history.append(responses[-1]) time.sleep(2) # Generate moderator summary # Show moderator thinking with visual indicator self.roundtable_state["thinking"] = ["Dr. Müller"] self.roundtable_state["currentSpeaker"] = None self.roundtable_state["messages"].append({ "speaker": "Dr. Müller", "text": "🎓 *analysiert die Diskussion...*" }) yield self.update_visual_state() time.sleep(1) moderator_summary, self.consensus_score = self.generate_moderator_summary(responses) # Clear thinking, set as speaker, update message self.roundtable_state["thinking"] = [] self.roundtable_state["currentSpeaker"] = "Dr. Müller" self.roundtable_state["messages"][-1] = { "speaker": "Dr. Müller", "text": f"🎓 {moderator_summary}" } if "Dr. Müller" not in self.roundtable_state["showBubbles"]: self.roundtable_state["showBubbles"].append("Dr. Müller") self.conversation_history.append({ "speaker": MODERATOR["name"], "text": moderator_summary, "emoji": MODERATOR["emoji"], "timestamp": datetime.now().strftime("%H:%M:%S"), "round": self.round_number, "is_moderator": True }) yield self.update_visual_state() def format_history(self): """Format conversation history for display""" if not self.conversation_history: return "*Keine Diskussion gestartet*" output = f"## 🗣️ Dialekt-Konsil: {self.current_topic}\n\n" current_round = 0 for entry in self.conversation_history: if entry.get("round", 0) > current_round: current_round = entry["round"] output += f"\n### 🔄 Runde {current_round}\n\n" if entry.get("is_moderator"): output += f"**{entry['emoji']} {entry['speaker']}** - Zusammenfassung:\n" output += f"```\n{entry['text']}\n```\n" output += f"**Konsensgrad: {self.consensus_score}/10**\n\n" else: full_name = COUNCIL_MEMBERS[entry['speaker']]['full_name'] output += f"**{entry['emoji']} {full_name}** ({entry['timestamp']}):\n" output += f"> {entry['text']}\n\n" return output def get_or_create_session(session_id=None): """Get existing session or create new one""" if not session_id: session_id = str(uuid.uuid4()) if session_id not in sessions: sessions[session_id] = DialektKonsil(session_id) return sessions[session_id], session_id def start_new_discussion(topic): """Start a new discussion with a fresh session""" if not topic: initial_state = json.dumps({ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"], "messages": [], "currentSpeaker": None, "thinking": [], "showBubbles": [], "avatarImages": avatar_images }) return initial_state, None, "*Keine Diskussion gestartet*", "❌ Bitte geben Sie ein Thema ein!" if not client: initial_state = json.dumps({ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"], "messages": [], "currentSpeaker": None, "thinking": [], "showBubbles": [], "avatarImages": avatar_images }) return initial_state, None, "*Keine Diskussion gestartet*", "❌ HF_TOKEN nicht konfiguriert. Bitte in Space Settings setzen." # Create new session konsil, session_id = get_or_create_session() konsil.current_topic = topic konsil.conversation_history = [] konsil.round_number = 0 konsil.consensus_score = 0 # Reset roundtable state konsil.roundtable_state = { "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"], "messages": [], "currentSpeaker": None, "thinking": [], "showBubbles": [], "avatarImages": avatar_images } return ( json.dumps(konsil.roundtable_state), session_id, konsil.format_history(), f"✅ Diskussion gestartet: {topic}\n\n➡️ Klicken Sie auf 'Nächste Runde' um die erste Runde zu beginnen." ) def run_next_round(session_id): """Run the next discussion round with streaming updates""" if not session_id or session_id not in sessions: empty_state = json.dumps({ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"], "messages": [], "currentSpeaker": None, "thinking": [], "showBubbles": [], "avatarImages": avatar_images }) return empty_state, "*Keine Diskussion gestartet*", "❌ Keine aktive Session. Bitte starten Sie eine neue Diskussion." konsil = sessions[session_id] if not konsil.current_topic: return json.dumps(konsil.roundtable_state), "*Keine Diskussion gestartet*", "❌ Kein Thema gesetzt. Bitte starten Sie eine neue Diskussion." if konsil.consensus_score >= 8: return json.dumps(konsil.roundtable_state), konsil.format_history(), f"✅ Konsens bereits erreicht (Score: {konsil.consensus_score}/10)" # Run discussion round with generator for updates final_state = None for state in konsil.run_discussion_round(): final_state = state history = konsil.format_history() if konsil.consensus_score >= 8: status = f"🎉 Konsens erreicht! (Score: {konsil.consensus_score}/10)" elif konsil.round_number >= 5: status = f"🤝 Maximale Rundenzahl erreicht. Finaler Konsensgrad: {konsil.consensus_score}/10" else: status = f"Runde {konsil.round_number} abgeschlossen. Konsensgrad: {konsil.consensus_score}/10" return final_state, history, status def create_interface(): """Create Gradio interface with roundtable visualization""" with gr.Blocks( title="Apertus Dialekt-Konsil", theme=gr.themes.Soft(), css=""" .roundtable-container { max-width: 800px; margin: 0 auto; } """ ) as demo: gr.Markdown(""" # 🇨🇭🥨🏰 Apertus Dialekt-Konsil mit Roundtable Ein KI-gestütztes Konsil mit drei süddeutschen Dialekt-Sprechern und Moderator: - 🇨🇭 **Ueli** aus Basel (Baseldytsch) - 🥨 **Sepp** aus München (Bayrisch) - 🏰 **Karl** aus Stuttgart (Schwäbisch) - 🎓 **Dr. Müller** als neutraler Moderator Geben Sie ein Thema ein und beobachten Sie die Diskussion am runden Tisch! """) # Hidden session ID session_id = gr.State() with gr.Row(): with gr.Column(scale=2): topic_input = gr.Textbox( label="💭 Diskussionsthema", placeholder="z.B. 'Wie sollten wir mit KI in der Bildung umgehen?' oder 'Was ist das beste Essen?'", lines=2 ) with gr.Row(): start_btn = gr.Button( "🚀 Neue Diskussion starten", variant="primary", size="lg" ) next_round_btn = gr.Button( "➡️ Nächste Runde", variant="secondary", size="lg" ) # Status display status_display = gr.Markdown(value="*Bereit für neue Diskussion*") # Roundtable visualization with gr.Row(): with gr.Column(scale=1, elem_classes="roundtable-container"): # Initial state im richtigen Format initial_roundtable_state = json.dumps({ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"], "messages": [], "currentSpeaker": None, "thinking": [], "showBubbles": [], "avatarImages": avatar_images }) roundtable = consilium_roundtable( value=initial_roundtable_state, label="Diskussionsrunde", min_width=600 ) # Consensus score display with gr.Row(): consensus_display = gr.Markdown( value="**Konsensgrad:** 0/10 ⚪" ) # Discussion history with gr.Row(): with gr.Column(): history_display = gr.Markdown( label="Diskussionsverlauf", value="*Warten auf Diskussionsstart...*" ) # Example topics gr.Examples( examples=[ ["Wie können wir die Umwelt besser schützen?"], ["Was ist wichtiger: Tradition oder Innovation?"], ["Sollte KI in Schulen eingesetzt werden?"], ["Was macht eine gute Nachbarschaft aus?"], ["Wie finden wir Work-Life-Balance?"], ["Brauchen wir mehr oder weniger Regeln?"] ], inputs=topic_input ) # Event handlers start_btn.click( start_new_discussion, inputs=[topic_input], outputs=[roundtable, session_id, history_display, status_display] ) next_round_btn.click( run_next_round, inputs=[session_id], outputs=[roundtable, history_display, status_display] ) # Update consensus display when roundtable updates def update_consensus(roundtable_state): if roundtable_state: try: state = json.loads(roundtable_state) if isinstance(roundtable_state, str) else roundtable_state # Try to extract consensus from moderator messages messages = state.get("messages", []) for msg in reversed(messages): if msg.get("speaker") == "Dr. Müller" and "KONSENSGRAD:" in msg.get("text", ""): import re match = re.search(r'KONSENSGRAD:\s*(\d+)', msg["text"]) if match: score = int(match.group(1)) return f"**Konsensgrad:** {score}/10 {'🟢' if score >= 8 else '🟡' if score >= 5 else '🔴'}" except: pass return "**Konsensgrad:** Bewertung läuft..." roundtable.change( update_consensus, inputs=[roundtable], outputs=[consensus_display] ) # Auto-refresh für bessere Visualisierung während der Diskussion def refresh_roundtable(session_id): if session_id and session_id in sessions: return json.dumps(sessions[session_id].roundtable_state) return json.dumps({ "participants": ["Ueli", "Sepp", "Karl", "Dr. Müller"], "messages": [], "currentSpeaker": None, "thinking": [], "showBubbles": [], "avatarImages": avatar_images }) # Timer für Auto-Update (alle 1 Sekunde) gr.Timer(1.0).tick(refresh_roundtable, inputs=[session_id], outputs=[roundtable]) gr.Markdown(""" --- ### 📌 Hinweise - Das Konsil verwendet das **Apertus-8B** Modell für authentische Dialekt-Generierung - Der Moderator bewertet nach jeder Runde den Konsensgrad (1-10) - Die Diskussion endet bei Konsensgrad ≥ 8 oder nach 5 Runden - Benötigt HF_TOKEN in den Space Settings ### 🎯 Features - **Visueller Roundtable**: Sehen Sie die Teilnehmer am runden Tisch - **Authentische Dialekte**: Schweizerdeutsch, Bayrisch, Schwäbisch - **Neutraler Moderator**: Fasst zusammen und bewertet Konsens - **Interaktive Runden**: Verfolgen Sie jede Diskussionsrunde einzeln - **Speech Bubbles**: Live-Anzeige der Diskussionsbeiträge *Powered by Apertus Swiss AI* 🇨🇭 """) return demo # Launch the app if __name__ == "__main__": print("🇨🇭🥨🏰 Apertus Dialekt-Konsil mit Roundtable") print(f"HF Token configured: {bool(HF_TOKEN)}") demo = create_interface() demo.launch()