feat(orchestrator): Faktencheck vor Lagebild mit Fallback (sequenziell)
Bislang liefen factcheck + analyze parallel via asyncio.gather. Folge:
Lagebild konnte Aussagen treffen, die der Faktencheck im selben Refresh als
contradicted markiert. Inkonsistenz zwischen Lagebild-Tab und Faktencheck-
Tab; im PDF/DOCX-Export schon kritisch.
Variante 1 aus der Diskussion: strikt sequenziell, mit Fallback bei
Faktencheck-Fail (Refresh bricht NICHT ab, Lagebild laeuft dann ohne
Faktenkontext wie bisher, ein Logeintrag dokumentiert den Fallback).
Aenderungen:
- analyzer.build_fact_context_block(): neuer Helper, baut den
GEPRUEFTE-FAKTEN-Block aus existing_facts + neuen/aktualisierten
Fakten. Status-Domaenen adhoc/research vereinheitlicht zu Bestaetigt /
Umstritten / Unbestaetigt / Entwicklung. Max 20 Fakten, sortiert nach
Status-Prioritaet desc und sources_count desc. Bei leerer Eingabe
leerer String -> Fallback-Pfad.
- analyzer.analyze() / analyze_incremental(): neuer Optional-Parameter
fact_context_block (default leer, Backward-Compat). 4 Prompt-Templates
bekommen {fact_context_block}-Platzhalter sowie eine AUSSAGE-DISZIPLIN-
Sektion: bestaetigte Fakten als Geruest, Umstrittenes explizit machen,
Unbestaetigtes klar einordnen, kein Spekulieren ueber ungedecktes.
- orchestrator: asyncio.gather durch sequenzielle Logik ersetzt.
Faktencheck zuerst, Pipeline-Step 6 done direkt nach dem Aufruf
(count_value ist Schaetzung; finale DB-Zahlen stehen spaeter). Lagebild
danach (Step 7) mit fact_context_block. _do_analysis-Closure um den
Parameter erweitert, kein toter Inline-Block.
- spaeteres _pipe_done(factcheck) entfernt -- der Step wird jetzt frueher
geschlossen, der spaetere Persistierungsblock laesst ihn unberuehrt.
UI-Pipeline zeigt automatisch sequenzielle Aktivitaet statt beide Steps
gleichzeitig -- keine Frontend-Aenderung noetig.
Latenz pro Refresh steigt um die factcheck-Dauer. Bewusst akzeptiert:
Konsistenz vor Geschwindigkeit.
Dieser Commit ist enthalten in:
@@ -1299,18 +1299,22 @@ class AgentOrchestrator:
|
||||
except Exception as e:
|
||||
logger.warning("Bias-Anreicherung fehlgeschlagen (Pipeline laeuft weiter): %s", e)
|
||||
|
||||
# --- Analyse-Task ---
|
||||
async def _do_analysis():
|
||||
# --- Analyse-Task (wird nach _do_factcheck mit fact_context_block aufgerufen) ---
|
||||
async def _do_analysis(fact_context_block: str = ""):
|
||||
analyzer = AnalyzerAgent()
|
||||
if previous_summary and new_count > 0:
|
||||
logger.info(f"Inkrementelle Analyse: {new_count} neue Artikel zum bestehenden Lagebild")
|
||||
return await analyzer.analyze_incremental(
|
||||
title, description, new_articles_for_analysis,
|
||||
previous_summary, previous_sources_json, incident_type,
|
||||
fact_context_block=fact_context_block,
|
||||
)
|
||||
else:
|
||||
logger.info("Erstanalyse: Alle Artikel werden analysiert")
|
||||
return await analyzer.analyze(title, description, all_articles_preloaded, incident_type)
|
||||
return await analyzer.analyze(
|
||||
title, description, all_articles_preloaded, incident_type,
|
||||
fact_context_block=fact_context_block,
|
||||
)
|
||||
|
||||
# --- Faktencheck-Task ---
|
||||
async def _do_factcheck():
|
||||
@@ -1344,20 +1348,61 @@ class AgentOrchestrator:
|
||||
articles_for_check = [dict(row) for row in await cursor.fetchall()]
|
||||
return await factchecker.check(title, articles_for_check, incident_type)
|
||||
|
||||
# Pipeline-Schritte 6+7: Lagebild verfassen + Fakten prüfen (Start, parallel)
|
||||
await _pipe_start("summary")
|
||||
# Pipeline-Schritt 6: Faktencheck zuerst (sequenziell). Liefert den
|
||||
# Faktenkontext fuer das Lagebild, damit dieses auf geprueftem Stand
|
||||
# schreibt und Unklarheiten explizit benennt. Variante 1: bei
|
||||
# Faktencheck-Fehler faellt das Lagebild auf den alten Pfad ohne
|
||||
# Faktenkontext zurueck (Refresh bricht NICHT ab).
|
||||
await _pipe_start("factcheck")
|
||||
factcheck_result: tuple = ([], None)
|
||||
fact_context_block = ""
|
||||
factcheck_failed_reason: str | None = None
|
||||
try:
|
||||
factcheck_result = await _do_factcheck()
|
||||
except Exception as fc_err:
|
||||
factcheck_failed_reason = str(fc_err)
|
||||
logger.warning(
|
||||
"Faktencheck fehlgeschlagen, Lagebild laeuft ohne Faktenkontext: %s",
|
||||
fc_err, exc_info=True,
|
||||
)
|
||||
|
||||
# Beide Tasks PARALLEL starten
|
||||
logger.info("Starte Analyse und Faktencheck parallel...")
|
||||
analysis_result, factcheck_result = await asyncio.gather(
|
||||
_do_analysis(),
|
||||
_do_factcheck(),
|
||||
fact_checks, fc_usage = factcheck_result if factcheck_result else ([], None)
|
||||
|
||||
# Pipeline-Schritt 6 done direkt nach dem Aufruf — die finale
|
||||
# DB-Persistierung passiert weiter unten, aber fuer die UI ist
|
||||
# der Faktencheck-Aufruf hier abgeschlossen. Der count_value
|
||||
# ist eine Schaetzung (echte Zahl steht spaeter in der DB).
|
||||
_fc_estimated_new = max(0, len(fact_checks or []) - len(existing_facts or []))
|
||||
await _pipe_done(
|
||||
"factcheck",
|
||||
count_value=_fc_estimated_new,
|
||||
count_secondary=len(fact_checks) if fact_checks else 0,
|
||||
)
|
||||
|
||||
# Faktenkontext fuer das Lagebild bauen.
|
||||
try:
|
||||
from agents.analyzer import build_fact_context_block as _build_fc_ctx
|
||||
fact_context_block = _build_fc_ctx(
|
||||
existing_facts or [], fact_checks or [], incident_type,
|
||||
)
|
||||
if fact_context_block:
|
||||
logger.info(
|
||||
"Faktenkontext fuer Lagebild: %d Zeichen, basierend auf %d alten + %d neuen Fakten",
|
||||
len(fact_context_block), len(existing_facts or []), len(fact_checks or []),
|
||||
)
|
||||
except Exception as ctx_err:
|
||||
logger.warning("build_fact_context_block fehlgeschlagen: %s", ctx_err, exc_info=True)
|
||||
fact_context_block = ""
|
||||
|
||||
# Pipeline-Schritt 7: Lagebild verfassen (jetzt mit Faktenkontext)
|
||||
await _pipe_start("summary")
|
||||
logger.info(
|
||||
"Starte Lagebild (sequenziell nach Faktencheck%s)",
|
||||
" — OHNE Faktenkontext (Fallback)" if factcheck_failed_reason else "",
|
||||
)
|
||||
analysis_result = await _do_analysis(fact_context_block)
|
||||
|
||||
analysis, analysis_usage = analysis_result
|
||||
fact_checks, fc_usage = factcheck_result
|
||||
# Pipeline-Schritt 6: Lagebild verfassen (fertig, keine Zahl, nur Status)
|
||||
await _pipe_done("summary", count_value=None, count_secondary=None)
|
||||
|
||||
# --- Analyse-Ergebnisse verarbeiten ---
|
||||
@@ -1656,9 +1701,10 @@ class AgentOrchestrator:
|
||||
|
||||
await db.commit()
|
||||
|
||||
# Pipeline-Schritt 7: Fakten prüfen (fertig)
|
||||
_new_facts_count = max(0, len(fact_checks) - len(existing_facts))
|
||||
await _pipe_done("factcheck", count_value=_new_facts_count, count_secondary=len(fact_checks) if fact_checks else 0)
|
||||
# Pipeline-Schritt 7 (Fakten pruefen) wurde bereits frueher als done
|
||||
# markiert (siehe weiter oben — direkt nach dem _do_factcheck-Aufruf,
|
||||
# bevor das Lagebild generiert wurde). Hier nur noch die DB-
|
||||
# Persistierung der Fakten, ohne den Step erneut zu schliessen.
|
||||
|
||||
# Pipeline-Schritt 8: Qualitätscheck (Start, ohne Zahlen)
|
||||
await _pipe_start("qc")
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren