Post Typen Verwalten + Strategy weight
This commit is contained in:
@@ -184,8 +184,10 @@ class PostClassifierAgent(BaseAgent):
|
||||
|
||||
system_prompt = """Du bist ein Content-Analyst, der LinkedIn-Posts in vordefinierte Kategorien einordnet.
|
||||
|
||||
WICHTIG: JEDER Post MUSS kategorisiert werden. Du MUSST für jeden Post den am besten passenden Post-Typ wählen, auch wenn die Übereinstimmung nicht perfekt ist.
|
||||
|
||||
Analysiere jeden Post und ordne ihn dem passendsten Post-Typ zu.
|
||||
Wenn kein Typ wirklich passt, gib "null" als post_type_id zurück.
|
||||
Es ist NICHT erlaubt, "null" als post_type_id zurückzugeben.
|
||||
|
||||
Bewerte die Zuordnung mit einer Confidence zwischen 0.3 und 1.0:
|
||||
- 0.9-1.0: Sehr sicher, Post passt perfekt zum Typ
|
||||
@@ -209,12 +211,14 @@ Gib ein JSON-Objekt zurück mit diesem Format:
|
||||
"classifications": [
|
||||
{{
|
||||
"post_id": "uuid-des-posts",
|
||||
"post_type_id": "uuid-des-typs oder null",
|
||||
"post_type_id": "uuid-des-typs (IMMER erforderlich, niemals null)",
|
||||
"confidence": 0.8,
|
||||
"reasoning": "Kurze Begründung"
|
||||
}}
|
||||
]
|
||||
}}"""
|
||||
}}
|
||||
|
||||
WICHTIG: Jeder Post MUSS einen post_type_id bekommen. Wähle den am besten passenden Typ, auch wenn die Übereinstimmung nicht perfekt ist."""
|
||||
|
||||
try:
|
||||
response = await self.call_openai(
|
||||
@@ -230,6 +234,8 @@ Gib ein JSON-Objekt zurück mit diesem Format:
|
||||
|
||||
# Process and validate results
|
||||
valid_results = []
|
||||
unclassified_posts = list(posts) # Track which posts still need classification
|
||||
|
||||
for c in classifications:
|
||||
post_id = c.get("post_id")
|
||||
post_type_id = c.get("post_type_id")
|
||||
@@ -244,7 +250,8 @@ Gib ein JSON-Objekt zurück mit diesem Format:
|
||||
# Validate post_type_id
|
||||
if post_type_id and post_type_id != "null" and post_type_id not in valid_type_ids:
|
||||
logger.warning(f"Invalid post_type_id in classification: {post_type_id}")
|
||||
continue
|
||||
# Fallback: assign to first available type
|
||||
post_type_id = list(valid_type_ids - {"null"})[0] if valid_type_ids - {"null"} else None
|
||||
|
||||
if post_type_id and post_type_id != "null":
|
||||
valid_results.append({
|
||||
@@ -253,6 +260,19 @@ Gib ein JSON-Objekt zurück mit diesem Format:
|
||||
"classification_method": "semantic",
|
||||
"classification_confidence": min(1.0, max(0.3, confidence))
|
||||
})
|
||||
unclassified_posts = [p for p in unclassified_posts if p.id != matching_post.id]
|
||||
|
||||
# FORCE CLASSIFY remaining unclassified posts
|
||||
if unclassified_posts and post_types:
|
||||
default_type = post_types[0] # Use first type as fallback
|
||||
logger.warning(f"Force-classifying {len(unclassified_posts)} posts to default type: {default_type.name}")
|
||||
for post in unclassified_posts:
|
||||
valid_results.append({
|
||||
"post_id": post.id,
|
||||
"post_type_id": default_type.id,
|
||||
"classification_method": "semantic_fallback",
|
||||
"classification_confidence": 0.3
|
||||
})
|
||||
|
||||
return valid_results
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ class ResearchAgent(BaseAgent):
|
||||
customer_data: Dict[str, Any],
|
||||
example_posts: List[str] = None,
|
||||
post_type: Any = None,
|
||||
post_type_analysis: Dict[str, Any] = None
|
||||
post_type_analysis: Dict[str, Any] = None,
|
||||
company_strategy: Dict[str, Any] = None,
|
||||
strategy_weight: float = 0.5
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Research new content topics.
|
||||
@@ -34,6 +36,8 @@ class ResearchAgent(BaseAgent):
|
||||
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
|
||||
company_strategy: Optional company strategy to align topics with
|
||||
strategy_weight: Strategy weight (0.0-1.0) controlling strategy influence
|
||||
|
||||
Returns:
|
||||
Research results with suggested topics
|
||||
@@ -90,7 +94,9 @@ class ResearchAgent(BaseAgent):
|
||||
example_posts=example_posts or [],
|
||||
existing_topics=existing_topics,
|
||||
post_type=post_type,
|
||||
post_type_analysis=post_type_analysis
|
||||
post_type_analysis=post_type_analysis,
|
||||
company_strategy=company_strategy,
|
||||
strategy_weight=strategy_weight
|
||||
)
|
||||
|
||||
response = await self.call_openai(
|
||||
@@ -379,7 +385,9 @@ QUALITÄTSKRITERIEN:
|
||||
example_posts: List[str],
|
||||
existing_topics: List[str],
|
||||
post_type: Any = None,
|
||||
post_type_analysis: Dict[str, Any] = None
|
||||
post_type_analysis: Dict[str, Any] = None,
|
||||
company_strategy: Dict[str, Any] = None,
|
||||
strategy_weight: float = 0.5
|
||||
) -> str:
|
||||
"""Transform raw research into personalized, concrete topic suggestions."""
|
||||
|
||||
@@ -428,8 +436,11 @@ QUALITÄTSKRITERIEN:
|
||||
|
||||
post_type_section += "\n**WICHTIG:** Alle Themenvorschläge müssen zu diesem Post-Typ passen!\n"
|
||||
|
||||
# Build company strategy section
|
||||
strategy_section = self._get_strategy_section(company_strategy, strategy_weight)
|
||||
|
||||
return f"""AUFGABE: Transformiere die Recherche-Ergebnisse in KONKRETE, PERSONALISIERTE Themenvorschläge.
|
||||
{post_type_section}
|
||||
{post_type_section}{strategy_section}
|
||||
|
||||
=== RECHERCHE-ERGEBNISSE (Rohdaten) ===
|
||||
{raw_research}
|
||||
@@ -492,6 +503,71 @@ WICHTIG:
|
||||
- Hook-Ideen müssen zum Stil der Beispiel-Posts passen!
|
||||
- Key Facts müssen aus der Recherche stammen (keine erfundenen Zahlen)"""
|
||||
|
||||
def _get_strategy_section(self, company_strategy: Dict[str, Any] = None, strategy_weight: float = 0.5) -> str:
|
||||
"""Build company strategy section for research prompt with weighted influence."""
|
||||
if not company_strategy:
|
||||
return ""
|
||||
|
||||
# Extract strategy elements
|
||||
mission = company_strategy.get("mission", "")
|
||||
vision = company_strategy.get("vision", "")
|
||||
content_pillars = company_strategy.get("content_pillars", [])
|
||||
target_audience = company_strategy.get("target_audience", "")
|
||||
dos = company_strategy.get("dos", [])
|
||||
donts = company_strategy.get("donts", [])
|
||||
|
||||
# Build section only if there's meaningful content
|
||||
if not any([mission, vision, content_pillars, dos, donts]):
|
||||
return ""
|
||||
|
||||
# Determine importance level based on strategy_weight
|
||||
if strategy_weight >= 0.8:
|
||||
importance = "KRITISCH - STRIKT BEACHTEN"
|
||||
instruction = "Alle Themen MÜSSEN sich an der Unternehmensstrategie orientieren! Nur Topics vorschlagen, die perfekt zur Strategie passen."
|
||||
elif strategy_weight >= 0.6:
|
||||
importance = "WICHTIG - BERÜCKSICHTIGEN"
|
||||
instruction = "Priorisiere Topics die zur Unternehmensstrategie passen! Die Strategie soll klar erkennbar sein."
|
||||
elif strategy_weight >= 0.4:
|
||||
importance = "ORIENTIERUNG"
|
||||
instruction = "Berücksichtige die Unternehmensstrategie wo sinnvoll - persönliche Expertise hat Priorität."
|
||||
elif strategy_weight >= 0.2:
|
||||
importance = "OPTIONAL"
|
||||
instruction = "Die Strategie dient als Hintergrundinformation - freie Themenauswahl erlaubt."
|
||||
else:
|
||||
importance = "NUR REFERENZ"
|
||||
instruction = "Ignoriere die Strategie weitgehend - Fokus auf persönliche Themen und Expertise."
|
||||
|
||||
section = f"""
|
||||
|
||||
=== UNTERNEHMENSSTRATEGIE ({importance}) ===
|
||||
Strategy Weight: {strategy_weight:.1f} / 1.0
|
||||
{instruction}
|
||||
"""
|
||||
if mission:
|
||||
section += f"\nMISSION: {mission}"
|
||||
if vision:
|
||||
section += f"\nVISION: {vision}"
|
||||
if content_pillars:
|
||||
section += f"\nCONTENT PILLARS: {', '.join(content_pillars)}"
|
||||
if target_audience:
|
||||
section += f"\nZIELGRUPPE: {target_audience}"
|
||||
if dos:
|
||||
section += f"\n\nEMPFOHLENE THEMEN-RICHTUNGEN:\n" + "\n".join([f" + {d}" for d in dos])
|
||||
if donts:
|
||||
section += f"\n\nZU VERMEIDENDE THEMEN:\n" + "\n".join([f" - {d}" for d in donts])
|
||||
|
||||
# Weight-specific closing instruction
|
||||
if strategy_weight >= 0.8:
|
||||
section += "\n\n**KRITISCH:** Alle Topics müssen zur Unternehmensstrategie passen - keine Ausnahmen!"
|
||||
elif strategy_weight >= 0.6:
|
||||
section += "\n\n**WICHTIG:** Stelle sicher, dass die Mehrheit der Topics die Unternehmenswerte widerspiegelt!"
|
||||
elif strategy_weight >= 0.4:
|
||||
section += "\n\n**BEACHTE:** Integriere die Strategie subtil - persönliche Expertise bleibt wichtig!"
|
||||
else:
|
||||
section += "\n\n**REFERENZ:** Die Strategie dient nur als Orientierung - persönliche Topics haben Vorrang!"
|
||||
|
||||
return section + "\n"
|
||||
|
||||
def _get_structure_prompt(
|
||||
self,
|
||||
raw_research: str,
|
||||
|
||||
@@ -30,7 +30,8 @@ class WriterAgent(BaseAgent):
|
||||
post_type_analysis: Optional[Dict[str, Any]] = None,
|
||||
user_thoughts: str = "",
|
||||
selected_hook: str = "",
|
||||
company_strategy: Optional[Dict[str, Any]] = None
|
||||
company_strategy: Optional[Dict[str, Any]] = None,
|
||||
strategy_weight: float = 0.5
|
||||
) -> str:
|
||||
"""
|
||||
Write a LinkedIn post.
|
||||
@@ -46,6 +47,7 @@ class WriterAgent(BaseAgent):
|
||||
post_type: Optional PostType object for type-specific writing
|
||||
post_type_analysis: Optional analysis of the post type
|
||||
company_strategy: Optional company strategy to consider during writing
|
||||
strategy_weight: Strategy weight (0.0-1.0) controlling how strongly the company strategy influences post generation
|
||||
|
||||
Returns:
|
||||
Written LinkedIn post
|
||||
@@ -65,7 +67,8 @@ class WriterAgent(BaseAgent):
|
||||
post_type_analysis=post_type_analysis,
|
||||
user_thoughts=user_thoughts,
|
||||
selected_hook=selected_hook,
|
||||
company_strategy=company_strategy
|
||||
company_strategy=company_strategy,
|
||||
strategy_weight=strategy_weight
|
||||
)
|
||||
else:
|
||||
logger.info(f"Writing initial post for topic: {topic.get('title', 'Unknown')}")
|
||||
@@ -90,7 +93,8 @@ class WriterAgent(BaseAgent):
|
||||
post_type_analysis=post_type_analysis,
|
||||
user_thoughts=user_thoughts,
|
||||
selected_hook=selected_hook,
|
||||
company_strategy=company_strategy
|
||||
company_strategy=company_strategy,
|
||||
strategy_weight=strategy_weight
|
||||
)
|
||||
else:
|
||||
return await self._write_single_draft(
|
||||
@@ -102,7 +106,8 @@ class WriterAgent(BaseAgent):
|
||||
post_type_analysis=post_type_analysis,
|
||||
user_thoughts=user_thoughts,
|
||||
selected_hook=selected_hook,
|
||||
company_strategy=company_strategy
|
||||
company_strategy=company_strategy,
|
||||
strategy_weight=strategy_weight
|
||||
)
|
||||
|
||||
def _select_example_posts(
|
||||
@@ -236,7 +241,8 @@ class WriterAgent(BaseAgent):
|
||||
post_type_analysis: Optional[Dict[str, Any]] = None,
|
||||
user_thoughts: str = "",
|
||||
selected_hook: str = "",
|
||||
company_strategy: Optional[Dict[str, Any]] = None
|
||||
company_strategy: Optional[Dict[str, Any]] = None,
|
||||
strategy_weight: float = 0.5
|
||||
) -> str:
|
||||
"""
|
||||
Generate multiple drafts and select the best one.
|
||||
@@ -249,6 +255,7 @@ class WriterAgent(BaseAgent):
|
||||
post_type: Optional PostType object
|
||||
post_type_analysis: Optional post type analysis
|
||||
company_strategy: Optional company strategy to consider
|
||||
strategy_weight: Strategy weight (0.0-1.0) for company strategy influence
|
||||
|
||||
Returns:
|
||||
Best selected draft
|
||||
@@ -256,7 +263,7 @@ class WriterAgent(BaseAgent):
|
||||
num_drafts = min(max(settings.writer_multi_draft_count, 2), 5) # Clamp between 2-5
|
||||
logger.info(f"Generating {num_drafts} drafts for selection")
|
||||
|
||||
system_prompt = self._get_system_prompt(profile_analysis, example_posts, learned_lessons, post_type, post_type_analysis, company_strategy)
|
||||
system_prompt = self._get_system_prompt(profile_analysis, example_posts, learned_lessons, post_type, post_type_analysis, company_strategy, strategy_weight)
|
||||
|
||||
# Generate drafts in parallel with different temperatures/approaches
|
||||
draft_configs = [
|
||||
@@ -500,7 +507,8 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format:
|
||||
post_type_analysis: Optional[Dict[str, Any]] = None,
|
||||
user_thoughts: str = "",
|
||||
selected_hook: str = "",
|
||||
company_strategy: Optional[Dict[str, Any]] = None
|
||||
company_strategy: Optional[Dict[str, Any]] = None,
|
||||
strategy_weight: float = 0.5
|
||||
) -> str:
|
||||
"""Write a single draft (original behavior)."""
|
||||
# Select examples if not already selected
|
||||
@@ -515,7 +523,7 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format:
|
||||
elif len(selected_examples) > 3:
|
||||
selected_examples = random.sample(selected_examples, 3)
|
||||
|
||||
system_prompt = self._get_system_prompt(profile_analysis, selected_examples, learned_lessons, post_type, post_type_analysis, company_strategy)
|
||||
system_prompt = self._get_system_prompt(profile_analysis, selected_examples, learned_lessons, post_type, post_type_analysis, company_strategy, strategy_weight)
|
||||
user_prompt = self._get_user_prompt(topic, feedback, previous_version, critic_result, user_thoughts, selected_hook)
|
||||
|
||||
# OPTIMIERT: Niedrigere Temperature (0.5 statt 0.6) für konsistenteren Stil
|
||||
@@ -536,7 +544,8 @@ Analysiere jeden Entwurf kurz und wähle den besten. Antworte im JSON-Format:
|
||||
learned_lessons: Optional[Dict[str, Any]] = None,
|
||||
post_type: Any = None,
|
||||
post_type_analysis: Optional[Dict[str, Any]] = None,
|
||||
company_strategy: Optional[Dict[str, Any]] = None
|
||||
company_strategy: Optional[Dict[str, Any]] = None,
|
||||
strategy_weight: float = 0.5
|
||||
) -> str:
|
||||
"""Get system prompt for writer - orientiert an bewährten n8n-Prompts."""
|
||||
# Extract key profile information
|
||||
@@ -788,13 +797,13 @@ Vermeide IMMER diese KI-typischen Muster:
|
||||
- Zu perfekte, glatte Formulierungen - echte Menschen schreiben mit Ecken und Kanten
|
||||
{lessons_section}
|
||||
{post_type_section}
|
||||
{self._get_company_strategy_section(company_strategy)}
|
||||
{self._get_company_strategy_section(company_strategy, strategy_weight)}
|
||||
DEIN AUFTRAG: Schreibe den Post so, dass er für die Zielgruppe ({audience.get('target_audience', 'Professionals')}) einen klaren Mehrwert bietet und ihre Pain Points ({pain_points_str}) adressiert. Mach die Persönlichkeit des linguistischen Fingerabdrucks spürbar.
|
||||
|
||||
Beginne DIREKT mit dem Hook. Keine einleitenden Sätze, kein "Hier ist der Post"."""
|
||||
|
||||
def _get_company_strategy_section(self, company_strategy: Optional[Dict[str, Any]] = None) -> str:
|
||||
"""Build the company strategy section for the system prompt."""
|
||||
def _get_company_strategy_section(self, company_strategy: Optional[Dict[str, Any]] = None, strategy_weight: float = 0.5) -> str:
|
||||
"""Build the company strategy section for the system prompt with weighted influence."""
|
||||
if not company_strategy:
|
||||
return ""
|
||||
|
||||
@@ -812,11 +821,29 @@ Beginne DIREKT mit dem Hook. Keine einleitenden Sätze, kein "Hier ist der Post"
|
||||
if not any([mission, vision, brand_voice, content_pillars, dos, donts]):
|
||||
return ""
|
||||
|
||||
section = """
|
||||
# Determine importance level and instruction based on strategy_weight
|
||||
if strategy_weight >= 0.8:
|
||||
importance = "KRITISCH WICHTIG - STRIKT BEFOLGEN"
|
||||
instruction = "Der Post MUSS sich eng an die Unternehmensstrategie halten! Die Strategy-Guidelines haben höchste Priorität."
|
||||
elif strategy_weight >= 0.6:
|
||||
importance = "WICHTIG - BEACHTEN"
|
||||
instruction = "Integriere die Unternehmenswerte deutlich, aber bewahre Authentizität! Die Strategie soll klar erkennbar sein."
|
||||
elif strategy_weight >= 0.4:
|
||||
importance = "ALS KONTEXT BERÜCKSICHTIGEN"
|
||||
instruction = "Berücksichtige die Strategie wo sinnvoll - Persönlicher Stil hat Priorität! Subtile Integration genügt."
|
||||
elif strategy_weight >= 0.2:
|
||||
importance = "OPTIONALE ORIENTIERUNG"
|
||||
instruction = "Die Strategie dient als Hintergrundinformation - freie Interpretation erlaubt! Fokus auf Authentizität."
|
||||
else:
|
||||
importance = "NUR ALS REFERENZ"
|
||||
instruction = "Ignoriere die Strategie weitgehend - voller Fokus auf persönlichen Stil! Strategy-Guidelines sind optional."
|
||||
|
||||
8. UNTERNEHMENSSTRATEGIE (WICHTIG - IMMER BEACHTEN!):
|
||||
section = f"""
|
||||
|
||||
Der Post muss zur Unternehmensstrategie passen, aber authentisch im Stil des Autors bleiben!
|
||||
8. UNTERNEHMENSSTRATEGIE ({importance}):
|
||||
|
||||
Strategy Weight: {strategy_weight:.1f} / 1.0
|
||||
{instruction}
|
||||
"""
|
||||
if mission:
|
||||
section += f"\nMISSION: {mission}"
|
||||
@@ -835,7 +862,15 @@ Der Post muss zur Unternehmensstrategie passen, aber authentisch im Stil des Aut
|
||||
if donts:
|
||||
section += f"\n\nDON'Ts (vermeiden):\n" + "\n".join([f" - {d}" for d in donts])
|
||||
|
||||
section += "\n\nWICHTIG: Integriere die Unternehmenswerte subtil - der Post soll nicht wie eine Werbung klingen!"
|
||||
# Weight-specific closing instruction
|
||||
if strategy_weight >= 0.8:
|
||||
section += "\n\nKRITISCH: Die Unternehmensstrategie ist nicht verhandelbar - jeder Post muss diese widerspiegeln!"
|
||||
elif strategy_weight >= 0.6:
|
||||
section += "\n\nWICHTIG: Stelle sicher, dass die Unternehmenswerte deutlich erkennbar sind!"
|
||||
elif strategy_weight >= 0.4:
|
||||
section += "\n\nINTEGRIERE: Berücksichtige die Strategie subtil - authentischer Stil bleibt wichtig!"
|
||||
else:
|
||||
section += "\n\nREFERENZ: Die Strategie dient nur als Orientierung - persönlicher Stil hat Vorrang!"
|
||||
|
||||
return section
|
||||
|
||||
|
||||
Reference in New Issue
Block a user