From 4b8e1d7e2243e31be760ff1be23ba9850f68eabf Mon Sep 17 00:00:00 2001 From: Floke Date: Tue, 1 Jul 2025 16:52:51 +0000 Subject: [PATCH] Advanced FSM Pitch Generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FEATURE: Prompt für `generate_fsm_argument` in `helpers.py` durch eine mehrstufige "Chain-of-Thought"-Anweisung ersetzt. - Die KI wird nun gezwungen, spezifische Produkte/Dienstleistungen aus dem Kontext zu extrahieren, was zu hochgradig personalisierten und weniger generischen Pitch-Sätzen führt. --- helpers.py | 141 +++++++++-------------------------------------------- 1 file changed, 24 insertions(+), 117 deletions(-) diff --git a/helpers.py b/helpers.py index 48b7dfb9..893d7d7f 100644 --- a/helpers.py +++ b/helpers.py @@ -998,6 +998,7 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg def generate_fsm_argument(company_name, crm_branche, website_summary, wiki_absatz, anzahl_ma, anzahl_techniker): """ Generiert einen maßgeschneiderten, nicht-werblichen Satz, warum das Unternehmen FSM einsetzen sollte. + Nutzt eine "Chain-of-Thought"-Anweisung für spezifischere Ergebnisse. """ logger = logging.getLogger(__name__) @@ -1008,141 +1009,47 @@ def generate_fsm_argument(company_name, crm_branche, website_summary, wiki_absat techniker_info = f"bei über {anzahl_ma} Mitarbeitern" else: techniker_info = "in einem Unternehmen Ihrer Größe" - + + # Nimm die bessere Beschreibung, wenn vorhanden + beschreibung = website_summary if website_summary and website_summary.lower() != 'k.a.' else wiki_absatz + prompt_parts = [ - "Du bist ein B2B-Kommunikationsexperte, der subtile und personalisierte Gesprächseinstiege formuliert.", - "Aufgabe: Formuliere EINEN EINZIGEN, flüssig lesbaren Satz (ca. 20-35 Wörter), der als hochpersonalisierter Einstieg in einer E-Mail an ein Unternehmen dient.", + "Du bist ein B2B-Stratege, der die verborgenen operativen Herausforderungen eines Unternehmens erkennt und präzise auf den Punkt bringt.", + "Aufgabe: Formuliere EINEN EINZIGEN, flüssig lesbaren Satz (ca. 20-35 Wörter), der als hochpersonalisierter Einstieg in einer E-Mail dient.", "Stil-Regeln:", "- Absolut NICHT werblich klingen. Keine Produktnamen, keine direkten Lösungsangebote.", - "- Formuliere es als eine nachvollziehbare Beobachtung oder eine implizite Herausforderung.", - "- Beginne den Satz natürlich, z.B. mit 'Angesichts...', 'Gerade in der...' oder 'Bei...'.", + "- Formuliere es als eine scharfsinnige Beobachtung über die operative Tätigkeit des Unternehmens.", + "- Der Satz muss spezifische Keywords aus der Unternehmensbeschreibung aufgreifen.", + + "\n--- Denkprozess (Schritt für Schritt): ---", + "1. Analysiere die untenstehenden Unternehmensdaten.", + "2. Identifiziere die **spezifischste genannte Dienstleistung oder das spezifischste Produkt** (z.B. 'Installation von Wärmepumpen', 'Wartung von Aufzügen', 'Verlegung von Glasfaser').", + "3. Leite daraus die **konkrete operative Tätigkeit** ab, die von mobilen Teams durchgeführt wird (z.B. 'Installations-Termine', 'Störungsbehebungen', 'Wartungsarbeiten').", + "4. Formuliere den finalen Satz, der diese spezifische Tätigkeit und die Personalinfo elegant verbindet.", + "\n--- Unternehmenskontext ---", f"Unternehmen: {company_name}", f"Branche: {crm_branche}", - f"Beschreibung: {website_summary if website_summary and website_summary.lower() != 'k.a.' else wiki_absatz}", + f"Beschreibung: {beschreibung}", f"Personalinfo für den Satz: {techniker_info}", - "\n--- Beispiele für den gewünschten Stil ---", - "Beispiel 1 (Stadtwerke): Angesichts des wachsenden Bedarfs an Ladeinfrastruktur ist die effiziente Koordination Ihrer mobilen Teams entscheidend für einen schnellen und bürgernahen Service.", - "Beispiel 2 (Anlagenbau): Gerade im Anlagenbau hängt die Kundenzufriedenheit direkt von der reibungslosen Abstimmung der Serviceeinsätze Ihrer Techniker ab, um Stillstandzeiten zu minimieren.", + + "\n--- Beispiele für den gewünschten Output-Stil ---", + "Beispiel 1 (Kontext: Branche 'Energie', Beschreibung '...baut Ladeinfrastruktur aus...'): Angesichts des beschleunigten Ausbaus der Ladeinfrastruktur ist die reibungslose Koordination der Installationstermine Ihrer mobilen Teams entscheidend für den Projekterfolg.", + "Beispiel 2 (Kontext: Branche 'Anlagenbau', Beschreibung '...Service für Produktionsanlagen...'): Bei der Wartung komplexer Produktionsanlagen hängt die Kundenzufriedenheit direkt von der pünktlichen und effizienten Durchführung der Serviceeinsätze ab.", + "\n--- Deine Aufgabe ---", - "Formuliere jetzt den EINEN perfekten Satz für das oben genannte Unternehmen:", + "Führe den Denkprozess durch und gib NUR den finalen, perfekten Satz für das oben genannte Unternehmen aus:", ] prompt = "\n".join(prompt_parts) try: - fsm_pitch = call_openai_chat(prompt, temperature=0.75) + fsm_pitch = call_openai_chat(prompt, temperature=0.7) return fsm_pitch.strip().replace('"', '') if fsm_pitch else "k.A. (Pitch-Generierung fehlgeschlagen)" except Exception as e: logger.error(f"Fehler bei der Generierung des FSM-Pitches für {company_name}: {e}") return "k.A. (Fehler bei Pitch-Generierung)" -# ============================================================================== -# 10. SERP API WRAPPERS (WIKIPEDIA, WEBSITE, LINKEDIN) -# ============================================================================== - -@retry_on_failure -def serp_wikipedia_lookup(company_name, website=None, min_score=0.4): - """ - Sucht ueber SerpAPI (Google) nach dem wahrscheinlichsten Wikipedia-Artikel. - Gibt die URL als String oder None bei Fehler/Nicht-Fund zurück. - """ - logger = logging.getLogger(__name__) - serp_key = Config.API_KEYS.get('serpapi') - if not serp_key: - # Kein harter Fehler mehr, nur eine Warnung und Rückgabe von None - logger.warning("SerpAPI Key nicht konfiguriert. Wikipedia-Suche via SerpAPI übersprungen.") - return None - - if not company_name or str(company_name).strip() == "": - logger.warning("serp_wikipedia_lookup: Kein Firmenname angegeben.") - return None - - query = f'{company_name} Wikipedia' - if website and simple_normalize_url(website) != "k.A.": - query = f'{company_name} Wikipedia {simple_normalize_url(website)}' - logger.info(f"Starte SerpAPI Wikipedia-Suche fuer '{company_name}' mit Query: '{query[:100]}...'") - - params = { - "engine": "google", - "q": query, - "api_key": serp_key, - "hl": "de", "gl": "de", "num": 10 - } - api_url = "https://serpapi.com/search" - - try: - response = requests.get(api_url, params=params, timeout=getattr(Config, 'REQUEST_TIMEOUT', 15)) - response.raise_for_status() - data = response.json() - - candidates = [] - if "organic_results" in data: - logger.debug(f" -> Pruefe {len(data['organic_results'])} organische Ergebnisse...") - for result in data["organic_results"]: - link = result.get("link") - if link and isinstance(link, str) and "wikipedia.org/wiki/" in link.lower() \ - and (link.lower().startswith("https://de.wikipedia.org") or link.lower().startswith("https://en.wikipedia.org")) \ - and not any(x in link.lower() for x in ['datei:', 'spezial:', 'portal:', 'hilfe:', 'diskussion:', 'template:']): - try: - title_part = link.split('/wiki/', 1)[1].split('#')[0] - title = unquote(title_part).replace('_', ' ') - candidates.append({'url': link, 'title': title}) - except Exception as e_title_extract: - logger.debug(f" -> Fehler beim Extrahieren des Titels aus Link {link[:100]}...: {e_title_extract}") - continue - - if not candidates: - logger.warning(f" -> SerpAPI: Keine de/en Wikipedia-Kandidaten-URLs in Ergebnissen fuer '{company_name}' gefunden.") - return None - - best_match_url = None - highest_score = -1.0 - normalized_search_name = normalize_company_name(company_name) - - logger.debug(f" -> Bewerte {len(candidates)} Kandidaten...") - for cand in candidates: - url, title = cand['url'], cand['title'] - try: - normalized_title = normalize_company_name(title) - title_lower = title.lower() - except Exception as e_norm: - logger.warning(f"Fehler beim Normalisieren des Titels '{title[:100]}...': {e_norm}. Ueberspringe Kandidatenbewertung.") - continue - - similarity = SequenceMatcher(None, normalized_title, normalized_search_name).ratio() - score = similarity - bonus = 0.0 - - if normalized_search_name and normalized_title and (normalized_search_name in normalized_title or normalized_title in normalized_search_name): - bonus += 0.3 - if "(unternehmen)" in title_lower: - bonus += 0.15 - elif re.search(r'\b(?:gmbh|ag|kg|ltd|inc|corp|s\.?a\.?|se|group|holding)\b$', title_lower): - bonus += 0.05 - if url.lower().startswith("https://de.wikipedia.org"): - bonus += 0.05 - - total_score = score + bonus - logger.debug(f" -> Gesamtscore fuer '{title[:100]}...': {total_score:.3f} (Aehnlichkeit={similarity:.2f}, Bonus={bonus:.2f})") - - if total_score > highest_score and total_score >= min_score: - highest_score = total_score - best_match_url = url - logger.debug(f" ====> Neuer bester Kandidat: {best_match_url[:100]}... (Score: {highest_score:.3f}) ====") - - if best_match_url: - logger.info(f" -> SerpAPI: Bester relevanter Wikipedia-Link ausgewaehlt: {best_match_url[:100]}... (Score: {highest_score:.3f})") - return best_match_url - else: - logger.warning(f" -> SerpAPI: Keiner der {len(candidates)} Kandidaten erreichte den Mindestscore ({min_score}) fuer '{company_name}'.") - return None - - except Exception as e: - logger.error(f"FEHLER bei der SerpAPI Wikipedia Suche fuer '{company_name}': {e}") - # Wir geben die Exception nicht mehr weiter, damit das Programm nicht abbricht - return None - @retry_on_failure def serp_website_lookup(company_name):