feat(pipeline): output_language pro Org durch die Pipeline reichen

- OUTPUT_LANGUAGE Konstante aus config.py entfernt (jetzt pro Org in
  organization_settings).
- Orchestrator laedt output_language einmal pro Refresh aus der Org-Sprache.
- researcher.search(), analyzer.analyze/.analyze_incremental/.generate_latest_developments,
  factchecker.check/.check_incremental/.check_incremental_twophase bekommen
  output_language als Parameter (Default Deutsch).
- LANG_INTERNATIONAL / LANG_GERMAN_ONLY (+ Deep-Varianten) sind Funktionen,
  die je nach output_language die Sprachanweisung erzeugen (Deutsch | English
  | Fallback).
- Sprachfilter in researcher.search ist org-relativ: bei nicht-international
  werden Artikel mit Sprache != output_language_iso gefiltert.

Phase 2 von 8 (eng_demo / Org-Sprache). Bestandsorgs unveraendert, weil
Default-Setting weiterhin de (siehe Phase-1-Migration).
Dieser Commit ist enthalten in:
Claude Code
2026-05-13 20:54:28 +00:00
Ursprung d27d586003
Commit f68d25dbce
5 geänderte Dateien mit 68 neuen und 32 gelöschten Zeilen

Datei anzeigen

@@ -153,12 +153,37 @@ Jedes Element hat diese Felder:
Antworte NUR mit dem JSON-Array. Keine Einleitung, keine Erklärung."""
# Sprach-Anweisungen
LANG_INTERNATIONAL = "- Suche in Deutsch UND Englisch für internationale Abdeckung"
LANG_GERMAN_ONLY = "- Suche NUR auf Deutsch bei deutschsprachigen Quellen (Deutschland, Österreich, Schweiz)\n- KEINE englischsprachigen oder anderssprachigen Quellen"
# Sprach-Anweisungen (org-sprach-relativ; primary_display = "Deutsch" | "English")
def lang_international(primary_display: str) -> str:
if primary_display == "Deutsch":
return "- Suche in Deutsch UND Englisch für internationale Abdeckung"
if primary_display == "English":
return "- Search in English AND other relevant languages for international coverage"
return f"- Suche in {primary_display} und weiteren relevanten Sprachen"
LANG_DEEP_INTERNATIONAL = "- Suche in Deutsch, Englisch und weiteren relevanten Sprachen"
LANG_DEEP_GERMAN_ONLY = "- Suche NUR auf Deutsch bei deutschsprachigen Quellen (Deutschland, Österreich, Schweiz)\n- KEINE englischsprachigen oder anderssprachigen Quellen"
def lang_primary_only(primary_display: str) -> str:
if primary_display == "Deutsch":
return "- Suche NUR auf Deutsch bei deutschsprachigen Quellen (Deutschland, Österreich, Schweiz)\n- KEINE englischsprachigen oder anderssprachigen Quellen"
if primary_display == "English":
return "- Search ONLY in English-language sources\n- NO sources in other languages"
return f"- Suche NUR auf {primary_display}\n- KEINE Quellen in anderen Sprachen"
def lang_deep_international(primary_display: str) -> str:
if primary_display == "Deutsch":
return "- Suche in Deutsch, Englisch und weiteren relevanten Sprachen"
if primary_display == "English":
return "- Search in English and other relevant languages"
return f"- Suche in {primary_display} und weiteren relevanten Sprachen"
def lang_deep_primary_only(primary_display: str) -> str:
if primary_display == "Deutsch":
return "- Suche NUR auf Deutsch bei deutschsprachigen Quellen (Deutschland, Österreich, Schweiz)\n- KEINE englischsprachigen oder anderssprachigen Quellen"
if primary_display == "English":
return "- Search ONLY in English-language sources\n- NO sources in other languages"
return f"- Suche NUR auf {primary_display}\n- KEINE Quellen in anderen Sprachen"
FEED_SELECTION_PROMPT_TEMPLATE = """Du bist ein OSINT-Analyst. Wähle aus dieser Feed-Liste die Feeds aus, die für die Lage relevant sein könnten. Generiere außerdem optimierte Suchbegriffe für das RSS-Matching.
@@ -392,7 +417,7 @@ class ResearcherAgent:
logger.warning(f"Keyword-Extraktion fehlgeschlagen: {e}")
return None, None
async def search(self, title: str, description: str = "", incident_type: str = "adhoc", international: bool = True, user_id: int = None, existing_articles: list[dict] = None, preferred_sources: list[dict] = None) -> tuple[list[dict], ClaudeUsage | None, bool]:
async def search(self, title: str, description: str = "", incident_type: str = "adhoc", international: bool = True, user_id: int = None, existing_articles: list[dict] = None, preferred_sources: list[dict] = None, output_language: str = "Deutsch", output_language_iso: str = "de") -> tuple[list[dict], ClaudeUsage | None, bool]:
"""Sucht nach Informationen zu einem Vorfall.
Returns:
@@ -400,8 +425,6 @@ class ResearcherAgent:
das JSON aber nicht extrahierbar war. So kann der Orchestrator zwischen
"echt keine Treffer" und "kaputte Antwort" unterscheiden.
"""
from config import OUTPUT_LANGUAGE
# Bevorzugte Web-Quellen als Prompt-Block (optional)
preferred_sources_block = ""
if preferred_sources:
@@ -422,7 +445,7 @@ class ResearcherAgent:
)
if incident_type == "research":
lang_instruction = LANG_DEEP_INTERNATIONAL if international else LANG_DEEP_GERMAN_ONLY
lang_instruction = lang_deep_international(output_language) if international else lang_deep_primary_only(output_language)
# Bestehende Artikel als Kontext für den Prompt aufbereiten
existing_context = ""
if existing_articles:
@@ -439,11 +462,11 @@ class ResearcherAgent:
)
prompt = DEEP_RESEARCH_PROMPT_TEMPLATE.format(
title=title, description=description, language_instruction=lang_instruction,
output_language=OUTPUT_LANGUAGE, existing_context=existing_context,
output_language=output_language, existing_context=existing_context,
preferred_sources_block=preferred_sources_block,
)
else:
lang_instruction = LANG_INTERNATIONAL if international else LANG_GERMAN_ONLY
lang_instruction = lang_international(output_language) if international else lang_primary_only(output_language)
# Bestehende Artikel als Kontext: bei Folge-Refreshes findet Claude andere Quellen
existing_context = ""
if existing_articles:
@@ -458,7 +481,7 @@ class ResearcherAgent:
)
prompt = RESEARCH_PROMPT_TEMPLATE.format(
title=title, description=description, language_instruction=lang_instruction,
output_language=OUTPUT_LANGUAGE, existing_context=existing_context,
output_language=output_language, existing_context=existing_context,
preferred_sources_block=preferred_sources_block,
)
@@ -486,8 +509,8 @@ class ResearcherAgent:
excluded = True
break
if not excluded:
# Bei nur-deutsch: nicht-deutsche Ergebnisse nachfiltern
if not international and article.get("language", "de") != "de":
# Bei nur-primary: andersprachige Ergebnisse nachfiltern
if not international and article.get("language", output_language_iso) != output_language_iso:
continue
filtered.append(article)