feat(eu-umbau): staan-Recherche-Schleife und Doppelspur (Phase 2)
Im EU-Modus laeuft die Recherche jetzt europaeisch: agents/eu_researcher.py steuert eine Schleife aus Bedrock-Planung (Opus, EU-Profile) und staan-Suchen (services/staan_client.py, Suche + Volltexte via full_content=markdown, Pflicht-Domain-Ausschlussliste je Anfrage). Bildet die 4-Phasen-Tiefenrecherche nach und liefert exakt das JSON der bisherigen CLI-WebSearch, Parsing und Filter in researcher.search bleiben unveraendert. Anti-Halluzination: nur URLs aus echten staan-Treffern werden akzeptiert; published_at nur wenn aus Inhalt oder URL ableitbar (staan liefert keine Daten). Doppelspur im Orchestrator: Lane-Schluessel ist jetzt (Organisation, Backend), Aufloesung zentral in _resolve_ai_backend (Lage > Org-Setting > ENV). CLI- und EU-Lauf derselben Organisation laufen gleichzeitig, gleiche Backends seriell. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -470,7 +470,12 @@ class AgentOrchestrator:
|
||||
return False
|
||||
|
||||
visibility, created_by, tenant_id = await self._get_incident_visibility(incident_id)
|
||||
lane_key = tenant_id if tenant_id else self.PUBLIC_LANE
|
||||
# Doppelspur (EU-Umbau Phase 2). Eine Lane je Organisation UND KI-Weg.
|
||||
# Ein CLI-Lauf und ein EU-Lauf derselben Organisation laufen dadurch
|
||||
# wirklich gleichzeitig (getrennte Ressourcen, Abo gegen AWS), während
|
||||
# Läufe über denselben Weg seriell bleiben (Rate-Limit-Schutz).
|
||||
ai_backend = await self._resolve_ai_backend(incident_id)
|
||||
lane_key = (tenant_id if tenant_id else self.PUBLIC_LANE, ai_backend)
|
||||
|
||||
async with self._lanes_lock:
|
||||
queue = self._lanes.get(lane_key)
|
||||
@@ -792,6 +797,35 @@ class AgentOrchestrator:
|
||||
await db.close()
|
||||
return visibility, created_by, tenant_id
|
||||
|
||||
async def _resolve_ai_backend(self, incident_id: int) -> str:
|
||||
"""Löst das KI-Backend einer Lage auf (EU-Umbau).
|
||||
|
||||
Lage (incidents.ai_backend) gewinnt vor dem Org-Setting 'ai_backend',
|
||||
das vor dem globalen Default AI_BACKEND aus config. Wird beim Einreihen
|
||||
(Lane-Wahl) und beim Refresh-Start (ContextVar) identisch genutzt."""
|
||||
from config import AI_BACKEND
|
||||
from database import get_db
|
||||
from services.org_settings import get_org_setting
|
||||
incident_backend = None
|
||||
tenant_id = None
|
||||
db = await get_db()
|
||||
try:
|
||||
cursor = await db.execute(
|
||||
"SELECT ai_backend, tenant_id FROM incidents WHERE id = ?", (incident_id,)
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
if row:
|
||||
incident_backend = row["ai_backend"] if "ai_backend" in row.keys() else None
|
||||
tenant_id = row["tenant_id"] if "tenant_id" in row.keys() else None
|
||||
org_backend = await get_org_setting(db, tenant_id, "ai_backend", default=None) if tenant_id else None
|
||||
finally:
|
||||
await db.close()
|
||||
ai_backend = (incident_backend or org_backend or AI_BACKEND or "cli").strip().lower()
|
||||
if ai_backend not in ("cli", "bedrock"):
|
||||
logger.warning("Unbekanntes ai_backend '%s' für Lage %s, fallback 'cli'", ai_backend, incident_id)
|
||||
ai_backend = "cli"
|
||||
return ai_backend
|
||||
|
||||
async def _run_refresh(self, incident_id: int, trigger_type: str = "manual", retry_count: int = 0, user_id: int = None, _suppress_complete: bool = False, _pass_info: dict = None, collect_only: bool = False):
|
||||
"""Führt einen kompletten Refresh-Zyklus durch."""
|
||||
import aiosqlite
|
||||
@@ -829,18 +863,11 @@ class AgentOrchestrator:
|
||||
from services.org_settings import (
|
||||
get_org_language, language_display, get_research_language,
|
||||
get_source_language_whitelist, get_translator_enabled,
|
||||
get_org_setting,
|
||||
)
|
||||
# KI-Backend für diesen Refresh auflösen (EU-Umbau Phase 1).
|
||||
# Lage gewinnt vor Organisation, die vor dem globalen Default.
|
||||
# Gilt über das ContextVar für alle call_claude-Aufrufe darunter.
|
||||
from config import AI_BACKEND
|
||||
incident_backend = incident["ai_backend"] if "ai_backend" in incident.keys() else None
|
||||
org_backend = await get_org_setting(db, tenant_id, "ai_backend", default=None) if tenant_id else None
|
||||
ai_backend = (incident_backend or org_backend or AI_BACKEND or "cli").strip().lower()
|
||||
if ai_backend not in ("cli", "bedrock"):
|
||||
logger.warning("Unbekanntes ai_backend '%s' für Lage %s, fallback 'cli'", ai_backend, incident_id)
|
||||
ai_backend = "cli"
|
||||
# KI-Backend für diesen Refresh auflösen und per ContextVar setzen
|
||||
# (EU-Umbau, gemeinsame Auflösung mit der Lane-Wahl beim Einreihen,
|
||||
# siehe _resolve_ai_backend).
|
||||
ai_backend = await self._resolve_ai_backend(incident_id)
|
||||
_ai_backend_var.set(ai_backend)
|
||||
if ai_backend != "cli":
|
||||
logger.info("Lage %s läuft über KI-Backend '%s'", incident_id, ai_backend)
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren