aktueller stand
This commit is contained in:
630
src/agents/researcher.py
Normal file
630
src/agents/researcher.py
Normal file
@@ -0,0 +1,630 @@
|
||||
"""Research agent using Perplexity."""
|
||||
import json
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any, List
|
||||
from loguru import logger
|
||||
|
||||
from src.agents.base import BaseAgent
|
||||
|
||||
|
||||
class ResearchAgent(BaseAgent):
|
||||
"""Agent for researching new content topics using Perplexity."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize research agent."""
|
||||
super().__init__("Researcher")
|
||||
|
||||
async def process(
|
||||
self,
|
||||
profile_analysis: Dict[str, Any],
|
||||
existing_topics: List[str],
|
||||
customer_data: Dict[str, Any],
|
||||
example_posts: List[str] = None,
|
||||
post_type: Any = None,
|
||||
post_type_analysis: Dict[str, Any] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Research new content topics.
|
||||
|
||||
Args:
|
||||
profile_analysis: Profile analysis results
|
||||
existing_topics: List of already covered topics
|
||||
customer_data: Customer data (contains persona, style_guide, etc.)
|
||||
example_posts: List of the person's actual posts for style reference
|
||||
post_type: Optional PostType object for targeted research
|
||||
post_type_analysis: Optional post type analysis for context
|
||||
|
||||
Returns:
|
||||
Research results with suggested topics
|
||||
"""
|
||||
logger.info("Starting research for new content topics")
|
||||
if post_type:
|
||||
logger.info(f"Targeting research for post type: {post_type.name}")
|
||||
|
||||
# Extract key information from profile analysis
|
||||
audience_insights = profile_analysis.get("audience_insights", {})
|
||||
topic_patterns = profile_analysis.get("topic_patterns", {})
|
||||
|
||||
industry = audience_insights.get("industry_context", "Business")
|
||||
target_audience = audience_insights.get("target_audience", "Professionals")
|
||||
content_pillars = topic_patterns.get("content_pillars", [])
|
||||
pain_points = audience_insights.get("pain_points_addressed", [])
|
||||
value_proposition = audience_insights.get("value_proposition", "")
|
||||
|
||||
# Extract customer-specific data
|
||||
persona = customer_data.get("persona", "") if customer_data else ""
|
||||
|
||||
# STEP 1: Use Perplexity for REAL internet research (has live data!)
|
||||
logger.info("Step 1: Researching with Perplexity (live internet data)")
|
||||
perplexity_prompt = self._get_perplexity_prompt(
|
||||
industry=industry,
|
||||
target_audience=target_audience,
|
||||
content_pillars=content_pillars,
|
||||
existing_topics=existing_topics,
|
||||
pain_points=pain_points,
|
||||
persona=persona
|
||||
)
|
||||
|
||||
# Dynamic system prompt for variety
|
||||
system_prompts = [
|
||||
"Du bist ein investigativer Journalist. Finde die neuesten, spannendsten Entwicklungen mit harten Fakten.",
|
||||
"Du bist ein Branchen-Analyst. Identifiziere aktuelle Trends und Marktbewegungen mit konkreten Daten.",
|
||||
"Du bist ein Trend-Scout. Spüre auf, was diese Woche wirklich neu und relevant ist.",
|
||||
"Du bist ein Research-Spezialist. Finde aktuelle Studien, Statistiken und News mit Quellenangaben."
|
||||
]
|
||||
|
||||
raw_research = await self.call_perplexity(
|
||||
system_prompt=random.choice(system_prompts),
|
||||
user_prompt=perplexity_prompt,
|
||||
model="sonar-pro"
|
||||
)
|
||||
|
||||
logger.info("Step 2: Transforming research into personalized topic ideas")
|
||||
# STEP 2: Transform raw research into PERSONALIZED topic suggestions
|
||||
transform_prompt = self._get_transform_prompt(
|
||||
raw_research=raw_research,
|
||||
target_audience=target_audience,
|
||||
persona=persona,
|
||||
content_pillars=content_pillars,
|
||||
example_posts=example_posts or [],
|
||||
existing_topics=existing_topics,
|
||||
post_type=post_type,
|
||||
post_type_analysis=post_type_analysis
|
||||
)
|
||||
|
||||
response = await self.call_openai(
|
||||
system_prompt=self._get_topic_creator_system_prompt(),
|
||||
user_prompt=transform_prompt,
|
||||
model="gpt-4o",
|
||||
temperature=0.7, # Higher for creative topic angles
|
||||
response_format={"type": "json_object"}
|
||||
)
|
||||
|
||||
# Parse JSON response
|
||||
result = json.loads(response)
|
||||
suggested_topics = result.get("topics", [])
|
||||
|
||||
# STEP 3: Ensure diversity - filter out similar topics
|
||||
suggested_topics = self._ensure_diversity(suggested_topics)
|
||||
|
||||
# Parse research results
|
||||
research_results = {
|
||||
"raw_response": response,
|
||||
"suggested_topics": suggested_topics,
|
||||
"industry": industry,
|
||||
"target_audience": target_audience
|
||||
}
|
||||
|
||||
logger.info(f"Research completed with {len(research_results['suggested_topics'])} topic suggestions")
|
||||
return research_results
|
||||
|
||||
def _get_topic_creator_system_prompt(self) -> str:
|
||||
"""Get system prompt for transforming research into personalized topics."""
|
||||
return """Du bist ein LinkedIn Content-Stratege, der aus Recherche-Ergebnissen KONKRETE, PERSONALISIERTE Themenvorschläge erstellt.
|
||||
|
||||
WICHTIG: Du erstellst KEINE Schlagzeilen oder News-Titel!
|
||||
Du erstellst KONKRETE CONTENT-IDEEN mit:
|
||||
- Einem klaren ANGLE (Perspektive/Blickwinkel)
|
||||
- Einer konkreten HOOK-IDEE
|
||||
- Einem NARRATIV das die Person erzählen könnte
|
||||
|
||||
Der Unterschied:
|
||||
❌ SCHLECHT (Schlagzeile): "KI verändert den Arbeitsmarkt"
|
||||
✅ GUT (Themenvorschlag): "Warum ich als [Rolle] plötzlich 50% meiner Zeit mit KI-Prompts verbringe - und was das für mein Team bedeutet"
|
||||
|
||||
❌ SCHLECHT: "Neue Studie zu Remote Work"
|
||||
✅ GUT: "3 Erkenntnisse aus der Stanford Remote-Studie, die mich als Führungskraft überrascht haben"
|
||||
|
||||
❌ SCHLECHT: "Fachkräftemangel in der IT"
|
||||
✅ GUT: "Unpopuläre Meinung: Wir haben keinen Fachkräftemangel - wir haben ein Ausbildungsproblem. Hier ist was ich damit meine..."
|
||||
|
||||
Deine Themenvorschläge müssen:
|
||||
1. ZUR PERSON PASSEN - Klingt wie etwas das diese spezifische Person posten würde
|
||||
2. EINEN KONKRETEN ANGLE HABEN - Nicht "über X schreiben" sondern "diesen spezifischen Aspekt von X aus dieser Perspektive beleuchten"
|
||||
3. EINEN HOOK VORSCHLAGEN - Eine konkrete Idee wie der Post starten könnte
|
||||
4. HINTERGRUND-INFOS LIEFERN - Fakten/Daten aus der Recherche die die Person nutzen kann
|
||||
5. ABWECHSLUNGSREICH SEIN - Verschiedene Formate und Kategorien
|
||||
|
||||
Antworte als JSON."""
|
||||
|
||||
def _get_system_prompt(self) -> str:
|
||||
"""Get system prompt for research (legacy, kept for compatibility)."""
|
||||
return """Du bist ein hochspezialisierter Trend-Analyst und Content-Researcher.
|
||||
|
||||
Deine Mission ist es, aktuelle, hochrelevante Content-Themen für LinkedIn zu identifizieren.
|
||||
|
||||
Du sollst:
|
||||
1. Aktuelle Trends, News und Diskussionen der letzten 7-14 Tage recherchieren
|
||||
2. Themen finden, die für die spezifische Zielgruppe relevant sind
|
||||
3. Verschiedene Kategorien abdecken:
|
||||
- Aktuelle News & Studien
|
||||
- Schmerzpunkt-Lösungen
|
||||
- Konträre Trends (gegen Mainstream-Meinung)
|
||||
- Emerging Topics
|
||||
|
||||
Für jedes Thema sollst du bereitstellen:
|
||||
- Einen prägnanten Titel
|
||||
- Den Kern-Fakt (mit Daten, Quellen, Beispielen)
|
||||
- Warum es relevant ist für die Zielgruppe
|
||||
- Die Kategorie
|
||||
|
||||
Fokussiere dich auf Themen, die:
|
||||
- AKTUELL sind (letzte 1-2 Wochen)
|
||||
- KONKRET sind (mit Daten/Fakten belegt)
|
||||
- RELEVANT sind für die Zielgruppe
|
||||
- UNIQUE sind (nicht bereits behandelt)
|
||||
|
||||
Gib deine Antwort als JSON zurück."""
|
||||
|
||||
def _get_user_prompt(
|
||||
self,
|
||||
industry: str,
|
||||
target_audience: str,
|
||||
content_pillars: List[str],
|
||||
existing_topics: List[str],
|
||||
pain_points: List[str] = None,
|
||||
value_proposition: str = "",
|
||||
persona: str = ""
|
||||
) -> str:
|
||||
"""Get user prompt for research."""
|
||||
pillars_text = ", ".join(content_pillars) if content_pillars else "Verschiedene Business-Themen"
|
||||
existing_text = ", ".join(existing_topics[:20]) if existing_topics else "Keine"
|
||||
pain_points_text = ", ".join(pain_points) if pain_points else "Nicht spezifiziert"
|
||||
|
||||
# Build persona section if available
|
||||
persona_section = ""
|
||||
if persona:
|
||||
persona_section = f"""
|
||||
**PERSONA DER PERSON (WICHTIG - Themen müssen zu dieser Expertise passen!):**
|
||||
{persona[:800]}
|
||||
"""
|
||||
|
||||
return f"""Recherchiere aktuelle LinkedIn-Content-Themen für folgendes Profil:
|
||||
|
||||
**KONTEXT:**
|
||||
- Branche: {industry}
|
||||
- Zielgruppe: {target_audience}
|
||||
- Content-Säulen: {pillars_text}
|
||||
- Pain Points der Zielgruppe: {pain_points_text}
|
||||
- Value Proposition: {value_proposition or 'Mehrwert für die Zielgruppe bieten'}
|
||||
{persona_section}
|
||||
**BEREITS BEHANDELTE THEMEN (diese NICHT vorschlagen):**
|
||||
{existing_text}
|
||||
|
||||
**AUFGABE:**
|
||||
Finde 5-7 verschiedene aktuelle Themen, die:
|
||||
1. ZUR EXPERTISE/PERSONA der Person passen
|
||||
2. Die PAIN POINTS der Zielgruppe addressieren
|
||||
3. AUTHENTISCH von dieser Person kommen könnten
|
||||
4. NICHT generisch oder beliebig sind
|
||||
|
||||
Kategorien:
|
||||
1. **News-Flash**: Aktuelle Nachrichten, Studien oder Entwicklungen
|
||||
2. **Schmerzpunkt-Löser**: Probleme/Diskussionen, die die Zielgruppe aktuell beschäftigen
|
||||
3. **Konträrer Trend**: Entwicklungen, die gegen die herkömmliche Meinung verstoßen
|
||||
4. **Emerging Topic**: Neue Trends, die gerade an Fahrt gewinnen
|
||||
|
||||
WICHTIG: Themen müssen zur Person passen! Ein Experte für {industry} würde keine generischen "Productivity-Tips" posten, sondern spezifische Insights aus seinem Fachgebiet.
|
||||
|
||||
Fokus auf deutsche/DACH-Region relevante Themen.
|
||||
|
||||
Gib deine Antwort im folgenden JSON-Format zurück:
|
||||
|
||||
{{
|
||||
"topics": [
|
||||
{{
|
||||
"title": "Prägnanter Arbeitstitel (spezifisch, nicht generisch!)",
|
||||
"category": "News-Flash / Schmerzpunkt-Löser / Konträrer Trend / Emerging Topic",
|
||||
"fact": "Detaillierte Zusammenfassung mit Daten, Fakten, Beispielen - SPEZIFISCH für diese Branche",
|
||||
"relevance": "Warum ist das für {target_audience} wichtig und warum sollte DIESE Person darüber schreiben?",
|
||||
"source": "Quellenangaben (Studien, Artikel, Statistiken)"
|
||||
}}
|
||||
]
|
||||
}}"""
|
||||
|
||||
def _get_perplexity_prompt(
|
||||
self,
|
||||
industry: str,
|
||||
target_audience: str,
|
||||
content_pillars: List[str],
|
||||
existing_topics: List[str],
|
||||
pain_points: List[str] = None,
|
||||
persona: str = ""
|
||||
) -> str:
|
||||
"""Get prompt for Perplexity research (optimized for live internet search)."""
|
||||
pillars_text = ", ".join(content_pillars) if content_pillars else "Business-Themen"
|
||||
existing_text = ", ".join(existing_topics[:20]) if existing_topics else "Keine bisherigen Themen"
|
||||
pain_points_text = ", ".join(pain_points) if pain_points else "Allgemeine Business-Probleme"
|
||||
|
||||
# Current date for time-specific searches
|
||||
today = datetime.now()
|
||||
date_str = today.strftime("%d. %B %Y")
|
||||
week_ago = (today - timedelta(days=7)).strftime("%d. %B %Y")
|
||||
|
||||
persona_hint = ""
|
||||
if persona:
|
||||
persona_hint = f"\nEXPERTISE DER PERSON: {persona[:600]}\n"
|
||||
|
||||
# Randomize the research focus for variety
|
||||
research_angles = [
|
||||
{
|
||||
"name": "Breaking News & Studien",
|
||||
"focus": "Suche nach brandneuen Studien, Reports, Umfragen oder Nachrichten",
|
||||
"examples": "Neue Statistiken, Forschungsergebnisse, Unternehmens-Announcements"
|
||||
},
|
||||
{
|
||||
"name": "Kontroverse & Debatten",
|
||||
"focus": "Suche nach aktuellen Kontroversen, Meinungsverschiedenheiten, heißen Diskussionen",
|
||||
"examples": "Polarisierende Meinungen, Kritik an Trends, unerwartete Entwicklungen"
|
||||
},
|
||||
{
|
||||
"name": "Technologie & Innovation",
|
||||
"focus": "Suche nach neuen Tools, Technologien, Methoden die gerade aufkommen",
|
||||
"examples": "Neue Software, AI-Entwicklungen, Prozess-Innovationen"
|
||||
},
|
||||
{
|
||||
"name": "Markt & Wirtschaft",
|
||||
"focus": "Suche nach wirtschaftlichen Entwicklungen, Marktveränderungen, Branchen-Shifts",
|
||||
"examples": "Fusionen, Insolvenzen, Markteintritt, Regulierungen"
|
||||
},
|
||||
{
|
||||
"name": "Menschen & Karriere",
|
||||
"focus": "Suche nach Personalien, Karriere-Trends, Arbeitsmarkt-Entwicklungen",
|
||||
"examples": "Führungswechsel, Hiring-Trends, Remote Work Updates, Skill-Demands"
|
||||
},
|
||||
{
|
||||
"name": "Fails & Learnings",
|
||||
"focus": "Suche nach öffentlichen Fehlern, Shitstorms, Lessons Learned",
|
||||
"examples": "PR-Desaster, gescheiterte Launches, öffentliche Kritik"
|
||||
}
|
||||
]
|
||||
|
||||
# Pick 3-4 random angles for this research session
|
||||
selected_angles = random.sample(research_angles, min(4, len(research_angles)))
|
||||
angles_text = "\n".join([
|
||||
f"- **{angle['name']}**: {angle['focus']} (z.B. {angle['examples']})"
|
||||
for angle in selected_angles
|
||||
])
|
||||
|
||||
# Random seed words for more variety
|
||||
seed_variations = [
|
||||
f"Was ist DIESE WOCHE ({week_ago} bis {date_str}) passiert in {industry}?",
|
||||
f"Welche BREAKING NEWS gibt es HEUTE ({date_str}) oder diese Woche in {industry}?",
|
||||
f"Was diskutiert die {industry}-Branche AKTUELL ({date_str})?",
|
||||
f"Welche NEUEN Entwicklungen gibt es seit {week_ago} in {industry}?"
|
||||
]
|
||||
seed_question = random.choice(seed_variations)
|
||||
|
||||
return f"""AKTUELLES DATUM: {date_str}
|
||||
|
||||
{seed_question}
|
||||
{persona_hint}
|
||||
KONTEXT:
|
||||
- Branche: {industry}
|
||||
- Zielgruppe: {target_audience}
|
||||
- Themen-Fokus: {pillars_text}
|
||||
- Pain Points: {pain_points_text}
|
||||
|
||||
RECHERCHE-SCHWERPUNKTE FÜR DIESE SESSION:
|
||||
{angles_text}
|
||||
|
||||
⛔ BEREITS BEHANDELTE THEMEN - NICHT NOCHMAL VORSCHLAGEN:
|
||||
{existing_text}
|
||||
|
||||
=== DEINE AUFGABE ===
|
||||
|
||||
Recherchiere FAKTEN, DATEN und ENTWICKLUNGEN - keine fertigen Themenvorschläge!
|
||||
Ich brauche ROHDATEN die ich dann in personalisierte Content-Ideen umwandeln kann.
|
||||
|
||||
Für jede Entwicklung/News sammle:
|
||||
1. **Was genau ist passiert?** - Konkrete Fakten, nicht Interpretationen
|
||||
2. **Zahlen & Daten** - Statistiken, Prozentsätze, Beträge, Veränderungen
|
||||
3. **Wer ist beteiligt?** - Unternehmen, Personen, Organisationen
|
||||
4. **Wann?** - Genaues Datum oder Zeitraum
|
||||
5. **Quelle** - URL oder Publikationsname
|
||||
6. **Kontext** - Warum ist das relevant? Was bedeutet es?
|
||||
|
||||
SUCHE NACH:
|
||||
✅ Neue Studien/Reports mit konkreten Zahlen
|
||||
✅ Unternehmens-Entscheidungen oder -Ankündigungen
|
||||
✅ Marktveränderungen mit Daten
|
||||
✅ Gesetzliche/Regulatorische Änderungen
|
||||
✅ Kontroverse Aussagen von Branchenführern
|
||||
✅ Überraschende Statistiken oder Trends
|
||||
✅ Gescheiterte Projekte oder unerwartete Erfolge
|
||||
|
||||
FORMAT DEINER ANTWORT:
|
||||
Liefere 8-10 verschiedene Entwicklungen/News mit möglichst vielen Fakten und Zahlen.
|
||||
Formatiere sie klar und strukturiert.
|
||||
|
||||
QUALITÄTSKRITERIEN:
|
||||
✅ AKTUALITÄT: Von dieser Woche oder letzter Woche
|
||||
✅ KONKRETHEIT: Echte Zahlen, Namen, Daten (nicht "Experten sagen...")
|
||||
✅ VERIFIZIERBARKEIT: Echte Quelle die man prüfen kann
|
||||
✅ BRANCHENRELEVANZ: Spezifisch für {industry}
|
||||
|
||||
❌ VERMEIDE:
|
||||
- Vage Aussagen ohne Daten ("KI wird wichtiger")
|
||||
- Generische Trends ohne konkreten Aufhänger
|
||||
- Alte News die jeder schon kennt
|
||||
- Themen ohne verifizierbare Fakten"""
|
||||
|
||||
def _get_transform_prompt(
|
||||
self,
|
||||
raw_research: str,
|
||||
target_audience: str,
|
||||
persona: str,
|
||||
content_pillars: List[str],
|
||||
example_posts: List[str],
|
||||
existing_topics: List[str],
|
||||
post_type: Any = None,
|
||||
post_type_analysis: Dict[str, Any] = None
|
||||
) -> str:
|
||||
"""Transform raw research into personalized, concrete topic suggestions."""
|
||||
|
||||
# Build example posts section
|
||||
examples_section = ""
|
||||
if example_posts:
|
||||
examples_section = "\n\n=== SO SCHREIBT DIESE PERSON (Beispiel-Posts) ===\n"
|
||||
for i, post in enumerate(example_posts[:5], 1):
|
||||
post_preview = post[:600] + "..." if len(post) > 600 else post
|
||||
examples_section += f"\n--- Beispiel {i} ---\n{post_preview}\n"
|
||||
examples_section += "--- Ende Beispiele ---\n"
|
||||
|
||||
# Build pillars section
|
||||
pillars_text = ", ".join(content_pillars[:5]) if content_pillars else "Keine spezifischen Säulen"
|
||||
|
||||
# Build existing topics section (to avoid)
|
||||
existing_text = ", ".join(existing_topics[:15]) if existing_topics else "Keine"
|
||||
|
||||
# Build post type context section
|
||||
post_type_section = ""
|
||||
if post_type:
|
||||
post_type_section = f"""
|
||||
|
||||
=== ZIEL-POST-TYP: {post_type.name} ===
|
||||
{f"Beschreibung: {post_type.description}" if post_type.description else ""}
|
||||
{f"Typische Hashtags: {', '.join(post_type.identifying_hashtags[:5])}" if post_type.identifying_hashtags else ""}
|
||||
{f"Keywords: {', '.join(post_type.identifying_keywords[:10])}" if post_type.identifying_keywords else ""}
|
||||
"""
|
||||
if post_type.semantic_properties:
|
||||
props = post_type.semantic_properties
|
||||
if props.get("purpose"):
|
||||
post_type_section += f"Zweck: {props['purpose']}\n"
|
||||
if props.get("typical_tone"):
|
||||
post_type_section += f"Tonalität: {props['typical_tone']}\n"
|
||||
if props.get("target_audience"):
|
||||
post_type_section += f"Zielgruppe: {props['target_audience']}\n"
|
||||
|
||||
if post_type_analysis and post_type_analysis.get("sufficient_data"):
|
||||
post_type_section += "\n**Analyse-basierte Anforderungen:**\n"
|
||||
if hooks := post_type_analysis.get("hooks"):
|
||||
post_type_section += f"- Hook-Typen: {', '.join(hooks.get('hook_types', [])[:3])}\n"
|
||||
if content := post_type_analysis.get("content_focus"):
|
||||
post_type_section += f"- Hauptthemen: {', '.join(content.get('main_themes', [])[:3])}\n"
|
||||
if content.get("target_emotion"):
|
||||
post_type_section += f"- Ziel-Emotion: {content['target_emotion']}\n"
|
||||
|
||||
post_type_section += "\n**WICHTIG:** Alle Themenvorschläge müssen zu diesem Post-Typ passen!\n"
|
||||
|
||||
return f"""AUFGABE: Transformiere die Recherche-Ergebnisse in KONKRETE, PERSONALISIERTE Themenvorschläge.
|
||||
{post_type_section}
|
||||
|
||||
=== RECHERCHE-ERGEBNISSE (Rohdaten) ===
|
||||
{raw_research}
|
||||
|
||||
=== PERSON/EXPERTISE ===
|
||||
{persona[:800] if persona else "Keine Persona definiert"}
|
||||
|
||||
=== CONTENT-SÄULEN DER PERSON ===
|
||||
{pillars_text}
|
||||
{examples_section}
|
||||
=== BEREITS BEHANDELT (NICHT NOCHMAL!) ===
|
||||
{existing_text}
|
||||
|
||||
=== DEINE AUFGABE ===
|
||||
|
||||
Erstelle 6-8 KONKRETE Themenvorschläge die:
|
||||
1. ZU DIESER PERSON PASSEN - Basierend auf Expertise und Beispiel-Posts
|
||||
2. EINEN KLAREN ANGLE HABEN - Nicht "über X schreiben" sondern eine spezifische Perspektive
|
||||
3. FAKTEN AUS DER RECHERCHE NUTZEN - Konkrete Daten/Zahlen einbauen
|
||||
4. ABWECHSLUNGSREICH SIND - Verschiedene Kategorien und Formate
|
||||
|
||||
KATEGORIEN (mindestens 3 verschiedene!):
|
||||
- **Meinung/Take**: Deine Perspektive zu einem aktuellen Thema
|
||||
- **Erfahrungsbericht**: "Was ich gelernt habe als..."
|
||||
- **Konträr**: "Unpopuläre Meinung: ..."
|
||||
- **How-To/Insight**: Konkrete Tipps basierend auf Daten
|
||||
- **Story**: Persönliche Geschichte mit Business-Lesson
|
||||
- **Analyse**: Daten/Trend analysiert durch deine Expertise-Brille
|
||||
|
||||
FORMAT DER THEMENVORSCHLÄGE:
|
||||
|
||||
{{
|
||||
"topics": [
|
||||
{{
|
||||
"title": "Konkreter Thementitel (kein Schlagzeilen-Stil!)",
|
||||
"category": "Meinung/Take | Erfahrungsbericht | Konträr | How-To/Insight | Story | Analyse",
|
||||
"angle": "Der spezifische Blickwinkel/die Perspektive für diesen Post",
|
||||
"hook_idea": "Konkrete Hook-Idee die zum Post passen würde (1-2 Sätze)",
|
||||
"key_facts": ["Fakt 1 aus der Recherche", "Fakt 2 mit Zahlen", "Fakt 3"],
|
||||
"why_this_person": "Warum passt dieses Thema zu DIESER Person und ihrer Expertise?",
|
||||
"source": "Quellenangabe"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
BEISPIEL EINES GUTEN THEMENVORSCHLAGS:
|
||||
{{
|
||||
"title": "Warum ich als Tech-Lead jetzt 30% meiner Zeit mit Prompt Engineering verbringe",
|
||||
"category": "Erfahrungsbericht",
|
||||
"angle": "Persönliche Erfahrung eines Tech-Leads mit der Veränderung seiner Rolle durch KI",
|
||||
"hook_idea": "Vor einem Jahr habe ich Code geschrieben. Heute schreibe ich Prompts. Und ehrlich? Ich weiß noch nicht ob das gut oder schlecht ist.",
|
||||
"key_facts": ["GitHub Copilot wird von 92% der Entwickler genutzt (Stack Overflow 2024)", "Durchschnittliche Zeitersparnis: 55%", "Aber: Code-Review-Zeit +40%"],
|
||||
"why_this_person": "Als Tech-Lead hat die Person direkten Einblick in diese Veränderung und kann authentisch darüber berichten",
|
||||
"source": "Stack Overflow Developer Survey 2024"
|
||||
}}
|
||||
|
||||
WICHTIG:
|
||||
- Jeder Vorschlag muss sich UNTERSCHEIDEN (anderer Angle, andere Kategorie)
|
||||
- Keine generischen "Die Zukunft von X" Themen
|
||||
- Hook-Ideen müssen zum Stil der Beispiel-Posts passen!
|
||||
- Key Facts müssen aus der Recherche stammen (keine erfundenen Zahlen)"""
|
||||
|
||||
def _get_structure_prompt(
|
||||
self,
|
||||
raw_research: str,
|
||||
target_audience: str,
|
||||
persona: str = ""
|
||||
) -> str:
|
||||
"""Get prompt to structure Perplexity research into JSON (legacy)."""
|
||||
return f"""Strukturiere die folgenden Recherche-Ergebnisse in ein sauberes JSON-Format.
|
||||
|
||||
RECHERCHE-ERGEBNISSE:
|
||||
{raw_research}
|
||||
|
||||
AUFGABE:
|
||||
Extrahiere die Themen und formatiere sie als JSON. Behalte ALLE Fakten, Quellen und Details bei.
|
||||
|
||||
Gib das Ergebnis in diesem Format zurück:
|
||||
|
||||
{{
|
||||
"topics": [
|
||||
{{
|
||||
"title": "Prägnanter Titel des Themas",
|
||||
"category": "News-Flash / Schmerzpunkt-Löser / Konträrer Trend / Emerging Topic",
|
||||
"fact": "Die kompletten Fakten, Zahlen und Details aus der Recherche - NICHTS weglassen!",
|
||||
"relevance": "Warum ist das für {target_audience} wichtig?",
|
||||
"source": "Quellenangaben aus der Recherche"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
|
||||
WICHTIG:
|
||||
- Behalte ALLE Fakten und Quellen aus der Recherche
|
||||
- Erfinde NICHTS dazu
|
||||
- Wenn etwas unklar ist, lass es weg
|
||||
- Mindestens 5 Themen wenn vorhanden"""
|
||||
|
||||
def _ensure_diversity(self, topics: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Ensure topic suggestions are diverse (different categories, angles).
|
||||
|
||||
Args:
|
||||
topics: List of topic suggestions
|
||||
|
||||
Returns:
|
||||
Filtered list with diverse topics
|
||||
"""
|
||||
if len(topics) <= 3:
|
||||
return topics
|
||||
|
||||
# Track categories used
|
||||
category_counts = {}
|
||||
diverse_topics = []
|
||||
|
||||
for topic in topics:
|
||||
category = topic.get("category", "Unknown")
|
||||
|
||||
# Allow max 2 topics per category
|
||||
if category_counts.get(category, 0) < 2:
|
||||
diverse_topics.append(topic)
|
||||
category_counts[category] = category_counts.get(category, 0) + 1
|
||||
|
||||
# If we filtered too many, add back some
|
||||
if len(diverse_topics) < 5 and len(topics) >= 5:
|
||||
for topic in topics:
|
||||
if topic not in diverse_topics:
|
||||
diverse_topics.append(topic)
|
||||
if len(diverse_topics) >= 6:
|
||||
break
|
||||
|
||||
logger.info(f"Diversity check: {len(topics)} -> {len(diverse_topics)} topics, categories: {category_counts}")
|
||||
return diverse_topics
|
||||
|
||||
def _extract_topics_from_response(self, response: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Extract structured topics from Perplexity response.
|
||||
|
||||
Args:
|
||||
response: Raw response from Perplexity
|
||||
|
||||
Returns:
|
||||
List of structured topic dictionaries
|
||||
"""
|
||||
topics = []
|
||||
|
||||
# Simple parsing - split by topic markers
|
||||
sections = response.split("[TITEL]:")
|
||||
|
||||
for section in sections[1:]: # Skip first empty section
|
||||
try:
|
||||
# Extract title
|
||||
title_end = section.find("[KATEGORIE]:")
|
||||
if title_end == -1:
|
||||
title_end = section.find("\n")
|
||||
title = section[:title_end].strip()
|
||||
|
||||
# Extract category
|
||||
category = ""
|
||||
if "[KATEGORIE]:" in section:
|
||||
cat_start = section.find("[KATEGORIE]:") + len("[KATEGORIE]:")
|
||||
cat_end = section.find("[DER FAKT]:")
|
||||
if cat_end == -1:
|
||||
cat_end = section.find("\n", cat_start)
|
||||
category = section[cat_start:cat_end].strip()
|
||||
|
||||
# Extract fact
|
||||
fact = ""
|
||||
if "[DER FAKT]:" in section:
|
||||
fact_start = section.find("[DER FAKT]:") + len("[DER FAKT]:")
|
||||
fact_end = section.find("[WARUM RELEVANT]:")
|
||||
if fact_end == -1:
|
||||
fact_end = section.find("[QUELLE]:")
|
||||
if fact_end == -1:
|
||||
fact_end = len(section)
|
||||
fact = section[fact_start:fact_end].strip()
|
||||
|
||||
# Extract relevance
|
||||
relevance = ""
|
||||
if "[WARUM RELEVANT]:" in section:
|
||||
rel_start = section.find("[WARUM RELEVANT]:") + len("[WARUM RELEVANT]:")
|
||||
rel_end = section.find("[QUELLE]:")
|
||||
if rel_end == -1:
|
||||
rel_end = len(section)
|
||||
relevance = section[rel_start:rel_end].strip()
|
||||
|
||||
if title and fact:
|
||||
topics.append({
|
||||
"title": title,
|
||||
"category": category or "Allgemein",
|
||||
"fact": fact,
|
||||
"relevance": relevance,
|
||||
"source": "perplexity_research"
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to parse topic section: {e}")
|
||||
continue
|
||||
|
||||
return topics
|
||||
Reference in New Issue
Block a user