diff --git a/src/agents/factchecker.py b/src/agents/factchecker.py index 6d4d61f..a269b42 100644 --- a/src/agents/factchecker.py +++ b/src/agents/factchecker.py @@ -262,6 +262,8 @@ class FactCheckerAgent: facts = self._parse_response(result) logger.info(f"Faktencheck: {len(facts)} Fakten geprüft") return facts, usage + except TimeoutError: + raise # Timeout nach oben durchreichen fuer Retry im Orchestrator except Exception as e: logger.error(f"Faktencheck-Fehler: {e}") return [], None @@ -302,6 +304,8 @@ class FactCheckerAgent: facts = self._parse_response(result) logger.info(f"Inkrementeller Faktencheck: {len(facts)} Fakten (neu + aktualisiert)") return facts, usage + except TimeoutError: + raise # Timeout nach oben durchreichen fuer Retry im Orchestrator except Exception as e: logger.error(f"Inkrementeller Faktencheck-Fehler: {e}") return [], None diff --git a/src/agents/orchestrator.py b/src/agents/orchestrator.py index a47ad26..769c7c6 100644 --- a/src/agents/orchestrator.py +++ b/src/agents/orchestrator.py @@ -392,7 +392,7 @@ class AgentOrchestrator: logger.info(f"Starte Refresh für Lage {incident_id} (Trigger: {trigger_type})") RETRY_DELAYS = [0, 120, 300] # Sekunden: sofort, 2min, 5min - TRANSIENT_ERRORS = (asyncio.TimeoutError, ConnectionError, OSError) + TRANSIENT_ERRORS = (asyncio.TimeoutError, TimeoutError, ConnectionError, OSError) last_error = None try: diff --git a/src/agents/researcher.py b/src/agents/researcher.py index 5b09914..3be95a6 100644 --- a/src/agents/researcher.py +++ b/src/agents/researcher.py @@ -233,6 +233,8 @@ class ResearcherAgent: logger.info(f"Recherche ergab {len(filtered)} Artikel (von {len(articles)} gefundenen, international={international})") return filtered, usage + except TimeoutError: + raise # Timeout nach oben durchreichen fuer Retry im Orchestrator except Exception as e: logger.error(f"Recherche-Fehler: {e}") return [], None @@ -255,11 +257,25 @@ class ResearcherAgent: data = json.loads(response) if isinstance(data, list): return data + if isinstance(data, dict) and "articles" in data: + return data["articles"] except json.JSONDecodeError: pass + # JSON-Code-Block extrahieren + code_pat = r'`{3}(?:json)?\s*\n?(\[.*?\])\s*`{3}' + code_match = re.search(code_pat, response, re.DOTALL) + if code_match: + try: + data = json.loads(code_match.group(1)) + if isinstance(data, list): + return data + except json.JSONDecodeError: + pass + # Versuche JSON aus der Antwort zu extrahieren (zwischen [ und ]) - match = re.search(r'\[.*\]', response, re.DOTALL) + arr_pat = r'\[\s*\{.*\}\s*\]' + match = re.search(arr_pat, response, re.DOTALL) if match: try: data = json.loads(match.group()) @@ -268,5 +284,20 @@ class ResearcherAgent: except json.JSONDecodeError: pass - logger.warning("Konnte Claude-Antwort nicht als JSON parsen") + # Letzter Versuch: einzelne JSON-Objekte mit headline + objects = re.findall(r'\{[^{}]*"headline"[^{}]*\}', response) + if objects: + results = [] + for obj_str in objects: + try: + obj = json.loads(obj_str) + if "headline" in obj: + results.append(obj) + except json.JSONDecodeError: + continue + if results: + logger.info(f"JSON-Recovery: {len(results)} Artikel aus Einzelobjekten extrahiert") + return results + + logger.warning(f"Konnte Claude-Antwort nicht als JSON parsen (Laenge: {len(response)})") return [] diff --git a/src/config.py b/src/config.py index 0bd939e..edaeeca 100644 --- a/src/config.py +++ b/src/config.py @@ -21,7 +21,7 @@ JWT_EXPIRE_HOURS = 24 # Claude CLI CLAUDE_PATH = os.environ.get("CLAUDE_PATH", "/home/claude-dev/.claude/local/claude") -CLAUDE_TIMEOUT = 300 # Sekunden (Claude mit WebSearch braucht oft 2-3 Min) +CLAUDE_TIMEOUT = 420 # Sekunden (Claude mit WebSearch braucht oft 2-4 Min) # Claude Modelle CLAUDE_MODEL_FAST = "claude-haiku-4-5-20251001" # Für einfache Aufgaben (Feed-Selektion)