diff --git a/src/agents/claude_client.py b/src/agents/claude_client.py index a79d72f..eb99629 100644 --- a/src/agents/claude_client.py +++ b/src/agents/claude_client.py @@ -77,7 +77,7 @@ def _sanitize_mdash(text: str) -> str: """Ersetzt Gedankenstriche durch Bindestriche (KI-Indikator reduzieren).""" return text.replace("\u2014", " - ").replace("\u2013", " - ") -async def call_claude(prompt: str, tools: str | None = "WebSearch,WebFetch", model: str | None = None, raw_text: bool = False) -> tuple[str, ClaudeUsage]: +async def call_claude(prompt: str, tools: str | None = "WebSearch,WebFetch", model: str | None = None, raw_text: bool = False, timeout: float | None = None) -> tuple[str, ClaudeUsage]: """Ruft Claude CLI auf. Gibt (result_text, usage) zurück. Prompt wird via stdin uebergeben um OS ARG_MAX Limits zu vermeiden. @@ -86,8 +86,10 @@ async def call_claude(prompt: str, tools: str | None = "WebSearch,WebFetch", mod prompt: Der Prompt fuer Claude tools: Kommagetrennte erlaubte Tools (None = keine Tools, --max-turns 1) model: Optionales Modell (z.B. CLAUDE_MODEL_FAST fuer Haiku). None = CLAUDE_MODEL_STANDARD (Opus 4.7). + timeout: Override in Sekunden. None = Fallback auf globalen CLAUDE_TIMEOUT (1800s). """ effective_model = model or CLAUDE_MODEL_STANDARD + effective_timeout = timeout if timeout is not None else CLAUDE_TIMEOUT cmd = [CLAUDE_PATH, "-p", "-", "--output-format", "json", "--model", effective_model] if tools: cmd.extend(["--allowedTools", tools]) @@ -118,7 +120,7 @@ async def call_claude(prompt: str, tools: str | None = "WebSearch,WebFetch", mod process.communicate(input=prompt.encode("utf-8")) ) cancel_wait_task = asyncio.create_task(cancel_event.wait()) - timeout_task = asyncio.create_task(asyncio.sleep(CLAUDE_TIMEOUT)) + timeout_task = asyncio.create_task(asyncio.sleep(effective_timeout)) done, pending = await asyncio.wait( [communicate_task, cancel_wait_task, timeout_task], @@ -137,14 +139,14 @@ async def call_claude(prompt: str, tools: str | None = "WebSearch,WebFetch", mod else: process.kill() await process.wait() - raise TimeoutError(f"Claude CLI Timeout nach {CLAUDE_TIMEOUT}s") + raise TimeoutError(f"Claude CLI Timeout nach {effective_timeout}s") else: stdout, stderr = await asyncio.wait_for( - process.communicate(input=prompt.encode("utf-8")), timeout=CLAUDE_TIMEOUT + process.communicate(input=prompt.encode("utf-8")), timeout=effective_timeout ) except asyncio.TimeoutError: process.kill() - raise TimeoutError(f"Claude CLI Timeout nach {CLAUDE_TIMEOUT}s") + raise TimeoutError(f"Claude CLI Timeout nach {effective_timeout}s") if process.returncode != 0: error_msg = stderr.decode("utf-8", errors="replace").strip() diff --git a/src/routers/chat.py b/src/routers/chat.py index 30c1277..737f925 100644 --- a/src/routers/chat.py +++ b/src/routers/chat.py @@ -51,7 +51,7 @@ async def _call_claude_chat(prompt: str) -> tuple[str, int, ClaudeUsage]: ) try: stdout, stderr = await asyncio.wait_for( - process.communicate(input=prompt.encode("utf-8")), timeout=60 + process.communicate(input=prompt.encode("utf-8")), timeout=120 ) except asyncio.TimeoutError: process.kill() diff --git a/src/routers/incidents.py b/src/routers/incidents.py index 42276ea..3cde9f4 100644 --- a/src/routers/incidents.py +++ b/src/routers/incidents.py @@ -197,31 +197,31 @@ async def get_refreshing_incidents( # --- Beschreibung generieren (Prompt Enhancement) --- ENHANCE_PROMPT_RESEARCH = """Du bist ein Recherche-Planer in einem OSINT-Lagemonitoring-System. -Deine Aufgabe: Strukturiere ein Recherche-Briefing, das Analysten als Leitfaden fuer ihre Suche verwenden. +Deine Aufgabe: Strukturiere ein Recherche-Briefing, das Analysten als Leitfaden für ihre Suche verwenden. Du behauptest KEINE Fakten und musst das Thema NICHT kennen oder verifizieren. Der Nutzer gibt das Thema vor -- du definierst Suchrichtungen, Schwerpunkte und Stichworte. Erstelle das Briefing IMMER, auch wenn dir das Thema unbekannt ist. -WICHTIG: Verwende IMMER echte UTF-8-Umlaute (ae, oe, ue, ss) und KEINE Umschreibungen. +WICHTIG: Verwende IMMER echte Umlaute (ä, ö, ü, ß) und KEINE Umschreibungen. Titel: {title} Vorhandener Kontext: {context} Typ: Hintergrundrecherche -Erstelle ein praezises Recherche-Briefing mit: -1. Fallbezeichnung (vollstaendige Benennung des Themas basierend auf Titel und Kontext) -2. Recherche-Schwerpunkte (5-8 thematische Punkte, z.B. Sachverhalt, beteiligte Parteien, rechtliche Aspekte, mediale Rezeption, Hintergruende, Chronologie) -3. Relevante Suchbegriffe (deutsch + englisch, inkl. Abkuerzungen und alternative Schreibweisen) +Erstelle ein präzises Recherche-Briefing mit: +1. Fallbezeichnung (vollständige Benennung des Themas basierend auf Titel und Kontext) +2. Recherche-Schwerpunkte (5-8 thematische Punkte, z.B. Sachverhalt, beteiligte Parteien, rechtliche Aspekte, mediale Rezeption, Hintergründe, Chronologie) +3. Relevante Suchbegriffe (deutsch + englisch, inkl. Abkürzungen und alternative Schreibweisen) -Schreibe NUR das Briefing als Fliesstext mit Aufzaehlungen. Keine Erklaerungen, Rueckfragen oder Disclaimer.""" +Schreibe NUR das Briefing als Fließtext mit Aufzählungen. Keine Erklärungen, Rückfragen oder Disclaimer.""" ENHANCE_PROMPT_ADHOC = """Du bist ein Recherche-Planer in einem OSINT-Lagemonitoring-System. -Deine Aufgabe: Erstelle eine knappe Vorfallsbeschreibung, die als Suchauftrag fuer Live-Monitoring dient. +Deine Aufgabe: Erstelle eine knappe Vorfallsbeschreibung, die als Suchauftrag für Live-Monitoring dient. Du behauptest KEINE Fakten und musst den Vorfall NICHT kennen oder verifizieren. Der Nutzer gibt das Thema vor -- du strukturierst, wonach gesucht werden soll. Erstelle die Beschreibung IMMER, auch wenn dir der Vorfall unbekannt ist. -WICHTIG: Verwende IMMER echte UTF-8-Umlaute (ae, oe, ue, ss) und KEINE Umschreibungen. +WICHTIG: Verwende IMMER echte Umlaute (ä, ö, ü, ß) und KEINE Umschreibungen. Titel: {title} Vorhandener Kontext: {context} @@ -230,10 +230,10 @@ Typ: Live-Monitoring (aktuelle Ereignisse) Erstelle eine knappe, informative Beschreibung mit: 1. Was ist passiert / worum geht es (basierend auf Titel und Kontext) 2. Wo (geographischer Kontext, falls ableitbar) -3. Wer ist beteiligt (Akteure, Organisationen, Laender) -4. Wonach soll gesucht werden (aktuelle Entwicklungen, Reaktionen, Hintergruende) +3. Wer ist beteiligt (Akteure, Organisationen, Länder) +4. Wonach soll gesucht werden (aktuelle Entwicklungen, Reaktionen, Hintergründe) -Schreibe NUR die Beschreibung als Fliesstext (3-5 Zeilen). Keine Erklaerungen, Rueckfragen oder Disclaimer.""" +Schreibe NUR die Beschreibung als Fließtext (3-5 Zeilen). Keine Erklärungen, Rückfragen oder Disclaimer.""" _enhance_logger = logging.getLogger("osint.enhance") @@ -254,7 +254,7 @@ async def enhance_description( prompt = template.format(title=data.title.strip(), context=context) try: - result, usage = await call_claude(prompt, tools=None, model=CLAUDE_MODEL_FAST, raw_text=True) + result, usage = await call_claude(prompt, tools=None, model=CLAUDE_MODEL_FAST, raw_text=True, timeout=60) except ClaudeCliError as e: _enhance_logger.error(f"Beschreibung generieren: ClaudeCliError [{e.error_type}]: {e.message}") if e.error_type == "auth_error":