feat(Phase 18): PM-Prompt verschaerft + Auto-Re-Generate bei zu kurzem Output

- SYSTEM_PROMPT mit explizitem 'Mindestens 320 Worte, < 280 ist
  Verstoss' + Hinweis 'wenn Substanz ausgeht: Lebenslage vertiefen
  statt abbrechen'.
- Output-Format-Beispiel mit MINDESTENS-Hinweis.
- generate_draft prüft nach LLM-Call die Wortzahl. Bei <280 Worten:
  ein einzelner Re-Prompt mit höherer Temperatur (0.5) und Hint zur
  ersten zu-kurzen Wortzahl. Wenn der zweite Versuch laenger ist,
  wird er übernommen — sonst bleibt der erste.
- max_retries=1 fuer den zweiten Call (nicht endlos).

Audit-Hauptbefund war 15/19 PMs unter Soll 320–380 Worten.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dotty Dotter 2026-05-06 23:47:42 +02:00
parent 727d7d2976
commit bdbfc1ff7d

View File

@ -95,7 +95,10 @@ Drücke das aus über:
## Stil
- 320380 Worte (länger als bisher konkrete Beispiele brauchen Platz)
- **Mindestens 320 Worte, höchstens 400.** Outputs unter 280 Worten
sind Verstoß gegen die Vorgabe wenn dir die Substanz ausgeht,
vertiefe eine der drei Lebenslagen mit konkreten Zahlen statt
einfach abzubrechen.
- Aktive Verben, kurze Sätze (max 22 Worte)
- Drucksachen-Nummer einmal im Lead nennen ("Drucksache 21/4757")
- Bezug zur News-Lage in 1 Satz, ohne den Medienanbieter zu nennen
@ -159,7 +162,7 @@ Niemals ganze Sätze fett, niemals Zwischenüberschriften.
Antworte NUR mit gültigem JSON:
{
"titel": "<thesenstark, max 100 Zeichen, NENNT die Bürger:innengruppe oder den konkreten Effekt — nicht den GWÖ-Score>",
"body": "<320380 Worte. Mindestens 3 Lebenslagen mit konkretem Effekt. Keine GWÖ-Werte-Aufzählung. Kein Score.>"
"body": "<MINDESTENS 320 Worte (≥ 280 Worte hartes Minimum), höchstens 400. Mindestens 3 Lebenslagen mit konkretem Effekt. Pro Lebenslage: konkrete Zahlen / Personen / Beträge. Keine GWÖ-Werte-Aufzählung. Kein Score.>"
}"""
@ -444,6 +447,41 @@ async def generate_draft(
if needs_split:
body = _split_into_thread_posts(body)
# PM-Wortzahl-Re-Generate: wenn deutlich unter Mindestwortzahl,
# ein Re-Prompt mit höherer Temperatur. Nur ein Versuch — sonst
# endlose LLM-Calls bei zähen Anträgen.
if style == "pm":
word_count = len(body.split())
if word_count < 280:
logger.info(
"PM-Wortzahl %s zu niedrig (Soll ≥320), re-generate mit Hint",
word_count,
)
req2 = LlmRequest(
system_prompt=system_prompt_active,
user_prompt=user_prompt + (
f"\n\nWICHTIG: Der erste Versuch hatte nur {word_count} Worte "
"und ist zu kurz. Liefere jetzt mindestens 320 Worte mit "
"konkreten Zahlen/Personen/Beträgen pro Lebenslage."
),
model=model,
base_temperature=0.5,
max_tokens=1800,
max_retries=1,
json_object_mode=True,
)
try:
result2 = await bewerter.bewerte(req2)
titel2 = (result2.get("titel") or "").strip()[:200]
body2 = (result2.get("body") or "").strip()
body2 = body2.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t")
body2 = _re.sub(r'([.!?])"([A-ZÄÖÜ])', r'\1\n\n\2', body2)
if len(body2.split()) > word_count and titel2 and body2:
titel = titel2
body = body2
except Exception:
logger.exception("PM-Wortzahl-Re-Generate fehlgeschlagen")
if not titel or not body:
raise ValueError("LLM-Response unvollständig (titel oder body leer)")