Protokoll #11612
| ID | 11612 |
|---|---|
| Zeitstempel | 2025-12-23 20:23:31.609394 |
| Client | root |
| IP | Warning: Undefined array key "request_ip" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13 Deprecated: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13 |
| Modell | claude-sonnet-4-20250514 |
| Status | completed |
| Tokens | 13,531 (Input: 0, Output: 0) |
| Dauer | 128680 ms |
| Request-Zeit | Warning: Undefined array key "request_timestamp" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 18 |
| Response-Zeit | - |
Warning: Undefined array key "error_message" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 22
Request
{
"event": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "\/opt\/scripts\/pipeline\/generate.py",
"old_string": "def build_generation_prompt(briefing, context, profile, contract, structure=None):\n \"\"\"Build the content generation prompt.\"\"\"\n\n # Format context\n context_text = \"\"\n for i, ctx in enumerate(context, 1):\n context_text += f\"\\n[Quelle {i}: {ctx['source']}]\\n{ctx['content']}\\n\"\n\n # Build rich profile instructions\n profile_text = \"\"\n if profile:\n config = profile.get(\"config\", {})\n autorenprofil = config.get(\"autorenprofil\", config) # Support both structures\n\n # Extract voice\/stimme\n stimme = autorenprofil.get(\"stimme\", {})\n stimme_text = \"\"\n if stimme:\n stimme_text = f\"\"\"\n### Stimme\/Tonalität:\n- Ton: {stimme.get(\"ton\", \"neutral\")}\n- Perspektive: {stimme.get(\"perspektive\", \"neutral\")}\n- Komplexität: {stimme.get(\"komplexitaet\", \"mittel\")}\n- Autorität: {stimme.get(\"autoritaet\", \"Experte\")}\"\"\"\n\n # Extract style\/stil\n stil = autorenprofil.get(\"stil\", {})\n stil_text = \"\"\n if stil:\n fachsprache_beispiele = stil.get(\"fachsprache_beispiele\", [])\n fachsprache_str = \", \".join(fachsprache_beispiele[:5]) if fachsprache_beispiele else \"\"\n stil_text = f\"\"\"\n### Stil:\n- Fachsprache: {\"Ja\" if stil.get(\"fachsprache\", False) else \"Nein\"}\n- Fachbegriffe: {fachsprache_str}\n- Satzlänge: {stil.get(\"satzlaenge\", \"mittel\")}\n- Absatzstruktur: {stil.get(\"absatzstruktur\", \"klar gegliedert\")}\"\"\"\n\n # Extract language patterns\/sprachliche_muster\n muster = autorenprofil.get(\"sprachliche_muster\", {})\n muster_text = \"\"\n if muster:\n phrasen = muster.get(\"phrasen\", [])\n phrasen_str = \", \".join([f'\"{p}\"' for p in phrasen[:4]]) if phrasen else \"\"\n uebergaenge = muster.get(\"uebergaenge\", [])\n uebergaenge_str = \", \".join([f'\"{u}\"' for u in uebergaenge[:3]]) if uebergaenge else \"\"\n muster_text = f\"\"\"\n### Sprachliche Muster:\n- Typische Phrasen: {phrasen_str}\n- Übergangsformulierungen: {uebergaenge_str}\"\"\"\n\n # Extract taboos\/tabus\n tabus = autorenprofil.get(\"tabus\", [])\n tabus_text = \"\"\n if tabus:\n tabus_str = \", \".join(tabus[:5])\n tabus_text = f\"\"\"\n### Zu vermeiden (Tabus):\n{tabus_str}\"\"\"\n\n # Extract core messages\/kernbotschaften\n kernbotschaften = autorenprofil.get(\"kernbotschaften\", [])\n kern_text = \"\"\n if kernbotschaften:\n kern_str = \"\\n\".join([f\"- {k}\" for k in kernbotschaften[:4]])\n kern_text = f\"\"\"\n### Kernbotschaften (implizit vermitteln):\n{kern_str}\"\"\"\n\n # Extract output format (HTML for web profiles)\n output_format = autorenprofil.get(\"output_format\", {})\n format_text = \"\"\n if output_format and output_format.get(\"typ\") == \"html\":\n erlaubte = output_format.get(\"erlaubte_elemente\", {})\n verboten = output_format.get(\"verbotene_elemente\", [])\n regeln = output_format.get(\"struktur_regeln\", [])\n\n erlaubte_str = []\n for _kategorie, elemente in erlaubte.items():\n erlaubte_str.extend(elemente[:3])\n\n format_text = f\"\"\"\n### Output-Format: HTML\n**Erlaubte Elemente:**\n{\", \".join(erlaubte_str[:10])}\n\n**Struktur-Regeln:**\n- {chr(10).join(\"- \" + r for r in regeln[:4]) if regeln else \"Semantisches HTML verwenden\"}\n\n**Verboten:** {\", \".join(verboten[:4]) if verboten else \"div, span, br, style\"}\"\"\"\n\n # Combine all profile sections\n profile_text = f\"\"\"\n## Autorenprofil: {profile.get(\"name\", \"Standard\")}\n{stimme_text}\n{stil_text}\n{muster_text}\n{tabus_text}\n{kern_text}\n{format_text}\n\"\"\"\n\n # Build contract requirements\n contract_text = \"\"\n if contract:\n config = contract.get(\"config\", {})\n req = config.get(\"requirements\", {})\n contract_text = f\"\"\"\nContract: {contract.get(\"name\", \"Standard\")}\n- Wortanzahl: {req.get(\"min_words\", 500)} - {req.get(\"max_words\", 5000)} Wörter\n- Struktur-Validierung: {req.get(\"structure_validation\", True)}\n\"\"\"\n\n # Build structure instructions\n structure_text = \"\"\n if structure:\n config = structure.get(\"config\", {})\n structure_text = f\"\"\"\nStruktur-Template: {structure.get(\"name\", \"\")}\n- Typ: {structure.get(\"type\", \"\")}\n- Abschnitte: {json.dumps(config.get(\"sections\", []), ensure_ascii=False)}\n\"\"\"\n\n # Determine if HTML output is required\n html_output = False\n if profile:\n config = profile.get(\"config\", {})\n autorenprofil = config.get(\"autorenprofil\", config)\n output_format = autorenprofil.get(\"output_format\", {})\n html_output = output_format.get(\"typ\") == \"html\"\n\n format_instruction = \"\"\n if html_output:\n format_instruction = \"\"\"7. **WICHTIG: Ausgabe als wohlformatiertes HTML**\n - Verwende nur: h1, h2, h3, h4, p, ul, ol, li, strong, a, section, table, tr, td\n - Jeder h2-Abschnitt muss in einer eigenen <section> stehen\n - Kein Markdown, kein div\/span\/br\n - Fließtext immer in <p>-Tags\"\"\"",
"new_string": "def _parse_new_author_profile(config):\n \"\"\"Parse new-style author profile (Cary format) into prompt text.\"\"\"\n sections = []\n\n # Haltung\n haltung = config.get(\"haltung\", {})\n if haltung:\n sections.append(f\"\"\"### Haltung:\n- Grundhaltung: {haltung.get(\"grundhaltung\", \"\")}\n- Ausrichtung: {haltung.get(\"ausrichtung\", \"\")}\n- Spannungstoleranz: {haltung.get(\"spannungstoleranz\", \"\")}\n- Vereinfachung: {haltung.get(\"vereinfachung\", \"\")}\"\"\")\n\n # Tonalität\n tonalitaet = config.get(\"tonalitaet\", {})\n if tonalitaet:\n sections.append(f\"\"\"### Tonalität:\n- Charakter: {tonalitaet.get(\"charakter\", \"\")}\n- Stil: {tonalitaet.get(\"stil\", \"\")}\n- Wirkung: {tonalitaet.get(\"wirkung\", \"\")}\n- Abgrenzung: {tonalitaet.get(\"abgrenzung\", \"\")}\"\"\")\n\n # Sprachmodus\n sprachmodus = config.get(\"sprachmodus\", {})\n if sprachmodus:\n sections.append(f\"\"\"### Sprachmodus:\n- Denkstil: {sprachmodus.get(\"denkstil\", \"\")}\n- Aussagenform: {sprachmodus.get(\"aussagenform\", \"\")}\n- Fragenfunktion: {sprachmodus.get(\"fragenfunktion\", \"\")}\"\"\")\n\n # Grammatik und Satzbau - WICHTIG für Verbote\n grammatik = config.get(\"grammatik_und_satzbau\", {})\n if grammatik:\n verbote = []\n if grammatik.get(\"stakkato\") == \"ausgeschlossen\":\n verbote.append(\"Stakkato-Sätze\")\n if grammatik.get(\"einschuebe\") == \"keine\":\n verbote.append(\"Einschübe\")\n if grammatik.get(\"gedankenstriche\") == \"verboten\":\n verbote.append(\"Gedankenstriche (–)\")\n\n sections.append(f\"\"\"### Grammatik und Satzbau:\n- Sätze: {grammatik.get(\"saetze\", \"\")}\n- Rhythmus: {grammatik.get(\"rhythmus\", \"\")}\n- **VERBOTEN:** {\", \".join(verbote) if verbote else \"keine\"}\"\"\")\n\n # Wortwahl\n wortwahl = config.get(\"wortwahl\", {})\n if wortwahl:\n verboten = []\n if wortwahl.get(\"buzzwords\") == \"ausgeschlossen\":\n verboten.append(\"Buzzwords\")\n if wortwahl.get(\"methodennamen\") == \"ausgeschlossen\":\n verboten.append(\"Methodennamen\")\n\n sections.append(f\"\"\"### Wortwahl:\n- Niveau: {wortwahl.get(\"niveau\", \"\")}\n- Begriffe: {wortwahl.get(\"begriffe\", \"\")}\n- **VERBOTEN:** {\", \".join(verboten) if verboten else \"keine\"}\"\"\")\n\n # Adressierung\n adressierung = config.get(\"adressierung\", {})\n if adressierung:\n sections.append(f\"\"\"### Adressierung:\n- Form: {adressierung.get(\"form\", \"Sie\")}\n- Beziehung: {adressierung.get(\"beziehung\", \"\")}\n- Einladung: {adressierung.get(\"einladung\", \"\")}\"\"\")\n\n # Metaphern\n metaphern = config.get(\"metaphern\", {})\n if metaphern:\n sections.append(f\"\"\"### Metaphern:\n- Einsatz: {metaphern.get(\"einsatz\", \"\")}\n- Herkunft: {metaphern.get(\"herkunft\", \"\")}\n- Konsistenz: {metaphern.get(\"konsistenz\", \"\")}\"\"\")\n\n return \"\\n\\n\".join(sections)\n\n\ndef _parse_new_structure(config):\n \"\"\"Parse new-style structure profile into prompt text and format info.\"\"\"\n sections = []\n output_format = None\n erlaubte_tags = []\n\n # Ausgabe-Format\n ausgabe = config.get(\"ausgabe\", {})\n if ausgabe:\n output_format = ausgabe.get(\"format\", \"markdown\")\n erlaubte_tags = ausgabe.get(\"erlaubte_tags\", [])\n verbotene_tags = ausgabe.get(\"verbotene_tags\", [])\n\n if output_format == \"body-html\":\n sections.append(f\"\"\"### Ausgabe-Format: HTML\n- **Nur diese Tags verwenden:** {\", \".join(erlaubte_tags)}\n- **Verboten:** {\", \".join(verbotene_tags)}\n- {ausgabe.get(\"hinweis\", \"Sauberes semantisches HTML\")}\"\"\")\n\n # Gesamtaufbau\n aufbau = config.get(\"gesamtaufbau\", {})\n if aufbau:\n sections.append(f\"\"\"### Gesamtaufbau:\n- Form: {aufbau.get(\"form\", \"\")}\n- Dramaturgie: {aufbau.get(\"dramaturgie\", \"\")}\n- Linearität: {aufbau.get(\"linearitaet\", \"\")}\n- Themensprünge: {aufbau.get(\"themenspruenge\", \"\")}\"\"\")\n\n # Einstieg\n einstieg = config.get(\"einstieg\", {})\n if einstieg:\n sections.append(f\"\"\"### Einstieg:\n- Funktion: {einstieg.get(\"funktion\", \"\")}\n- Inhaltstyp: {einstieg.get(\"inhaltstyp\", \"\")}\n- Ausschluss: {einstieg.get(\"ausschluss\", \"\")}\"\"\")\n\n # Hauptteil-Blöcke\n hauptteil = config.get(\"hauptteil\", {})\n bloecke = hauptteil.get(\"bloecke\", [])\n if bloecke:\n bloecke_text = \"\\n\".join([f\" {i+1}. {b.get('fokus', '')} → {b.get('ziel', '')}\" for i, b in enumerate(bloecke)])\n sections.append(f\"\"\"### Hauptteil-Struktur:\n{bloecke_text}\"\"\")\n\n # Schluss\n schluss = config.get(\"schluss\", {})\n if schluss:\n sections.append(f\"\"\"### Schluss:\n- Typ: {schluss.get(\"typ\", \"\")}\n- Funktion: {schluss.get(\"funktion\", \"\")}\n- Abgrenzung: {schluss.get(\"abgrenzung\", \"\")}\"\"\")\n\n # Formatierung\n formatierung = config.get(\"formatierung\", {})\n if formatierung:\n ausschluss = formatierung.get(\"ausschluss\", [])\n if ausschluss:\n sections.append(f\"\"\"### Formatierung verboten:\n{\", \".join(ausschluss)}\"\"\")\n\n return \"\\n\\n\".join(sections), output_format, erlaubte_tags\n\n\ndef build_generation_prompt(briefing, context, profile, contract, structure=None):\n \"\"\"Build the content generation prompt.\"\"\"\n\n # Format context\n context_text = \"\"\n for i, ctx in enumerate(context, 1):\n context_text += f\"\\n[Quelle {i}: {ctx['source']}]\\n{ctx['content']}\\n\"\n\n # Build profile instructions - detect new vs old format\n profile_text = \"\"\n if profile:\n config = profile.get(\"config\", {})\n\n # Detect new format (has \"haltung\" or \"tonalitaet\" at top level)\n if \"haltung\" in config or \"tonalitaet\" in config or \"grammatik_und_satzbau\" in config:\n # New Cary-style profile\n profile_text = f\"\"\"\n## Autorenprofil: {profile.get(\"name\", \"Standard\")}\n\n{_parse_new_author_profile(config)}\n\"\"\"\n else:\n # Old format - keep backwards compatibility\n autorenprofil = config.get(\"autorenprofil\", config)\n\n stimme = autorenprofil.get(\"stimme\", {})\n stimme_text = \"\"\n if stimme:\n stimme_text = f\"\"\"\n### Stimme\/Tonalität:\n- Ton: {stimme.get(\"ton\", \"neutral\")}\n- Perspektive: {stimme.get(\"perspektive\", \"neutral\")}\n- Komplexität: {stimme.get(\"komplexitaet\", \"mittel\")}\"\"\"\n\n stil = autorenprofil.get(\"stil\", {})\n stil_text = \"\"\n if stil:\n stil_text = f\"\"\"\n### Stil:\n- Fachsprache: {\"Ja\" if stil.get(\"fachsprache\", False) else \"Nein\"}\n- Satzlänge: {stil.get(\"satzlaenge\", \"mittel\")}\"\"\"\n\n tabus = autorenprofil.get(\"tabus\", [])\n tabus_text = \"\"\n if tabus:\n tabus_text = f\"\"\"\n### Zu vermeiden:\n{\", \".join(tabus[:5])}\"\"\"\n\n profile_text = f\"\"\"\n## Autorenprofil: {profile.get(\"name\", \"Standard\")}\n{stimme_text}\n{stil_text}\n{tabus_text}\n\"\"\"\n\n # Build contract requirements\n contract_text = \"\"\n if contract:\n config = contract.get(\"config\", {})\n req = config.get(\"requirements\", {})\n contract_text = f\"\"\"\nContract: {contract.get(\"name\", \"Standard\")}\n- Wortanzahl: {req.get(\"min_words\", 500)} - {req.get(\"max_words\", 5000)} Wörter\n\"\"\"\n\n # Build structure instructions - detect new vs old format\n structure_text = \"\"\n output_format = \"markdown\"\n erlaubte_tags = []\n\n if structure:\n config = structure.get(\"config\", {})\n\n # Detect new format (has \"ausgabe\" at top level)\n if \"ausgabe\" in config or \"gesamtaufbau\" in config:\n # New Blog-Struktur format\n parsed_text, output_format, erlaubte_tags = _parse_new_structure(config)\n structure_text = f\"\"\"\n## Struktur: {structure.get(\"name\", \"\")}\n\n{parsed_text}\n\"\"\"\n else:\n # Old format\n structure_text = f\"\"\"\nStruktur-Template: {structure.get(\"name\", \"\")}\n- Abschnitte: {json.dumps(config.get(\"sections\", []), ensure_ascii=False)}\n\"\"\"\n\n # Build format instruction based on structure's ausgabe\n format_instruction = \"\"\n if output_format == \"body-html\":\n tags_str = \", \".join(erlaubte_tags) if erlaubte_tags else \"h1, h2, h3, h4, p, a, ol, ul, li, strong, table, hr\"\n format_instruction = f\"\"\"7. **KRITISCH - Ausgabe als sauberes HTML:**\n - NUR diese Tags: {tags_str}\n - KEIN Markdown (keine ##, keine **, keine -)\n - KEIN div, span, br, img, script, style\n - Jeder Absatz in <p>-Tags\n - Überschriften als <h2>, <h3>, <h4>\n - Listen als <ul>\/<ol> mit <li>\"\"\""
}
}
Response
{
"tool_response": {
"filePath": "\/opt\/scripts\/pipeline\/generate.py",
"oldString": "def build_generation_prompt(briefing, context, profile, contract, structure=None):\n \"\"\"Build the content generation prompt.\"\"\"\n\n # Format context\n context_text = \"\"\n for i, ctx in enumerate(context, 1):\n context_text += f\"\\n[Quelle {i}: {ctx['source']}]\\n{ctx['content']}\\n\"\n\n # Build rich profile instructions\n profile_text = \"\"\n if profile:\n config = profile.get(\"config\", {})\n autorenprofil = config.get(\"autorenprofil\", config) # Support both structures\n\n # Extract voice\/stimme\n stimme = autorenprofil.get(\"stimme\", {})\n stimme_text = \"\"\n if stimme:\n stimme_text = f\"\"\"\n### Stimme\/Tonalität:\n- Ton: {stimme.get(\"ton\", \"neutral\")}\n- Perspektive: {stimme.get(\"perspektive\", \"neutral\")}\n- Komplexität: {stimme.get(\"komplexitaet\", \"mittel\")}\n- Autorität: {stimme.get(\"autoritaet\", \"Experte\")}\"\"\"\n\n # Extract style\/stil\n stil = autorenprofil.get(\"stil\", {})\n stil_text = \"\"\n if stil:\n fachsprache_beispiele = stil.get(\"fachsprache_beispiele\", [])\n fachsprache_str = \", \".join(fachsprache_beispiele[:5]) if fachsprache_beispiele else \"\"\n stil_text = f\"\"\"\n### Stil:\n- Fachsprache: {\"Ja\" if stil.get(\"fachsprache\", False) else \"Nein\"}\n- Fachbegriffe: {fachsprache_str}\n- Satzlänge: {stil.get(\"satzlaenge\", \"mittel\")}\n- Absatzstruktur: {stil.get(\"absatzstruktur\", \"klar gegliedert\")}\"\"\"\n\n # Extract language patterns\/sprachliche_muster\n muster = autorenprofil.get(\"sprachliche_muster\", {})\n muster_text = \"\"\n if muster:\n phrasen = muster.get(\"phrasen\", [])\n phrasen_str = \", \".join([f'\"{p}\"' for p in phrasen[:4]]) if phrasen else \"\"\n uebergaenge = muster.get(\"uebergaenge\", [])\n uebergaenge_str = \", \".join([f'\"{u}\"' for u in uebergaenge[:3]]) if uebergaenge else \"\"\n muster_text = f\"\"\"\n### Sprachliche Muster:\n- Typische Phrasen: {phrasen_str}\n- Übergangsformulierungen: {uebergaenge_str}\"\"\"\n\n # Extract taboos\/tabus\n tabus = autorenprofil.get(\"tabus\", [])\n tabus_text = \"\"\n if tabus:\n tabus_str = \", \".join(tabus[:5])\n tabus_text = f\"\"\"\n### Zu vermeiden (Tabus):\n{tabus_str}\"\"\"\n\n # Extract core messages\/kernbotschaften\n kernbotschaften = autorenprofil.get(\"kernbotschaften\", [])\n kern_text = \"\"\n if kernbotschaften:\n kern_str = \"\\n\".join([f\"- {k}\" for k in kernbotschaften[:4]])\n kern_text = f\"\"\"\n### Kernbotschaften (implizit vermitteln):\n{kern_str}\"\"\"\n\n # Extract output format (HTML for web profiles)\n output_format = autorenprofil.get(\"output_format\", {})\n format_text = \"\"\n if output_format and output_format.get(\"typ\") == \"html\":\n erlaubte = output_format.get(\"erlaubte_elemente\", {})\n verboten = output_format.get(\"verbotene_elemente\", [])\n regeln = output_format.get(\"struktur_regeln\", [])\n\n erlaubte_str = []\n for _kategorie, elemente in erlaubte.items():\n erlaubte_str.extend(elemente[:3])\n\n format_text = f\"\"\"\n### Output-Format: HTML\n**Erlaubte Elemente:**\n{\", \".join(erlaubte_str[:10])}\n\n**Struktur-Regeln:**\n- {chr(10).join(\"- \" + r for r in regeln[:4]) if regeln else \"Semantisches HTML verwenden\"}\n\n**Verboten:** {\", \".join(verboten[:4]) if verboten else \"div, span, br, style\"}\"\"\"\n\n # Combine all profile sections\n profile_text = f\"\"\"\n## Autorenprofil: {profile.get(\"name\", \"Standard\")}\n{stimme_text}\n{stil_text}\n{muster_text}\n{tabus_text}\n{kern_text}\n{format_text}\n\"\"\"\n\n # Build contract requirements\n contract_text = \"\"\n if contract:\n config = contract.get(\"config\", {})\n req = config.get(\"requirements\", {})\n contract_text = f\"\"\"\nContract: {contract.get(\"name\", \"Standard\")}\n- Wortanzahl: {req.get(\"min_words\", 500)} - {req.get(\"max_words\", 5000)} Wörter\n- Struktur-Validierung: {req.get(\"structure_validation\", True)}\n\"\"\"\n\n # Build structure instructions\n structure_text = \"\"\n if structure:\n config = structure.get(\"config\", {})\n structure_text = f\"\"\"\nStruktur-Template: {structure.get(\"name\", \"\")}\n- Typ: {structure.get(\"type\", \"\")}\n- Abschnitte: {json.dumps(config.get(\"sections\", []), ensure_ascii=False)}\n\"\"\"\n\n # Determine if HTML output is required\n html_output = False\n if profile:\n config = profile.get(\"config\", {})\n autorenprofil = config.get(\"autorenprofil\", config)\n output_format = autorenprofil.get(\"output_format\", {})\n html_output = output_format.get(\"typ\") == \"html\"\n\n format_instruction = \"\"\n if html_output:\n format_instruction = \"\"\"7. **WICHTIG: Ausgabe als wohlformatiertes HTML**\n - Verwende nur: h1, h2, h3, h4, p, ul, ol, li, strong, a, section, table, tr, td\n - Jeder h2-Abschnitt muss in einer eigenen <section> stehen\n - Kein Markdown, kein div\/span\/br\n - Fließtext immer in <p>-Tags\"\"\"",
"newString": "def _parse_new_author_profile(config):\n \"\"\"Parse new-style author profile (Cary format) into prompt text.\"\"\"\n sections = []\n\n # Haltung\n haltung = config.get(\"haltung\", {})\n if haltung:\n sections.append(f\"\"\"### Haltung:\n- Grundhaltung: {haltung.get(\"grundhaltung\", \"\")}\n- Ausrichtung: {haltung.get(\"ausrichtung\", \"\")}\n- Spannungstoleranz: {haltung.get(\"spannungstoleranz\", \"\")}\n- Vereinfachung: {haltung.get(\"vereinfachung\", \"\")}\"\"\")\n\n # Tonalität\n tonalitaet = config.get(\"tonalitaet\", {})\n if tonalitaet:\n sections.append(f\"\"\"### Tonalität:\n- Charakter: {tonalitaet.get(\"charakter\", \"\")}\n- Stil: {tonalitaet.get(\"stil\", \"\")}\n- Wirkung: {tonalitaet.get(\"wirkung\", \"\")}\n- Abgrenzung: {tonalitaet.get(\"abgrenzung\", \"\")}\"\"\")\n\n # Sprachmodus\n sprachmodus = config.get(\"sprachmodus\", {})\n if sprachmodus:\n sections.append(f\"\"\"### Sprachmodus:\n- Denkstil: {sprachmodus.get(\"denkstil\", \"\")}\n- Aussagenform: {sprachmodus.get(\"aussagenform\", \"\")}\n- Fragenfunktion: {sprachmodus.get(\"fragenfunktion\", \"\")}\"\"\")\n\n # Grammatik und Satzbau - WICHTIG für Verbote\n grammatik = config.get(\"grammatik_und_satzbau\", {})\n if grammatik:\n verbote = []\n if grammatik.get(\"stakkato\") == \"ausgeschlossen\":\n verbote.append(\"Stakkato-Sätze\")\n if grammatik.get(\"einschuebe\") == \"keine\":\n verbote.append(\"Einschübe\")\n if grammatik.get(\"gedankenstriche\") == \"verboten\":\n verbote.append(\"Gedankenstriche (–)\")\n\n sections.append(f\"\"\"### Grammatik und Satzbau:\n- Sätze: {grammatik.get(\"saetze\", \"\")}\n- Rhythmus: {grammatik.get(\"rhythmus\", \"\")}\n- **VERBOTEN:** {\", \".join(verbote) if verbote else \"keine\"}\"\"\")\n\n # Wortwahl\n wortwahl = config.get(\"wortwahl\", {})\n if wortwahl:\n verboten = []\n if wortwahl.get(\"buzzwords\") == \"ausgeschlossen\":\n verboten.append(\"Buzzwords\")\n if wortwahl.get(\"methodennamen\") == \"ausgeschlossen\":\n verboten.append(\"Methodennamen\")\n\n sections.append(f\"\"\"### Wortwahl:\n- Niveau: {wortwahl.get(\"niveau\", \"\")}\n- Begriffe: {wortwahl.get(\"begriffe\", \"\")}\n- **VERBOTEN:** {\", \".join(verboten) if verboten else \"keine\"}\"\"\")\n\n # Adressierung\n adressierung = config.get(\"adressierung\", {})\n if adressierung:\n sections.append(f\"\"\"### Adressierung:\n- Form: {adressierung.get(\"form\", \"Sie\")}\n- Beziehung: {adressierung.get(\"beziehung\", \"\")}\n- Einladung: {adressierung.get(\"einladung\", \"\")}\"\"\")\n\n # Metaphern\n metaphern = config.get(\"metaphern\", {})\n if metaphern:\n sections.append(f\"\"\"### Metaphern:\n- Einsatz: {metaphern.get(\"einsatz\", \"\")}\n- Herkunft: {metaphern.get(\"herkunft\", \"\")}\n- Konsistenz: {metaphern.get(\"konsistenz\", \"\")}\"\"\")\n\n return \"\\n\\n\".join(sections)\n\n\ndef _parse_new_structure(config):\n \"\"\"Parse new-style structure profile into prompt text and format info.\"\"\"\n sections = []\n output_format = None\n erlaubte_tags = []\n\n # Ausgabe-Format\n ausgabe = config.get(\"ausgabe\", {})\n if ausgabe:\n output_format = ausgabe.get(\"format\", \"markdown\")\n erlaubte_tags = ausgabe.get(\"erlaubte_tags\", [])\n verbotene_tags = ausgabe.get(\"verbotene_tags\", [])\n\n if output_format == \"body-html\":\n sections.append(f\"\"\"### Ausgabe-Format: HTML\n- **Nur diese Tags verwenden:** {\", \".join(erlaubte_tags)}\n- **Verboten:** {\", \".join(verbotene_tags)}\n- {ausgabe.get(\"hinweis\", \"Sauberes semantisches HTML\")}\"\"\")\n\n # Gesamtaufbau\n aufbau = config.get(\"gesamtaufbau\", {})\n if aufbau:\n sections.append(f\"\"\"### Gesamtaufbau:\n- Form: {aufbau.get(\"form\", \"\")}\n- Dramaturgie: {aufbau.get(\"dramaturgie\", \"\")}\n- Linearität: {aufbau.get(\"linearitaet\", \"\")}\n- Themensprünge: {aufbau.get(\"themenspruenge\", \"\")}\"\"\")\n\n # Einstieg\n einstieg = config.get(\"einstieg\", {})\n if einstieg:\n sections.append(f\"\"\"### Einstieg:\n- Funktion: {einstieg.get(\"funktion\", \"\")}\n- Inhaltstyp: {einstieg.get(\"inhaltstyp\", \"\")}\n- Ausschluss: {einstieg.get(\"ausschluss\", \"\")}\"\"\")\n\n # Hauptteil-Blöcke\n hauptteil = config.get(\"hauptteil\", {})\n bloecke = hauptteil.get(\"bloecke\", [])\n if bloecke:\n bloecke_text = \"\\n\".join([f\" {i+1}. {b.get('fokus', '')} → {b.get('ziel', '')}\" for i, b in enumerate(bloecke)])\n sections.append(f\"\"\"### Hauptteil-Struktur:\n{bloecke_text}\"\"\")\n\n # Schluss\n schluss = config.get(\"schluss\", {})\n if schluss:\n sections.append(f\"\"\"### Schluss:\n- Typ: {schluss.get(\"typ\", \"\")}\n- Funktion: {schluss.get(\"funktion\", \"\")}\n- Abgrenzung: {schluss.get(\"abgrenzung\", \"\")}\"\"\")\n\n # Formatierung\n formatierung = config.get(\"formatierung\", {})\n if formatierung:\n ausschluss = formatierung.get(\"ausschluss\", [])\n if ausschluss:\n sections.append(f\"\"\"### Formatierung verboten:\n{\", \".join(ausschluss)}\"\"\")\n\n return \"\\n\\n\".join(sections), output_format, erlaubte_tags\n\n\ndef build_generation_prompt(briefing, context, profile, contract, structure=None):\n \"\"\"Build the content generation prompt.\"\"\"\n\n # Format context\n context_text = \"\"\n for i, ctx in enumerate(context, 1):\n context_text += f\"\\n[Quelle {i}: {ctx['source']}]\\n{ctx['content']}\\n\"\n\n # Build profile instructions - detect new vs old format\n profile_text = \"\"\n if profile:\n config = profile.get(\"config\", {})\n\n # Detect new format (has \"haltung\" or \"tonalitaet\" at top level)\n if \"haltung\" in config or \"tonalitaet\" in config or \"grammatik_und_satzbau\" in config:\n # New Cary-style profile\n profile_text = f\"\"\"\n## Autorenprofil: {profile.get(\"name\", \"Standard\")}\n\n{_parse_new_author_profile(config)}\n\"\"\"\n else:\n # Old format - keep backwards compatibility\n autorenprofil = config.get(\"autorenprofil\", config)\n\n stimme = autorenprofil.get(\"stimme\", {})\n stimme_text = \"\"\n if stimme:\n stimme_text = f\"\"\"\n### Stimme\/Tonalität:\n- Ton: {stimme.get(\"ton\", \"neutral\")}\n- Perspektive: {stimme.get(\"perspektive\", \"neutral\")}\n- Komplexität: {stimme.get(\"komplexitaet\", \"mittel\")}\"\"\"\n\n stil = autorenprofil.get(\"stil\", {})\n stil_text = \"\"\n if stil:\n stil_text = f\"\"\"\n### Stil:\n- Fachsprache: {\"Ja\" if stil.get(\"fachsprache\", False) else \"Nein\"}\n- Satzlänge: {stil.get(\"satzlaenge\", \"mittel\")}\"\"\"\n\n tabus = autorenprofil.get(\"tabus\", [])\n tabus_text = \"\"\n if tabus:\n tabus_text = f\"\"\"\n### Zu vermeiden:\n{\", \".join(tabus[:5])}\"\"\"\n\n profile_text = f\"\"\"\n## Autorenprofil: {profile.get(\"name\", \"Standard\")}\n{stimme_text}\n{stil_text}\n{tabus_text}\n\"\"\"\n\n # Build contract requirements\n contract_text = \"\"\n if contract:\n config = contract.get(\"config\", {})\n req = config.get(\"requirements\", {})\n contract_text = f\"\"\"\nContract: {contract.get(\"name\", \"Standard\")}\n- Wortanzahl: {req.get(\"min_words\", 500)} - {req.get(\"max_words\", 5000)} Wörter\n\"\"\"\n\n # Build structure instructions - detect new vs old format\n structure_text = \"\"\n output_format = \"markdown\"\n erlaubte_tags = []\n\n if structure:\n config = structure.get(\"config\", {})\n\n # Detect new format (has \"ausgabe\" at top level)\n if \"ausgabe\" in config or \"gesamtaufbau\" in config:\n # New Blog-Struktur format\n parsed_text, output_format, erlaubte_tags = _parse_new_structure(config)\n structure_text = f\"\"\"\n## Struktur: {structure.get(\"name\", \"\")}\n\n{parsed_text}\n\"\"\"\n else:\n # Old format\n structure_text = f\"\"\"\nStruktur-Template: {structure.get(\"name\", \"\")}\n- Abschnitte: {json.dumps(config.get(\"sections\", []), ensure_ascii=False)}\n\"\"\"\n\n # Build format instruction based on structure's ausgabe\n format_instruction = \"\"\n if output_format == \"body-html\":\n tags_str = \", \".join(erlaubte_tags) if erlaubte_tags else \"h1, h2, h3, h4, p, a, ol, ul, li, strong, table, hr\"\n format_instruction = f\"\"\"7. **KRITISCH - Ausgabe als sauberes HTML:**\n - NUR diese Tags: {tags_str}\n - KEIN Markdown (keine ##, keine **, keine -)\n - KEIN div, span, br, img, script, style\n - Jeder Absatz in <p>-Tags\n - Überschriften als <h2>, <h3>, <h4>\n - Listen als <ul>\/<ol> mit <li>\"\"\"",
"originalFile": "#!\/usr\/bin\/env python3\n\"\"\"\nContent Generation for KI-System\nGenerates content using RAG context, author profiles, and contracts.\n\"\"\"\n\nimport json\nimport sys\n\nsys.path.insert(0, \"\/opt\/scripts\/pipeline\")\n\nfrom config import ANTHROPIC_API_KEY, ANTHROPIC_MODEL, OLLAMA_CHAT_MODEL, OLLAMA_HOST # noqa: I001, E402\nfrom db import db # noqa: E402\nfrom embed import search_similar # noqa: E402\n\n\ndef _repair_json(json_str):\n \"\"\"\n Attempt to repair common JSON issues from LLM output.\n\n Fixes:\n - Unescaped quotes in strings\n - Missing commas between array elements\n - Trailing commas\n - Control characters in strings\n \"\"\"\n import re\n\n # Remove control characters except newlines and tabs\n json_str = re.sub(r\"[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]\", \"\", json_str)\n\n # Fix common issue: missing comma before closing bracket in arrays\n json_str = re.sub(r'\"\\s*\\n\\s*]', '\"\\n]', json_str)\n\n # Fix trailing commas before closing brackets\/braces\n json_str = re.sub(r\",\\s*}\", \"}\", json_str)\n json_str = re.sub(r\",\\s*]\", \"]\", json_str)\n\n # Fix missing commas between array elements (string followed by string)\n json_str = re.sub(r'\"\\s*\\n\\s*\"', '\",\\n\"', json_str)\n\n # Fix unescaped quotes within strings (heuristic: quotes not at boundaries)\n # This is tricky, so we do a simple fix for common patterns\n lines = json_str.split(\"\\n\")\n fixed_lines = []\n for line in lines:\n # Count quotes - if odd number and line has content, try to fix\n quote_count = line.count('\"') - line.count('\\\\\"')\n if quote_count % 2 != 0 and \":\" in line:\n # Try to escape internal quotes (very basic heuristic)\n parts = line.split(\":\", 1)\n if len(parts) == 2:\n key_part = parts[0]\n value_part = parts[1]\n # If value has odd quotes, try to balance\n if value_part.count('\"') % 2 != 0:\n # Add escaped quote or remove problematic char\n value_part = value_part.rstrip().rstrip(\",\")\n if not value_part.endswith('\"'):\n value_part += '\"'\n line = key_part + \":\" + value_part\n fixed_lines.append(line)\n\n return \"\\n\".join(fixed_lines)\n\n\ndef get_prompt(name):\n \"\"\"Load prompt from database by name.\"\"\"\n cursor = db.execute(\n \"\"\"SELECT content FROM prompts WHERE name = %s AND is_active = 1 ORDER BY version DESC LIMIT 1\"\"\",\n (name,),\n )\n result = cursor.fetchone()\n cursor.close()\n return result[\"content\"] if result else None\n\n\ndef get_rag_context(briefing, collection=\"documents\", limit=5):\n \"\"\"\n Get relevant context from Qdrant based on briefing.\n Returns list of chunks with content and metadata.\n \"\"\"\n results = search_similar(briefing, collection=collection, limit=limit)\n\n context_items = []\n for result in results:\n context_items.append(\n {\n \"content\": result[\"payload\"].get(\"content\", \"\"),\n \"source\": result[\"payload\"].get(\"document_title\", \"Unknown\"),\n \"score\": round(result[\"score\"], 4),\n }\n )\n\n return context_items\n\n\ndef get_config_item(item_id, item_type):\n \"\"\"Load configuration item from content_config table.\"\"\"\n if not item_id:\n return None\n\n cursor = db.execute(\n \"SELECT name, content FROM content_config WHERE id = %s AND type = %s AND status = 'active'\",\n (item_id, item_type),\n )\n result = cursor.fetchone()\n cursor.close()\n\n if result:\n config = json.loads(result[\"content\"]) if isinstance(result[\"content\"], str) else result[\"content\"]\n return {\"name\": result[\"name\"], \"config\": config}\n return None\n\n\ndef get_author_profile(profile_id):\n \"\"\"Load author profile from database.\"\"\"\n return get_config_item(profile_id, \"author_profile\")\n\n\ndef get_contract(contract_id):\n \"\"\"Load content contract from database.\"\"\"\n return get_config_item(contract_id, \"contract\")\n\n\ndef get_structure(structure_id):\n \"\"\"Load content structure from database.\"\"\"\n result = get_config_item(structure_id, \"structure\")\n if result:\n # Structure has additional 'type' field in config\n result[\"type\"] = result[\"config\"].get(\"type\", \"article\")\n return result\n\n\ndef get_order(order_id):\n \"\"\"Load content order with all related data.\"\"\"\n cursor = db.execute(\n \"\"\"SELECT co.*,\n ap.name as profile_name, ap.content as profile_config,\n cc.name as contract_name, cc.content as contract_config,\n cs.name as structure_name, cs.content as structure_config\n FROM content_orders co\n LEFT JOIN content_config ap ON co.author_profile_id = ap.id AND ap.type = 'author_profile'\n LEFT JOIN content_config cc ON co.contract_id = cc.id AND cc.type = 'contract'\n LEFT JOIN content_config cs ON co.structure_id = cs.id AND cs.type = 'structure'\n WHERE co.id = %s\"\"\",\n (order_id,),\n )\n result = cursor.fetchone()\n cursor.close()\n return result\n\n\ndef build_generation_prompt(briefing, context, profile, contract, structure=None):\n \"\"\"Build the content generation prompt.\"\"\"\n\n # Format context\n context_text = \"\"\n for i, ctx in enumerate(context, 1):\n context_text += f\"\\n[Quelle {i}: {ctx['source']}]\\n{ctx['content']}\\n\"\n\n # Build rich profile instructions\n profile_text = \"\"\n if profile:\n config = profile.get(\"config\", {})\n autorenprofil = config.get(\"autorenprofil\", config) # Support both structures\n\n # Extract voice\/stimme\n stimme = autorenprofil.get(\"stimme\", {})\n stimme_text = \"\"\n if stimme:\n stimme_text = f\"\"\"\n### Stimme\/Tonalität:\n- Ton: {stimme.get(\"ton\", \"neutral\")}\n- Perspektive: {stimme.get(\"perspektive\", \"neutral\")}\n- Komplexität: {stimme.get(\"komplexitaet\", \"mittel\")}\n- Autorität: {stimme.get(\"autoritaet\", \"Experte\")}\"\"\"\n\n # Extract style\/stil\n stil = autorenprofil.get(\"stil\", {})\n stil_text = \"\"\n if stil:\n fachsprache_beispiele = stil.get(\"fachsprache_beispiele\", [])\n fachsprache_str = \", \".join(fachsprache_beispiele[:5]) if fachsprache_beispiele else \"\"\n stil_text = f\"\"\"\n### Stil:\n- Fachsprache: {\"Ja\" if stil.get(\"fachsprache\", False) else \"Nein\"}\n- Fachbegriffe: {fachsprache_str}\n- Satzlänge: {stil.get(\"satzlaenge\", \"mittel\")}\n- Absatzstruktur: {stil.get(\"absatzstruktur\", \"klar gegliedert\")}\"\"\"\n\n # Extract language patterns\/sprachliche_muster\n muster = autorenprofil.get(\"sprachliche_muster\", {})\n muster_text = \"\"\n if muster:\n phrasen = muster.get(\"phrasen\", [])\n phrasen_str = \", \".join([f'\"{p}\"' for p in phrasen[:4]]) if phrasen else \"\"\n uebergaenge = muster.get(\"uebergaenge\", [])\n uebergaenge_str = \", \".join([f'\"{u}\"' for u in uebergaenge[:3]]) if uebergaenge else \"\"\n muster_text = f\"\"\"\n### Sprachliche Muster:\n- Typische Phrasen: {phrasen_str}\n- Übergangsformulierungen: {uebergaenge_str}\"\"\"\n\n # Extract taboos\/tabus\n tabus = autorenprofil.get(\"tabus\", [])\n tabus_text = \"\"\n if tabus:\n tabus_str = \", \".join(tabus[:5])\n tabus_text = f\"\"\"\n### Zu vermeiden (Tabus):\n{tabus_str}\"\"\"\n\n # Extract core messages\/kernbotschaften\n kernbotschaften = autorenprofil.get(\"kernbotschaften\", [])\n kern_text = \"\"\n if kernbotschaften:\n kern_str = \"\\n\".join([f\"- {k}\" for k in kernbotschaften[:4]])\n kern_text = f\"\"\"\n### Kernbotschaften (implizit vermitteln):\n{kern_str}\"\"\"\n\n # Extract output format (HTML for web profiles)\n output_format = autorenprofil.get(\"output_format\", {})\n format_text = \"\"\n if output_format and output_format.get(\"typ\") == \"html\":\n erlaubte = output_format.get(\"erlaubte_elemente\", {})\n verboten = output_format.get(\"verbotene_elemente\", [])\n regeln = output_format.get(\"struktur_regeln\", [])\n\n erlaubte_str = []\n for _kategorie, elemente in erlaubte.items():\n erlaubte_str.extend(elemente[:3])\n\n format_text = f\"\"\"\n### Output-Format: HTML\n**Erlaubte Elemente:**\n{\", \".join(erlaubte_str[:10])}\n\n**Struktur-Regeln:**\n- {chr(10).join(\"- \" + r for r in regeln[:4]) if regeln else \"Semantisches HTML verwenden\"}\n\n**Verboten:** {\", \".join(verboten[:4]) if verboten else \"div, span, br, style\"}\"\"\"\n\n # Combine all profile sections\n profile_text = f\"\"\"\n## Autorenprofil: {profile.get(\"name\", \"Standard\")}\n{stimme_text}\n{stil_text}\n{muster_text}\n{tabus_text}\n{kern_text}\n{format_text}\n\"\"\"\n\n # Build contract requirements\n contract_text = \"\"\n if contract:\n config = contract.get(\"config\", {})\n req = config.get(\"requirements\", {})\n contract_text = f\"\"\"\nContract: {contract.get(\"name\", \"Standard\")}\n- Wortanzahl: {req.get(\"min_words\", 500)} - {req.get(\"max_words\", 5000)} Wörter\n- Struktur-Validierung: {req.get(\"structure_validation\", True)}\n\"\"\"\n\n # Build structure instructions\n structure_text = \"\"\n if structure:\n config = structure.get(\"config\", {})\n structure_text = f\"\"\"\nStruktur-Template: {structure.get(\"name\", \"\")}\n- Typ: {structure.get(\"type\", \"\")}\n- Abschnitte: {json.dumps(config.get(\"sections\", []), ensure_ascii=False)}\n\"\"\"\n\n # Determine if HTML output is required\n html_output = False\n if profile:\n config = profile.get(\"config\", {})\n autorenprofil = config.get(\"autorenprofil\", config)\n output_format = autorenprofil.get(\"output_format\", {})\n html_output = output_format.get(\"typ\") == \"html\"\n\n format_instruction = \"\"\n if html_output:\n format_instruction = \"\"\"7. **WICHTIG: Ausgabe als wohlformatiertes HTML**\n - Verwende nur: h1, h2, h3, h4, p, ul, ol, li, strong, a, section, table, tr, td\n - Jeder h2-Abschnitt mu... [TRUNCATED-b1a598d104e1d301]",
"structuredPatch": [
{
"oldStart": 151,
"oldLines": 6,
"newStart": 151,
"newLines": 148,
"lines": [
" return result",
" ",
" ",
"+def _parse_new_author_profile(config):",
"+ \"\"\"Parse new-style author profile (Cary format) into prompt text.\"\"\"",
"+ sections = []",
"+",
"+ # Haltung",
"+ haltung = config.get(\"haltung\", {})",
"+ if haltung:",
"+ sections.append(f\"\"\"### Haltung:",
"+- Grundhaltung: {haltung.get(\"grundhaltung\", \"\")}",
"+- Ausrichtung: {haltung.get(\"ausrichtung\", \"\")}",
"+- Spannungstoleranz: {haltung.get(\"spannungstoleranz\", \"\")}",
"+- Vereinfachung: {haltung.get(\"vereinfachung\", \"\")}\"\"\")",
"+",
"+ # Tonalität",
"+ tonalitaet = config.get(\"tonalitaet\", {})",
"+ if tonalitaet:",
"+ sections.append(f\"\"\"### Tonalität:",
"+- Charakter: {tonalitaet.get(\"charakter\", \"\")}",
"+- Stil: {tonalitaet.get(\"stil\", \"\")}",
"+- Wirkung: {tonalitaet.get(\"wirkung\", \"\")}",
"+- Abgrenzung: {tonalitaet.get(\"abgrenzung\", \"\")}\"\"\")",
"+",
"+ # Sprachmodus",
"+ sprachmodus = config.get(\"sprachmodus\", {})",
"+ if sprachmodus:",
"+ sections.append(f\"\"\"### Sprachmodus:",
"+- Denkstil: {sprachmodus.get(\"denkstil\", \"\")}",
"+- Aussagenform: {sprachmodus.get(\"aussagenform\", \"\")}",
"+- Fragenfunktion: {sprachmodus.get(\"fragenfunktion\", \"\")}\"\"\")",
"+",
"+ # Grammatik und Satzbau - WICHTIG für Verbote",
"+ grammatik = config.get(\"grammatik_und_satzbau\", {})",
"+ if grammatik:",
"+ verbote = []",
"+ if grammatik.get(\"stakkato\") == \"ausgeschlossen\":",
"+ verbote.append(\"Stakkato-Sätze\")",
"+ if grammatik.get(\"einschuebe\") == \"keine\":",
"+ verbote.append(\"Einschübe\")",
"+ if grammatik.get(\"gedankenstriche\") == \"verboten\":",
"+ verbote.append(\"Gedankenstriche (–)\")",
"+",
"+ sections.append(f\"\"\"### Grammatik und Satzbau:",
"+- Sätze: {grammatik.get(\"saetze\", \"\")}",
"+- Rhythmus: {grammatik.get(\"rhythmus\", \"\")}",
"+- **VERBOTEN:** {\", \".join(verbote) if verbote else \"keine\"}\"\"\")",
"+",
"+ # Wortwahl",
"+ wortwahl = config.get(\"wortwahl\", {})",
"+ if wortwahl:",
"+ verboten = []",
"+ if wortwahl.get(\"buzzwords\") == \"ausgeschlossen\":",
"+ verboten.append(\"Buzzwords\")",
"+ if wortwahl.get(\"methodennamen\") == \"ausgeschlossen\":",
"+ verboten.append(\"Methodennamen\")",
"+",
"+ sections.append(f\"\"\"### Wortwahl:",
"+- Niveau: {wortwahl.get(\"niveau\", \"\")}",
"+- Begriffe: {wortwahl.get(\"begriffe\", \"\")}",
"+- **VERBOTEN:** {\", \".join(verboten) if verboten else \"keine\"}\"\"\")",
"+",
"+ # Adressierung",
"+ adressierung = config.get(\"adressierung\", {})",
"+ if adressierung:",
"+ sections.append(f\"\"\"### Adressierung:",
"+- Form: {adressierung.get(\"form\", \"Sie\")}",
"+- Beziehung: {adressierung.get(\"beziehung\", \"\")}",
"+- Einladung: {adressierung.get(\"einladung\", \"\")}\"\"\")",
"+",
"+ # Metaphern",
"+ metaphern = config.get(\"metaphern\", {})",
"+ if metaphern:",
"+ sections.append(f\"\"\"### Metaphern:",
"+- Einsatz: {metaphern.get(\"einsatz\", \"\")}",
"+- Herkunft: {metaphern.get(\"herkunft\", \"\")}",
"+- Konsistenz: {metaphern.get(\"konsistenz\", \"\")}\"\"\")",
"+",
"+ return \"\\n\\n\".join(sections)",
"+",
"+",
"+def _parse_new_structure(config):",
"+ \"\"\"Parse new-style structure profile into prompt text and format info.\"\"\"",
"+ sections = []",
"+ output_format = None",
"+ erlaubte_tags = []",
"+",
"+ # Ausgabe-Format",
"+ ausgabe = config.get(\"ausgabe\", {})",
"+ if ausgabe:",
"+ output_format = ausgabe.get(\"format\", \"markdown\")",
"+ erlaubte_tags = ausgabe.get(\"erlaubte_tags\", [])",
"+ verbotene_tags = ausgabe.get(\"verbotene_tags\", [])",
"+",
"+ if output_format == \"body-html\":",
"+ sections.append(f\"\"\"### Ausgabe-Format: HTML",
"+- **Nur diese Tags verwenden:** {\", \".join(erlaubte_tags)}",
"+- **Verboten:** {\", \".join(verbotene_tags)}",
"+- {ausgabe.get(\"hinweis\", \"Sauberes semantisches HTML\")}\"\"\")",
"+",
"+ # Gesamtaufbau",
"+ aufbau = config.get(\"gesamtaufbau\", {})",
"+ if aufbau:",
"+ sections.append(f\"\"\"### Gesamtaufbau:",
"+- Form: {aufbau.get(\"form\", \"\")}",
"+- Dramaturgie: {aufbau.get(\"dramaturgie\", \"\")}",
"+- Linearität: {aufbau.get(\"linearitaet\", \"\")}",
"+- Themensprünge: {aufbau.get(\"themenspruenge\", \"\")}\"\"\")",
"+",
"+ # Einstieg",
"+ einstieg = config.get(\"einstieg\", {})",
"+ if einstieg:",
"+ sections.append(f\"\"\"### Einstieg:",
"+- Funktion: {einstieg.get(\"funktion\", \"\")}",
"+- Inhaltstyp: {einstieg.get(\"inhaltstyp\", \"\")}",
"+- Ausschluss: {einstieg.get(\"ausschluss\", \"\")}\"\"\")",
"+",
"+ # Hauptteil-Blöcke",
"+ hauptteil = config.get(\"hauptteil\", {})",
"+ bloecke = hauptteil.get(\"bloecke\", [])",
"+ if bloecke:",
"+ bloecke_text = \"\\n\".join([f\" {i+1}. {b.get('fokus', '')} → {b.get('ziel', '')}\" for i, b in enumerate(bloecke)])",
"+ sections.append(f\"\"\"### Hauptteil-Struktur:",
"+{bloecke_text}\"\"\")",
"+",
"+ # Schluss",
"+ schluss = config.get(\"schluss\", {})",
"+ if schluss:",
"+ sections.append(f\"\"\"### Schluss:",
"+- Typ: {schluss.get(\"typ\", \"\")}",
"+- Funktion: {schluss.get(\"funktion\", \"\")}",
"+- Abgrenzung: {schluss.get(\"abgrenzung\", \"\")}\"\"\")",
"+",
"+ # Formatierung",
"+ formatierung = config.get(\"formatierung\", {})",
"+ if formatierung:",
"+ ausschluss = formatierung.get(\"ausschluss\", [])",
"+ if ausschluss:",
"+ sections.append(f\"\"\"### Formatierung verboten:",
"+{\", \".join(ausschluss)}\"\"\")",
"+",
"+ return \"\\n\\n\".join(sections), output_format, erlaubte_tags",
"+",
"+",
" def build_generation_prompt(briefing, context, profile, contract, structure=None):",
" \"\"\"Build the content generation prompt.\"\"\"",
" "
]
},
{
"oldStart": 159,
"oldLines": 98,
"newStart": 301,
"newLines": 52,
"lines": [
" for i, ctx in enumerate(context, 1):",
" context_text += f\"\\n[Quelle {i}: {ctx['source']}]\\n{ctx['content']}\\n\"",
" ",
"- # Build rich profile instructions",
"+ # Build profile instructions - detect new vs old format",
" profile_text = \"\"",
" if profile:",
" config = profile.get(\"config\", {})",
"- autorenprofil = config.get(\"autorenprofil\", config) # Support both structures",
" ",
"- # Extract voice\/stimme",
"- stimme = autorenprofil.get(\"stimme\", {})",
"- stimme_text = \"\"",
"- if stimme:",
"- stimme_text = f\"\"\"",
"+ # Detect new format (has \"haltung\" or \"tonalitaet\" at top level)",
"+ if \"haltung\" in config or \"tonalitaet\" in config or \"grammatik_und_satzbau\" in config:",
"+ # New Cary-style profile",
"+ profile_text = f\"\"\"",
"+## Autorenprofil: {profile.get(\"name\", \"Standard\")}",
"+",
"+{_parse_new_author_profile(config)}",
"+\"\"\"",
"+ else:",
"+ # Old format - keep backwards compatibility",
"+ autorenprofil = config.get(\"autorenprofil\", config)",
"+",
"+ stimme = autorenprofil.get(\"stimme\", {})",
"+ stimme_text = \"\"",
"+ if stimme:",
"+ stimme_text = f\"\"\"",
" ### Stimme\/Tonalität:",
" - Ton: {stimme.get(\"ton\", \"neutral\")}",
" - Perspektive: {stimme.get(\"perspektive\", \"neutral\")}",
"-- Komplexität: {stimme.get(\"komplexitaet\", \"mittel\")}",
"-- Autorität: {stimme.get(\"autoritaet\", \"Experte\")}\"\"\"",
"+- Komplexität: {stimme.get(\"komplexitaet\", \"mittel\")}\"\"\"",
" ",
"- # Extract style\/stil",
"- stil = autorenprofil.get(\"stil\", {})",
"- stil_text = \"\"",
"- if stil:",
"- fachsprache_beispiele = stil.get(\"fachsprache_beispiele\", [])",
"- fachsprache_str = \", \".join(fachsprache_beispiele[:5]) if fachsprache_beispiele else \"\"",
"- stil_text = f\"\"\"",
"+ stil = autorenprofil.get(\"stil\", {})",
"+ stil_text = \"\"",
"+ if stil:",
"+ stil_text = f\"\"\"",
" ### Stil:",
" - Fachsprache: {\"Ja\" if stil.get(\"fachsprache\", False) else \"Nein\"}",
"-- Fachbegriffe: {fachsprache_str}",
"-- Satzlänge: {stil.get(\"satzlaenge\", \"mittel\")}",
"-- Absatzstruktur: {stil.get(\"absatzstruktur\", \"klar gegliedert\")}\"\"\"",
"+- Satzlänge: {stil.get(\"satzlaenge\", \"mittel\")}\"\"\"",
" ",
"- # Extract language patterns\/sprachliche_muster",
"- muster = autorenprofil.get(\"sprachliche_muster\", {})",
"- muster_text = \"\"",
"- if muster:",
"- phrasen = muster.get(\"phrasen\", [])",
"- phrasen_str = \", \".join([f'\"{p}\"' for p in phrasen[:4]]) if phrasen else \"\"",
"- uebergaenge = muster.get(\"uebergaenge\", [])",
"- uebergaenge_str = \", \".join([f'\"{u}\"' for u in uebergaenge[:3]]) if uebergaenge else \"\"",
"- muster_text = f\"\"\"",
"-### Sprachliche Muster:",
"-- Typische Phrasen: {phrasen_str}",
"-- Übergangsformulierungen: {uebergaenge_str}\"\"\"",
"+ tabus = autorenprofil.get(\"tabus\", [])",
"+ tabus_text = \"\"",
"+ if tabus:",
"+ tabus_text = f\"\"\"",
"+### Zu vermeiden:",
"+{\", \".join(tabus[:5])}\"\"\"",
" ",
"- # Extract taboos\/tabus",
"- tabus = autorenprofil.get(\"tabus\", [])",
"- tabus_text = \"\"",
"- if tabus:",
"- tabus_str = \", \".join(tabus[:5])",
"- tabus_text = f\"\"\"",
"-### Zu vermeiden (Tabus):",
"-{tabus_str}\"\"\"",
"-",
"- # Extract core messages\/kernbotschaften",
"- kernbotschaften = autorenprofil.get(\"kernbotschaften\", [])",
"- kern_text = \"\"",
"- if kernbotschaften:",
"- kern_str = \"\\n\".join([f\"- {k}\" for k in kernbotschaften[:4]])",
"- kern_text = f\"\"\"",
"-### Kernbotschaften (implizit vermitteln):",
"-{kern_str}\"\"\"",
"-",
"- # Extract output format (HTML for web profiles)",
"- output_format = autorenprofil.get(\"output_format\", {})",
"- format_text = \"\"",
"- if output_format and output_format.get(\"typ\") == \"html\":",
"- erlaubte = output_format.get(\"erlaubte_elemente\", {})",
"- verboten = output_format.get(\"verbotene_elemente\", [])",
"- regeln = output_format.get(\"struktur_regeln\", [])",
"-",
"- erlaubte_str = []",
"- for _kategorie, elemente in erlaubte.items():",
"- erlaubte_str.extend(elemente[:3])",
"-",
"- format_text = f\"\"\"",
"-### Output-Format: HTML",
"-**Erlaubte Elemente:**",
"-{\", \".join(erlaubte_str[:10])}",
"-",
"-**Struktur-Regeln:**",
"-- {chr(10).join(\"- \" + r for r in regeln[:4]) if regeln else \"Semantisches HTML verwenden\"}",
"-",
"-**Verboten:** {\", \".join(verboten[:4]) if verboten else \"div, span, br, style\"}\"\"\"",
"-",
"- # Combine all profile sections",
"- profile_text = f\"\"\"",
"+ profile_text = f\"\"\"",
" ## Autorenprofil: {profile.get(\"name\", \"Standard\")}",
" {stimme_text}",
" {stil_text}",
"-{muster_text}",
" {tabus_text}",
"-{kern_text}",
"-{format_text}",
" \"\"\"",
" ",
" # Build contract requirements"
]
},
{
"oldStart": 261,
"oldLines": 34,
"newStart": 357,
"newLines": 43,
"lines": [
" contract_text = f\"\"\"",
" Contract: {contract.get(\"name\", \"Standard\")}",
" - Wortanzahl: {req.get(\"min_words\", 500)} - {req.get(\"max_words\", 5000)} Wörter",
"-- Struktur-Validierung: {req.get(\"structure_validation\", True)}",
" \"\"\"",
" ",
"- # Build structure instructions",
"+ # Build structure instructions - detect new vs old format",
" structure_text = \"\"",
"+ output_format = \"markdown\"",
"+ erlaubte_tags = []",
"+",
" if structure:",
" config = structure.get(\"config\", {})",
"- structure_text = f\"\"\"",
"+",
"+ # Detect new format (has \"ausgabe\" at top level)",
"+ if \"ausgabe\" in config or \"gesamtaufbau\" in config:",
"+ # New Blog-Struktur format",
"+ parsed_text, output_format, erlaubte_tags = _parse_new_structure(config)",
"+ structure_text = f\"\"\"",
"+## Struktur: {structure.get(\"name\", \"\")}",
"+",
"+{parsed_text}",
"+\"\"\"",
"+ else:",
"+ # Old format",
"+ structure_text = f\"\"\"",
" Struktur-Template: {structure.get(\"name\", \"\")}",
"-- Typ: {structure.get(\"type\", \"\")}",
" - Abschnitte: {json.dumps(config.get(\"sections\", []), ensure_ascii=False)}",
" \"\"\"",
" ",
"- # Determine if HTML output is required",
"- html_output = False",
"- if profile:",
"- config = profile.get(\"config\", {})",
"- autorenprofil = config.get(\"autorenprofil\", config)",
"- output_format = autorenprofil.get(\"output_format\", {})",
"- html_output = output_format.get(\"typ\") == \"html\"",
"-",
"+ # Build format instruction based on structure's ausgabe",
" format_instruction = \"\"",
"- if html_output:",
"- format_instruction = \"\"\"7. **WICHTIG: Ausgabe als wohlformatiertes HTML**",
"- - Verwende nur: h1, h2, h3, h4, p, ul, ol, li, strong, a, section, table, tr, td",
"- - Jeder h2-Abschnitt muss in einer eigenen <section> stehen",
"- - Kein Markdown, kein div\/span\/br",
"- - Fließtext immer in <p>-Tags\"\"\"",
"+ if output_format == \"body-html\":",
"+ tags_str = \", \".join(erlaubte_tags) if erlaubte_tags else \"h1, h2, h3, h4, p, a, ol, ul, li, strong, table, hr\"",
"+ format_instruction = f\"\"\"7. **KRITISCH - Ausgabe als sauberes HTML:**",
"+ - NUR diese Tags: {tags_str}",
"+ - KEIN Markdown (keine ##, keine **, keine -)",
"+ - KEIN div, span, br, img, script, style",
"+ - Jeder Absatz in <p>-Tags",
"+ - Überschriften als <h2>, <h3>, <h4>",
"+ - Listen als <ul>\/<ol> mit <li>\"\"\"",
" ",
" # Load generate prompt template from database",
" # Note: Assumes db.connect() was called by the caller (generate_content)"
]
}
],
"userModified": false,
"replaceAll": false
}
}