feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
"""
|
|
|
|
|
|
KI-gestützte semantische Inhaltsanalyse via OpenRouter.
|
|
|
|
|
|
|
|
|
|
|
|
Ergänzung zum Integritäts-Kern, niemals dessen Ersatz. Erkennt problematische
|
|
|
|
|
|
*Inhalte ohne Link-Signal*: Pornografie, Propaganda, diffamierende/strafbare
|
|
|
|
|
|
Texte, versteckten Spam, thematisch unpassende Werbung, widersprüchliche
|
|
|
|
|
|
Aussagen — in Text und in Bildern (inkl. eingebettetem Text via OCR).
|
|
|
|
|
|
|
|
|
|
|
|
Kostengate: Jeder Kandidat bekommt einen Fingerprint (SHA-256). Ein bereits
|
|
|
|
|
|
geprüfter Fingerprint liegt mit seinem Verdikt im Ledger (data/ai_ledger.json)
|
|
|
|
|
|
→ Cache-Treffer → KEIN API-Call. Nur neue/geänderte Inhalte kosten etwas.
|
|
|
|
|
|
|
|
|
|
|
|
Robustheit: Fehlt der API-Key oder schlägt ein Call fehl, wird die Analyse
|
|
|
|
|
|
übersprungen und der Scan läuft unverändert weiter (graceful degradation).
|
|
|
|
|
|
KI darf den Kern-Scan nie brechen.
|
|
|
|
|
|
"""
|
2026-06-14 19:02:17 +02:00
|
|
|
|
import base64
|
2026-06-13 02:11:14 +02:00
|
|
|
|
import concurrent.futures
|
2026-06-14 19:02:17 +02:00
|
|
|
|
import hashlib
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
import json
|
|
|
|
|
|
import logging
|
|
|
|
|
|
import os
|
2026-06-14 19:02:17 +02:00
|
|
|
|
import subprocess
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
import threading
|
2026-06-13 00:50:36 +02:00
|
|
|
|
import time
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
from datetime import datetime, timezone
|
2026-06-14 19:02:17 +02:00
|
|
|
|
from pathlib import Path
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
|
|
from .checker import fetch_asset_hashes
|
|
|
|
|
|
from .differ import normalize_text
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
# Primärer Endpunkt: OpenRouter (wie bisher).
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
_OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
|
2026-06-14 19:02:17 +02:00
|
|
|
|
_MODELS_URL = "https://openrouter.ai/api/v1/models"
|
|
|
|
|
|
|
|
|
|
|
|
# Lokale llama.cpp-Server als Fallback — werden nur genutzt, wenn OpenRouter
|
|
|
|
|
|
# nicht erreichbar ist (Rate-Limit, Timeout, Ausfall).
|
|
|
|
|
|
_LOCAL_SERVERS: list[str] = [
|
|
|
|
|
|
"http://localhost:8001",
|
|
|
|
|
|
"http://localhost:8002",
|
|
|
|
|
|
]
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
|
|
|
|
|
# Erlaubte Kategorien, die das Modell zurückgeben darf.
|
|
|
|
|
|
_CATEGORIES = [
|
|
|
|
|
|
"clean", "pornography", "propaganda", "defamation_illegal",
|
|
|
|
|
|
"hidden_spam", "off_topic_commercial", "contradiction",
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
# Kategorie → Scoring-Schlüssel (Fallback-Punkte, falls cfg sie nicht liefert).
|
|
|
|
|
|
_CATEGORY_SCORE_KEY = {
|
|
|
|
|
|
"pornography": ("ai_pornography", 50),
|
|
|
|
|
|
"defamation_illegal": ("ai_defamation_illegal", 50),
|
|
|
|
|
|
"propaganda": ("ai_propaganda", 40),
|
|
|
|
|
|
"hidden_spam": ("ai_hidden_spam", 40),
|
|
|
|
|
|
"off_topic_commercial": ("ai_off_topic_commercial", 30),
|
|
|
|
|
|
"contradiction": ("ai_contradiction", 20),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_SEVERITY_ORDER = {"none": 0, "low": 1, "medium": 2, "high": 3}
|
|
|
|
|
|
|
|
|
|
|
|
# JSON-Schema für die strukturierte Modell-Antwort.
|
|
|
|
|
|
_RESPONSE_SCHEMA = {
|
|
|
|
|
|
"type": "json_schema",
|
|
|
|
|
|
"json_schema": {
|
|
|
|
|
|
"name": "content_verdict",
|
|
|
|
|
|
"strict": True,
|
|
|
|
|
|
"schema": {
|
|
|
|
|
|
"type": "object",
|
|
|
|
|
|
"properties": {
|
feat: Inhaltsinventar in SQLite — KI-Inhaltsangabe je Objekt (URL + Hash)
Vereinheitlicht den bisherigen JSON-Ledger zu EINEM abfragbaren SQLite-Store
pro Site (data/content_inventory.db): jedes geprüfte Objekt mit Inhalts-Hash,
URL(s), Sicherheits-Verdikt UND neutraler KI-Inhaltsangabe.
- baseline.py: load/save_ai_ledger jetzt SQLite-gestützt (Dict-Schnittstelle
bleibt → Analyzer unverändert). Tabellen objects + object_urls. Einmalige
Migration eines vorhandenen ai_ledger.json → SQLite (.migrated). UPSERT mit
first_seen-Erhalt; object_urls transaktional ersetzt. Neu: query_inventory,
export_inventory_csv (beide migrieren failsafe).
- ai_analyzer.py: Antwort-Schema + Prompt um neutrales Feld 'description'
erweitert (im selben Call, keine Extrakosten); _make_entry speichert es.
- __main__.py: neues Kommando 'inventory' (Übersicht, --search, --kind, --csv).
- Doku: README + Bedienungsanleitung.
Audio/Video sind im Schema (kind) vorbereitet (Phase 2: Extractor + Modalitäten).
bredelar.info real migriert: 245 Objekte (200 Bilder, 45 Texte).
Tests: 266 grün (+6: Round-Trip mit description, Migration, first_seen-Erhalt,
invalidate, Suche/CSV-Export, description landet im Ledger).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 05:12:19 +02:00
|
|
|
|
"description": {"type": "string"},
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
"category": {"type": "string", "enum": _CATEGORIES},
|
|
|
|
|
|
"severity": {"type": "string", "enum": ["none", "low", "medium", "high"]},
|
|
|
|
|
|
"confidence": {"type": "number"},
|
|
|
|
|
|
"explanation": {"type": "string"},
|
|
|
|
|
|
},
|
feat: Inhaltsinventar in SQLite — KI-Inhaltsangabe je Objekt (URL + Hash)
Vereinheitlicht den bisherigen JSON-Ledger zu EINEM abfragbaren SQLite-Store
pro Site (data/content_inventory.db): jedes geprüfte Objekt mit Inhalts-Hash,
URL(s), Sicherheits-Verdikt UND neutraler KI-Inhaltsangabe.
- baseline.py: load/save_ai_ledger jetzt SQLite-gestützt (Dict-Schnittstelle
bleibt → Analyzer unverändert). Tabellen objects + object_urls. Einmalige
Migration eines vorhandenen ai_ledger.json → SQLite (.migrated). UPSERT mit
first_seen-Erhalt; object_urls transaktional ersetzt. Neu: query_inventory,
export_inventory_csv (beide migrieren failsafe).
- ai_analyzer.py: Antwort-Schema + Prompt um neutrales Feld 'description'
erweitert (im selben Call, keine Extrakosten); _make_entry speichert es.
- __main__.py: neues Kommando 'inventory' (Übersicht, --search, --kind, --csv).
- Doku: README + Bedienungsanleitung.
Audio/Video sind im Schema (kind) vorbereitet (Phase 2: Extractor + Modalitäten).
bredelar.info real migriert: 245 Objekte (200 Bilder, 45 Texte).
Tests: 266 grün (+6: Round-Trip mit description, Migration, first_seen-Erhalt,
invalidate, Suche/CSV-Export, description landet im Ledger).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 05:12:19 +02:00
|
|
|
|
"required": ["description", "category", "severity", "confidence", "explanation"],
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
"additionalProperties": False,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Adaptive model router (circuit-breaker + latency telemetry + /models pruning)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class ModelRouter:
|
|
|
|
|
|
"""Wählt programmatisch das beste Modell aus einer Kette: gesunde zuerst,
|
2026-06-14 19:02:17 +02:00
|
|
|
|
nach Latenz; ausgefallene Modelle bekommen einen Cooldown (Circuit-Breaker).
|
|
|
|
|
|
Katalog-Pruning funktioniert für OpenRouter- und lokale Modell-Slugs.
|
|
|
|
|
|
Thread-sicher (für Parallelität)."""
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, ai_cfg: dict, state: dict | None = None):
|
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
self._threshold = ai_cfg.get("breaker_failure_threshold", 2)
|
|
|
|
|
|
self._cooldown = ai_cfg.get("breaker_cooldown_seconds", 120)
|
|
|
|
|
|
self._refresh = ai_cfg.get("refresh_models", True)
|
|
|
|
|
|
state = state or {}
|
|
|
|
|
|
self._models: dict = state.get("models", {}) or {}
|
2026-06-14 19:02:17 +02:00
|
|
|
|
# catalog: {"slugs": [...], "servers": {...}, "fetched_at": ...}
|
|
|
|
|
|
# slugs = OpenRouter-Katalog (primär), servers = lokale Server-Modelle
|
|
|
|
|
|
saved = state.get("catalog", {"slugs": [], "servers": {}, "fetched_at": None})
|
|
|
|
|
|
self._catalog: dict = saved if isinstance(saved, dict) else {"slugs": [], "servers": {}, "fetched_at": None}
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
|
|
|
|
|
|
def state(self) -> dict:
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
return {"models": self._models, "catalog": self._catalog}
|
|
|
|
|
|
|
|
|
|
|
|
def record_success(self, model: str, latency: float) -> None:
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
m = self._models.setdefault(model, {})
|
|
|
|
|
|
m["failures"] = 0
|
|
|
|
|
|
m["open_until"] = 0
|
|
|
|
|
|
prev = m.get("latency")
|
|
|
|
|
|
m["latency"] = latency if prev is None else 0.7 * prev + 0.3 * latency
|
|
|
|
|
|
|
|
|
|
|
|
def record_failure(self, model: str) -> None:
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
m = self._models.setdefault(model, {})
|
|
|
|
|
|
m["failures"] = m.get("failures", 0) + 1
|
|
|
|
|
|
m["last_failure"] = time.time()
|
|
|
|
|
|
if m["failures"] >= self._threshold:
|
|
|
|
|
|
m["open_until"] = time.time() + self._cooldown
|
|
|
|
|
|
logger.info("KI-Router: Breaker für %s offen (%.0fs Cooldown).",
|
|
|
|
|
|
model, self._cooldown)
|
|
|
|
|
|
|
|
|
|
|
|
def order(self, models: list[str]) -> list[str]:
|
|
|
|
|
|
"""Reiht die Kette: geschlossener Breaker zuerst, dann nach Latenz aufsteigend.
|
|
|
|
|
|
Tote Slugs werden entfernt. Gibt nie eine leere Liste zurück."""
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
candidates = self._prune_locked(models)
|
|
|
|
|
|
now = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
def sort_key(slug):
|
|
|
|
|
|
m = self._models.get(slug, {})
|
|
|
|
|
|
is_open = m.get("open_until", 0) > now
|
|
|
|
|
|
# Unbekannte Latenz → inf: auf dem Erstlauf bleibt die Config-Reihenfolge
|
|
|
|
|
|
# erhalten (free zuerst); ein bewährtes (gemessenes) Modell schlägt ein
|
|
|
|
|
|
# noch untestetes. sorted() ist stabil → Ties = Config-Reihenfolge.
|
|
|
|
|
|
latency = m.get("latency")
|
|
|
|
|
|
latency = latency if latency is not None else float("inf")
|
|
|
|
|
|
return (is_open, latency)
|
|
|
|
|
|
|
|
|
|
|
|
ordered = sorted(candidates, key=sort_key)
|
|
|
|
|
|
return ordered or list(models)
|
|
|
|
|
|
|
|
|
|
|
|
def _prune_locked(self, models: list[str]) -> list[str]:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
# Kombiniere OpenRouter-Slugs + lokale Server-Modelle.
|
|
|
|
|
|
all_slugs: set[str] = set(self._catalog.get("slugs", []))
|
|
|
|
|
|
for slugs in self._catalog.get("servers", {}).values():
|
|
|
|
|
|
all_slugs.update(slugs)
|
|
|
|
|
|
if not all_slugs:
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
return list(models) # kein Katalog → nicht filtern (failsafe)
|
2026-06-14 19:02:17 +02:00
|
|
|
|
keep = [m for m in models if m in all_slugs]
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
return keep or list(models) # nie alles wegfiltern
|
|
|
|
|
|
|
|
|
|
|
|
def refresh_catalog(self) -> None:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
"""OpenRouter /models + lokale Server /models ziehen (24 h gecacht).
|
|
|
|
|
|
Failsafe: blockiert nie."""
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
if not self._refresh:
|
|
|
|
|
|
return
|
|
|
|
|
|
fetched_at = self._catalog.get("fetched_at")
|
2026-06-14 19:02:17 +02:00
|
|
|
|
slugs = self._catalog.get("slugs") or []
|
|
|
|
|
|
servers = self._catalog.get("servers") or {}
|
|
|
|
|
|
if fetched_at and (slugs or servers) and (time.time() - fetched_at) < 86400:
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
return # Cache frisch
|
2026-06-14 19:02:17 +02:00
|
|
|
|
|
|
|
|
|
|
new_slugs, new_servers = [], {}
|
|
|
|
|
|
# 1. OpenRouter-Katalog (primär)
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
try:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
resp = requests.get(_MODELS_URL, timeout=10)
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
if resp.status_code == 200:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
new_slugs = [m["id"] for m in resp.json().get("data", []) if m.get("id")]
|
|
|
|
|
|
logger.info("KI-Router: OpenRouter-Katalog aktualisiert (%d Modelle).",
|
|
|
|
|
|
len(new_slugs))
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
except (requests.exceptions.RequestException, ValueError, KeyError) as exc:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
logger.debug("KI-Router: OpenRouter /models nicht abrufbar: %s", exc)
|
|
|
|
|
|
|
|
|
|
|
|
# 2. Lokale Server-Kataloge (Fallback)
|
|
|
|
|
|
for base_url in _LOCAL_SERVERS:
|
|
|
|
|
|
try:
|
|
|
|
|
|
resp = requests.get(f"{base_url}/v1/models", timeout=5)
|
|
|
|
|
|
if resp.status_code == 200:
|
|
|
|
|
|
data = resp.json()
|
|
|
|
|
|
raw = data.get("data") or data.get("models", [])
|
|
|
|
|
|
local_slugs = [m["id"] for m in raw if isinstance(m, dict) and m.get("id")]
|
|
|
|
|
|
if local_slugs:
|
|
|
|
|
|
new_servers[base_url] = local_slugs
|
|
|
|
|
|
logger.info("KI-Router: %s — %d Modelle gefunden.", base_url, len(local_slugs))
|
|
|
|
|
|
elif resp.status_code == 503:
|
|
|
|
|
|
logger.debug("KI-Router: %s — Modell wird geladen (503).", base_url)
|
|
|
|
|
|
except requests.exceptions.RequestException as exc:
|
|
|
|
|
|
logger.debug("KI-Router: %s nicht erreichbar: %s", base_url, exc)
|
|
|
|
|
|
|
|
|
|
|
|
if new_slugs or new_servers:
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
self._catalog = {"slugs": new_slugs, "servers": new_servers,
|
|
|
|
|
|
"fetched_at": time.time()}
|
|
|
|
|
|
total = len(new_slugs) + sum(len(v) for v in new_servers.values())
|
|
|
|
|
|
logger.info("KI-Router: Katalog aktualisiert (%d Modelle gesamt).", total)
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Public API
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
def run_ai_analysis(cfg: dict, bm, snap: dict, diff: dict) -> dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Hash-gegate KI-Analyse über neue/geänderte Texte und Bilder.
|
|
|
|
|
|
|
|
|
|
|
|
Returns {"findings": [...], "checked": int, "cache_hits": int,
|
|
|
|
|
|
"api_calls": int, "skipped": str|None}.
|
|
|
|
|
|
Bricht nie mit einer Exception — bei Problemen wird "skipped" gesetzt.
|
|
|
|
|
|
"""
|
2026-06-13 00:50:36 +02:00
|
|
|
|
result = {"findings": [], "unchecked": [], "checked": 0, "cache_hits": 0,
|
|
|
|
|
|
"api_calls": 0, "skipped": None}
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
ai_cfg = cfg.get("ai_analysis", {})
|
|
|
|
|
|
|
|
|
|
|
|
if not ai_cfg.get("enabled"):
|
|
|
|
|
|
result["skipped"] = "deaktiviert"
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
api_key = os.environ.get(ai_cfg.get("api_key_env", "OPENROUTER_API_KEY"), "")
|
|
|
|
|
|
if not api_key:
|
|
|
|
|
|
result["skipped"] = f"kein API-Key ({ai_cfg.get('api_key_env', 'OPENROUTER_API_KEY')})"
|
|
|
|
|
|
logger.warning("KI-Analyse übersprungen: %s", result["skipped"])
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
router = ModelRouter(ai_cfg, bm.load_ai_router_state())
|
|
|
|
|
|
router.refresh_catalog() # einmal /models (24 h gecacht), failsafe
|
|
|
|
|
|
max_workers = max(1, int(ai_cfg.get("max_concurrency", 8)))
|
|
|
|
|
|
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
try:
|
|
|
|
|
|
ledger = bm.load_ai_ledger()
|
|
|
|
|
|
entries = ledger.setdefault("entries", {})
|
2026-06-13 04:17:31 +02:00
|
|
|
|
url_hashes = ledger.setdefault("url_hashes", {}) # URL→Byte-Hash-Erinnerung (Bilder)
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
dirty = False
|
2026-06-13 02:11:14 +02:00
|
|
|
|
# Ledger wird zwischengesichert (nach Textphase + im finally), damit bereits
|
|
|
|
|
|
# berechnete Verdikte einen späteren Hang/Abbruch überleben und nicht erneut
|
|
|
|
|
|
# bezahlt werden müssen.
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
|
|
|
|
try:
|
|
|
|
|
|
if ai_cfg.get("text", {}).get("enabled", True):
|
|
|
|
|
|
dirty |= _analyze_text(cfg, ai_cfg, api_key, snap, diff,
|
|
|
|
|
|
entries, result, router, executor)
|
|
|
|
|
|
if dirty:
|
|
|
|
|
|
bm.save_ai_ledger(ledger)
|
|
|
|
|
|
if ai_cfg.get("image", {}).get("enabled", True):
|
|
|
|
|
|
dirty |= _analyze_images(cfg, ai_cfg, api_key, snap, diff,
|
2026-06-13 04:17:31 +02:00
|
|
|
|
entries, result, router, executor, url_hashes)
|
2026-06-14 19:02:17 +02:00
|
|
|
|
if ai_cfg.get("audio", {}).get("enabled", False):
|
|
|
|
|
|
dirty |= _analyze_audio(cfg, ai_cfg, api_key, snap, diff,
|
|
|
|
|
|
entries, result, router, executor, url_hashes)
|
|
|
|
|
|
if ai_cfg.get("video", {}).get("enabled", False):
|
|
|
|
|
|
dirty |= _analyze_video(cfg, ai_cfg, api_key, snap, diff,
|
|
|
|
|
|
entries, result, router, executor, url_hashes)
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
finally:
|
2026-06-13 02:11:14 +02:00
|
|
|
|
if dirty:
|
|
|
|
|
|
bm.save_ai_ledger(ledger)
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
except Exception as exc: # pragma: no cover - Schutzschirm, darf Scan nie brechen
|
|
|
|
|
|
logger.error("KI-Analyse mit unerwartetem Fehler abgebrochen: %s", exc)
|
|
|
|
|
|
result["skipped"] = f"interner Fehler: {exc}"
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
# Deterministische Report-Reihenfolge (unabhängig von der Completion-Reihenfolge)
|
|
|
|
|
|
result["findings"].sort(key=lambda f: (f.get("kind", ""), f.get("url", ""), f.get("asset_url", "")))
|
|
|
|
|
|
result["unchecked"].sort(key=lambda u: (u.get("kind", ""), u.get("url", "")))
|
|
|
|
|
|
bm.save_ai_router_state(router.state())
|
|
|
|
|
|
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def score_ai_findings(ai_result: dict, cfg: dict) -> dict:
|
|
|
|
|
|
"""
|
2026-06-13 01:16:23 +02:00
|
|
|
|
Bewertet KI-Funde additiv. Das Level ist auf 'yellow' gedeckelt — AUSSER
|
|
|
|
|
|
schwerwiegende Kategorien (red_categories, default Pornografie/strafbar) lösen
|
|
|
|
|
|
bei hoher Schwere + hoher Konfidenz ROT aus. Die Punktsumme selbst ergibt nie
|
|
|
|
|
|
Rot; nur dieser kategoriebasierte Override tut das.
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
|
|
|
|
|
Returns {score, level, reasons, exit_code}.
|
|
|
|
|
|
"""
|
|
|
|
|
|
sc = cfg.get("scoring", {})
|
|
|
|
|
|
thr = cfg.get("thresholds", {"yellow": 20, "red": 60})
|
|
|
|
|
|
yellow = thr.get("yellow", 20)
|
|
|
|
|
|
|
|
|
|
|
|
score = 0
|
|
|
|
|
|
reasons: list[str] = []
|
|
|
|
|
|
for f in ai_result.get("findings", []):
|
|
|
|
|
|
score += (pts := _finding_points(f, sc))
|
|
|
|
|
|
conf = f.get("confidence", 0.0)
|
|
|
|
|
|
reasons.append(
|
|
|
|
|
|
f"KI [{f.get('kind', '?')}] {f.get('url', '?')}: "
|
|
|
|
|
|
f"{f.get('category', '?')} ({f.get('severity', '?')}, {conf:.0%}) (+{pts})"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Deckelung auf gelb: auch bei Score ≥ rot-Schwelle bleibt es gelb.
|
|
|
|
|
|
if score >= yellow:
|
|
|
|
|
|
level, exit_code = "yellow", 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
level, exit_code = "green", 0
|
|
|
|
|
|
|
2026-06-13 00:50:36 +02:00
|
|
|
|
# Ungeprüfte Inhalte: "konnte nicht prüfen" ≠ "sauber".
|
|
|
|
|
|
unchecked = ai_result.get("unchecked", [])
|
|
|
|
|
|
if unchecked:
|
|
|
|
|
|
reasons.append(
|
|
|
|
|
|
f"{len(unchecked)} Inhalt(e) konnten von der KI nicht geprüft werden "
|
|
|
|
|
|
"(Dienst nicht erreichbar) — bitte später erneut prüfen."
|
|
|
|
|
|
)
|
|
|
|
|
|
if cfg.get("ai_analysis", {}).get("unchecked_level") == "yellow" and level == "green":
|
|
|
|
|
|
level, exit_code = "yellow", 1
|
|
|
|
|
|
|
2026-06-13 01:16:23 +02:00
|
|
|
|
# Grave-Override: nur die schwersten Kategorien lösen ROT aus (Vorrang vor gelb).
|
|
|
|
|
|
ai_cfg = cfg.get("ai_analysis", {})
|
|
|
|
|
|
red_cats = set(ai_cfg.get("red_categories", []))
|
|
|
|
|
|
red_min_sev = _SEVERITY_ORDER.get(ai_cfg.get("red_min_severity", "high"), 3)
|
|
|
|
|
|
red_min_conf = ai_cfg.get("red_min_confidence", 0.9)
|
|
|
|
|
|
for f in ai_result.get("findings", []):
|
|
|
|
|
|
if (f.get("category") in red_cats
|
|
|
|
|
|
and _SEVERITY_ORDER.get(f.get("severity", "none"), 0) >= red_min_sev
|
|
|
|
|
|
and f.get("confidence", 0.0) >= red_min_conf):
|
|
|
|
|
|
level, exit_code = "red", 2
|
|
|
|
|
|
reasons.append(
|
|
|
|
|
|
f"Schwerwiegender KI-Fund ({f['category']}, {f.get('confidence', 0):.0%}) "
|
|
|
|
|
|
f"auf {f.get('url', '?')} → ROT"
|
|
|
|
|
|
)
|
|
|
|
|
|
break
|
|
|
|
|
|
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
return {"score": score, "level": level, "reasons": reasons, "exit_code": exit_code}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Text analysis
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
def _analyze_text(cfg, ai_cfg, api_key, snap, diff, entries, result, router, executor) -> bool:
|
|
|
|
|
|
"""Prüft neue/geänderte Seitentexte (parallel). Returns True wenn Ledger verändert."""
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
text_cfg = ai_cfg.get("text", {})
|
|
|
|
|
|
min_chars = text_cfg.get("min_chars", 200)
|
|
|
|
|
|
models = _models_for(text_cfg)
|
|
|
|
|
|
site_context = ai_cfg.get("site_context", "")
|
|
|
|
|
|
|
|
|
|
|
|
# Fingerprint je Seite (lokal, gratis). Geänderte/neue Seiten zuerst (Risiko).
|
|
|
|
|
|
changed = {pd["url"] for pd in diff.get("page_diffs", [])} | set(diff.get("new_internal_urls", []))
|
|
|
|
|
|
candidates: list[tuple[str, str, str]] = []
|
|
|
|
|
|
for url, page in snap.get("pages", {}).items():
|
|
|
|
|
|
if page.get("status") != 200:
|
|
|
|
|
|
continue
|
|
|
|
|
|
text = page.get("text", "") or ""
|
|
|
|
|
|
if len(text) < min_chars:
|
|
|
|
|
|
continue
|
|
|
|
|
|
candidates.append((url, text, _text_fingerprint(text, cfg)))
|
|
|
|
|
|
candidates.sort(key=lambda c: c[0] not in changed) # changed first (False < True)
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
# Nach Fingerprint gruppieren: identischer Text wird nur EINMAL klassifiziert,
|
|
|
|
|
|
# die Findings aber für alle betroffenen URLs emittiert (wie sequenziell).
|
|
|
|
|
|
fp_urls: dict[str, list[str]] = {}
|
|
|
|
|
|
fp_text: dict[str, str] = {}
|
|
|
|
|
|
order_fps: list[str] = []
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
for url, text, fp in candidates:
|
|
|
|
|
|
result["checked"] += 1
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
if fp not in fp_urls:
|
|
|
|
|
|
fp_urls[fp] = []
|
|
|
|
|
|
fp_text[fp] = text
|
|
|
|
|
|
order_fps.append(fp)
|
|
|
|
|
|
fp_urls[fp].append(url)
|
|
|
|
|
|
|
2026-06-13 04:12:11 +02:00
|
|
|
|
# Drossel NUR für den historischen Bestand; 0 = unbegrenzt. Geänderte/neue Seiten
|
|
|
|
|
|
# werden IMMER geprüft (Echtzeit-Schutz), ungeachtet des Limits.
|
|
|
|
|
|
backlog_limit = text_cfg.get("max_pages_per_scan", 0)
|
|
|
|
|
|
backlog_used = 0
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
tasks, miss_fps = [], []
|
|
|
|
|
|
for fp in order_fps:
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
entry = entries.get(fp)
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
if entry is not None:
|
|
|
|
|
|
for url in fp_urls[fp]:
|
|
|
|
|
|
result["cache_hits"] += 1
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url, ai_cfg)
|
2026-06-13 04:12:11 +02:00
|
|
|
|
continue
|
|
|
|
|
|
is_changed = any(u in changed for u in fp_urls[fp])
|
|
|
|
|
|
if not is_changed and backlog_limit and backlog_used >= backlog_limit:
|
|
|
|
|
|
continue # Bestands-Drossel erreicht — nächster Scan holt den Rest nach
|
|
|
|
|
|
miss_fps.append(fp)
|
|
|
|
|
|
tasks.append((fp, _text_task(fp_text[fp], site_context, models, ai_cfg, api_key, router)))
|
|
|
|
|
|
if not is_changed:
|
|
|
|
|
|
backlog_used += 1
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
|
|
|
|
|
|
classified = _classify_batch(tasks, executor)
|
|
|
|
|
|
dirty = False
|
|
|
|
|
|
for fp in miss_fps:
|
|
|
|
|
|
res = classified.get(fp)
|
|
|
|
|
|
if res is None:
|
|
|
|
|
|
for url in fp_urls[fp]:
|
|
|
|
|
|
result["unchecked"].append({"kind": "text", "url": url})
|
|
|
|
|
|
continue
|
|
|
|
|
|
verdict, used_model = res
|
|
|
|
|
|
entry = _make_entry("text", fp_urls[fp][0], verdict, used_model)
|
|
|
|
|
|
entries[fp] = entry
|
|
|
|
|
|
result["api_calls"] += 1
|
|
|
|
|
|
dirty = True
|
|
|
|
|
|
for url in fp_urls[fp]:
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url, ai_cfg)
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
return dirty
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Image analysis
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-06-13 04:17:31 +02:00
|
|
|
|
def _analyze_images(cfg, ai_cfg, api_key, snap, diff, entries, result, router, executor,
|
|
|
|
|
|
url_hashes) -> bool:
|
|
|
|
|
|
"""Prüft neue/geänderte Bilder (parallel). Returns True wenn Ledger verändert.
|
|
|
|
|
|
|
|
|
|
|
|
url_hashes ist die persistente URL→Byte-Hash-Erinnerung: eine bereits gesehene
|
|
|
|
|
|
Bild-URL wird NICHT erneut heruntergeladen (kein redundanter Fetch, keine doppelte
|
|
|
|
|
|
Analyse). Nur nie gesehene Bilder sowie geänderte/geflaggte werden geholt."""
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
img_cfg = ai_cfg.get("image", {})
|
|
|
|
|
|
models = _models_for(img_cfg)
|
|
|
|
|
|
site_context = ai_cfg.get("site_context", "")
|
|
|
|
|
|
|
|
|
|
|
|
current_img_urls = _all_image_urls(snap)
|
|
|
|
|
|
if not current_img_urls:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
changed = {pd["url"] for pd in diff.get("page_diffs", [])} | set(diff.get("new_internal_urls", []))
|
|
|
|
|
|
changed_img_urls = _all_image_urls(snap, only_pages=changed)
|
|
|
|
|
|
# Markierte Bilder erneut holen: ersetztes Bild → neue Bytes → Neubewertung
|
|
|
|
|
|
# (verhindert Dauer-Fehlalarm nach Bereinigung); unverändert → Cache-Treffer.
|
|
|
|
|
|
flagged_img_urls = {
|
|
|
|
|
|
e["url"] for e in entries.values()
|
|
|
|
|
|
if e.get("kind") == "image" and e.get("category", "clean") != "clean"
|
|
|
|
|
|
and not e.get("dismissed") and e.get("url") in current_img_urls
|
|
|
|
|
|
}
|
2026-06-13 04:17:31 +02:00
|
|
|
|
|
|
|
|
|
|
# Geänderte/geflaggte Bilder: IMMER (erneut) prüfen (Echtzeit-Schutz, nie gedeckelt).
|
|
|
|
|
|
# Abdeckung: nur noch NIE gesehene URLs (nicht in url_hashes) — schon bekannte Bilder
|
|
|
|
|
|
# werden nicht erneut geladen (Hashwert gemerkt). Volle Abdeckung baut sich so EINMALIG
|
|
|
|
|
|
# auf; Duplikate (gleiche Bytes an mehreren URLs) werden je URL höchstens einmal geholt
|
|
|
|
|
|
# und nie doppelt analysiert (Ledger ist nach Byte-Hash indiziert). Drossel 0 = unbegrenzt.
|
2026-06-13 04:02:35 +02:00
|
|
|
|
priority = sorted((changed_img_urls | flagged_img_urls) & current_img_urls)
|
2026-06-13 04:17:31 +02:00
|
|
|
|
coverage = sorted((current_img_urls - set(url_hashes)) - set(priority))
|
2026-06-13 04:12:11 +02:00
|
|
|
|
backlog_limit = img_cfg.get("max_images_per_scan", 0)
|
|
|
|
|
|
if backlog_limit and backlog_limit > 0:
|
|
|
|
|
|
coverage = coverage[:backlog_limit]
|
|
|
|
|
|
fetch_urls = priority + coverage
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
if not fetch_urls:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
# url → Seite(n) für den Report
|
|
|
|
|
|
url_to_page = _image_url_sources(snap)
|
|
|
|
|
|
hashes = fetch_asset_hashes(fetch_urls, timeout=cfg.get("request_timeout", 15))
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
# Nach Bild-Hash (Bytes) gruppieren: identische Bilder werden nur einmal klassifiziert.
|
2026-06-13 04:17:31 +02:00
|
|
|
|
# Zugleich URL→Hash merken (auch für Duplikate), damit künftige Scans sie überspringen.
|
|
|
|
|
|
dirty = False
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
fp_assets: dict[str, list[str]] = {}
|
|
|
|
|
|
order_fps: list[str] = []
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
for url in fetch_urls:
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
fp = hashes.get(url, {}).get("sha256")
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
if not fp:
|
2026-06-13 04:17:31 +02:00
|
|
|
|
continue # nicht abrufbar — überspringen (nicht merken → später erneut versuchen)
|
|
|
|
|
|
if url_hashes.get(url) != fp:
|
|
|
|
|
|
url_hashes[url] = fp # neu gesehen oder Bytes geändert
|
|
|
|
|
|
dirty = True
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
result["checked"] += 1
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
if fp not in fp_assets:
|
|
|
|
|
|
fp_assets[fp] = []
|
|
|
|
|
|
order_fps.append(fp)
|
|
|
|
|
|
fp_assets[fp].append(url)
|
|
|
|
|
|
|
|
|
|
|
|
tasks, miss_fps = [], []
|
|
|
|
|
|
for fp in order_fps:
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
entry = entries.get(fp)
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
if entry is not None:
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
result["cache_hits"] += 1
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url_to_page.get(url, url), ai_cfg, asset_url=url)
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
else:
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
miss_fps.append(fp)
|
|
|
|
|
|
tasks.append((fp, _image_task(fp_assets[fp][0], site_context, models, ai_cfg, api_key, router)))
|
|
|
|
|
|
|
|
|
|
|
|
classified = _classify_batch(tasks, executor)
|
|
|
|
|
|
for fp in miss_fps:
|
|
|
|
|
|
res = classified.get(fp)
|
|
|
|
|
|
rep_url = fp_assets[fp][0]
|
|
|
|
|
|
if res is None:
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
result["unchecked"].append(
|
|
|
|
|
|
{"kind": "image", "url": url_to_page.get(url, url), "asset_url": url})
|
|
|
|
|
|
continue
|
|
|
|
|
|
verdict, used_model = res
|
|
|
|
|
|
entry = _make_entry("image", rep_url, verdict, used_model)
|
|
|
|
|
|
entries[fp] = entry
|
|
|
|
|
|
result["api_calls"] += 1
|
|
|
|
|
|
dirty = True
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url_to_page.get(url, url), ai_cfg, asset_url=url)
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
return dirty
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-06-14 19:02:17 +02:00
|
|
|
|
# Audio analysis
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
def _analyze_audio(cfg, ai_cfg, api_key, snap, diff, entries, result, router, executor,
|
|
|
|
|
|
url_hashes) -> bool:
|
|
|
|
|
|
"""Prüft neue/geänderte Audio-Dateien (parallel). Returns True wenn Ledger verändert."""
|
|
|
|
|
|
aud_cfg = ai_cfg.get("audio", {})
|
|
|
|
|
|
models = _models_for(aud_cfg)
|
|
|
|
|
|
site_context = ai_cfg.get("site_context", "")
|
|
|
|
|
|
|
|
|
|
|
|
current_urls = _all_media_urls(snap, "audio")
|
|
|
|
|
|
if not current_urls:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
changed = {pd["url"] for pd in diff.get("page_diffs", [])} | set(diff.get("new_internal_urls", []))
|
|
|
|
|
|
changed_urls = _all_media_urls(snap, "audio", only_pages=changed)
|
|
|
|
|
|
flagged_urls = {
|
|
|
|
|
|
e["url"] for e in entries.values()
|
|
|
|
|
|
if e.get("kind") == "audio" and e.get("category", "clean") != "clean"
|
|
|
|
|
|
and not e.get("dismissed") and e.get("url") in current_urls
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
priority = sorted((changed_urls | flagged_urls) & current_urls)
|
|
|
|
|
|
coverage = sorted((current_urls - set(url_hashes)) - set(priority))
|
|
|
|
|
|
backlog_limit = aud_cfg.get("max_files_per_scan", 5)
|
|
|
|
|
|
if backlog_limit and backlog_limit > 0:
|
|
|
|
|
|
coverage = coverage[:backlog_limit]
|
|
|
|
|
|
fetch_urls = priority + coverage
|
|
|
|
|
|
if not fetch_urls:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
url_to_page = _media_url_sources(snap, "audio")
|
|
|
|
|
|
|
|
|
|
|
|
# Sampling: für jede URL via ffmpeg einen 90-s-Ausschnitt holen (hash + bytes).
|
|
|
|
|
|
# Ersetzt _local_file_hashes + fetch_asset_hashes — kein 40-60 MB Download mehr.
|
|
|
|
|
|
samples: dict[str, tuple[bytes, str]] = {} # url → (mp3_bytes, sha256)
|
|
|
|
|
|
dirty = False
|
|
|
|
|
|
fp_assets: dict[str, list[str]] = {}
|
|
|
|
|
|
order_fps: list[str] = []
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
for url in fetch_urls:
|
|
|
|
|
|
sample = _audio_sample(url, timeout=ai_cfg.get("request_timeout", 60))
|
|
|
|
|
|
if sample is None:
|
|
|
|
|
|
result["unchecked"].append(
|
|
|
|
|
|
{"kind": "audio", "url": url_to_page.get(url, url), "asset_url": url}
|
|
|
|
|
|
)
|
|
|
|
|
|
continue
|
|
|
|
|
|
data, fp = sample
|
|
|
|
|
|
samples[url] = (data, fp)
|
|
|
|
|
|
if url_hashes.get(url) != fp:
|
|
|
|
|
|
url_hashes[url] = fp
|
|
|
|
|
|
dirty = True
|
|
|
|
|
|
result["checked"] += 1
|
|
|
|
|
|
if fp not in fp_assets:
|
|
|
|
|
|
fp_assets[fp] = []
|
|
|
|
|
|
order_fps.append(fp)
|
|
|
|
|
|
fp_assets[fp].append(url)
|
|
|
|
|
|
|
|
|
|
|
|
tasks, miss_fps = [], []
|
|
|
|
|
|
for fp in order_fps:
|
|
|
|
|
|
entry = entries.get(fp)
|
|
|
|
|
|
if entry is not None:
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
result["cache_hits"] += 1
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url_to_page.get(url, url), ai_cfg, asset_url=url)
|
|
|
|
|
|
else:
|
|
|
|
|
|
miss_fps.append(fp)
|
|
|
|
|
|
rep_bytes = samples[fp_assets[fp][0]][0]
|
|
|
|
|
|
tasks.append((fp, _audio_task(rep_bytes, site_context, models, ai_cfg, api_key, router)))
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
classified = _classify_batch(tasks, executor)
|
|
|
|
|
|
for fp in miss_fps:
|
|
|
|
|
|
res = classified.get(fp)
|
|
|
|
|
|
rep_url = fp_assets[fp][0]
|
|
|
|
|
|
if res is None:
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
result["unchecked"].append(
|
|
|
|
|
|
{"kind": "audio", "url": url_to_page.get(url, url), "asset_url": url})
|
|
|
|
|
|
continue
|
|
|
|
|
|
verdict, used_model = res
|
|
|
|
|
|
entry = _make_entry("audio", rep_url, verdict, used_model)
|
|
|
|
|
|
entries[fp] = entry
|
|
|
|
|
|
result["api_calls"] += 1
|
|
|
|
|
|
dirty = True
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url_to_page.get(url, url), ai_cfg, asset_url=url)
|
|
|
|
|
|
return dirty
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Video analysis
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
def _analyze_video(cfg, ai_cfg, api_key, snap, diff, entries, result, router, executor,
|
|
|
|
|
|
url_hashes) -> bool:
|
|
|
|
|
|
"""Prüft neue/geänderte Video-Dateien (parallel). Returns True wenn Ledger verändert."""
|
|
|
|
|
|
vid_cfg = ai_cfg.get("video", {})
|
|
|
|
|
|
models = _models_for(vid_cfg)
|
|
|
|
|
|
site_context = ai_cfg.get("site_context", "")
|
|
|
|
|
|
|
|
|
|
|
|
current_urls = _all_media_urls(snap, "video")
|
|
|
|
|
|
if not current_urls:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
changed = {pd["url"] for pd in diff.get("page_diffs", [])} | set(diff.get("new_internal_urls", []))
|
|
|
|
|
|
changed_urls = _all_media_urls(snap, "video", only_pages=changed)
|
|
|
|
|
|
flagged_urls = {
|
|
|
|
|
|
e["url"] for e in entries.values()
|
|
|
|
|
|
if e.get("kind") == "video" and e.get("category", "clean") != "clean"
|
|
|
|
|
|
and not e.get("dismissed") and e.get("url") in current_urls
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
priority = sorted((changed_urls | flagged_urls) & current_urls)
|
|
|
|
|
|
coverage = sorted((current_urls - set(url_hashes)) - set(priority))
|
|
|
|
|
|
backlog_limit = vid_cfg.get("max_files_per_scan", 3)
|
|
|
|
|
|
if backlog_limit and backlog_limit > 0:
|
|
|
|
|
|
coverage = coverage[:backlog_limit]
|
|
|
|
|
|
fetch_urls = priority + coverage
|
|
|
|
|
|
if not fetch_urls:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
url_to_page = _media_url_sources(snap, "video")
|
|
|
|
|
|
local_urls = [u for u in fetch_urls if u.startswith("file://")]
|
|
|
|
|
|
remote_urls = [u for u in fetch_urls if not u.startswith("file://")]
|
|
|
|
|
|
hashes = _local_file_hashes(local_urls)
|
|
|
|
|
|
if remote_urls:
|
|
|
|
|
|
hashes.update(fetch_asset_hashes(remote_urls, timeout=cfg.get("request_timeout", 15)))
|
|
|
|
|
|
|
|
|
|
|
|
dirty = False
|
|
|
|
|
|
fp_assets: dict[str, list[str]] = {}
|
|
|
|
|
|
order_fps: list[str] = []
|
|
|
|
|
|
for url in fetch_urls:
|
|
|
|
|
|
fp = hashes.get(url, {}).get("sha256")
|
|
|
|
|
|
if not fp:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if url_hashes.get(url) != fp:
|
|
|
|
|
|
url_hashes[url] = fp
|
|
|
|
|
|
dirty = True
|
|
|
|
|
|
result["checked"] += 1
|
|
|
|
|
|
if fp not in fp_assets:
|
|
|
|
|
|
fp_assets[fp] = []
|
|
|
|
|
|
order_fps.append(fp)
|
|
|
|
|
|
fp_assets[fp].append(url)
|
|
|
|
|
|
|
|
|
|
|
|
tasks, miss_fps = [], []
|
|
|
|
|
|
for fp in order_fps:
|
|
|
|
|
|
entry = entries.get(fp)
|
|
|
|
|
|
if entry is not None:
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
result["cache_hits"] += 1
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url_to_page.get(url, url), ai_cfg, asset_url=url)
|
|
|
|
|
|
else:
|
|
|
|
|
|
miss_fps.append(fp)
|
|
|
|
|
|
tasks.append((fp, _video_task(fp_assets[fp][0], site_context, models, ai_cfg, api_key, router)))
|
|
|
|
|
|
|
|
|
|
|
|
classified = _classify_batch(tasks, executor)
|
|
|
|
|
|
for fp in miss_fps:
|
|
|
|
|
|
res = classified.get(fp)
|
|
|
|
|
|
rep_url = fp_assets[fp][0]
|
|
|
|
|
|
if res is None:
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
result["unchecked"].append(
|
|
|
|
|
|
{"kind": "video", "url": url_to_page.get(url, url), "asset_url": url})
|
|
|
|
|
|
continue
|
|
|
|
|
|
verdict, used_model = res
|
|
|
|
|
|
entry = _make_entry("video", rep_url, verdict, used_model)
|
|
|
|
|
|
entries[fp] = entry
|
|
|
|
|
|
result["api_calls"] += 1
|
|
|
|
|
|
dirty = True
|
|
|
|
|
|
for url in fp_assets[fp]:
|
|
|
|
|
|
_maybe_finding(result, entry, fp, url_to_page.get(url, url), ai_cfg, asset_url=url)
|
|
|
|
|
|
return dirty
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# Fingerprinting & helpers
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
def _text_fingerprint(text: str, cfg: dict) -> str:
|
|
|
|
|
|
"""SHA-256 des normalisierten Texts (Reuse von differ.normalize_text → Cache-
|
|
|
|
|
|
Buster wie CF7-Platzhalter ändern den Hash nicht)."""
|
|
|
|
|
|
norm = normalize_text(text, cfg)
|
|
|
|
|
|
return hashlib.sha256(norm.encode("utf-8")).hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_entry(kind: str, url: str, verdict: dict, model: str) -> dict:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"kind": kind,
|
|
|
|
|
|
"url": url,
|
feat: Inhaltsinventar in SQLite — KI-Inhaltsangabe je Objekt (URL + Hash)
Vereinheitlicht den bisherigen JSON-Ledger zu EINEM abfragbaren SQLite-Store
pro Site (data/content_inventory.db): jedes geprüfte Objekt mit Inhalts-Hash,
URL(s), Sicherheits-Verdikt UND neutraler KI-Inhaltsangabe.
- baseline.py: load/save_ai_ledger jetzt SQLite-gestützt (Dict-Schnittstelle
bleibt → Analyzer unverändert). Tabellen objects + object_urls. Einmalige
Migration eines vorhandenen ai_ledger.json → SQLite (.migrated). UPSERT mit
first_seen-Erhalt; object_urls transaktional ersetzt. Neu: query_inventory,
export_inventory_csv (beide migrieren failsafe).
- ai_analyzer.py: Antwort-Schema + Prompt um neutrales Feld 'description'
erweitert (im selben Call, keine Extrakosten); _make_entry speichert es.
- __main__.py: neues Kommando 'inventory' (Übersicht, --search, --kind, --csv).
- Doku: README + Bedienungsanleitung.
Audio/Video sind im Schema (kind) vorbereitet (Phase 2: Extractor + Modalitäten).
bredelar.info real migriert: 245 Objekte (200 Bilder, 45 Texte).
Tests: 266 grün (+6: Round-Trip mit description, Migration, first_seen-Erhalt,
invalidate, Suche/CSV-Export, description landet im Ledger).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 05:12:19 +02:00
|
|
|
|
"description": verdict.get("description", ""),
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
"category": verdict.get("category", "clean"),
|
|
|
|
|
|
"severity": verdict.get("severity", "none"),
|
|
|
|
|
|
"confidence": float(verdict.get("confidence", 0.0)),
|
|
|
|
|
|
"explanation": verdict.get("explanation", ""),
|
|
|
|
|
|
"checked_at": datetime.now(timezone.utc).isoformat(),
|
|
|
|
|
|
"model": model,
|
|
|
|
|
|
"dismissed": False,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _maybe_finding(result, entry, fp, url, ai_cfg, asset_url=None) -> None:
|
|
|
|
|
|
"""Hängt einen Fund an, wenn das Verdikt gewertet werden soll
|
|
|
|
|
|
(nicht clean, nicht quittiert, severity≥medium, confidence≥Schwelle)."""
|
|
|
|
|
|
if entry.get("dismissed"):
|
|
|
|
|
|
return
|
|
|
|
|
|
if entry.get("category", "clean") == "clean":
|
|
|
|
|
|
return
|
|
|
|
|
|
if _SEVERITY_ORDER.get(entry.get("severity", "none"), 0) < _SEVERITY_ORDER["medium"]:
|
|
|
|
|
|
return
|
|
|
|
|
|
if entry.get("confidence", 0.0) < ai_cfg.get("ai_confidence_min", 0.7):
|
|
|
|
|
|
return
|
|
|
|
|
|
finding = {
|
|
|
|
|
|
"kind": entry["kind"],
|
|
|
|
|
|
"url": url,
|
|
|
|
|
|
"fingerprint": fp,
|
|
|
|
|
|
"category": entry["category"],
|
|
|
|
|
|
"severity": entry["severity"],
|
|
|
|
|
|
"confidence": entry["confidence"],
|
|
|
|
|
|
"explanation": entry.get("explanation", ""),
|
|
|
|
|
|
}
|
|
|
|
|
|
if asset_url:
|
|
|
|
|
|
finding["asset_url"] = asset_url
|
|
|
|
|
|
result["findings"].append(finding)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _finding_points(finding: dict, sc: dict) -> int:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
"""Punkte für einen Fund. Bilder/Audio/Video mindestens ai_suspicious_image."""
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
key, default = _CATEGORY_SCORE_KEY.get(finding.get("category", ""), (None, 30))
|
|
|
|
|
|
pts = sc.get(key, default) if key else 30
|
2026-06-14 19:02:17 +02:00
|
|
|
|
if finding.get("kind") in ("image", "audio", "video"):
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
pts = max(pts, sc.get("ai_suspicious_image", 40))
|
|
|
|
|
|
return pts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _all_image_urls(snap: dict, only_pages: set | None = None) -> set[str]:
|
|
|
|
|
|
urls: set[str] = set()
|
|
|
|
|
|
for page_url, page in snap.get("pages", {}).items():
|
|
|
|
|
|
if only_pages is not None and page_url not in only_pages:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if page.get("status") != 200:
|
|
|
|
|
|
continue
|
|
|
|
|
|
for link in page.get("links", {}).get("img", []):
|
|
|
|
|
|
if isinstance(link, dict) and link.get("url"):
|
|
|
|
|
|
urls.add(link["url"])
|
|
|
|
|
|
return urls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _image_url_sources(snap: dict) -> dict[str, str]:
|
|
|
|
|
|
"""Map image-URL → erste Seite, auf der es vorkommt (für den Report)."""
|
|
|
|
|
|
sources: dict[str, str] = {}
|
|
|
|
|
|
for page_url, page in snap.get("pages", {}).items():
|
|
|
|
|
|
for link in page.get("links", {}).get("img", []):
|
|
|
|
|
|
if isinstance(link, dict) and link.get("url"):
|
|
|
|
|
|
sources.setdefault(link["url"], page_url)
|
|
|
|
|
|
return sources
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
def _all_media_urls(snap: dict, kind: str, only_pages: set | None = None) -> set[str]:
|
|
|
|
|
|
urls: set[str] = set()
|
|
|
|
|
|
for page_url, page in snap.get("pages", {}).items():
|
|
|
|
|
|
if only_pages is not None and page_url not in only_pages:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if page.get("status") != 200:
|
|
|
|
|
|
continue
|
|
|
|
|
|
for link in page.get("links", {}).get(kind, []):
|
|
|
|
|
|
if isinstance(link, dict) and link.get("url"):
|
|
|
|
|
|
urls.add(link["url"])
|
|
|
|
|
|
return urls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _media_url_sources(snap: dict, kind: str) -> dict[str, str]:
|
|
|
|
|
|
"""Map Medien-URL → erste Seite, auf der sie vorkommt (für den Report)."""
|
|
|
|
|
|
sources: dict[str, str] = {}
|
|
|
|
|
|
for page_url, page in snap.get("pages", {}).items():
|
|
|
|
|
|
for link in page.get("links", {}).get(kind, []):
|
|
|
|
|
|
if isinstance(link, dict) and link.get("url"):
|
|
|
|
|
|
sources.setdefault(link["url"], page_url)
|
|
|
|
|
|
return sources
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# OpenRouter classification
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
_SYSTEM_PROMPT = (
|
|
|
|
|
|
"Du bist ein Sicherheits-Prüfer für Website-Inhalte. Die zu prüfende Website "
|
|
|
|
|
|
"hat folgendes Thema:\n\"{context}\"\n\n"
|
|
|
|
|
|
"Beurteile, ob der folgende Inhalt problematisch ist. Achte auf: sexuell "
|
|
|
|
|
|
"expliziten Text/Pornografie, politische Propaganda, diffamierende oder "
|
|
|
|
|
|
"strafbare Aussagen, versteckten Spam, thematisch völlig unpassende "
|
|
|
|
|
|
"kommerzielle Werbung, in sich widersprüchliche Aussagen.\n"
|
|
|
|
|
|
"WICHTIG: Beurteile die thematische PASSUNG zum oben genannten Thema, nicht "
|
|
|
|
|
|
"einzelne Schlüsselwörter. Heimat-, Orts- oder Vereinsbegriffe allein machen "
|
|
|
|
|
|
"einen Werbe- oder Spam-Text nicht unproblematisch — ein Angreifer kann sie "
|
|
|
|
|
|
"gezielt einstreuen. Bewerte den Gesamtcharakter des Inhalts.\n"
|
|
|
|
|
|
"Gib 'clean' zurück, wenn der Inhalt zum Thema passt und unproblematisch ist. "
|
feat: Inhaltsinventar in SQLite — KI-Inhaltsangabe je Objekt (URL + Hash)
Vereinheitlicht den bisherigen JSON-Ledger zu EINEM abfragbaren SQLite-Store
pro Site (data/content_inventory.db): jedes geprüfte Objekt mit Inhalts-Hash,
URL(s), Sicherheits-Verdikt UND neutraler KI-Inhaltsangabe.
- baseline.py: load/save_ai_ledger jetzt SQLite-gestützt (Dict-Schnittstelle
bleibt → Analyzer unverändert). Tabellen objects + object_urls. Einmalige
Migration eines vorhandenen ai_ledger.json → SQLite (.migrated). UPSERT mit
first_seen-Erhalt; object_urls transaktional ersetzt. Neu: query_inventory,
export_inventory_csv (beide migrieren failsafe).
- ai_analyzer.py: Antwort-Schema + Prompt um neutrales Feld 'description'
erweitert (im selben Call, keine Extrakosten); _make_entry speichert es.
- __main__.py: neues Kommando 'inventory' (Übersicht, --search, --kind, --csv).
- Doku: README + Bedienungsanleitung.
Audio/Video sind im Schema (kind) vorbereitet (Phase 2: Extractor + Modalitäten).
bredelar.info real migriert: 245 Objekte (200 Bilder, 45 Texte).
Tests: 266 grün (+6: Round-Trip mit description, Migration, first_seen-Erhalt,
invalidate, Suche/CSV-Export, description landet im Ledger).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 05:12:19 +02:00
|
|
|
|
"Gib im Feld 'description' eine knappe, neutrale Inhaltsangabe (1–2 Sätze, deutsch): "
|
|
|
|
|
|
"was ist auf dem Bild zu sehen bzw. worum geht es im Text — sachlich, unabhängig vom "
|
|
|
|
|
|
"Sicherheitsurteil. Antworte ausschließlich im vorgegebenen JSON-Format."
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _models_for(modality_cfg: dict) -> list[str]:
|
|
|
|
|
|
"""Modell-Kette einer Modalität. Rückwärts-kompatibel: einzelnes `model` → Liste."""
|
|
|
|
|
|
models = modality_cfg.get("models")
|
|
|
|
|
|
if models:
|
|
|
|
|
|
return list(models)
|
|
|
|
|
|
single = modality_cfg.get("model")
|
|
|
|
|
|
return [single] if single else []
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
def _classify(messages, models, ai_cfg, api_key, router) -> tuple[dict, str] | None:
|
|
|
|
|
|
"""Klassifiziert über die vom Router gereihte Modell-Kette.
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
Erste gültige Antwort → (verdict, model_used). Alle gescheitert → None.
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
Der Router reiht gesunde/schnelle Modelle nach vorn und überspringt
|
|
|
|
|
|
rate-limited/ausgefallene (Circuit-Breaker). Harter Deadline je Versuch;
|
|
|
|
|
|
bei Komplettausfall bis max_retries Wiederholung (Backoff)."""
|
|
|
|
|
|
timeout = ai_cfg.get("attempt_timeout", 20)
|
2026-06-13 00:50:36 +02:00
|
|
|
|
max_retries = ai_cfg.get("max_retries", 1)
|
|
|
|
|
|
backoff = ai_cfg.get("retry_backoff_seconds", 2.0)
|
|
|
|
|
|
|
|
|
|
|
|
for attempt in range(max_retries + 1):
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
ordered = router.order(models)
|
|
|
|
|
|
for model in ordered:
|
|
|
|
|
|
t0 = time.time()
|
2026-06-13 00:50:36 +02:00
|
|
|
|
verdict = _openrouter_chat(model, messages, ai_cfg, api_key, timeout=timeout)
|
|
|
|
|
|
if verdict is not None:
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
router.record_success(model, time.time() - t0)
|
2026-06-13 00:50:36 +02:00
|
|
|
|
return verdict, model
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
router.record_failure(model)
|
2026-06-13 00:50:36 +02:00
|
|
|
|
if attempt < max_retries:
|
|
|
|
|
|
logger.info("KI-Kette komplett erfolglos — Wiederholung %d/%d in %.1fs.",
|
|
|
|
|
|
attempt + 1, max_retries, backoff)
|
|
|
|
|
|
time.sleep(backoff)
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
def _classify_text(text, site_context, models, ai_cfg, api_key, router) -> tuple[dict, str] | None:
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
messages = [
|
|
|
|
|
|
{"role": "system", "content": _SYSTEM_PROMPT.format(context=site_context or "(nicht angegeben)")},
|
|
|
|
|
|
{"role": "user", "content": f"Zu prüfender Seitentext:\n\n{text[:8000]}"},
|
|
|
|
|
|
]
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
return _classify(messages, models, ai_cfg, api_key, router)
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
def _classify_image(image_url, site_context, models, ai_cfg, api_key, router) -> tuple[dict, str] | None:
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
messages = [
|
|
|
|
|
|
{"role": "system", "content": _SYSTEM_PROMPT.format(context=site_context or "(nicht angegeben)")},
|
|
|
|
|
|
{"role": "user", "content": [
|
|
|
|
|
|
{"type": "text", "text": "Prüfe dieses Bild auf problematische Inhalte. "
|
|
|
|
|
|
"Beziehe auch im Bild sichtbaren Text mit ein (OCR)."},
|
|
|
|
|
|
{"type": "image_url", "image_url": {"url": image_url}},
|
|
|
|
|
|
]},
|
|
|
|
|
|
]
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
return _classify(messages, models, ai_cfg, api_key, router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _text_task(text, site_context, models, ai_cfg, api_key, router):
|
|
|
|
|
|
"""Factory: gibt einen parameterlosen Aufruf für den Thread-Pool zurück."""
|
|
|
|
|
|
return lambda: _classify_text(text, site_context, models, ai_cfg, api_key, router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _image_task(image_url, site_context, models, ai_cfg, api_key, router):
|
|
|
|
|
|
return lambda: _classify_image(image_url, site_context, models, ai_cfg, api_key, router)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
def _local_file_hashes(urls: list[str]) -> dict[str, dict]:
|
|
|
|
|
|
"""Berechnet SHA-256 für file://-URLs direkt aus dem Dateisystem (kein HTTP)."""
|
|
|
|
|
|
result: dict[str, dict] = {}
|
|
|
|
|
|
for url in urls:
|
|
|
|
|
|
if not url.startswith("file://"):
|
|
|
|
|
|
continue
|
|
|
|
|
|
path = Path(url[7:])
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = path.read_bytes()
|
|
|
|
|
|
result[url] = {"sha256": hashlib.sha256(data).hexdigest(), "size": len(data)}
|
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
|
logger.warning("Lokale Datei nicht lesbar (%s): %s", url, exc)
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _audio_sample(url: str, timeout: int = 60) -> tuple[bytes, str] | None:
|
|
|
|
|
|
"""Extrahiert via ffmpeg einen 90-s-Ausschnitt ab Sekunde 30 (Intro überspringen).
|
|
|
|
|
|
Arbeitet für file://-Pfade und https://-URLs gleich; lädt nur ~3 MB statt 40-60 MB.
|
|
|
|
|
|
Gibt (mp3_bytes, sha256_hex) zurück oder None bei Fehler."""
|
|
|
|
|
|
input_path = url[7:] if url.startswith("file://") else url
|
|
|
|
|
|
|
|
|
|
|
|
def _run(ss: str) -> subprocess.CompletedProcess:
|
|
|
|
|
|
return subprocess.run(
|
|
|
|
|
|
["ffmpeg", "-hide_banner", "-loglevel", "error",
|
|
|
|
|
|
"-i", input_path, "-ss", ss, "-t", "90",
|
|
|
|
|
|
"-ar", "22050", "-ac", "1", "-b:a", "32k", "-f", "mp3", "pipe:1"],
|
|
|
|
|
|
capture_output=True, timeout=timeout,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
r = _run("30")
|
|
|
|
|
|
if r.returncode != 0 or not r.stdout:
|
|
|
|
|
|
r = _run("0") # Fallback: Datei kürzer als 30 s
|
|
|
|
|
|
if r.returncode != 0 or not r.stdout:
|
|
|
|
|
|
logger.warning("ffmpeg-Sampling fehlgeschlagen für %s: %s",
|
|
|
|
|
|
url, r.stderr.decode()[:200])
|
|
|
|
|
|
return None
|
|
|
|
|
|
return r.stdout, hashlib.sha256(r.stdout).hexdigest()
|
|
|
|
|
|
except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as exc:
|
|
|
|
|
|
logger.warning("ffmpeg nicht aufrufbar für %s: %s", url, exc)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _media_content(url: str, media_type: str) -> dict:
|
|
|
|
|
|
"""Gibt den OpenRouter-Message-Content für eine Medien-URL zurück.
|
|
|
|
|
|
Bei file://-Pfaden: Datei einlesen und als inline-base64 senden (input_audio/input_video).
|
|
|
|
|
|
Bei http(s)-URLs: URL-Referenz (audio_url/video_url)."""
|
|
|
|
|
|
if url.startswith("file://"):
|
|
|
|
|
|
path = Path(url[7:])
|
|
|
|
|
|
data = path.read_bytes()
|
|
|
|
|
|
b64 = base64.b64encode(data).decode()
|
|
|
|
|
|
fmt = path.suffix.lstrip(".").lower() or media_type
|
|
|
|
|
|
if media_type == "audio":
|
|
|
|
|
|
return {"type": "input_audio", "input_audio": {"data": b64, "format": fmt}}
|
|
|
|
|
|
return {"type": "video_url", "video_url": {"url": f"data:video/{fmt};base64,{b64}"}}
|
|
|
|
|
|
if media_type == "audio":
|
|
|
|
|
|
return {"type": "audio_url", "audio_url": {"url": url}}
|
|
|
|
|
|
return {"type": "video_url", "video_url": {"url": url}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _classify_audio(audio_bytes: bytes, site_context, models, ai_cfg, api_key, router) -> tuple[dict, str] | None:
|
|
|
|
|
|
b64 = base64.b64encode(audio_bytes).decode()
|
|
|
|
|
|
messages = [
|
|
|
|
|
|
{"role": "system", "content": _SYSTEM_PROMPT.format(context=site_context or "(nicht angegeben)")},
|
|
|
|
|
|
{"role": "user", "content": [
|
|
|
|
|
|
{"type": "text", "text": (
|
|
|
|
|
|
"Prüfe diesen Audio-Ausschnitt (bis zu 90 Sekunden, ab Sekunde 30) "
|
|
|
|
|
|
"auf problematische Inhalte (Sprache, Geräusche, Musik — soweit erkennbar)."
|
|
|
|
|
|
)},
|
|
|
|
|
|
{"type": "input_audio", "input_audio": {"data": b64, "format": "mp3"}},
|
|
|
|
|
|
]},
|
|
|
|
|
|
]
|
|
|
|
|
|
return _classify(messages, models, ai_cfg, api_key, router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _classify_video(video_url, site_context, models, ai_cfg, api_key, router) -> tuple[dict, str] | None:
|
|
|
|
|
|
messages = [
|
|
|
|
|
|
{"role": "system", "content": _SYSTEM_PROMPT.format(context=site_context or "(nicht angegeben)")},
|
|
|
|
|
|
{"role": "user", "content": [
|
|
|
|
|
|
{"type": "text", "text": "Prüfe dieses Video auf problematische Inhalte "
|
|
|
|
|
|
"(Bild, Ton, eingebetteter Text — soweit erkennbar)."},
|
|
|
|
|
|
_media_content(video_url, "video"),
|
|
|
|
|
|
]},
|
|
|
|
|
|
]
|
|
|
|
|
|
return _classify(messages, models, ai_cfg, api_key, router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _audio_task(audio_bytes: bytes, site_context, models, ai_cfg, api_key, router):
|
|
|
|
|
|
return lambda: _classify_audio(audio_bytes, site_context, models, ai_cfg, api_key, router)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _video_task(video_url, site_context, models, ai_cfg, api_key, router):
|
|
|
|
|
|
return lambda: _classify_video(video_url, site_context, models, ai_cfg, api_key, router)
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:
1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
persistent in data/ai_router_state.json.
2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
(Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.
Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.
Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
|
|
|
|
def _classify_batch(tasks, executor) -> dict:
|
|
|
|
|
|
"""Führt [(key, fn)] nebenläufig im Pool aus. Returns {key: fn()-Ergebnis|None}.
|
|
|
|
|
|
Eine fehlgeschlagene Aufgabe blubbert nie hoch (→ None, der Aufrufer wertet das
|
|
|
|
|
|
als 'ungeprüft')."""
|
|
|
|
|
|
if not tasks:
|
|
|
|
|
|
return {}
|
|
|
|
|
|
futures = {executor.submit(fn): key for key, fn in tasks}
|
|
|
|
|
|
out: dict = {}
|
|
|
|
|
|
for fut in concurrent.futures.as_completed(futures):
|
|
|
|
|
|
key = futures[fut]
|
|
|
|
|
|
try:
|
|
|
|
|
|
out[key] = fut.result()
|
|
|
|
|
|
except Exception as exc: # pragma: no cover - Schutzschirm
|
|
|
|
|
|
logger.warning("KI-Klassifikation fehlgeschlagen (%s): %s", key, exc)
|
|
|
|
|
|
out[key] = None
|
|
|
|
|
|
return out
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-06-14 19:02:17 +02:00
|
|
|
|
def _post_with_deadline(url: str, headers, payload, timeout):
|
2026-06-13 02:11:14 +02:00
|
|
|
|
"""requests.post in einem Worker-Thread mit HARTEM Wanduhr-Limit.
|
|
|
|
|
|
|
|
|
|
|
|
Der requests-Timeout ist nur ein Inaktivitäts-Timeout: ein Server, der die
|
|
|
|
|
|
Verbindung offen hält oder Tokens langsam tröpfeln lässt, kann ihn umgehen und
|
|
|
|
|
|
den Aufruf unbegrenzt hängen lassen. Dieser Deadline kappt die Gesamtdauer hart;
|
|
|
|
|
|
bei Überschreitung läuft der verwaiste Thread im Hintergrund aus und wir eskalieren.
|
|
|
|
|
|
"""
|
|
|
|
|
|
ex = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
2026-06-14 19:02:17 +02:00
|
|
|
|
fut = ex.submit(requests.post, url, headers=headers,
|
2026-06-13 02:11:14 +02:00
|
|
|
|
json=payload, timeout=timeout)
|
|
|
|
|
|
try:
|
|
|
|
|
|
resp = fut.result(timeout=timeout)
|
|
|
|
|
|
ex.shutdown(wait=False)
|
|
|
|
|
|
return resp
|
|
|
|
|
|
except concurrent.futures.TimeoutError:
|
|
|
|
|
|
ex.shutdown(wait=False) # Thread nicht abwarten — wir machen weiter
|
|
|
|
|
|
raise requests.exceptions.Timeout(f"Hartes Zeitlimit {timeout}s überschritten")
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
def _openrouter_chat(model, messages, ai_cfg, api_key, timeout=None) -> dict | None:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
"""Chat-Call: zuerst OpenRouter (primär), dann lokale llama.cpp-Server als Fallback.
|
|
|
|
|
|
|
|
|
|
|
|
Returns das Verdikt-Dict oder None wenn alle Endpunkte scheitern (graceful)."""
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
if timeout is None:
|
2026-06-13 02:11:14 +02:00
|
|
|
|
timeout = ai_cfg.get("attempt_timeout", ai_cfg.get("request_timeout", 20))
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
payload = {
|
|
|
|
|
|
"model": model,
|
|
|
|
|
|
"messages": messages,
|
|
|
|
|
|
"response_format": _RESPONSE_SCHEMA,
|
|
|
|
|
|
"temperature": 0,
|
|
|
|
|
|
}
|
2026-06-14 19:02:17 +02:00
|
|
|
|
|
|
|
|
|
|
# --- 1. OpenRouter (primär) ---
|
feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)
Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.
Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
übersprungen, Scan läuft unverändert weiter.
Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.
API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).
Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).
Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:04:33 +02:00
|
|
|
|
headers = {
|
|
|
|
|
|
"Authorization": f"Bearer {api_key}",
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
"X-Title": "integrity-scanner",
|
|
|
|
|
|
}
|
|
|
|
|
|
try:
|
2026-06-14 19:02:17 +02:00
|
|
|
|
resp = _post_with_deadline(_OPENROUTER_URL, headers, payload, timeout)
|
|
|
|
|
|
if resp.status_code == 200:
|
|
|
|
|
|
content = resp.json()["choices"][0]["message"]["content"]
|
|
|
|
|
|
verdict = json.loads(content)
|
|
|
|
|
|
if verdict.get("category") not in _CATEGORIES:
|
|
|
|
|
|
logger.warning("OpenRouter (%s): unbekannte Kategorie %r",
|
|
|
|
|
|
model, verdict.get("category"))
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.debug("KI-Antwort von OpenRouter (Modell: %s).", model)
|
|
|
|
|
|
return verdict
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.info("OpenRouter HTTP %s (%s) — Fallback zu lokalen Servern.",
|
|
|
|
|
|
resp.status_code, model)
|
|
|
|
|
|
except (requests.exceptions.RequestException, KeyError, ValueError,
|
|
|
|
|
|
json.JSONDecodeError) as exc:
|
|
|
|
|
|
logger.debug("OpenRouter-Call fehlgeschlagen (%s): %s", model, exc)
|
|
|
|
|
|
|
|
|
|
|
|
# --- 2. Lokale Server (Fallback) ---
|
|
|
|
|
|
headers = {"Content-Type": "application/json"}
|
|
|
|
|
|
if api_key:
|
|
|
|
|
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
|
|
|
|
for base_url in _LOCAL_SERVERS:
|
|
|
|
|
|
url = f"{base_url}/v1/chat/completions"
|
|
|
|
|
|
try:
|
|
|
|
|
|
resp = _post_with_deadline(url, headers, payload, timeout)
|
|
|
|
|
|
if resp.status_code == 200:
|
|
|
|
|
|
content = resp.json()["choices"][0]["message"]["content"]
|
|
|
|
|
|
verdict = json.loads(content)
|
|
|
|
|
|
if verdict.get("category") not in _CATEGORIES:
|
|
|
|
|
|
logger.warning("KI (%s@%s): unbekannte Kategorie %r",
|
|
|
|
|
|
model, base_url, verdict.get("category"))
|
|
|
|
|
|
continue
|
|
|
|
|
|
logger.debug("KI-Antwort von %s (Modell: %s).", base_url, model)
|
|
|
|
|
|
return verdict
|
|
|
|
|
|
elif resp.status_code == 503:
|
|
|
|
|
|
logger.info("KI (%s@%s): Modell wird geladen — nächster Server.", model, base_url)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.warning("KI HTTP %s (%s@%s): %s",
|
|
|
|
|
|
resp.status_code, model, base_url, resp.text[:200])
|
|
|
|
|
|
except (requests.exceptions.RequestException, KeyError, ValueError,
|
|
|
|
|
|
json.JSONDecodeError) as exc:
|
|
|
|
|
|
logger.debug("KI-Call fehlgeschlagen (%s@%s): %s", model, base_url, exc)
|
|
|
|
|
|
|
|
|
|
|
|
logger.warning("KI: OpenRouter + alle lokalen Server erfolglos für Modell %s.", model)
|
|
|
|
|
|
return None
|