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

Datei anzeigen

@@ -10,6 +10,9 @@ logger = logging.getLogger("blog.writer")
def _extract_json(text):
"""Extrahiert JSON aus Claude-Antworten (robust)."""
text = text.strip()
# Typographische Anfuehrungszeichen ersetzen (brechen JSON)
text = text.replace("", "'").replace("", "'").replace("", "'")
text = text.replace("«", "'").replace("»", "'")
# 1. Direktes Parsen versuchen
try:
return json.loads(text, strict=False)
@@ -20,10 +23,11 @@ def _extract_json(text):
start = text.find(open_c)
end = text.rfind(close_c)
if start != -1 and end > start:
candidate = text[start:end+1]
try:
return json.loads(text[start:end+1], strict=False)
except json.JSONDecodeError:
pass
return json.loads(candidate, strict=False)
except json.JSONDecodeError as e:
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)
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
3. Verwende echte Umlaute (ü, ä, ö, ß)
4. Markdown-Format: ## für Zwischenüberschriften, **fett** für Betonung
5. 800-1500 Wörter, gut strukturiert
6. Nenne und verlinke Quellen im Text wo möglich
7. Meta-Description: 1 Satz, max 155 Zeichen, für Suchmaschinen
8. Am Ende: Einordnung/Ausblick (was bedeutet das?)
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. 800-1500 Wörter, gut strukturiert
7. Nenne und verlinke Quellen im Text wo möglich
8. Meta-Description: 1 Satz, max 155 Zeichen, für Suchmaschinen
9. Am Ende: Einordnung/Ausblick (was bedeutet das?)
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})")
return article
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