Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a14fe87f9 | |||
| a6c46f34e4 |
@@ -39,6 +39,9 @@ Alle wesentlichen Projektänderungen werden hier in verständlicher Form dokumen
|
|||||||
bleiben ladbar.
|
bleiben ladbar.
|
||||||
- Nicht geheime Providerkonfiguration liegt lokal außerhalb von Git; ein Key wird
|
- Nicht geheime Providerkonfiguration liegt lokal außerhalb von Git; ein Key wird
|
||||||
erst unmittelbar vor einem zulässigen Cloudaufruf geladen.
|
erst unmittelbar vor einem zulässigen Cloudaufruf geladen.
|
||||||
|
- Allgemeine unpersönliche Technikfragen werden präziser als `allowed`
|
||||||
|
klassifiziert; persönliche, interne und geheime Inhalte behalten Vorrang.
|
||||||
|
- Privacy-Ausgabe unterscheidet lokalen Modus von technischem Fallback.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
@@ -48,3 +51,5 @@ Alle wesentlichen Projektänderungen werden hier in verständlicher Form dokumen
|
|||||||
Nicht-Billing deaktiviert; Javis aktiviert niemals Billing.
|
Nicht-Billing deaktiviert; Javis aktiviert niemals Billing.
|
||||||
- Cloudkontext wird lokal klassifiziert und minimiert; 429, Netzwerk-, Timeout-,
|
- Cloudkontext wird lokal klassifiziert und minimiert; 429, Netzwerk-, Timeout-,
|
||||||
Key- und Modellfehler fallen auf Ollama zurück.
|
Key- und Modellfehler fallen auf Ollama zurück.
|
||||||
|
- Gemini-/Datenschutzablauf wurde am 30.07.2026 zusätzlich von Pascal manuell
|
||||||
|
erfolgreich abgenommen; kein Keywert erschien in der Ausgabe.
|
||||||
|
|||||||
+21
-11
@@ -99,6 +99,11 @@ Rootserver:
|
|||||||
- `scripts/start-javis.ps1` verifiziert das Repository, setzt nur
|
- `scripts/start-javis.ps1` verifiziert das Repository, setzt nur
|
||||||
Prozessvariablen, prüft Ollama auf Loopback, startet es bei Bedarf versteckt
|
Prozessvariablen, prüft Ollama auf Loopback, startet es bei Bedarf versteckt
|
||||||
und wartet begrenzt. Pfade sind per Parameter oder `JAVIS_*` überschreibbar.
|
und wartet begrenzt. Pfade sind per Parameter oder `JAVIS_*` überschreibbar.
|
||||||
|
- Allgemeine unpersönliche Technikfragen zu RAM/SSD, SQLite, HTTP, Git und
|
||||||
|
ähnlichen Begriffen werden gezielter `allowed`; `never` und `ask` behalten
|
||||||
|
Vorrang bei Secrets, interner Infrastruktur und persönlichem Kontext.
|
||||||
|
- `/privacy` trennt Inhaltsklassifikation, tatsächlichen Provider, Unterdrückung
|
||||||
|
durch Modus `local` und technischen Provider-Fallback.
|
||||||
|
|
||||||
## Aktuelle Architektur
|
## Aktuelle Architektur
|
||||||
|
|
||||||
@@ -171,12 +176,11 @@ Letzter bestätigter Projektstand:
|
|||||||
- uv-Lock und `uv sync --dev`: bestanden
|
- uv-Lock und `uv sync --dev`: bestanden
|
||||||
- Python in `.venv`: 3.12.13
|
- Python in `.venv`: 3.12.13
|
||||||
- Ruff in `.venv`: 0.16.0
|
- Ruff in `.venv`: 0.16.0
|
||||||
- Unit-Tests: 62 bestanden; Timeout und fehlendes Modell fallen nachweislich lokal zurück
|
- Unit-Tests: 66 bestanden; neue Technikklassifikation und Privacy-Ausgabe abgedeckt
|
||||||
- PowerShell-Syntax des Startskripts: erfolgreich geparst
|
- PowerShell-Syntax des Startskripts: erfolgreich geparst
|
||||||
- Ruff Lint: bestanden
|
- Ruff Lint: bestanden
|
||||||
- Ruff Formatprüfung: bestanden
|
- Ruff Formatprüfung: bestanden
|
||||||
- lokaler nachrichtenfreier CLI-Smoke: bestanden; Nullkostenmodus aktiv,
|
- lokaler CLI-Smoke ohne Secretzugriff: bestanden
|
||||||
Gemini nicht konfiguriert, Schlüssel nicht vorhanden, 0 Cloudanfragen
|
|
||||||
- Obsidian-Vault unverändert: 1 Markdown-, 11 Gesamtdateien, letzter
|
- Obsidian-Vault unverändert: 1 Markdown-, 11 Gesamtdateien, letzter
|
||||||
Schreibzeitpunkt weiterhin 30.07.2026 15:42:31
|
Schreibzeitpunkt weiterhin 30.07.2026 15:42:31
|
||||||
- Rootserver und Ubuntu-Laptop wurden nicht kontaktiert
|
- Rootserver und Ubuntu-Laptop wurden nicht kontaktiert
|
||||||
@@ -203,15 +207,21 @@ Abnahmestatus:
|
|||||||
- manuell durch Pascal getestet: am 30.07.2026 erfolgreich; CLI-Start, echte
|
- manuell durch Pascal getestet: am 30.07.2026 erfolgreich; CLI-Start, echte
|
||||||
deutsche Antworten, Sitzungskontext, `/sessions`, `/exit`, Neustart,
|
deutsche Antworten, Sitzungskontext, `/sessions`, `/exit`, Neustart,
|
||||||
`/load <UUID>`, vollständiger Verlauf und Fortsetzung
|
`/load <UUID>`, vollständiger Verlauf und Fortsetzung
|
||||||
- Gemini/Datenschutz/Fallback automatisiert durch Codex getestet: ja, nur mit
|
- Gemini/Datenschutz/Fallback automatisiert durch Codex getestet: ja
|
||||||
Test-Doubles und lokalem nachrichtenfreiem CLI-Smoke
|
- Gemini/Datenschutz/Fallback manuell durch Pascal getestet: am 30.07.2026;
|
||||||
- Gemini/Datenschutz/Fallback manuell durch Pascal getestet: noch nicht
|
verdeckte Key-Speicherung ohne Wertausgabe, vollständiger `/status`, echter
|
||||||
- von Pascal für produktive Verwendung freigegeben: noch nicht ausdrücklich erklärt
|
Gemini-Erfolg, Cloudzähler, `ask`-Ablehnung, `never`, `/provider local` und
|
||||||
|
Startskript erfolgreich
|
||||||
|
- für Merge freigegeben: ja, nach den dokumentierten kleinen Korrekturen
|
||||||
|
- für produktive Nutzung freigegeben: nicht separat erklärt; keine Freigabe für
|
||||||
|
spätere Tools, Obsidian oder autonome Aktionen
|
||||||
|
|
||||||
## Git-Stand
|
## Git-Stand
|
||||||
|
|
||||||
- aktueller Branch: `feat/gemini-privacy-router`
|
- aktueller Branch: `feat/gemini-privacy-router`
|
||||||
- stabiler Branch: `main`
|
- stabiler Branch: `main`
|
||||||
|
- Feature-Branch ist zu `origin/feat/gemini-privacy-router` gepusht und verfolgt
|
||||||
|
diesen Remote-Branch; `main` und `origin/main` blieben auf `8a678ae`
|
||||||
- Grundgerüstcommit: `6d04171`
|
- Grundgerüstcommit: `6d04171`
|
||||||
- letzter Commit vor der Handoff-Verdichtung: `0e82748`
|
- letzter Commit vor der Handoff-Verdichtung: `0e82748`
|
||||||
- Handoff-Verdichtung auf `main`: `e6eb995`
|
- Handoff-Verdichtung auf `main`: `e6eb995`
|
||||||
@@ -243,7 +253,7 @@ Abnahmestatus:
|
|||||||
- aktuell: portable Ollama-Laufzeit und `qwen3:8b` außerhalb von Git verifiziert
|
- aktuell: portable Ollama-Laufzeit und `qwen3:8b` außerhalb von Git verifiziert
|
||||||
- aktuell: lokaler CLI-Chat und persistente SQLite-Sitzungen vollständig abgenommen
|
- aktuell: lokaler CLI-Chat und persistente SQLite-Sitzungen vollständig abgenommen
|
||||||
- aktuell: Gemini-Free-Hybridrouting, Datenschutz und Nullkostenmodus
|
- aktuell: Gemini-Free-Hybridrouting, Datenschutz und Nullkostenmodus
|
||||||
automatisiert vollständig geprüft; manueller Live-Test steht aus
|
automatisiert und durch Pascal manuell vollständig abgenommen
|
||||||
|
|
||||||
## Offene Entscheidungen und Fehler
|
## Offene Entscheidungen und Fehler
|
||||||
|
|
||||||
@@ -273,9 +283,9 @@ Abnahmestatus:
|
|||||||
|
|
||||||
## Nächster sinnvoller Auftrag
|
## Nächster sinnvoller Auftrag
|
||||||
|
|
||||||
Pascal führt anhand `docs/NEXT_SESSION.md` den echten Gemini-, Ask- und
|
Nach dem Merge ein separates, kleines Paket für Chatkomfort und Robustheit
|
||||||
Never-Test mit ausschließlich künstlichen beziehungsweise unpersönlichen Daten
|
planen, beispielsweise Sitzungstitel/Suche oder Streaming. Noch keine
|
||||||
durch. Bis zu seiner Bestätigung nicht nach `main` mergen.
|
Obsidian-Integration, Tools, Sprache oder Serversteuerung beginnen.
|
||||||
|
|
||||||
Keinen API-Key in Chat, Git, Obsidian, Logs oder Dokumentation übernehmen.
|
Keinen API-Key in Chat, Git, Obsidian, Logs oder Dokumentation übernehmen.
|
||||||
|
|
||||||
|
|||||||
+15
-34
@@ -1,45 +1,26 @@
|
|||||||
# Nächste Sitzung
|
# Nächste Sitzung
|
||||||
|
|
||||||
## Ziel
|
## Ausgangsstand
|
||||||
|
|
||||||
Pascal nimmt den Gemini-Free-Provider und die lokalen Fallbacks manuell ab.
|
Der Core-Chat und der Gemini-/Datenschutz-Meilenstein sind automatisiert durch
|
||||||
Bis dahin bleibt `feat/gemini-privacy-router` ungemergt.
|
Codex und am 30.07.2026 manuell durch Pascal abgenommen. Der Featurestand ist zum
|
||||||
|
Merge freigegeben.
|
||||||
|
|
||||||
## Vorbereitung durch Pascal
|
Bestätigt sind echter Gemini-Erfolg, verdeckte Key-Speicherung, Nullkostenmodus,
|
||||||
|
bereinigter Status, Cloudzähler, `ask`- und `never`-Routing, erzwungener lokaler
|
||||||
|
Modus sowie das Startskript.
|
||||||
|
|
||||||
1. In Google AI Studio einen neuen, nur für Javis verwendeten Gemini-Auth-Key
|
## Nächstes sinnvolles Paket
|
||||||
erstellen.
|
|
||||||
2. Für das zugehörige Google-Projekt kein Billing verbinden beziehungsweise den
|
|
||||||
externen Billingstatus selbst prüfen.
|
|
||||||
3. Den Key niemals in Chat, Codex, Git, Obsidian oder einen sichtbaren
|
|
||||||
Kommandozeilenparameter kopieren.
|
|
||||||
4. Im Repository ausführen:
|
|
||||||
|
|
||||||
```powershell
|
Nur ein kleines Chatkomfort-/Robustheitsthema auswählen, zum Beispiel:
|
||||||
& 'D:\Javis-Tools\uv\uv.exe' run javis configure gemini
|
|
||||||
& 'D:\Javis-Tools\uv\uv.exe' run javis secrets set gemini
|
|
||||||
powershell -ExecutionPolicy Bypass -File .\scripts\start-javis.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Die erste Einrichtung verlangt zwei lokale Bestätigungen. Der zweite Befehl liest
|
- automatische, lokal erzeugte Sitzungstitel und Sitzungssuche oder
|
||||||
den Key verdeckt ein und speichert ihn im Windows Credential Manager.
|
- Streaming lokaler und Cloudantworten mit sauberem Abbruchverhalten
|
||||||
|
|
||||||
## Manueller Test
|
Vorher wie immer Branch, Arbeitsbaum, aktuellen Handoff und Tests prüfen.
|
||||||
|
|
||||||
1. `/status`: Key und Konfiguration `ja`, Nullkostenmodus `aktiv`.
|
|
||||||
2. Unpersönlich: `Erkläre in zwei Sätzen, was eine SQLite-Transaktion ist.`
|
|
||||||
3. `/privacy`: `allowed`, Provider `gemini`, kein Fallback.
|
|
||||||
4. Ask ohne private Details: `Ich habe eine private Alltagssituation. Nenne nur
|
|
||||||
allgemeine Möglichkeiten.` Bei der Freigabe Enter drücken; Antwort muss lokal
|
|
||||||
über Ollama kommen.
|
|
||||||
5. Never mit Kunstbeispiel: `Nur lokal: Der Beispiel-API-Key ist absichtlich
|
|
||||||
erfunden.` Es darf keine Cloudfreigabe angeboten werden; Antwort bleibt lokal.
|
|
||||||
6. `/status`: bereinigte Zähler prüfen, danach `/exit`.
|
|
||||||
|
|
||||||
Erst wenn Pascal diese Punkte bestätigt, darf über den Merge nach `main`
|
|
||||||
entschieden werden. Google-Quoten sind projektabhängig und in AI Studio zu prüfen.
|
|
||||||
|
|
||||||
## Weiterhin nicht beginnen
|
## Weiterhin nicht beginnen
|
||||||
|
|
||||||
Kein Obsidian-Zugriff, SSH-/Serverzugriff, Sprache, Mobile Client, Tools oder
|
Noch keine Obsidian-Integration, Tools, SSH-/Serversteuerung, Sprache, Mobile
|
||||||
Living Mind. Keine echten persönlichen Daten für Cloudtests verwenden.
|
Client oder Living Mind. Der vorhandene Gemini-Key bleibt im Betriebssystem-
|
||||||
|
Keyring und darf nicht erneut in Chat, Logs oder Dokumentation erscheinen.
|
||||||
|
|||||||
+14
-8
@@ -33,8 +33,10 @@ Stand: Gemini-Free-Feature automatisiert geprüft, 30.07.2026.
|
|||||||
- Core-Chat automatisiert durch Codex getestet: ja
|
- Core-Chat automatisiert durch Codex getestet: ja
|
||||||
- Core-Chat manuell durch Pascal getestet: ja, am 30.07.2026
|
- Core-Chat manuell durch Pascal getestet: ja, am 30.07.2026
|
||||||
- Gemini/Datenschutz/Fallback automatisiert durch Codex getestet: ja
|
- Gemini/Datenschutz/Fallback automatisiert durch Codex getestet: ja
|
||||||
- Gemini/Datenschutz/Fallback manuell durch Pascal getestet: noch nicht
|
- Gemini/Datenschutz/Fallback manuell durch Pascal getestet: ja, am 30.07.2026
|
||||||
- von Pascal für produktive Verwendung freigegeben: noch nicht ausdrücklich erklärt
|
- für Merge freigegeben: ja, nach den zwei kleinen Korrekturen
|
||||||
|
- für produktive Nutzung freigegeben: nicht separat erklärt; keine spätere Tool-,
|
||||||
|
Obsidian- oder Autonomiefunktion
|
||||||
|
|
||||||
## Aktuelles Featurepaket
|
## Aktuelles Featurepaket
|
||||||
|
|
||||||
@@ -60,9 +62,12 @@ Stand: Gemini-Free-Feature automatisiert geprüft, 30.07.2026.
|
|||||||
- `javis configure gemini` mit doppelter ausdrücklicher Bestätigung
|
- `javis configure gemini` mit doppelter ausdrücklicher Bestätigung
|
||||||
- echter Chat auf Hybridprovider verdrahtet; Key wird verzögert geladen
|
- echter Chat auf Hybridprovider verdrahtet; Key wird verzögert geladen
|
||||||
- `/provider`, `/privacy` und `/status` geben keine Inhalte oder Keywerte aus
|
- `/provider`, `/privacy` und `/status` geben keine Inhalte oder Keywerte aus
|
||||||
|
- allgemeine unpersönliche Technikbegriffe werden gezielter `allowed`, während
|
||||||
|
persönliche, interne oder geheime technische Inhalte `ask`/`never` bleiben
|
||||||
|
- `/privacy` trennt lokalen Modus von einem technischen Provider-Fallback
|
||||||
- sicheres `scripts/start-javis.ps1` mit relativer Repositoryprüfung,
|
- sicheres `scripts/start-javis.ps1` mit relativer Repositoryprüfung,
|
||||||
Loopback-Ollama und überschreibbaren externen Pfaden
|
Loopback-Ollama und überschreibbaren externen Pfaden
|
||||||
- 62 Unit-Tests aktuell erfolgreich; PowerShell-Syntax, Lockfile, Sync,
|
- 66 Unit-Tests aktuell erfolgreich; PowerShell-Syntax, Lockfile, Sync,
|
||||||
Strukturprüfung, Secret-Scan, Gitignore, Ruff-Format und Lint sauber
|
Strukturprüfung, Secret-Scan, Gitignore, Ruff-Format und Lint sauber
|
||||||
|
|
||||||
## Nicht implementiert
|
## Nicht implementiert
|
||||||
@@ -74,6 +79,7 @@ Living-Mind-Frontend.
|
|||||||
|
|
||||||
- stabiler Branch: `main`
|
- stabiler Branch: `main`
|
||||||
- aktueller Branch: `feat/gemini-privacy-router`
|
- aktueller Branch: `feat/gemini-privacy-router`
|
||||||
|
- Feature-Branch: zu `origin/feat/gemini-privacy-router` gepusht
|
||||||
- Grundgerüstcommit: `6d04171`
|
- Grundgerüstcommit: `6d04171`
|
||||||
- Basis des Feature-Branches: `e6eb995`
|
- Basis des Feature-Branches: `e6eb995`
|
||||||
- Toolchain: `fa90a5c`
|
- Toolchain: `fa90a5c`
|
||||||
@@ -85,9 +91,9 @@ Living-Mind-Frontend.
|
|||||||
|
|
||||||
## Bekannte Einschränkung
|
## Bekannte Einschränkung
|
||||||
|
|
||||||
Ollama wird bewusst manuell in einem eigenen Terminal gestartet. Antworten werden
|
Das Startskript startet Ollama bei Bedarf lokal. Antworten werden noch nicht
|
||||||
noch nicht gestreamt; Sitzungen haben noch keine Titel, Suche oder Löschfunktion.
|
gestreamt; Sitzungen haben noch keine Titel, Suche oder Löschfunktion.
|
||||||
`qwen3:8b` ist nicht automatisch für die GTX 1050 des späteren Zielhosts geeignet.
|
`qwen3:8b` ist nicht automatisch für die GTX 1050 des späteren Zielhosts geeignet.
|
||||||
Der echte Gemini-/Fallback-Test durch Pascal und danach die Mergeentscheidung
|
Der Gemini-Key liegt ausschließlich im Betriebssystem-Keyring; sein Wert wurde
|
||||||
fehlen. Es wurde kein echter API-Key angefordert, angezeigt oder konfiguriert und
|
von Codex weder gelesen noch ausgegeben. Pascal hat den echten Cloud- und
|
||||||
keine Cloudanfrage ausgeführt.
|
Fallbackablauf erfolgreich geprüft.
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class RouteStatus:
|
|||||||
provider: str
|
provider: str
|
||||||
privacy_policy: CloudPolicy
|
privacy_policy: CloudPolicy
|
||||||
fallback: bool
|
fallback: bool
|
||||||
|
cloud_suppressed_by_local_mode: bool
|
||||||
|
technical_fallback: bool
|
||||||
reason: str
|
reason: str
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +96,13 @@ class HybridProvider:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.mode == "local":
|
if self.mode == "local":
|
||||||
return self._local(messages, decision, "lokaler Modus", fallback=False)
|
return self._local(
|
||||||
|
messages,
|
||||||
|
decision,
|
||||||
|
"lokaler Modus",
|
||||||
|
fallback=False,
|
||||||
|
cloud_suppressed_by_local_mode=True,
|
||||||
|
)
|
||||||
if decision.policy is CloudPolicy.NEVER:
|
if decision.policy is CloudPolicy.NEVER:
|
||||||
return self._local(messages, decision, decision.reason, fallback=True)
|
return self._local(messages, decision, decision.reason, fallback=True)
|
||||||
|
|
||||||
@@ -148,10 +156,12 @@ class HybridProvider:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.last_route = RouteStatus(
|
self.last_route = RouteStatus(
|
||||||
cloud_provider.name,
|
provider=cloud_provider.name,
|
||||||
decision.policy,
|
privacy_policy=decision.policy,
|
||||||
False,
|
fallback=False,
|
||||||
"Cloudaufruf erfolgreich",
|
cloud_suppressed_by_local_mode=False,
|
||||||
|
technical_fallback=False,
|
||||||
|
reason="Cloudaufruf erfolgreich",
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -192,7 +202,13 @@ class HybridProvider:
|
|||||||
reason = "Gemini nicht verfügbar"
|
reason = "Gemini nicht verfügbar"
|
||||||
if disable_notice:
|
if disable_notice:
|
||||||
reason = "Gemini-Schlüssel abgelehnt; Cloud für diese Sitzung deaktiviert"
|
reason = "Gemini-Schlüssel abgelehnt; Cloud für diese Sitzung deaktiviert"
|
||||||
return self._local(messages, decision, reason, fallback=True)
|
return self._local(
|
||||||
|
messages,
|
||||||
|
decision,
|
||||||
|
reason,
|
||||||
|
fallback=True,
|
||||||
|
technical_fallback=True,
|
||||||
|
)
|
||||||
|
|
||||||
def _local(
|
def _local(
|
||||||
self,
|
self,
|
||||||
@@ -201,6 +217,8 @@ class HybridProvider:
|
|||||||
reason: str,
|
reason: str,
|
||||||
*,
|
*,
|
||||||
fallback: bool,
|
fallback: bool,
|
||||||
|
cloud_suppressed_by_local_mode: bool = False,
|
||||||
|
technical_fallback: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
if fallback:
|
if fallback:
|
||||||
self._notice_callback(f"{reason} – lokale Antwort mit Ollama.")
|
self._notice_callback(f"{reason} – lokale Antwort mit Ollama.")
|
||||||
@@ -216,10 +234,12 @@ class HybridProvider:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.last_route = RouteStatus(
|
self.last_route = RouteStatus(
|
||||||
self.local_provider.name,
|
provider=self.local_provider.name,
|
||||||
decision.policy,
|
privacy_policy=decision.policy,
|
||||||
fallback,
|
fallback=fallback,
|
||||||
reason,
|
cloud_suppressed_by_local_mode=cloud_suppressed_by_local_mode,
|
||||||
|
technical_fallback=technical_fallback,
|
||||||
|
reason=reason,
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -227,9 +227,11 @@ def run_chat(
|
|||||||
output("Noch keine Datenschutzentscheidung in dieser Sitzung.")
|
output("Noch keine Datenschutzentscheidung in dieser Sitzung.")
|
||||||
else:
|
else:
|
||||||
output(
|
output(
|
||||||
"Letzte Datenschutzentscheidung: "
|
"Datenschutzentscheidung: "
|
||||||
f"{route.privacy_policy.value}; Provider: {route.provider}; "
|
f"{route.privacy_policy.value}; Provider: {route.provider}; "
|
||||||
f"Fallback: {'ja' if route.fallback else 'nein'}"
|
"Cloud durch Modus local unterdrückt: "
|
||||||
|
f"{'ja' if route.cloud_suppressed_by_local_mode else 'nein'}; "
|
||||||
|
f"Technischer Fallback: {'ja' if route.technical_fallback else 'nein'}"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
if entered == "/status":
|
if entered == "/status":
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ _ASK_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
re.compile(
|
re.compile(
|
||||||
r"(?i)\b(intern(?:e[rsn]?|er)? hostname|interne? domain|rootserver|"
|
r"(?i)\b(intern(?:e|er|es|en)?\s+"
|
||||||
|
r"(?:hostname|server|domain|infrastruktur)|rootserver|"
|
||||||
r"privates? projekt|nicht öffentlich|systeminformation|"
|
r"privates? projekt|nicht öffentlich|systeminformation|"
|
||||||
r"private[rs]? termin)\b"
|
r"private[rs]? termin)\b"
|
||||||
),
|
),
|
||||||
@@ -90,7 +91,9 @@ _ASK_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|||||||
_ALLOWED_PATTERNS: tuple[re.Pattern[str], ...] = (
|
_ALLOWED_PATTERNS: tuple[re.Pattern[str], ...] = (
|
||||||
re.compile(
|
re.compile(
|
||||||
r"(?i)\b(python|programmier|quellcode|algorithmus|sqlite|linux|windows|"
|
r"(?i)\b(python|programmier|quellcode|algorithmus|sqlite|linux|windows|"
|
||||||
r"öffentliche dokumentation|allgemeine wissensfrage|was ist|wie funktioniert)\b"
|
r"git|ram|arbeitsspeicher|ssd|solid state drive|http|statuscode|"
|
||||||
|
r"transaktion|öffentliche dokumentation|allgemeine wissensfrage|"
|
||||||
|
r"was ist|wie funktioniert)\b"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -70,7 +70,8 @@ class CliTests(unittest.TestCase):
|
|||||||
provider.last_route = SimpleNamespace(
|
provider.last_route = SimpleNamespace(
|
||||||
privacy_policy=CloudPolicy.ASK,
|
privacy_policy=CloudPolicy.ASK,
|
||||||
provider="ollama",
|
provider="ollama",
|
||||||
fallback=True,
|
cloud_suppressed_by_local_mode=True,
|
||||||
|
technical_fallback=False,
|
||||||
)
|
)
|
||||||
service = ChatService(store, provider)
|
service = ChatService(store, provider)
|
||||||
inputs = iter(
|
inputs = iter(
|
||||||
@@ -97,7 +98,14 @@ class CliTests(unittest.TestCase):
|
|||||||
self.assertEqual(result, 0)
|
self.assertEqual(result, 0)
|
||||||
self.assertIn("Aktiver Providermodus: auto", output)
|
self.assertIn("Aktiver Providermodus: auto", output)
|
||||||
self.assertIn("Providermodus: local", output)
|
self.assertIn("Providermodus: local", output)
|
||||||
self.assertTrue(any("ask; Provider: ollama; Fallback: ja" in line for line in output))
|
self.assertTrue(
|
||||||
|
any(
|
||||||
|
"Datenschutzentscheidung: ask; Provider: ollama; "
|
||||||
|
"Cloud durch Modus local unterdrückt: ja; "
|
||||||
|
"Technischer Fallback: nein" in line
|
||||||
|
for line in output
|
||||||
|
)
|
||||||
|
)
|
||||||
self.assertIn("Gemini-Schlüssel vorhanden: ja", output)
|
self.assertIn("Gemini-Schlüssel vorhanden: ja", output)
|
||||||
|
|
||||||
def test_gemini_configuration_defaults_to_no(self) -> None:
|
def test_gemini_configuration_defaults_to_no(self) -> None:
|
||||||
|
|||||||
@@ -9,11 +9,34 @@ class PrivacyRouterTests(unittest.TestCase):
|
|||||||
self.router = PrivacyRouter()
|
self.router = PrivacyRouter()
|
||||||
|
|
||||||
def test_general_technical_question_is_allowed(self) -> None:
|
def test_general_technical_question_is_allowed(self) -> None:
|
||||||
|
for question in (
|
||||||
|
"Erkläre in zwei Sätzen den Unterschied zwischen RAM und SSD.",
|
||||||
|
"Wie funktioniert eine SQLite-Transaktion?",
|
||||||
|
"Was bedeutet HTTP 404?",
|
||||||
|
"Wie funktioniert Git?",
|
||||||
|
):
|
||||||
|
with self.subTest(question=question):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.router.classify("Wie funktioniert SQLite in Python?").policy,
|
self.router.classify(question).policy,
|
||||||
CloudPolicy.ALLOWED,
|
CloudPolicy.ALLOWED,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_internal_server_question_requires_approval(self) -> None:
|
||||||
|
decision = self.router.classify(
|
||||||
|
"Wie konfiguriere ich den internen Server mit Hostname srv-intern-01?"
|
||||||
|
)
|
||||||
|
self.assertEqual(decision.policy, CloudPolicy.ASK)
|
||||||
|
|
||||||
|
def test_technical_question_with_api_key_is_never(self) -> None:
|
||||||
|
decision = self.router.classify("Wie nutze ich diesen API-Key in Python: AIza" + "x" * 25)
|
||||||
|
self.assertEqual(decision.policy, CloudPolicy.NEVER)
|
||||||
|
|
||||||
|
def test_personal_technical_situation_requires_approval(self) -> None:
|
||||||
|
decision = self.router.classify(
|
||||||
|
"Meine SSD ist voll. Wie verschiebe ich meine persönlichen Dateien?"
|
||||||
|
)
|
||||||
|
self.assertEqual(decision.policy, CloudPolicy.ASK)
|
||||||
|
|
||||||
def test_personal_information_requires_approval(self) -> None:
|
def test_personal_information_requires_approval(self) -> None:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.router.classify("Ich möchte meinen Alltag besser planen.").policy,
|
self.router.classify("Ich möchte meinen Alltag besser planen.").policy,
|
||||||
|
|||||||
@@ -253,6 +253,18 @@ class HybridProviderTests(unittest.TestCase):
|
|||||||
self.assertEqual(answer, "Lokal")
|
self.assertEqual(answer, "Lokal")
|
||||||
self.assertFalse(self.approvals)
|
self.assertFalse(self.approvals)
|
||||||
self.assertFalse(self.cloud.calls)
|
self.assertFalse(self.cloud.calls)
|
||||||
|
self.assertFalse(router.last_route.fallback)
|
||||||
|
self.assertTrue(router.last_route.cloud_suppressed_by_local_mode)
|
||||||
|
self.assertFalse(router.last_route.technical_fallback)
|
||||||
|
|
||||||
|
def test_network_failure_is_reported_as_technical_fallback(self) -> None:
|
||||||
|
self.cloud.error = CloudNetworkError("offline")
|
||||||
|
router = self._router()
|
||||||
|
|
||||||
|
router.chat([ChatMessage("user", "Wie funktioniert Python?")])
|
||||||
|
|
||||||
|
self.assertFalse(router.last_route.cloud_suppressed_by_local_mode)
|
||||||
|
self.assertTrue(router.last_route.technical_fallback)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user