feat(multitenancy): Sprach-Whitelist + Translator-Override + Forum-Quellenklasse
Vorbereitung fuer jp_demo-Organisation: drei separate Sprach-Settings statt einer einzigen output_language. org_settings.py: - get_source_language_whitelist: Liste erlaubter Quellsprachen als JSON-Array (z.B. ["ja"] beschraenkt RSS/Telegram auf japanische Quellen). - get_research_language: Sprache fuer WebSearch-Prompts (Default: output_language). - get_translator_enabled: Pro-Org-Override des globalen TRANSLATOR_ENABLED-Flags. - LANGUAGE_DISPLAY_NAMES um ja/zh/ko/ru/ar/fa/he/fr/es erweitert. source_rules.py: - get_feeds_with_metadata filtert nach source_language_whitelist, wenn gesetzt. - Feeds ohne primary_language fallen bei aktiver Whitelist raus (gewollt). - SELECT um media_type erweitert, damit es im Feed-Dict ankommt. orchestrator.py: - Laedt research_language, source_language_whitelist, translator_enabled aus den Org-Settings. - Wenn Whitelist gesetzt: international_sources-Flag wird ignoriert. - research_language_iso wird an researcher.search() weitergegeben. - translate_articles bekommt enabled-Parameter aus Org-Setting. - Geoparsing ueberspringt media_type='forum' Artikel. - SELECT * FROM articles wird zu JOIN sources, damit media_type beim Reload am Article-Dict haengt. researcher.py: - search() akzeptiert research_language_iso. Asymmetrische Sprach-Auswahl (Recherche != Output) erzeugt eigene Prompt-Anweisung "primaer in Quell- sprache, englische Region-Outlets erlaubt". translator.py: - translate_articles akzeptiert enabled-Parameter. Ueberschreibt die globale TRANSLATOR_ENABLED-Konstante pro Aufruf. factchecker.py: - _format_articles_text filtert Artikel mit media_type='forum' aus. Anonyme Foren-Posts gelten nicht als Faktenbeleg. rss_parser.py: - _fetch_feed traegt media_type aus feed_config ins Article-Dict ein, damit downstream Pipeline-Schritte Foren-Quellen erkennen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -562,14 +562,27 @@ 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, output_language: str = "Deutsch", output_language_iso: str = "de") -> 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", research_language_iso: str | None = None) -> tuple[list[dict], ClaudeUsage | None, bool]:
|
||||
"""Sucht nach Informationen zu einem Vorfall.
|
||||
|
||||
Args:
|
||||
output_language / output_language_iso: Ausgabesprache (Lagebild-Sprache).
|
||||
research_language_iso: optionaler Override fuer die Sprache, in der gesucht
|
||||
werden soll. Default = output_language_iso. Bei jp_demo z.B. 'ja',
|
||||
waehrend output_language_iso 'de' bleibt (Lagebild deutsch, Recherche japanisch).
|
||||
|
||||
Returns:
|
||||
(artikel, usage, parse_failed) — parse_failed ist True, wenn Claude geantwortet hat,
|
||||
das JSON aber nicht extrahierbar war. So kann der Orchestrator zwischen
|
||||
"echt keine Treffer" und "kaputte Antwort" unterscheiden.
|
||||
"""
|
||||
# research_language defaultet auf output_language. Wenn das aber abweicht
|
||||
# (z.B. jp_demo: research='ja', output='de'), ueberschreiben wir die
|
||||
# Sprach-Anweisung im Prompt mit einer eigenen, dual-sprachigen Variante.
|
||||
research_language_iso = (research_language_iso or output_language_iso or "de").lower()
|
||||
# Display-Name der Recherche-Sprache fuer Prompts ("Japanese", "Russian", ...)
|
||||
from services.org_settings import language_display as _lang_display
|
||||
research_language_display = _lang_display(research_language_iso)
|
||||
# Bevorzugte Web-Quellen als Prompt-Block (optional)
|
||||
preferred_sources_block = ""
|
||||
if preferred_sources:
|
||||
@@ -589,8 +602,31 @@ class ResearcherAgent:
|
||||
"aber nicht deine sonstige Recherche.\n"
|
||||
)
|
||||
|
||||
# Asymmetrische Sprach-Auswahl: research_language weicht von output_language ab
|
||||
# -> eigene Anweisung "primaer in research-language, englische Quellen aus der
|
||||
# Region auch erlaubt". Sonst die bisherige Logik (primary_only vs international).
|
||||
asymmetric_lang = research_language_iso != output_language_iso
|
||||
|
||||
def _build_lang_instruction(deep: bool) -> str:
|
||||
if asymmetric_lang:
|
||||
# jp_demo & Co.: Recherche in Quellsprache + lokale Englisch-Outlets.
|
||||
return (
|
||||
f"- Fokus liegt auf {research_language_display}-sprachigen Quellen "
|
||||
f"(Behoerden, Qualitaetszeitungen, oeffentlich-rechtliche Medien dieser Sprache).\n"
|
||||
f"- Englischsprachige Outlets mit Fokus auf demselben Sprachraum/Region sind "
|
||||
f"ebenfalls willkommen (z.B. Japan Times, Nikkei Asia, Kyodo English fuer Japan; "
|
||||
f"Moscow Times English fuer Russland).\n"
|
||||
f"- Quellen ausserhalb des Sprachraums NUR, wenn sie exklusive Informationen "
|
||||
f"ueber die Region liefern (z.B. Reuters/AFP/AP-Berichte aus der Region).\n"
|
||||
f"- Antworte in der Ausgabesprache {output_language} (das Lagebild wird in "
|
||||
f"{output_language} angezeigt), aber zitiere die Original-Headlines/Quellen unveraendert."
|
||||
)
|
||||
if deep:
|
||||
return lang_deep_international(output_language) if international else lang_deep_primary_only(output_language)
|
||||
return lang_international(output_language) if international else lang_primary_only(output_language)
|
||||
|
||||
if incident_type == "research":
|
||||
lang_instruction = lang_deep_international(output_language) if international else lang_deep_primary_only(output_language)
|
||||
lang_instruction = _build_lang_instruction(deep=True)
|
||||
# Bestehende Artikel als Kontext für den Prompt aufbereiten
|
||||
existing_context = ""
|
||||
if existing_articles:
|
||||
@@ -611,7 +647,7 @@ class ResearcherAgent:
|
||||
preferred_sources_block=preferred_sources_block,
|
||||
)
|
||||
else:
|
||||
lang_instruction = lang_international(output_language) if international else lang_primary_only(output_language)
|
||||
lang_instruction = _build_lang_instruction(deep=False)
|
||||
# Bestehende Artikel als Kontext: bei Folge-Refreshes findet Claude andere Quellen
|
||||
existing_context = ""
|
||||
if existing_articles:
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren