"""B18 wiring — Specialist-Agents Phase 2 (Impressum LLM). Ruft den LLM-Agent (impressum_agent_llm.evaluate_llm) auf, mergt das Ergebnis mit dem Pattern-Match-Agent und deduplet nach field_id. Rendert einen V2-HTML-Block (impressum_agent_html). """ from __future__ import annotations import html import logging import os from compliance.services.specialist_agents.impressum_agent import ( PFLICHTANGABEN, evaluate as evaluate_pattern, ) from compliance.services.specialist_agents.impressum_agent_llm import ( evaluate_llm, ) logger = logging.getLogger(__name__) _DISABLED = os.environ.get("IMPRESSUM_AGENT_DISABLED", "").lower() in ( "1", "true", "yes", ) async def run_b18(state: dict) -> None: if _DISABLED: return doc_texts = state.get("doc_texts") or {} imp = (doc_texts.get("impressum") or "").strip() if len(imp) < 100: return # Business-scope-Inferenz aus dem profile, falls vorhanden. profile_dict = state.get("profile_dict") or {} scope: set[str] = set() if profile_dict.get("has_online_shop"): scope.add("ecommerce") if profile_dict.get("is_regulated_profession"): scope.add("regulated_profession") if profile_dict.get("industry") in ("insurance", "Finance", "finance"): scope.add("insurance") pattern_findings = evaluate_pattern(imp, scope) llm_findings = await evaluate_llm(imp, scope) # Dedup: pattern-agent + llm-agent können ähnliche field_ids melden. # Keep first, prefer pattern (deterministisch + stable). seen_keys: set[str] = set() merged: list[dict] = [] for f in pattern_findings + llm_findings: # Stable dedup key: field_id (normalised). Both agents emit # the same field for the same gap → fold to one. key = (f.get("field_id") or "").lower() if key and key in seen_keys: continue seen_keys.add(key) merged.append(f) if not merged: return extras = state.get("extra_findings") or [] extras.extend(merged) state["extra_findings"] = extras state["impressum_agent_html"] = _render(merged, pattern_findings, llm_findings) logger.info( "B18 impressum-agent: pattern=%d llm=%d merged=%d", len(pattern_findings), len(llm_findings), len(merged), ) def _render(merged: list[dict], pattern: list[dict], llm: list[dict]) -> str: cards = [] for f in merged: sev = (f.get("severity") or "").upper() color = "#dc2626" if sev == "HIGH" else ( "#f59e0b" if sev == "MEDIUM" else "#64748b" ) agent_tag = f.get("agent") or "" tag_html = "" if agent_tag: short = "LLM" if "llm" in agent_tag.lower() else "KB" bg = "#dbeafe" if short == "LLM" else "#f1f5f9" col = "#1e40af" if short == "LLM" else "#475569" tag_html = ( f"{short}" ) evidence_html = "" if f.get("evidence"): evidence_html = ( "
" f"{html.escape(f['evidence'])}
" ) cards.append( f"
" f"
" f"{sev} · {html.escape(f.get('check_id') or '')}{tag_html}
" f"
" f"{html.escape(f.get('title') or '')}
" f"
" f"{html.escape(f.get('norm') or '')}
" f"{evidence_html}" f"
" f"→ Empfehlung: " f"{html.escape(f.get('action') or '')}
" "
" ) return ( "
" "

" "🤖 Impressum-Specialist-Agent (Pattern-KB + LLM)" "

" f"

" f"Pattern-Match: {len(pattern)} · LLM-Analyse: {len(llm)} · " f"dedupliziert: {len(merged)}

" + "".join(cards) + "
" )