Advanced FSM Pitch Generation

- 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.
This commit is contained in:
2025-07-01 16:52:51 +00:00
parent 0b8c459ddb
commit c19288da6b

View File

@@ -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):