From 4a3b6ee352916fc6285f28798e68e1afa21707fc Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Sun, 29 Mar 2026 16:26:07 +0200 Subject: [PATCH] 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) --- src/agents/blog/blog_curator.py | 6 +++++- src/agents/blog/blog_writer.py | 21 +++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/agents/blog/blog_curator.py b/src/agents/blog/blog_curator.py index 85bd6cb..e2a506a 100644 --- a/src/agents/blog/blog_curator.py +++ b/src/agents/blog/blog_curator.py @@ -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) diff --git a/src/agents/blog/blog_writer.py b/src/agents/blog/blog_writer.py index 5acf041..5e54acf 100644 --- a/src/agents/blog/blog_writer.py +++ b/src/agents/blog/blog_writer.py @@ -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