fix: Pipeline 4/4 Artikel erfolgreich

- _extract_json: Typographische Anfuehrungszeichen ersetzen
- _extract_json: Detailliertes Error-Logging bei Parse-Fehlern
- Writer-Prompt: Regel 5 verbietet doppelte Anfuehrungszeichen
  im Markdown (brechen JSON-String-Werte)
- json.loads(strict=False) fuer rohe Newlines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Dev
2026-03-29 16:26:07 +02:00
Ursprung 8baa4b4716
Commit 4a3b6ee352
2 geänderte Dateien mit 18 neuen und 9 gelöschten Zeilen

Datei anzeigen

@@ -10,6 +10,9 @@ logger = logging.getLogger("blog.curator")
def _extract_json(text): def _extract_json(text):
"""Extrahiert JSON aus Claude-Antworten (robust).""" """Extrahiert JSON aus Claude-Antworten (robust)."""
text = text.strip() text = text.strip()
# Typographische Anfuehrungszeichen ersetzen (brechen JSON)
text = text.replace("", "'").replace("", "'").replace("", "'")
text = text.replace("«", "'").replace("»", "'")
# 1. Direktes Parsen versuchen # 1. Direktes Parsen versuchen
try: try:
return json.loads(text, strict=False) return json.loads(text, strict=False)
@@ -20,8 +23,9 @@ def _extract_json(text):
start = text.find(open_c) start = text.find(open_c)
end = text.rfind(close_c) end = text.rfind(close_c)
if start != -1 and end > start: if start != -1 and end > start:
candidate = text[start:end+1]
try: try:
return json.loads(text[start:end+1], strict=False) return json.loads(candidate, strict=False)
except json.JSONDecodeError: except json.JSONDecodeError:
pass pass
raise json.JSONDecodeError("Kein gueltiges JSON gefunden", text, 0) raise json.JSONDecodeError("Kein gueltiges JSON gefunden", text, 0)

Datei anzeigen

@@ -10,6 +10,9 @@ logger = logging.getLogger("blog.writer")
def _extract_json(text): def _extract_json(text):
"""Extrahiert JSON aus Claude-Antworten (robust).""" """Extrahiert JSON aus Claude-Antworten (robust)."""
text = text.strip() text = text.strip()
# Typographische Anfuehrungszeichen ersetzen (brechen JSON)
text = text.replace("", "'").replace("", "'").replace("", "'")
text = text.replace("«", "'").replace("»", "'")
# 1. Direktes Parsen versuchen # 1. Direktes Parsen versuchen
try: try:
return json.loads(text, strict=False) return json.loads(text, strict=False)
@@ -20,10 +23,11 @@ def _extract_json(text):
start = text.find(open_c) start = text.find(open_c)
end = text.rfind(close_c) end = text.rfind(close_c)
if start != -1 and end > start: if start != -1 and end > start:
candidate = text[start:end+1]
try: try:
return json.loads(text[start:end+1], strict=False) return json.loads(candidate, strict=False)
except json.JSONDecodeError: except json.JSONDecodeError as e:
pass import logging; logging.getLogger("blog.writer.json").warning(f"Parse at {open_c}..{close_c}: {e.msg} at pos {e.pos}, ctx: {repr(candidate[max(0,e.pos-40):e.pos+40])}")
raise json.JSONDecodeError("Kein gueltiges JSON gefunden", text, 0) raise json.JSONDecodeError("Kein gueltiges JSON gefunden", text, 0)
DB_PATH = "/mnt/gitea/osint-data/osint.db" DB_PATH = "/mnt/gitea/osint-data/osint.db"
@@ -115,10 +119,11 @@ REGELN FÜR DEN ARTIKEL:
2. Erzählerischer Fließtext mit Kontext und Einordnung 2. Erzählerischer Fließtext mit Kontext und Einordnung
3. Verwende echte Umlaute (ü, ä, ö, ß) 3. Verwende echte Umlaute (ü, ä, ö, ß)
4. Markdown-Format: ## für Zwischenüberschriften, **fett** für Betonung 4. Markdown-Format: ## für Zwischenüberschriften, **fett** für Betonung
5. 800-1500 Wörter, gut strukturiert 5. WICHTIG: Verwende im Markdown-Text KEINE doppelten Anführungszeichen ("). Nutze stattdessen einfache Anführungszeichen (') oder *kursiv*. Doppelte Anführungszeichen brechen das JSON-Format.
6. Nenne und verlinke Quellen im Text wo möglich 6. 800-1500 Wörter, gut strukturiert
7. Meta-Description: 1 Satz, max 155 Zeichen, für Suchmaschinen 7. Nenne und verlinke Quellen im Text wo möglich
8. Am Ende: Einordnung/Ausblick (was bedeutet das?) 8. Meta-Description: 1 Satz, max 155 Zeichen, für Suchmaschinen
9. Am Ende: Einordnung/Ausblick (was bedeutet das?)
Antworte als JSON: Antworte als JSON:
{{ {{
@@ -153,5 +158,5 @@ Falls das Thema einen geographischen Bezug hat, fülle geo_data:
logger.info(f"Writer: Artikel '{article['title']}' geschrieben (${usage.cost_usd:.4f})") logger.info(f"Writer: Artikel '{article['title']}' geschrieben (${usage.cost_usd:.4f})")
return article return article
except (json.JSONDecodeError, IndexError, KeyError, TypeError) as e: except (json.JSONDecodeError, IndexError, KeyError, TypeError) as e:
logger.error(f"Writer JSON-Parse-Fehler: {e}\nRaw: {result[:300]}") logger.error(f"Writer JSON-Parse-Fehler: {e} | repr: {repr(result[:200])} | has_brace: {chr(123) in result}")
return None return None