Spaces:
Sleeping
Sleeping
| """ | |
| Elite Agent Tools – Alle Function Tools für den Webstark KI-Agenten. | |
| Modular aufgebaut, damit agent.py schlank bleibt. | |
| """ | |
| import os | |
| import json | |
| import logging | |
| import aiohttp | |
| from datetime import datetime | |
| from livekit.agents import RunContext, function_tool | |
| from livekit.agents.llm import ToolError | |
| # Professionelle Email-Templates importieren | |
| from email_templates import ( | |
| build_package_overview_email, | |
| build_thankyou_email, | |
| build_custom_email, | |
| build_developer_briefing, | |
| ) | |
| logger = logging.getLogger("livekit-agent") | |
| # ============================================================ | |
| # TOOL 1: Websuche via Tavily API | |
| # Ermöglicht Elite, aktuelle Infos aus dem Internet zu holen. | |
| # ============================================================ | |
| async def search_web(context: RunContext, query: str) -> str: | |
| """Durchsuche das Internet nach aktuellen Informationen. | |
| Nutze dieses Tool, wenn der Nutzer nach aktuellen Trends, | |
| Webseiten-Infos oder Branchenwissen fragt. | |
| Args: | |
| query: Die Suchanfrage, z.B. 'Webdesign Trends 2025 Schweiz' | |
| """ | |
| tavily_key = os.environ.get("TAVILY_API_KEY") | |
| if not tavily_key: | |
| raise ToolError("Websuche ist momentan nicht verfügbar. Bitte kontaktiere icarus.mod56@gmail.com.") | |
| try: | |
| from tavily import AsyncTavilyClient | |
| client = AsyncTavilyClient(api_key=tavily_key) | |
| result = await client.search(query=query, max_results=3, search_depth="basic") | |
| # Ergebnisse für die KI aufbereiten | |
| summary_parts = [] | |
| for item in result.get("results", []): | |
| title = item.get("title", "") | |
| content = item.get("content", "")[:300] | |
| url = item.get("url", "") | |
| summary_parts.append(f"**{title}**\n{content}\nQuelle: {url}") | |
| if not summary_parts: | |
| return "Keine relevanten Ergebnisse gefunden." | |
| return "\n\n".join(summary_parts) | |
| except Exception as e: | |
| logger.error(f"Tavily-Sucherror: {e}") | |
| raise ToolError("Die Websuche ist fehlgeschlagen. Bitte versuche es später erneut.") | |
| # ============================================================ | |
| # TOOL 2: Lead speichern | |
| # Speichert Kontaktdaten von Interessenten als JSON-Datei. | |
| # ============================================================ | |
| async def save_lead( | |
| context: RunContext, | |
| name: str, | |
| email: str, | |
| interest: str, | |
| phone: str = "", | |
| ) -> str: | |
| """Speichere die Kontaktdaten eines interessierten Kunden. | |
| Nutze dieses Tool, wenn der Nutzer seinen Namen und seine E-Mail | |
| hinterlassen möchte oder Interesse an einem Paket bekundet. | |
| Frage aktiv nach Name und E-Mail, bevor du dieses Tool aufrufst. | |
| Args: | |
| name: Vollständiger Name des Kunden | |
| email: E-Mail-Adresse des Kunden | |
| interest: Woran ist der Kunde interessiert (z.B. 'Starter-Paket', 'SEO-Optimierung') | |
| phone: Telefonnummer (optional) | |
| """ | |
| if not name or not email: | |
| raise ToolError("Name und E-Mail sind erforderlich, um den Lead zu speichern.") | |
| if "@" not in email: | |
| raise ToolError("Die E-Mail-Adresse scheint ungültig zu sein. Bitte erneut fragen.") | |
| # Unterbrechungen verbieten – wir schreiben Daten | |
| context.disallow_interruptions() | |
| lead_data = { | |
| "name": name, | |
| "email": email, | |
| "phone": phone, | |
| "interest": interest, | |
| "created_at": datetime.utcnow().isoformat(), | |
| } | |
| # Lead als JSON-Datei speichern | |
| leads_dir = os.path.join(os.path.dirname(__file__), "leads") | |
| os.makedirs(leads_dir, exist_ok=True) | |
| timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S") | |
| safe_name = name.replace(" ", "_").lower()[:20] | |
| filename = f"lead_{safe_name}_{timestamp}.json" | |
| filepath = os.path.join(leads_dir, filename) | |
| with open(filepath, "w", encoding="utf-8") as f: | |
| json.dump(lead_data, f, ensure_ascii=False, indent=2) | |
| logger.info(f"Lead gespeichert: {filepath}") | |
| # Benachrichtigungs-E-Mail an Webstark senden (wenn Resend konfiguriert) | |
| await _notify_new_lead(lead_data) | |
| return f"Kontaktdaten von {name} wurden erfolgreich gespeichert. Wir melden uns in Kürze per E-Mail bei {email}." | |
| # ============================================================ | |
| # TOOL 3: Kostenvoranschlag berechnen | |
| # ============================================================ | |
| async def calculate_quote( | |
| context: RunContext, | |
| package: str, | |
| extra_pages: int = 0, | |
| seo_hours: int = 0, | |
| chatbot: bool = False, | |
| ) -> str: | |
| """Berechne einen Kostenvoranschlag für ein Webstark-Projekt. | |
| Nutze dieses Tool, wenn der Nutzer nach einem Preis oder Angebot fragt. | |
| Args: | |
| package: Das gewählte Paket ('starter', 'professional', 'enterprise') | |
| extra_pages: Anzahl zusätzlicher Unterseiten (über die im Paket enthaltenen hinaus) | |
| seo_hours: Gewünschte SEO-Optimierungsstunden | |
| chatbot: Ob ein KI-Chatbot gewünscht ist (im Starter bereits enthalten) | |
| """ | |
| # Webstark Preisliste (Stand: webstark.org) | |
| packages = { | |
| "starter": { | |
| "name": "Starter (AI-Enhanced)", | |
| "base_price": 890, | |
| "included_pages": 1, | |
| "includes_chatbot": True, | |
| "description": "KI-generiertes Design, Automatisches Basis-SEO, Responsive One-Page, Chatbot-Integration (Basis)" | |
| }, | |
| "professional": { | |
| "name": "Professional (AI-Powered)", | |
| "base_price": 1490, | |
| "included_pages": 5, | |
| "includes_chatbot": True, | |
| "description": "Alles aus Starter, Content-Automation Engine, Predictive Analytics, A/B-Testing AI, Erweiterter KI-Support" | |
| }, | |
| "enterprise": { | |
| "name": "Enterprise (AI-First)", | |
| "base_price": 0, | |
| "included_pages": 0, | |
| "includes_chatbot": True, | |
| "is_custom": True, | |
| "description": "Custom AI-Workflows, Dedicated AI-Team, 24/7 Priority Support, On-site Workshops" | |
| } | |
| } | |
| EXTRA_PAGE_PRICE = 200 | |
| SEO_HOUR_PRICE = 120 | |
| CHATBOT_ADDON_PRICE = 350 | |
| pkg_key = package.lower().strip() | |
| if pkg_key not in packages: | |
| return ( | |
| f"Das Paket '{package}' ist nicht verfügbar. " | |
| f"Verfügbare Pakete: Starter (890 CHF), Professional (1.490 CHF), Enterprise (Individuell)." | |
| ) | |
| pkg = packages[pkg_key] | |
| # Enterprise = individuell | |
| if pkg.get("is_custom"): | |
| return ( | |
| f"Das {pkg['name']}-Paket ist massgeschneidert und wird individuell kalkuliert.\n" | |
| f"Enthält: {pkg['description']}\n\n" | |
| f"Bitte kontaktiere uns unter icarus.mod56@gmail.com oder hinterlasse deine Kontaktdaten." | |
| ) | |
| total = pkg["base_price"] | |
| breakdown = [f"{pkg['name']}: {pkg['base_price']} CHF"] | |
| if extra_pages > 0: | |
| page_cost = extra_pages * EXTRA_PAGE_PRICE | |
| total += page_cost | |
| breakdown.append(f"{extra_pages} Zusatzseiten: {page_cost} CHF") | |
| if seo_hours > 0: | |
| seo_cost = seo_hours * SEO_HOUR_PRICE | |
| total += seo_cost | |
| breakdown.append(f"{seo_hours} SEO-Stunden: {seo_cost} CHF") | |
| if chatbot and not pkg["includes_chatbot"]: | |
| total += CHATBOT_ADDON_PRICE | |
| breakdown.append(f"KI-Chatbot Add-on: {CHATBOT_ADDON_PRICE} CHF") | |
| result = f"Kostenvoranschlag für {pkg['name']}:\n" | |
| result += "\n".join(f" • {item}" for item in breakdown) | |
| result += f"\n\nGesamtpreis: {total} CHF (zzgl. MwSt.)" | |
| result += f"\n\nInklusive: {pkg['description']}" | |
| return result | |
| # ============================================================ | |
| # TOOL 4: E-Mail senden via Resend API | |
| # Elite kann Zusammenfassungen oder Angebote per E-Mail senden. | |
| # ============================================================ | |
| async def send_email( | |
| context: RunContext, | |
| to_email: str, | |
| message: str = "", | |
| subject: str = "Informationen von Webstark", | |
| email_type: str = "custom", | |
| customer_name: str = "Kunde", | |
| ) -> str: | |
| """Sende eine E-Mail an den Kunden. | |
| Nutze dieses Tool, um dem Kunden Informationen per E-Mail zu schicken. | |
| Frage den Kunden vorher um Erlaubnis. | |
| Args: | |
| to_email: Empfänger E-Mail-Adresse | |
| message: Inhalt der E-Mail (Klartext, optional bei email_type='angebot') | |
| subject: Betreff der E-Mail | |
| email_type: Art der Email. 'angebot' für Paketübersicht, 'custom' für freien Text. | |
| customer_name: Name des Kunden für die persönliche Anrede | |
| """ | |
| resend_key = os.environ.get("RESEND_API_KEY") | |
| if not resend_key: | |
| logger.error("RESEND_API_KEY ist NICHT gesetzt!") | |
| raise ToolError("E-Mail-Versand nicht möglich: API-Key fehlt.") | |
| if "@" not in to_email: | |
| raise ToolError("Die E-Mail-Adresse scheint ungültig zu sein.") | |
| # Unterbrechungen verbieten – E-Mail wird gesendet | |
| context.disallow_interruptions() | |
| # Email-Template basierend auf Typ auswählen | |
| if email_type == "angebot": | |
| html_body = build_package_overview_email(customer_name) | |
| if subject == "Informationen von Webstark": | |
| subject = "Deine Webstark Paketübersicht" | |
| else: | |
| html_body = build_custom_email(message) | |
| logger.info(f"E-Mail-Versand: an={to_email}, typ={email_type}") | |
| try: | |
| async with aiohttp.ClientSession() as session: | |
| async with session.post( | |
| "https://api.resend.com/emails", | |
| headers={ | |
| "Authorization": f"Bearer {resend_key}", | |
| "Content-Type": "application/json", | |
| "Accept-Encoding": "identity", | |
| }, | |
| json={ | |
| "from": "Elite <elite@webstark.org>", | |
| "to": [to_email], | |
| "subject": subject, | |
| "html": html_body, | |
| }, | |
| timeout=aiohttp.ClientTimeout(total=10), | |
| ) as resp: | |
| if resp.status in (200, 201): | |
| logger.info(f"✅ E-Mail gesendet an {to_email}: {subject}") | |
| return f"E-Mail wurde erfolgreich an {to_email} gesendet." | |
| else: | |
| error_text = await resp.text() | |
| logger.error(f"❌ Resend HTTP {resp.status}: {error_text}") | |
| raise ToolError(f"E-Mail fehlgeschlagen (HTTP {resp.status}): {error_text}") | |
| except aiohttp.ClientError as e: | |
| logger.error(f"❌ Netzwerkfehler: {e}") | |
| raise ToolError(f"E-Mail fehlgeschlagen (Netzwerk): {e}") | |
| # ============================================================ | |
| # TOOL 5: Termin-Link vorschlagen | |
| # Gibt dem Kunden einen direkten Buchungslink. | |
| # ============================================================ | |
| async def suggest_appointment( | |
| context: RunContext, | |
| topic: str, | |
| ) -> str: | |
| """Schlage dem Kunden einen Beratungstermin vor. | |
| Nutze dieses Tool, wenn der Kunde ein tiefergehendes Gespräch möchte, | |
| eine komplexe Anfrage hat, oder du das Enterprise-Paket empfiehlst. | |
| Args: | |
| topic: Worum geht es im Termin (z.B. 'SEO-Beratung', 'Webdesign-Projekt', 'Enterprise-Angebot') | |
| """ | |
| # Termin-Link (anpassbar – Cal.com, Calendly, etc.) | |
| booking_url = os.environ.get("BOOKING_URL", "https://webstark.org/kontakt") | |
| return ( | |
| f"Gerne! Für eine ausführliche Beratung zum Thema '{topic}' " | |
| f"kannst du direkt einen Termin buchen:\n\n" | |
| f"📅 Termin buchen: {booking_url}\n\n" | |
| f"Alternativ erreichst du uns auch unter icarus.mod56@gmail.com." | |
| ) | |
| # ============================================================ | |
| # TOOL 6: FAQ-Wissensbasis durchsuchen | |
| # Lokale Wissensbasis für häufige Fragen (ohne API-Kosten). | |
| # ============================================================ | |
| # FAQ-Datenbank – schnelle Antworten ohne LLM-Kosten | |
| FAQ_DATABASE = [ | |
| { | |
| "keywords": ["dauer", "wie lange", "zeitraum", "fertig"], | |
| "question": "Wie lange dauert ein Website-Projekt?", | |
| "answer": "Ein Starter-Projekt ist in ca. 1-2 Wochen fertig. Professional-Projekte dauern 3-5 Wochen, Enterprise-Projekte werden individuell geplant." | |
| }, | |
| { | |
| "keywords": ["zahlung", "bezahlen", "rechnung", "anzahlung"], | |
| "question": "Wie läuft die Zahlung?", | |
| "answer": "Wir arbeiten mit 50% Anzahlung bei Projektstart und 50% bei Fertigstellung. Ratenzahlung ist bei grösseren Projekten möglich." | |
| }, | |
| { | |
| "keywords": ["hosting", "server", "domain", "online"], | |
| "question": "Ist Hosting inklusive?", | |
| "answer": "Hosting wird separat berechnet. Wir empfehlen Vercel (kostenlos für kleine Projekte) oder managed Hosting ab 15 CHF/Monat. Die Domain-Registrierung können wir ebenfalls übernehmen." | |
| }, | |
| { | |
| "keywords": ["änderung", "revision", "korrektur", "anpassen"], | |
| "question": "Wie viele Änderungen sind inklusive?", | |
| "answer": "Im Starter-Paket sind 2 Korrekturschleifen inklusive, im Professional-Paket 5. Zusätzliche Änderungen werden zum Stundensatz von 120 CHF berechnet." | |
| }, | |
| { | |
| "keywords": ["garantie", "geld zurück", "zufriedenheit", "unzufrieden"], | |
| "question": "Gibt es eine Zufriedenheitsgarantie?", | |
| "answer": "Ja! Wir bieten eine 30-Tage Geld-zurück-Garantie. Wenn du nicht zufrieden bist, erstatten wir den vollen Betrag." | |
| }, | |
| { | |
| "keywords": ["seo", "google", "ranking", "suchmaschine", "auffindbar"], | |
| "question": "Was beinhaltet die SEO-Optimierung?", | |
| "answer": "Basis-SEO (im Starter inklusive) umfasst: Meta-Tags, Seitengeschwindigkeit, Mobile-Optimierung und strukturierte Daten. Erweitertes SEO (Professional) beinhaltet zusätzlich Keyword-Analyse, Content-Strategie und monatliches Reporting." | |
| }, | |
| { | |
| "keywords": ["chatbot", "ki", "bot", "automatisch", "chat"], | |
| "question": "Was kann der Chatbot?", | |
| "answer": "Der Basis-Chatbot (im Starter) beantwortet häufige Fragen automatisch. Der erweiterte KI-Support (Professional) lernt aus Gesprächen und kann komplexere Anfragen bearbeiten." | |
| }, | |
| { | |
| "keywords": ["kontakt", "erreichen", "email", "telefon", "support"], | |
| "question": "Wie erreiche ich Webstark?", | |
| "answer": "Du erreichst uns per E-Mail unter icarus.mod56@gmail.com oder über den Live-Chat auf webstark.org. Telefon-Support ist aktuell nicht verfügbar." | |
| }, | |
| ] | |
| async def lookup_faq(context: RunContext, question: str) -> str: | |
| """Suche in der Webstark FAQ-Wissensbasis nach einer Antwort. | |
| Nutze dieses Tool ZUERST, bevor du search_web verwendest. | |
| Es enthält häufig gestellte Fragen zu Preisen, Abläufen und Services. | |
| Args: | |
| question: Die Frage des Kunden, z.B. 'Wie lange dauert ein Projekt?' | |
| """ | |
| question_lower = question.lower() | |
| # Relevanz-Score berechnen basierend auf Keyword-Matches | |
| best_match = None | |
| best_score = 0 | |
| for faq in FAQ_DATABASE: | |
| score = sum(1 for kw in faq["keywords"] if kw in question_lower) | |
| if score > best_score: | |
| best_score = score | |
| best_match = faq | |
| if best_match and best_score > 0: | |
| return f"FAQ: {best_match['question']}\n\n{best_match['answer']}" | |
| return "Keine passende FAQ gefunden. Nutze search_web für eine Internet-Recherche oder verweise auf icarus.mod56@gmail.com." | |
| # ============================================================ | |
| # TOOL 7: Gesprächs-Zusammenfassung erstellen | |
| # Erstellt eine strukturierte Zusammenfassung am Ende des Gesprächs. | |
| # ============================================================ | |
| async def create_conversation_summary( | |
| context: RunContext, | |
| customer_name: str, | |
| topics_discussed: str, | |
| action_items: str, | |
| interest_level: str = "mittel", | |
| customer_email: str = "", | |
| ) -> str: | |
| """Erstelle eine Zusammenfassung des Gesprächs. | |
| Nutze dieses Tool am Ende eines Gesprächs oder wenn der Kunde | |
| sich verabschiedet, um alle wichtigen Punkte festzuhalten. | |
| Wenn die E-Mail des Kunden bekannt ist, wird automatisch eine | |
| Danke-Email mit Zusammenfassung gesendet. | |
| Args: | |
| customer_name: Name des Kunden (oder 'Unbekannt') | |
| topics_discussed: Komma-getrennte Liste der besprochenen Themen | |
| action_items: Nächste Schritte oder offene Aufgaben | |
| interest_level: Wie interessiert ist der Kunde? ('hoch', 'mittel', 'niedrig') | |
| customer_email: E-Mail des Kunden (falls bekannt, für Danke-Email) | |
| """ | |
| context.disallow_interruptions() | |
| summary = { | |
| "customer_name": customer_name, | |
| "timestamp": datetime.utcnow().isoformat(), | |
| "topics": topics_discussed, | |
| "action_items": action_items, | |
| "interest_level": interest_level, | |
| "customer_email": customer_email, | |
| } | |
| # Zusammenfassung speichern | |
| summaries_dir = os.path.join(os.path.dirname(__file__), "summaries") | |
| os.makedirs(summaries_dir, exist_ok=True) | |
| timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S") | |
| filename = f"summary_{timestamp}.json" | |
| filepath = os.path.join(summaries_dir, filename) | |
| with open(filepath, "w", encoding="utf-8") as f: | |
| json.dump(summary, f, ensure_ascii=False, indent=2) | |
| logger.info(f"Gesprächs-Zusammenfassung gespeichert: {filepath}") | |
| # Developer-Briefing an Webstark senden | |
| await _notify_summary(summary) | |
| # Automatische Danke-Email an den Kunden (falls Email bekannt) | |
| if customer_email and "@" in customer_email: | |
| await _send_thankyou_email(customer_email, customer_name, topics_discussed, action_items) | |
| result = ( | |
| f"Zusammenfassung gespeichert!\n" | |
| f"Kunde: {customer_name}\n" | |
| f"Themen: {topics_discussed}\n" | |
| f"Nächste Schritte: {action_items}\n" | |
| f"Interesse: {interest_level}" | |
| ) | |
| if customer_email: | |
| result += f"\nDanke-Email gesendet an: {customer_email}" | |
| return result | |
| # ============================================================ | |
| # INTERNE HILFSFUNKTIONEN (nicht als Tool exponiert) | |
| # ============================================================ | |
| async def _notify_new_lead(lead_data: dict) -> None: | |
| """Sendet eine Benachrichtigungs-E-Mail an Webstark bei neuem Lead.""" | |
| resend_key = os.environ.get("RESEND_API_KEY") | |
| notify_email = os.environ.get("NOTIFY_EMAIL", "icarus.mod56@gmail.com") | |
| if not resend_key: | |
| logger.info("Kein RESEND_API_KEY – Lead-Benachrichtigung übersprungen.") | |
| return | |
| resend_from_name = os.environ.get("RESEND_FROM_NAME", "Elite") | |
| resend_from_email = os.environ.get("RESEND_FROM_EMAIL", "elite@webstark.org") | |
| try: | |
| async with aiohttp.ClientSession() as session: | |
| await session.post( | |
| "https://api.resend.com/emails", | |
| headers={ | |
| "Authorization": f"Bearer {resend_key}", | |
| "Content-Type": "application/json", | |
| "Accept-Encoding": "identity", | |
| }, | |
| json={ | |
| "from": "Elite <elite@webstark.org>", | |
| "to": [notify_email], | |
| "subject": f"🔥 Neuer Lead: {lead_data['name']} – {lead_data['interest']}", | |
| "html": f""" | |
| <h2>Neuer Lead von Elite</h2> | |
| <p><b>Name:</b> {lead_data['name']}</p> | |
| <p><b>E-Mail:</b> {lead_data['email']}</p> | |
| <p><b>Telefon:</b> {lead_data.get('phone', '-')}</p> | |
| <p><b>Interesse:</b> {lead_data['interest']}</p> | |
| <p><b>Zeitpunkt:</b> {lead_data['created_at']}</p> | |
| """, | |
| }, | |
| timeout=aiohttp.ClientTimeout(total=10), | |
| ) | |
| logger.info(f"Lead-Benachrichtigung gesendet an {notify_email}") | |
| except Exception as e: | |
| logger.warning(f"Lead-Benachrichtigung fehlgeschlagen: {e}") | |
| async def _notify_summary(summary: dict) -> None: | |
| """Sendet ein detailliertes Developer-Briefing per E-Mail an Webstark.""" | |
| resend_key = os.environ.get("RESEND_API_KEY") | |
| notify_email = os.environ.get("NOTIFY_EMAIL", "icarus.mod56@gmail.com") | |
| if not resend_key: | |
| return | |
| try: | |
| # Professionelles Briefing-Template verwenden | |
| html_body = build_developer_briefing(summary) | |
| async with aiohttp.ClientSession() as session: | |
| await session.post( | |
| "https://api.resend.com/emails", | |
| headers={ | |
| "Authorization": f"Bearer {resend_key}", | |
| "Content-Type": "application/json", | |
| "Accept-Encoding": "identity", | |
| }, | |
| json={ | |
| "from": "Elite <elite@webstark.org>", | |
| "to": [notify_email], | |
| "subject": f"📊 Briefing: {summary['customer_name']} ({summary.get('interest_level', 'mittel')})", | |
| "html": html_body, | |
| }, | |
| timeout=aiohttp.ClientTimeout(total=10), | |
| ) | |
| logger.info(f"Developer-Briefing gesendet an {notify_email}") | |
| except Exception as e: | |
| logger.warning(f"Developer-Briefing fehlgeschlagen: {e}") | |
| async def _send_thankyou_email(to_email: str, name: str, topics: str, next_steps: str) -> None: | |
| """Sendet eine automatische Danke-Email an den Kunden nach dem Gespräch.""" | |
| resend_key = os.environ.get("RESEND_API_KEY") | |
| if not resend_key: | |
| return | |
| try: | |
| html_body = build_thankyou_email(name, topics, next_steps) | |
| async with aiohttp.ClientSession() as session: | |
| await session.post( | |
| "https://api.resend.com/emails", | |
| headers={ | |
| "Authorization": f"Bearer {resend_key}", | |
| "Content-Type": "application/json", | |
| "Accept-Encoding": "identity", | |
| }, | |
| json={ | |
| "from": "Elite <elite@webstark.org>", | |
| "to": [to_email], | |
| "subject": f"Danke für dein Interesse, {name}! – Webstark", | |
| "html": html_body, | |
| }, | |
| timeout=aiohttp.ClientTimeout(total=10), | |
| ) | |
| logger.info(f"Danke-Email gesendet an {to_email}") | |
| except Exception as e: | |
| logger.warning(f"Danke-Email fehlgeschlagen: {e}") | |
| # Alle Tools als Liste exportieren (wird in agent.py importiert) | |
| ALL_TOOLS = [ | |
| search_web, | |
| save_lead, | |
| calculate_quote, | |
| send_email, | |
| suggest_appointment, | |
| lookup_faq, | |
| create_conversation_summary, | |
| ] | |