Initial commit: AegisSight-Monitor (OSINT-Monitoringsystem)
Dieser Commit ist enthalten in:
143
src/agents/factchecker.py
Normale Datei
143
src/agents/factchecker.py
Normale Datei
@@ -0,0 +1,143 @@
|
||||
"""Factchecker-Agent: Prüft Fakten gegen mehrere unabhängige Quellen."""
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from agents.claude_client import call_claude, ClaudeUsage
|
||||
|
||||
logger = logging.getLogger("osint.factchecker")
|
||||
|
||||
FACTCHECK_PROMPT_TEMPLATE = """Du bist ein Faktencheck-Agent für ein OSINT-Lagemonitoring-System.
|
||||
AUSGABESPRACHE: {output_language}
|
||||
|
||||
VORFALL: {title}
|
||||
|
||||
VORLIEGENDE MELDUNGEN:
|
||||
{articles_text}
|
||||
|
||||
STRENGE REGELN - KEINE HALLUZINATIONEN:
|
||||
- Du darfst NUR Fakten bewerten, die direkt aus den oben übergebenen Meldungen stammen
|
||||
- KEINE Fakten aus deinem Trainingskorpus - NUR aus den übergebenen Meldungen + WebSearch
|
||||
- Nutze WebSearch um jeden Claim gegen mindestens 1 weitere unabhängige Quelle zu prüfen
|
||||
- Rufe die gefundenen URLs per WebFetch ab um den Inhalt zu verifizieren
|
||||
- Nur wenn du den Claim in der tatsächlich abgerufenen Quelle findest, darfst du ihn als bestätigt markieren
|
||||
- Jeder Claim MUSS eine konkrete Quellen-URL als Beleg enthalten
|
||||
- "confirmed" erst bei 2+ unabhängigen Quellen mit überprüfbarer URL
|
||||
- Lieber "unconfirmed" als falsch bestätigt
|
||||
|
||||
AUFTRAG:
|
||||
1. Identifiziere die 5-10 wichtigsten Faktenaussagen aus den Meldungen
|
||||
2. Prüfe jeden Claim aktiv per WebSearch gegen mindestens eine weitere unabhängige Quelle
|
||||
3. Kategorisiere jede Aussage:
|
||||
- "confirmed": Durch 2+ unabhängige seriöse Quellen mit überprüfbarer URL bestätigt
|
||||
- "unconfirmed": Nur 1 Quelle oder nicht unabhängig verifizierbar
|
||||
- "contradicted": Widersprüchliche Informationen aus verschiedenen Quellen
|
||||
- "developing": Situation noch unklar, entwickelt sich
|
||||
4. Markiere WICHTIGE NEUE Entwicklungen mit is_notification: true
|
||||
|
||||
Antworte AUSSCHLIESSLICH als JSON-Array. Jedes Element hat:
|
||||
- "claim": Die Faktenaussage auf {output_language}
|
||||
- "status": "confirmed" | "unconfirmed" | "contradicted" | "developing"
|
||||
- "sources_count": Anzahl unabhängiger Quellen mit überprüfbarer URL
|
||||
- "evidence": Begründung MIT konkreten Quellen-URLs als Beleg (z.B. "Bestätigt durch: tagesschau.de (URL), Reuters (URL)")
|
||||
- "is_notification": true/false (nur bei wichtigen Entwicklungen true)
|
||||
|
||||
Antworte NUR mit dem JSON-Array. Keine Einleitung, keine Erklärung."""
|
||||
|
||||
RESEARCH_FACTCHECK_PROMPT_TEMPLATE = """Du bist ein Faktencheck-Agent für eine Hintergrundrecherche in einem OSINT-Lagemonitoring-System.
|
||||
AUSGABESPRACHE: {output_language}
|
||||
|
||||
THEMA: {title}
|
||||
|
||||
VORLIEGENDE QUELLEN:
|
||||
{articles_text}
|
||||
|
||||
STRENGE REGELN - KEINE HALLUZINATIONEN:
|
||||
- Du darfst NUR Fakten bewerten, die direkt aus den oben übergebenen Quellen stammen
|
||||
- KEINE Fakten aus deinem Trainingskorpus - NUR aus den übergebenen Quellen + WebSearch
|
||||
- Nutze WebSearch um jeden Claim gegen mindestens 1 weitere unabhängige Quelle zu prüfen
|
||||
- Rufe die gefundenen URLs per WebFetch ab um den Inhalt zu verifizieren
|
||||
- Nur wenn du den Claim in der tatsächlich abgerufenen Quelle findest, darfst du ihn als gesichert markieren
|
||||
- Jeder Claim MUSS eine konkrete Quellen-URL als Beleg enthalten
|
||||
- Lieber "unverified" als falsch bestätigt
|
||||
|
||||
AUFTRAG:
|
||||
Fokus: "Was sind die gesicherten Fakten zu diesem Thema?"
|
||||
|
||||
1. Identifiziere die 5-10 wichtigsten Faktenaussagen aus den Quellen
|
||||
2. Prüfe jeden Claim aktiv per WebSearch gegen weitere unabhängige Quellen
|
||||
3. Kategorisiere jede Aussage:
|
||||
- "established": Breit dokumentierter, gesicherter Fakt (3+ unabhängige Quellen mit URL)
|
||||
- "disputed": Umstrittener Sachverhalt, verschiedene Positionen dokumentiert
|
||||
- "unverified": Einzelbehauptung, nicht unabhängig verifizierbar
|
||||
- "developing": Aktuelle Entwicklung, Faktenlage noch im Fluss
|
||||
4. Markiere WICHTIGE Erkenntnisse mit is_notification: true
|
||||
|
||||
Antworte AUSSCHLIESSLICH als JSON-Array. Jedes Element hat:
|
||||
- "claim": Die Faktenaussage auf {output_language}
|
||||
- "status": "established" | "disputed" | "unverified" | "developing"
|
||||
- "sources_count": Anzahl unabhängiger Quellen mit überprüfbarer URL
|
||||
- "evidence": Begründung MIT konkreten Quellen-URLs als Beleg
|
||||
- "is_notification": true/false
|
||||
|
||||
Antworte NUR mit dem JSON-Array. Keine Einleitung, keine Erklärung."""
|
||||
|
||||
|
||||
class FactCheckerAgent:
|
||||
"""Prüft Fakten über Claude CLI gegen unabhängige Quellen."""
|
||||
|
||||
async def check(self, title: str, articles: list[dict], incident_type: str = "adhoc") -> tuple[list[dict], ClaudeUsage | None]:
|
||||
"""Führt Faktencheck für eine Lage durch."""
|
||||
if not articles:
|
||||
return [], None
|
||||
|
||||
articles_text = ""
|
||||
for i, article in enumerate(articles[:20]):
|
||||
articles_text += f"\n--- Meldung {i+1} ---\n"
|
||||
articles_text += f"Quelle: {article.get('source', 'Unbekannt')}\n"
|
||||
source_url = article.get('source_url', '')
|
||||
if source_url:
|
||||
articles_text += f"URL: {source_url}\n"
|
||||
headline = article.get('headline_de') or article.get('headline', '')
|
||||
articles_text += f"Überschrift: {headline}\n"
|
||||
content = article.get('content_de') or article.get('content_original', '')
|
||||
if content:
|
||||
articles_text += f"Inhalt: {content[:300]}\n"
|
||||
|
||||
from config import OUTPUT_LANGUAGE
|
||||
template = RESEARCH_FACTCHECK_PROMPT_TEMPLATE if incident_type == "research" else FACTCHECK_PROMPT_TEMPLATE
|
||||
prompt = template.format(
|
||||
title=title,
|
||||
articles_text=articles_text,
|
||||
output_language=OUTPUT_LANGUAGE,
|
||||
)
|
||||
|
||||
try:
|
||||
result, usage = await call_claude(prompt)
|
||||
facts = self._parse_response(result)
|
||||
logger.info(f"Faktencheck: {len(facts)} Fakten geprüft")
|
||||
return facts, usage
|
||||
except Exception as e:
|
||||
logger.error(f"Faktencheck-Fehler: {e}")
|
||||
return [], None
|
||||
|
||||
def _parse_response(self, response: str) -> list[dict]:
|
||||
"""Parst die Claude-Antwort als JSON-Array."""
|
||||
try:
|
||||
data = json.loads(response)
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
match = re.search(r'\[.*\]', response, re.DOTALL)
|
||||
if match:
|
||||
try:
|
||||
data = json.loads(match.group())
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
logger.warning("Konnte Faktencheck-Antwort nicht als JSON parsen")
|
||||
return []
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren