Block C: Prompt-Umlaute korrigiert + Timeout parametrisiert
- ENHANCE_PROMPT_ADHOC und ENHANCE_PROMPT_RESEARCH: Umschreibungen durch echte Umlaute ersetzt (fuer -> fuer, praezises -> praezises, ...). Behebt den Widerspruch, dass der Prompt "echte Umlaute verwenden" forderte, die Anweisung selbst aber ae/oe/ue/ss nutzte. - call_claude() bekommt neuen timeout-Parameter. None = Fallback auf CLAUDE_TIMEOUT (1800s), sonst Override in Sekunden. asyncio.wait_for und die cancel-aware Variante nutzen durchgaengig den effective_timeout. - Enhance-Endpoint ruft call_claude mit timeout=60 auf (Haiku-Single-Shot, vorher global 1800s). - chat.py _call_claude_chat: Timeout von 60s auf 120s erhoeht (Chat-Antworten koennen etwas laenger dauern, haben aber keinen Anspruch auf 30 Min).
Dieser Commit ist enthalten in:
@@ -77,7 +77,7 @@ def _sanitize_mdash(text: str) -> str:
|
|||||||
"""Ersetzt Gedankenstriche durch Bindestriche (KI-Indikator reduzieren)."""
|
"""Ersetzt Gedankenstriche durch Bindestriche (KI-Indikator reduzieren)."""
|
||||||
return text.replace("\u2014", " - ").replace("\u2013", " - ")
|
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.
|
"""Ruft Claude CLI auf. Gibt (result_text, usage) zurück.
|
||||||
|
|
||||||
Prompt wird via stdin uebergeben um OS ARG_MAX Limits zu vermeiden.
|
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
|
prompt: Der Prompt fuer Claude
|
||||||
tools: Kommagetrennte erlaubte Tools (None = keine Tools, --max-turns 1)
|
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).
|
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_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]
|
cmd = [CLAUDE_PATH, "-p", "-", "--output-format", "json", "--model", effective_model]
|
||||||
if tools:
|
if tools:
|
||||||
cmd.extend(["--allowedTools", 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"))
|
process.communicate(input=prompt.encode("utf-8"))
|
||||||
)
|
)
|
||||||
cancel_wait_task = asyncio.create_task(cancel_event.wait())
|
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(
|
done, pending = await asyncio.wait(
|
||||||
[communicate_task, cancel_wait_task, timeout_task],
|
[communicate_task, cancel_wait_task, timeout_task],
|
||||||
@@ -137,14 +139,14 @@ async def call_claude(prompt: str, tools: str | None = "WebSearch,WebFetch", mod
|
|||||||
else:
|
else:
|
||||||
process.kill()
|
process.kill()
|
||||||
await process.wait()
|
await process.wait()
|
||||||
raise TimeoutError(f"Claude CLI Timeout nach {CLAUDE_TIMEOUT}s")
|
raise TimeoutError(f"Claude CLI Timeout nach {effective_timeout}s")
|
||||||
else:
|
else:
|
||||||
stdout, stderr = await asyncio.wait_for(
|
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:
|
except asyncio.TimeoutError:
|
||||||
process.kill()
|
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:
|
if process.returncode != 0:
|
||||||
error_msg = stderr.decode("utf-8", errors="replace").strip()
|
error_msg = stderr.decode("utf-8", errors="replace").strip()
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ async def _call_claude_chat(prompt: str) -> tuple[str, int, ClaudeUsage]:
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
stdout, stderr = await asyncio.wait_for(
|
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:
|
except asyncio.TimeoutError:
|
||||||
process.kill()
|
process.kill()
|
||||||
|
|||||||
@@ -197,31 +197,31 @@ async def get_refreshing_incidents(
|
|||||||
# --- Beschreibung generieren (Prompt Enhancement) ---
|
# --- Beschreibung generieren (Prompt Enhancement) ---
|
||||||
|
|
||||||
ENHANCE_PROMPT_RESEARCH = """Du bist ein Recherche-Planer in einem OSINT-Lagemonitoring-System.
|
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.
|
Du behauptest KEINE Fakten und musst das Thema NICHT kennen oder verifizieren.
|
||||||
Der Nutzer gibt das Thema vor -- du definierst Suchrichtungen, Schwerpunkte und Stichworte.
|
Der Nutzer gibt das Thema vor -- du definierst Suchrichtungen, Schwerpunkte und Stichworte.
|
||||||
Erstelle das Briefing IMMER, auch wenn dir das Thema unbekannt ist.
|
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}
|
Titel: {title}
|
||||||
Vorhandener Kontext: {context}
|
Vorhandener Kontext: {context}
|
||||||
Typ: Hintergrundrecherche
|
Typ: Hintergrundrecherche
|
||||||
|
|
||||||
Erstelle ein praezises Recherche-Briefing mit:
|
Erstelle ein präzises Recherche-Briefing mit:
|
||||||
1. Fallbezeichnung (vollstaendige Benennung des Themas basierend auf Titel und Kontext)
|
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, Hintergruende, Chronologie)
|
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. Abkuerzungen und alternative Schreibweisen)
|
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.
|
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.
|
Du behauptest KEINE Fakten und musst den Vorfall NICHT kennen oder verifizieren.
|
||||||
Der Nutzer gibt das Thema vor -- du strukturierst, wonach gesucht werden soll.
|
Der Nutzer gibt das Thema vor -- du strukturierst, wonach gesucht werden soll.
|
||||||
Erstelle die Beschreibung IMMER, auch wenn dir der Vorfall unbekannt ist.
|
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}
|
Titel: {title}
|
||||||
Vorhandener Kontext: {context}
|
Vorhandener Kontext: {context}
|
||||||
@@ -230,10 +230,10 @@ Typ: Live-Monitoring (aktuelle Ereignisse)
|
|||||||
Erstelle eine knappe, informative Beschreibung mit:
|
Erstelle eine knappe, informative Beschreibung mit:
|
||||||
1. Was ist passiert / worum geht es (basierend auf Titel und Kontext)
|
1. Was ist passiert / worum geht es (basierend auf Titel und Kontext)
|
||||||
2. Wo (geographischer Kontext, falls ableitbar)
|
2. Wo (geographischer Kontext, falls ableitbar)
|
||||||
3. Wer ist beteiligt (Akteure, Organisationen, Laender)
|
3. Wer ist beteiligt (Akteure, Organisationen, Länder)
|
||||||
4. Wonach soll gesucht werden (aktuelle Entwicklungen, Reaktionen, Hintergruende)
|
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")
|
_enhance_logger = logging.getLogger("osint.enhance")
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ async def enhance_description(
|
|||||||
prompt = template.format(title=data.title.strip(), context=context)
|
prompt = template.format(title=data.title.strip(), context=context)
|
||||||
|
|
||||||
try:
|
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:
|
except ClaudeCliError as e:
|
||||||
_enhance_logger.error(f"Beschreibung generieren: ClaudeCliError [{e.error_type}]: {e.message}")
|
_enhance_logger.error(f"Beschreibung generieren: ClaudeCliError [{e.error_type}]: {e.message}")
|
||||||
if e.error_type == "auth_error":
|
if e.error_type == "auth_error":
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren