mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 16:56:42 +00:00
506 lines
30 KiB
Markdown
506 lines
30 KiB
Markdown
## User
|
||
|
||
bitte plane das folgende:
|
||
|
||
Eine Wissensdatenbank und ein Agent der Fragen zu Oesterreichischer Personalverrechnung mit dem Wissen aus der Wissensdatenbank beantwortet.
|
||
|
||
Entwicklungsumgebung-GPU: AMD Radeon Ai Pro r9700 - 32GB vram
|
||
Ollama-Server: http://100.183.83.12:11435
|
||
|
||
Agent: lokales Modell (ollama): empfehle ein Modell. der Agent darf nur wissen aus der wissensbasis verwenden, kein trainingswissen, kein web search
|
||
|
||
Wissensdatenbank: bitte um einen Vorschlag fuer die Umsetzung. bereits vorhanden: [@wissensbasis](file:///home/fegger/Projects/odoo-projects/pv-agent/wissensbasis/)
|
||
|
||
in weiterer Folge soll der Agent in Odoo-Enterprise verwendet werden.
|
||
|
||
---
|
||
|
||
# Plan: Wissensbasis-Agent Österreichische Personalverrechnung
|
||
|
||
*Stand: 2026-09-14 — Planung, noch keine Implementierung.*
|
||
|
||
## 1. Ist-Zustand (verifiziert)
|
||
|
||
- **Wissensbasis** (`wissensbasis/`): **601 kuratierte Layer-2-Einträge**
|
||
(571 Lexis Briefings Personalrecht, 30 WIKU), verteilt über 69 Cluster
|
||
(`INDEX.md`). Median ~7,3 KB, max ~31 KB, gesamt ~4,8 MB — ideale
|
||
Größe für vollständige Indizierung.
|
||
- **Struktur retrieval-gerecht:** verbindliches Frontmatter-Schema
|
||
(`id`, `stand`, `topic`, `tags`, `legal_bases`, `cross_refs`, …),
|
||
`kb.json` als generierte/validierte Registry, fixer Dokumentaufbau
|
||
(`## Zusammenfassung` → `## Kernwerte & Fristen` → `## Rechtsgrundlagen` →
|
||
`## Payroll-Relevanz (Odoo)` → `## Verweise`).
|
||
- **Layer-1-Volltexte:** `.lexis360/md/` (572 Dateien) lokal vorhanden,
|
||
lizenzbeschränkt + unversioniert. `.wiku/` fehlt in diesem Checkout —
|
||
für Phase A irrelevant (Retrieval läuft auf Layer 2).
|
||
- **Ollama-Server** `http://100.103.83.12:11435` (Ziel-Instanz mit Modell-Zoo,
|
||
Custom-Port; der Host betreibt zusätzlich eine fast leere Instanz auf 11434):
|
||
aus der Zed-Sandbox erreichbar (2026-09-14, nach URL-Korrektur) —
|
||
`bge-m3` wurde per API gepullt, `qwen3.8:27b` war bereits installiert.
|
||
- **GPU:** Radeon AI Pro R9700, 32 GB (Strix Halo / RDNA 5) — budgetiert
|
||
Modellwahl auf ~26–28 GB nutzbar.
|
||
|
||
## 2. Zielbild
|
||
|
||
Ein RAG-Agent, der Fragen zur österreichischen Personalverrechnung
|
||
**ausschließlich** aus den Layer-2-Einträgen beantwortet:
|
||
|
||
- jede fachliche Aussage mit KB-ID und `stand` belegt,
|
||
- **kein** Trainingswissen, **kein** Web, **keine** Tools,
|
||
- ehrliche Verweigerung, wenn die Wissensbasis nichts hergibt,
|
||
- Korpuskonflikte (z. B. ATZ-Ersatzquote 28,5 % vs. 27,5 %, lb-atz-07/09/12)
|
||
werden **beidseitig mit ⚠** referenziert — nie still aufgelöst,
|
||
- später Chat-Oberfläche in Odoo Enterprise.
|
||
|
||
## 3. Architektur (Phase A — eigener schlanker RAG-Service)
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
KB[wissensbasis/dokumente/*.md + kb.json] -->|Ingest: Frontmatter + H2-Sektionen| IDX[(data/index.db - SQLite: FTS5 BM25 + Vektoren + Metadaten)]
|
||
IDX -->|Hybrid-Retrieval: BM25 + Dense, Metadaten-Filter, cross_refs| CTX[Kontextblöcke 8-12]
|
||
CTX -->|Systemprompt: nur Kontext, Zitierpflicht| LLM[Ollama qwen3:32b]
|
||
LLM -->|Antwort-Entwurf| CHECK{Post-Validierung: zitierte IDs ⊆ retrieved IDs?}
|
||
CHECK -->|ja| A[Antwort + Quellenblock]
|
||
CHECK -->|nein, 1x| LLM
|
||
CHECK -->|nein, 2x| R[Antwort als unsicher markiert / verweigert]
|
||
A --> API[FastAPI: POST /ask, GET /health, POST /reindex]
|
||
R --> API
|
||
API --> CLI[CLI + Mini-Web-UI zum Testen]
|
||
```
|
||
|
||
**Warum kein LangChain/LlamaIndex:** 601 Dokumente, sauberes Schema —
|
||
die Pipeline ist als eigenständiger Python-Code (~400–600 Zeilen)
|
||
überschaubar und gibt volle Kontrolle über die Grounding- und
|
||
Zitier-Regeln, die hier der kritische Teil sind. Frameworks würden
|
||
Abhängigkeiten einführen, ohne das Kernproblem (Grounding) abzunehmen.
|
||
|
||
**Warum Layer 2 als Retrieval-Korpus:** kuratiert, eigene Worte
|
||
(lizenzkonform), versioniert, Metadaten-annotiert. Layer-1-Volltexte
|
||
bleiben außen vor (offener Lizenz-/Provisioning-Punkt laut README);
|
||
eine spätere Erweiterung für Tiefenzitate ist möglich, ohne die
|
||
Architektur zu ändern.
|
||
|
||
## 4. Modell-Empfehlung (Ollama)
|
||
|
||
| Modell | Rolle | Größe (Q4) | Begründung |
|
||
|---|---|---|---|
|
||
| **`qwen3.8:27b`** | **primärer Bake-off-Kandidat** | ~18 GB · 256k ctx | neueste Qwen-Generation (Ollama-Library, verifiziert 2026-09); Thinking per Request abschaltbar; Vision vorhanden, hier ungenutzt |
|
||
| `qwen3:32b` | bekannte Größe / Fallback | ~20 GB | bestes Deutsch + Instruction-Following im ≤32B-Bereich; Zitierdisziplin entscheidender als Weltwissen (das wir unterdrücken) |
|
||
| `gemma3:27b` | Alternative (Bake-off) | ~17 GB | sehr gutes Deutsch, 128k Kontext |
|
||
| `mistral-small3.2:24b` | Bake-off (Deutsch-Kandidat) | ~15 GB · 128k ctx | europäischer Anbieter, Europasprachen-Fokus → starke Deutsch-Hypothese (im Bake-off verifizieren); starkes Instruction-Following + wenige Wiederholungsfehler; kein Thinking-Modus |
|
||
| `qwen3:30b-a3b` | Latenz-Alternative | ~18 GB | MoE (3,3B aktiv) → deutlich schneller, etwas schwächer |
|
||
| `qwen3:14b` | Dev/Bake-off | ~9 GB | falls Zitierqualität reicht → halbe Latenz |
|
||
| **`bge-m3`** | **Embeddings** | ~1,2 GB | multilingual (Deutsch stark), 8k Kontext, über Ollama `/api/embed` |
|
||
| `bge-reranker-v2-m3` | optionales Reranking | ~1,1 GB | Rerank-API der installierten Ollama-Version vorher verifizieren; Fallback: Hybrid-Score ohne Reranker |
|
||
|
||
**VRAM-Budget:** Antwortmodell (15–20 GB) + bge-m3 (~1,5 GB) + KV-Cache für
|
||
RAG-Prompts (4–8k Tokens Kontext, ~16k Kontextfenster ≈ 3–4 GB) ⇒
|
||
~20–26 GB von 32 GB — passt mit Reserve. Thinking-Modus abschalten
|
||
(Latenz; bei `qwen3.8:27b` ist Thinking Default **an** → per Request
|
||
deaktivieren, API-Option lt. Library: `reasoning_effort`,
|
||
`preserve_thinking` — beim Implementieren verifizieren).
|
||
|
||
**Empfehlung:** Bake-off-Feld (M3): `qwen3.8:27b` und `qwen3:32b`
|
||
(Front-Runner) sowie `gemma3:27b` und `mistral-small3.2:24b` als
|
||
Deutsch-Kandidaten — Deutsch-/Zitierqualität ist jeweils unverifiziert.
|
||
Verbindliche Entscheidung erst nach Bake-off auf dem Goldset — Kriterium
|
||
bleibt Zitier-Präzision vor Latenz; liegt `qwen3:14b` bei gleicher
|
||
Zitierqualität auf, gewinnt Latenz.
|
||
|
||
> **Bake-off-Ergebnis (2026-09-14, Abschnitt 13):** `qwen3.8:27b` hat
|
||
> gewonnen und ist als Antwortmodell fixiert.
|
||
|
||
## 5. Grounding-Konzept (der kritische Teil)
|
||
|
||
1. **Systemprompt (deutsch):** antworte ausschließlich aus den
|
||
nummerierten Kontextblöcken; jede fachliche Aussage mit
|
||
`[kb-id]`-Beleg; Werte **immer mit** `(Stand YYYY-MM)`; fehlt etwas →
|
||
„Dazu enthält die Wissensbasis keine Aussage“ + ggf. verwandte
|
||
Cluster nennen; §-Zitate nur wenn die Quelle sie nennt; Korpus-
|
||
konflikte beidseitig mit ⚠ darstellen; keine Ergänzungen aus
|
||
Trainingswissen.
|
||
2. **Kontextblöcke** mit Metadatenkopf (ID · Titel · Stand · topic ·
|
||
Quellenwerk) — das Modell sieht nur, was im Retrieval war.
|
||
3. **Post-Validierung:** jede zitierte ID muss in der Retrieved-Menge
|
||
stehen; Verstoß → eine Regenerierung mit härterem Hinweis, dann
|
||
Verweigern/„unsicher“. Temperatur ~0,1.
|
||
4. **Kein Ausweg nach außen:** keine Tools, kein Browsing, kein
|
||
Web-Search-Hook — architektonisch gibt es nur Wissensbasis → Prompt.
|
||
|
||
## 6. Wissensdatenbank-Umsetzung (Vorschlag)
|
||
|
||
Die bestehende Wissensbasis ist bereits retrieval-gerecht — **kein
|
||
Umbau nötig**, nur ein Ingest-Index:
|
||
|
||
- **Chunking:** H2-Sektionen je Eintrag als Retrieval-Einheit (Parent-
|
||
Child: Treffer auf Sektion, Kontext = ganze Sektion + Metadatenkopf);
|
||
`Kernwerte & Fristen`-Tabellen als eigene Chunks (Zahlenfragen!);
|
||
Frontmatter im Index (topic, tags, legal_bases, stand).
|
||
- **Hybrid-Retrieval:** SQLite FTS5 (BM25; deutsche Normalisierung:
|
||
Umlaut-Folding beim Indexing) + Dense-Embeddings (bge-m3) + optional
|
||
Reranker; Reciprocal-Rank-Fusion; `cross_refs` der Top-Treffer als
|
||
kontrollierte Kontext-Erweiterung.
|
||
- **Metadaten-Filter:** `topic`-Vorfokus aus der Frage, Aktualitäts-
|
||
gewichtung über `stand`.
|
||
- **Quelle der Ingestion:** Layer-2-Frontmatter direkt (Single Source of
|
||
Truth); `kb.json` zusätzlich als Konsistenz-Gate (Anzahl/IDs müssen
|
||
matchen).
|
||
- **Update-Zyklus:** nach jedem neuen Batch einmal `POST /reindex`
|
||
(vollständiger Rebuild dauert bei 601 Einträgen Sekunden; Embeddings
|
||
gecacht, nur neue Einträge einbetten).
|
||
- **Speicherung:** eine SQLite-Datei `data/index.db` (gitignored) —
|
||
keine externe Vektor-DB nötig; Skalierungsreserve bis ~10.000
|
||
Einträge ohne Architekturwechsel.
|
||
|
||
## 7. Neue Dateien (Phase A)
|
||
|
||
```
|
||
agent/
|
||
config.py # Ollama-URL, Modellnamen, Ports (ENV-override)
|
||
ingest.py # Layer-2 → index.db (FTS5 + Vektoren via /api/embed)
|
||
retrieve.py # Hybrid-Suche + Filter + cross_refs (+ optional Rerank)
|
||
generate.py # Prompt-Bau, Ollama-Chat, Post-Validierung, Antwortformat
|
||
api.py # FastAPI: /ask, /health, /reindex
|
||
cli.py # Frage im Terminal (Dev-Loop)
|
||
eval/goldset.yaml # 30–50 Fragen → Soll-IDs (inkl. Konfliktfälle)
|
||
eval/evaluate.py # Recall@k, Zitier-Präzision, Verweigerungsraten, Latenz
|
||
web/index.html # minimalistischer Test-Chat
|
||
data/ # index.db (gitignored)
|
||
tests/ # pytest: Ingest-, Retrieval-, Grounding-Unit-Tests
|
||
```
|
||
|
||
## 8. Validierung
|
||
|
||
- **Goldset:** 30–50 Fragen mit Soll-IDs je Cluster, inkl. 3–5
|
||
Outside-KB-Fragen (Verweigerung!) und Konfliktfragen (ATZ-Quoten).
|
||
- **Metriken:** Retrieval-Recall@8 (Ziel >0,9), Zitier-Präzision
|
||
(100 % zitierte IDs ∈ retrieved), Verweigerungskorrektheit,
|
||
End-zu-End-Latenz.
|
||
- **Modell-Bake-off (M3):** qwen3.8:27b vs. qwen3:32b vs. gemma3:27b vs.
|
||
mistral-small3.2:24b (plus qwen3:14b als Latenz-Untergrenze) auf dem
|
||
Goldset; Entscheidung dokumentieren (analog D1/D2-Stil des Projekts).
|
||
- **Unit-Tests:** Ingest-Schema, Umlaut-Normalisierung, Post-Validierung
|
||
(Halluzinations-ID → Regenerierung), Konflikt-Darstellung.
|
||
|
||
## 9. Phase B: Odoo-Enterprise-Integration (später)
|
||
|
||
- **Option A (empfohlen):** dünnes Custom-Modul mit OWL-Chat-Panel,
|
||
`ir.config_parameter` für die Service-URL, rollenbasierter Zugriff.
|
||
Der RAG-Service bleibt Single Source of Truth für Grounding und
|
||
Zitate; Ollama bleibt extern. Unabhängig von Odoo-Version-Features.
|
||
- **Option B (zu prüfen):** nativer Odoo-LLM-Stack (in Odoo 19 neu
|
||
eingeführte `llm`/Agent-/Knowledge-Module) mit Ollama als
|
||
OpenAI-kompatiblem Provider. **Muss zuerst gegen Euren konkreten
|
||
Odoo-19-Quellstand verifiziert werden** (Modulnamen/APIs sind hier
|
||
nicht im Projekt und werden nicht aus Trainingswissen behauptet).
|
||
Striktes Grounding + Zitierdisziplin wären dort nachzubauen.
|
||
- Entscheidung erst nach Verifikation; Phase A läuft davon unabhängig
|
||
und wird von beiden Optionen unverändert genutzt.
|
||
- **Datenschutz-Bonus:** der Agent ist reiner Wissensassistent — es
|
||
fließen keine Mitarbeiter-/Abrechnungsdaten ins Modell, nur die
|
||
Frage und Layer-2-Fachtexte.
|
||
|
||
## 10. Risiken & offene Punkte
|
||
|
||
1. **Ollama-Erreichbarkeit** gegeben (Ziel-Instanz 11435; Sandbox kann
|
||
verbinden). Achtung Doppel-Instanz auf 11434 — URL nicht "korrigieren".
|
||
Cloud-Modelle (`*:cloud`) nicht für Antworten verwenden (Anforderung:
|
||
lokal).
|
||
2. **Ollama-Version:** `/api/embed` + allfällige Rerank-Unterstützung
|
||
prüfen; Fallback ohne Reranker ist unkritisch.
|
||
3. **Strix Halo (gfx-1151):** Ollama/ROCm muss die Karte unterstützen
|
||
(Server läuft offenbar bereits — im Bake-off Performance messen).
|
||
4. **Halluzination trotz allem:** Prompt + Post-Validierung reduzieren,
|
||
aber nicht eliminieren → Eval-Suite als Dauerschutz; Antworten
|
||
führen immer Quellen-IDs (Nachprüfbarkeit durch den Nutzer).
|
||
5. **WIKU-Layer-1** fehlt lokal — nur relevant, falls später Layer-1-
|
||
Tiefenzitate gewünscht (Lizenzpunkt aus README bleibt offen).
|
||
6. **Odoo-19-LLM-Module** unverifiziert → Phase B separat planen.
|
||
|
||
## 11. Meilensteine
|
||
|
||
| # | Inhalt | Ergebnis |
|
||
|---|---|---|
|
||
| M1 | Ingest + Index + Hybrid-Retrieval (ohne LLM) | Recall@8 auf Goldset messbar |
|
||
| M2 | Ollama-Anbindung (Embed + Generate), Grounding-Regeln, `/ask`-API, CLI | nutzbarer Agent im Terminal |
|
||
| M3 | Eval-Suite + Modell-Bake-off | dokumentierte Modell-Entscheidung |
|
||
| M4 | Odoo-Integration | separater Plan nach Odoo-19-Verifikation |
|
||
|
||
## 12. Entscheidungspunkte (an Dich)
|
||
|
||
1. **Modell:** `qwen3.8:27b` als primärer Bake-off-Kandidat ok — oder
|
||
gleich als Startmodell festlegen (und `qwen3:32b` nur als Fallback)?
|
||
2. **Bake-off:** vergleichst Du die drei Antwortmodelle auf dem Goldset
|
||
(empfohlen) oder legen wir qwen3:32b direkt fest?
|
||
3. **Layer 1:** bewusst außen vor in Phase A — einverstanden?
|
||
4. **Odoo-Version für Phase B:** Odoo 19 Enterprise (passend zu
|
||
`l10n_at_hr_payroll*`) — bitte bestätigen.
|
||
|
||
## 13. Umsetzungsstand (2026-09-14)
|
||
|
||
- **M1 erledigt und akzeptiert:** Hybrid-Index (601 Einträge → 3.005 Chunks,
|
||
FTS5-BM25 + bge-m3-Dense, Ollama :11435), Goldset 31 Fragen. **Recall@8
|
||
0,952 > 0,9** (Hit-Rate 0,968, MRR 0,690; BM25-only-Vergleich:
|
||
0,855 — die vier Komposita-Fehltreffer behebt die Dense-Suche alle).
|
||
41 Unit-Tests grün.
|
||
- **M2 erledigt und gegen das echte Modell validiert** (qwen3.8:27b,
|
||
Thinking aus): Zitier-Präzision **100 %** (4 Verletzungen → Post-
|
||
Validierung → Regenerierung, alle geheilt), Verweigerung korrekt 94,3 %,
|
||
Latenz mean 32 s / p95 53 s. ATZ-Konfliktfall wird korrekt beidseitig
|
||
mit ⚠ beantwortet; harte Verweigerungsfälle (UStVA) funktionieren.
|
||
- **M3 erledigt — Bake-off (2026-09-14, Goldset 35 Fragen, Thinking aus):**
|
||
|
||
| Kandidat | Zitier-Präzision | Verweigerung korrekt | Erw. Quelle | mean/p95 | Regen |
|
||
|---|---|---|---|---|---|
|
||
| **qwen3.8:27b** ✅ | **100 %** | **94,3 %** | **80,6 %** | 32 s / 53 s | 4 |
|
||
| gemma4:26b | 100 % | 91,4 % | 67,7 % | 7,9 s / 12 s | 4 |
|
||
| gemma4:12B | 94,3 % | 91,4 % | 77,4 % | 21 s / 40 s | 8 |
|
||
| qwen3.6:27B | 94,3 % | 85,7 % | 80,6 % | 43 s / 89 s | 10 |
|
||
| muse-glimmer:latest | 100 % | 77,1 % | 74,2 % | 37,5 s / 51 s | 1 |
|
||
| mistral-small3.1:24b | 100 % | 71,4 % | 58,1 % | 20 s / 43 s | 2 |
|
||
|
||
**Entscheidung (D7):** `qwen3.8:27b` ist das Antwortmodell (Protokoll:
|
||
Zitier-Präzision → Verweigerungskorrektheit → Latenz). `gemma4:26b`
|
||
wird als dokumentierter Latenz-Kandidat für späteres interaktives Tuning
|
||
geführt (4× schneller bei 100 % Zitier-Präzision, aber schwächere
|
||
Quellentreue und 3 statt 2 Fehlverweigerungen). mistral-small3.1
|
||
(Deutsch-Hypothese) ist praktisch widerlegt: 71,4 % Verweigerungs-
|
||
korrektheit. **muse-glimmer** (2026-09-14 nachgereicht): 100 %
|
||
Zitier-Präzision bei nur 1 Regenerierung (diszipliniertestes Modell),
|
||
aber 8/35 falsche Verweigerungen (Überverweigerung teils trotz
|
||
vorhandener Zitate) und 37,5 s mean — Rang 5 von 6, schlägt qwen3.8
|
||
in keiner Kennzahl. q-008 und q-031 verweigern alle Top-Kandidaten —
|
||
Prompt-/Retrieval-Tuning-Thema, kein Modellthema.
|
||
|
||
**Prompt v2 + Kontext-Tuning (2026-09-14):** (a) Regel 4 erlaubt
|
||
Teilantworten, Regel 8 verlangt Prämisse-Korrektur mit Muster-Beispiel;
|
||
(b) Kontextblöcke pro Eintrag = beste Inhaltssektion statt bester
|
||
Rangfolge-Chunk („Verweise“-Sektionen zuletzt — BM25-Längennormalisierung
|
||
rangiert die dünnen Navigations-Chips bevorzugt, Ursache q-008).
|
||
Bestätigungslauf qwen3.8:27b (35 Fragen): Zitier-Präzision **100 %**,
|
||
Verweigerung korrekt 94,3 % (q-008/q-031 behoben; neuer bekannter Fall
|
||
q-029 — breite Survey-Frage, sicherer Fehlermodus), erwartete Quelle
|
||
**83,9 %** (v1: 80,6 %), mean 34 s. Report `data/eval-qwen38-v2.json`.
|
||
- **M4 offen:** Odoo-Integration (separater Plan nach Verifikation der
|
||
Odoo-19-LLM-Module).
|
||
- **KV/RIS-Erweiterung (2026-09-15, D9/D10):** Korpus 601 → 1 274 Einträge
|
||
(614 WKO-KV-Dokumente `kv-kvt-…`, 59 RIS-Gesetze `ris-…`; quelltreu
|
||
generiert — D9), 14 984 Chunks. Retrieval kalibriert (D10:
|
||
dense_weight 2.0, rrf_k 20, Pool 150) → Recall@8 0,923 (>0,9 ✓).
|
||
Antwortmodus: Zitier-Präzision 95,2 %, Verweigerung 90,5 % —
|
||
Fehlverweigerungs-Tuning (offen, laufender Arbeitsstand) fortsetzen.
|
||
Intake: `tools/ingest_sources.py`, Registry: `tools/build_registry.py`.
|
||
- **Tuning abgeschlossen (2026-09-15, D11):** Ursache der Fehlverweige-
|
||
rungen war Prompt-Overflow (lange KV-Chunks > 16k — Systemprompt
|
||
trunciert weg). Fix: num_ctx 32 768, max_context_chars=90 000
|
||
(`trim_results`), cross_ref_expand 6 → alle 4 Fälle geheilt (10/10
|
||
Bestätigungsläufe). Retrieval Recall@8 0,946 · Antworten: Zitier-
|
||
Präzision 97,6 %, Verweigerung 97,6 % (Gate ✓), erwartete Quelle
|
||
91,9 %, Latenz mean 33 s. Offen: q-015 Branchen-Noise, q-024
|
||
transiente Flakiness (leerer Draft).
|
||
- **Komplexe-Fragen Stufe 1 (2026-09-15, M6/D12):** Query-Planer
|
||
(Heuristik-Gate → 1-3 Sub-Queries mit Stand-Jahr und Scope), Multi-
|
||
Query-Retrieval mit Per-Query-Slots und Scope-Filtern („gesetz“/„kv“).
|
||
Eval (46 Fragen): Zitier-Präzision 97,8 %, Verweigerung 97,8 %
|
||
(Gate ✓), erwartete Quelle 90,2 %, Latenz mean 33,5 s. Goldset 42 →
|
||
46 Fragen (q-110–113). Offen für Stufe 2: Aggregation → Map-Reduce
|
||
(q-029), Rückfragen statt Verweigerung (API-first), danach
|
||
Rechtsprechung-Intake (`rj-*`, Lexis-md/json).
|
||
- **Antworttyp-Routing Stufe 2 (2026-09-15, M6/D13):** Planer-Typ
|
||
`survey` → Map-Reduce (survey_blocks=16; Map destilliert je Block
|
||
mit KB-ID, Reduce synthetisiert; Validierung unverändert über die
|
||
Union). Systemprompt-Regel 9: Kontext-Abhängigkeit → belegte allgemeine
|
||
Aussage + eine Rückfrage (API-first). q-029 geheilt (vorher hart-
|
||
näckigste Fehlverweigerung), q-015 mit KV-Abhängigkeits-Hinweis.
|
||
Eval: 97,8 % / 97,8 % / 90,2 %, Latenz mean 34,2 s (Map-Reduce nur
|
||
bei Survey-Fragen, ~95 s). Nächster Schritt: Rechtsprechung-Intake
|
||
(`rj-*`), dann M4 (Odoo; Privacy-Neubewertung für Lohndaten-Zugriff).
|
||
- **Output-Budget + length-Retry (2026-09-15, M6/D14):** q-024-Flakiness
|
||
war num_predict=1024 (done_reason=length, abgeschnittene Zitationen),
|
||
nicht Thinking. Fix: num_predict=2048, chat_full() mit done_reason,
|
||
technischer 2×-Budget-Retry bei length. **Voll-Eval erstmals mit allen
|
||
Gates erfüllt: Zitier-Präzision 100 %, Verweigerung 100 %, erwartete
|
||
Quelle 92,7 %** (46/46), Latenz mean 39,6 s. M6 damit abgeschlossen;
|
||
nächster Schritt: Rechtsprechung-Intake (`rj-*`), dann M4 (Odoo;
|
||
Privacy-Neubewertung für Lohndaten-Zugriff).
|
||
- **Rechtsprechungs-Intake (2026-09-15, D15):** `.rechtsprechung/` wurde
|
||
über `tools/ingest_sources.py --source rj` in den neuen ID-Raum
|
||
`rj-rjs-*` übernommen: 320 RIS-OGD-Entscheidungen/Rechtssätze, 23
|
||
zitierte RIS-Normauszüge sowie 5 als nichtamtliche lexetius-
|
||
Textwiedergaben markierte EuGH-Urteile. Korpus: 1.274 → **1.622**
|
||
Einträge, 77 Cluster; lange Volltexte werden an Absatzgrenzen in H2-
|
||
Chunks geteilt. Reindex: 16.480 Chunks, 1.496 neue bge-m3-Embeddings,
|
||
75 s. Erweiterter Retrieval-Eval (50 Fragen, inkl. q-120–123): Hit-Rate
|
||
0,956 · Recall@8 **0,922** (Gate >0,9 ✓) · MRR 0,661; alle neuen Fälle
|
||
gefunden. Die gezielten Antwortläufe q-120–123 sind alle zitiergültig,
|
||
nicht verweigert und ohne Regenerierung; EuGH q-123 weist nach einer
|
||
Prompt-Regel explizit auf die nichtamtliche Wiedergabe hin. Voller
|
||
Antwortmodus-Eval (50 Fragen): **100 % Zitier-Präzision**, **100 %
|
||
Verweigerung korrekt**, erwartete Quelle 93,3 %, Latenz mean 39,4 s /
|
||
p95 82,2 s, fünf Regenerierungen; Report `data/eval-qwen38-rj.json`
|
||
(lokal, unversioniert). Vor einem Commit der
|
||
quellentreuen Volltexte ist die Publikations-/Lizenzfreigabe bewusst zu
|
||
bestätigen; sie folgt nicht automatisch aus der Regel zu amtlichen
|
||
Gesetzestexten.
|
||
- **API-first-Festigung (2026-09-16, D19):** Der eigenständige Service hat
|
||
einen versionierten, strikt validierten Vertrag unter `/v1/ask`,
|
||
`/v1/health` und `/v1/reindex`; die alten Pfade bleiben deprecated. Antworten
|
||
liefern Status, Quellen, extrahierte belegte Konflikte, Rückfrage,
|
||
Planungsmetadaten und einen expliziten Grounding-Block. Request-IDs erlauben
|
||
technische Korrelation ohne Personenkennzeichen. Optionaler Bearer-Schutz
|
||
trennt Service- und Admin-Key; ein nicht-lokaler Bind ohne Service-Key wird
|
||
fail-closed abgelehnt. Health und Fehlerantworten geben keine interne
|
||
Ollama-URL bzw. Exception-Details mehr aus. Der gemeinsam genutzte SQLite-
|
||
Retriever ist für FastAPI-Worker-Threads serialisiert. **Privacy-Grenze:** v1
|
||
akzeptiert ausschließlich `mode=knowledge` und keine freien Payroll-/
|
||
Mitarbeiterdatenfelder. Die spätere Odoo-Lohndatenintegration erhält einen
|
||
getrennten tenant-autorisierten Vertrag; sie wird nicht durch Anhängen von
|
||
Rohdaten an `/v1/ask` umgesetzt. Vertrag und Odoo-Clientregeln:
|
||
`docs/API.md`.
|
||
- **Test-Frontend (2026-09-16, D20):** FastAPI liefert ein responsives,
|
||
dependency-freies same-origin UI unter `/` aus. Es rendert Antworttext
|
||
XSS-sicher, zeigt Health, Quellen, Konflikte, Rückfragen sowie Grounding-
|
||
Metadaten und speichert weder Fragen noch Chatverlauf. Ein Service-Key wird
|
||
nur vom Benutzer eingegeben und optional im `sessionStorage` des Tabs
|
||
gehalten. Zielbetrieb ist `http://100.103.83.12:8080/` mit Bind an das
|
||
Tailscale-Interface und gesetztem `PV_API_KEY`; CSP und weitere Security-
|
||
Header schützen die UI, ohne FastAPI `/docs` zu blockieren.
|
||
- **Docker-Deployment (2026-09-16, D21):** Ein gehärteter Compose-Service
|
||
betreibt UI und API gemeinsam auf `100.103.83.12:8080` und nutzt das externe
|
||
Netz `ollama_default`. Ollama bleibt ein separater bestehender Container;
|
||
sein Netzwerk-DNS-Alias wird über `OLLAMA_URL` konfiguriert. Index und KB
|
||
bleiben Host-Bind-Mounts, Secrets und lokale Korpora außerhalb des Images.
|
||
Der Stack verlangt einen Service-Key, läuft als konfigurierbare unprivilegierte
|
||
UID/GID mit read-only Root-FS und besitzt einen Readiness-Healthcheck.
|
||
Deployment und initialer Indexaufbau: `docs/DOCKER.md`.
|
||
- **Bootstrap, Audit und Bewertungen (2026-09-16, D22):** Vor jedem API-Start
|
||
prüft der Container, ob Chunks und Embeddings im persistenten Index vollständig
|
||
sind, und baut einen fehlenden/unvollständigen Index automatisch über Ollama
|
||
auf; bei Embedding-Fehlern startet die API nicht. Interaktionen werden
|
||
strukturiert in `data/audit.db` und optional als JSON nach stdout geloggt;
|
||
Inhaltsprotokoll und 30-Tage-Aufbewahrung sind konfigurierbar. Eine
|
||
authentisierte `/v1/ratings`-API sowie die UI erfassen Daumen hoch/runter und
|
||
optionales Feedback pro Request-ID. Im Metadatenmodus werden auch indirekte
|
||
Freitexte aus Quellen, Konflikten und Suchplan entfernt.
|
||
- **Antwortkommentare (2026-09-16, D23):** Unabhängig von `up|down` können
|
||
Nutzer über eine dauerhaft sichtbare Kommentarbox mehrere Kommentare pro
|
||
Antwort erfassen. `/v1/comments` bindet jeden Kommentar an die verifizierte
|
||
Request-ID; Audit-CLI und SQLite-Log führen die datierten Kommentare mit der
|
||
ursprünglichen Frage/Antwort zusammen. Im Metadatenmodus bleibt der
|
||
Kommentartext aus Persistenz und stdout entfernt.
|
||
- **Kostenaufstellung für Gestaltungsfragen (2026-09-16, D24):** Der
|
||
Decision-Support-Trigger matched jetzt auf normalisierter Frage (NFKD-Folding
|
||
plus ue-Varianten) — Fragen wie "guenstigste loesung" ohne Umlaute liefen
|
||
vorher in den generischen LLM-Planer und verweigerten. Bei expliziter
|
||
Kostennnabsicht ("wieviel kostet mich das", "einmalig ... bar auszahlen")
|
||
ergänzt der deterministische Plan zwei gesetzlich gescopte Queries: Lohnsteuer
|
||
einmaliger Bezüge (lb-son-04) und Arbeitgeberbelastung (lb-sva-06, lb-lnk);
|
||
die Zukunftssicherungs-Query fällt dann zugunsten der Slots weg. Systemprompt
|
||
Regel 11 verlangt nun die Anwendung auf den konkreten Fall: Arbeitgeberkosten
|
||
Schritt für Schritt aus belegten Sätzen, Annahmen explizit, Rückfrage nur bei
|
||
wesentlichem Fehlen. think=true: Leerer Content (Antwort nur im thinking-Feld)
|
||
führt zu einem einmaligen Retry ohne Thinking statt HTTP 503. Goldset +2
|
||
(q-128/q-129, beide recall=1,00); Offline-Eval 56 Fragen: Hit-Rate 0,98 ·
|
||
Recall@8 **0,95** · MRR 0,67. Real-Läufe beider User-Fragen: verifiziert,
|
||
mit belegter Rechnung (AG-SV auf 500 €, Lohnsteuer, Prämienvergleich).
|
||
- Betrieb: `agent/README.md`.
|
||
|
||
## 14. Phase B / M4 — Odoo-Integration (D25-Planung, Stand 2026-09-16)
|
||
|
||
**Vorentscheidung (mit User):** Odoo orchestriert und rechnet (System of
|
||
Record); der Agent konsumiert nur das vorgegebene Ergebnis und prüft
|
||
Plausibilität gegen die KB. Kein Rückpfad Odoo → Agent-Tools im Agenten;
|
||
die Aufweichung der Privacy-Regel 8 passiert bewusst erst hier und wird
|
||
im Agenten per Feature-Flag (`PV_REVIEW_MODE`, default aus) freigeschaltet.
|
||
|
||
### 14.1 Modul-Review (verlinkt als `.oddo-module/` → ../odoo-at-payroll/addons)
|
||
|
||
- Vier Module, Odoo **19.0** (`l10n_at_hr_payroll` 19.0.10.0.0 auf der
|
||
echten `hr_payroll`-Engine; `l10n_at_hr_payroll_private` 19.0.17.0.0;
|
||
`l10n_at_gemeinde_payroll` Bgld./GemBG; `l10n_at_payroll_dokumente`),
|
||
LGPL-3, ~22k Zeilen Python + ~7,5k XML, ~25 Testdateien.
|
||
- **Kein Agenten-/HTTP-Code vorhanden** (keine Controller, requests,
|
||
ir.config_parameter) — M4 startet bei null, nichts ist zurückzubauen.
|
||
- **Tenant-Isolation nativ:** `ir.rule`-Company-Regeln (GP9/AP9-Muster),
|
||
Felder am Vertrag mit `group_hr_payroll_user`-Gruppen.
|
||
- **Rechenkern komplett:** `sozialversicherung.py` (SVDN/SVDG, §-49-
|
||
Ausschaltungen **mit Jahres-Kumulative** `_l10n_at_sv49_ytd`, WF-Satzvektor
|
||
Bundesland×Jahr, DAG), `lohnsteuer.py` (§ 66 kumulativ, § 67 Sechstel/
|
||
Fünftel, § 68-Freibeträge mit YTD-Verbrauch `_l10n_at_st_frei_ytd`),
|
||
`payslip_private.py` (KommSt § 9, DZ §§ 122/126 WKG, FLAF-DB § 41 FLAG,
|
||
SZ-Basis/Dienstzeitfaktor), `sachbezuege`/`reisekosten` (km-YTD-Split).
|
||
Genau die Jahres-Salden, die im KB-Chat nur Annahmen sind, sind hier real.
|
||
- **Parametersystem:** `hr.rule.parameter`-Seeds 2026 (SV-Werte aus ÖGK-
|
||
TASY-Export, gegen offiziellen Report gespiegelt; LST 2026; 2027-Rahmen),
|
||
NSchAB/Wien als Company-Flags.
|
||
- **KV-Katalog in Odoo:** `l10n.at.payroll.kv` (+ versionierte `kv.wert`,
|
||
Gruppen/Stufen, Import-Wizard per CSV-Paste mit Sprungwarnung >10 %).
|
||
`library_variant_id` verlinkt auf die KV-Library-Variante (z. B. SI-2203,
|
||
Seed SI-2203/SI-2748). **Brücke gebaut (2026-09-16):** die KV-Library führt
|
||
bereits einen WKO-Match-Report (`wko/match-report.json`: wko_slug →
|
||
oegb_variant_id, 407 matched / 32 low / 175 unmatched); daraus erzeugt
|
||
`tools/build_kv_variant_map.py` die versionierte Map
|
||
`tools/catalogs/kv_variant_map.json` (105 Varianten, 439/614 KB-Einträge
|
||
abgedeckt; Docs je Variante mit kv_kvt_id+slug+doctype; Low-Confidence
|
||
markiert). Odoo kann pro `library_variant_id` die zugehörigen KB-Einträge
|
||
auflösen; die 175 unmatched sind WKO-aktuelle Dokumente ohne ÖGB-
|
||
Gegenstück (z. B. KV-Abschluss-News). Tests: `tests/test_kv_variant_map.py`
|
||
(inkl. Seed-Abdeckung SI-2203/SI-2748).
|
||
- Vertragsfelder für Kontext-Whitelist vorhanden: `hr.version` (KV, Gruppe,
|
||
Erfahrungsstufe, Überzahlung, Vordienstzeiten), Company (Bundesland,
|
||
KommSt-Gemeinde, NSchAB).
|
||
|
||
### 14.2 Architektur M4 (geplant)
|
||
|
||
1. **Neues fünftes Modul** `l10n_at_payroll_agent` (statt Einbau in die vier
|
||
bestehenden): Service-Client, Kontext-Builder, Verdict-UI. Depends:
|
||
`l10n_at_hr_payroll_private` (+ `hr_payroll`).
|
||
2. **Konfiguration:** Service-URL + API-Key über `ir.config_parameter`, nur
|
||
lesbar für eine eigene Gruppe `pv_agent_user`; Key nie in Views/Logs.
|
||
3. **Serverseitiger Client** (`pv.agent.client`, Odoo-`requests`, Timeout,
|
||
neutrale Fehler): Aufruf `/v1/ask` mit `mode=review` und schematisiertem
|
||
Kontext — **Whitelist hart kodiert** (facts: key/value/note; keine Namen,
|
||
SVNR, Geburtsdaten; KV-Name/Code, Bundesland/Gemeinde, Beträge, Jahres-
|
||
salden, Berechnungsergebnis mit Basis). `X-Request-ID` aus Odoo.
|
||
4. **Workflow „Plausibilitätsprüfung einer geplanten Auszahlung“** (Pilot,
|
||
**M4.1-Gerüst umgesetzt 2026-09-16** im neuen Modul
|
||
`l10n_at_payroll_agent` 19.0.1.0.0 in odoo-at-payroll/addons):
|
||
- `pv.agent.context.builder` projiziert den gerechneten Draft-Lohnzettel
|
||
(ATP_PRAMIE-Eingabe) auf facts (bruttolohn_monat via `version.wage`,
|
||
anwendender KV via `version.kv_id`, Zahlung) und computation (Draft-
|
||
Linien SVDG_*/LST_*/FLAFDB/KOMMST/DZ/BVG/WIEN_DAG als components,
|
||
SVDG-Summe als result) — alle APIs gegen den Bestand verifiziert
|
||
(lohnsteuer.py:148/266-268, versichertenmeldung.py:274,
|
||
reisekosten.py:215, hr_version_private).
|
||
- `pv.agent.client` (stdlib urllib, kein Zusatz-Dependency): POST /v1/ask
|
||
mode=review mit X-Request-ID; Konfiguration mandantenspezifisch über
|
||
`res.company.pv_agent_url` / `pv_agent_api_key` (group_hr_payroll_user,
|
||
Company-Formular-Seite) statt unverifiziertem ir.config_parameter;
|
||
neutrale UserError-Meldungen (401/422/5xx), Details nur im Log.
|
||
- `pv.agent.review.wizard` (Transient, ACL group_hr_payroll_user):
|
||
Prüfung starten → Kontext bauen → Agent aufrufen → Antwort/Verdict/
|
||
Prüfpunkte im Dialog; Re-Render über act_window/res_id. Menü unter
|
||
„AT-Payroll (Privat)“.
|
||
- Tests `tests/test_pv_agent_review.py` (TransactionCase, patched `_post_json`:
|
||
Params/Payload/Fehlerabbildung/Rendering); laufen in der Odoo-Test-Session,
|
||
lokal nur Syntax/XML/CSV-Checks. Builder-Test am echten Lohnzettel folgt
|
||
mit der Pilot-Verifikation.
|
||
5. **Agent-seitig (M4.2, umgesetzt 2026-09-16):** v1.x-Contract `mode=review`
|
||
+ `context`-Feld (extra=forbid, Whitelist-Schema: facts ≤40 mit key-Muster
|
||
`[a-z0-9_.-]`/value ≤200, computation mit components ≤40). Prompt-Addendum
|
||
mit drei Beweisklassen (KB-Beleg vs. übermittelter Wert vs. Odoo-Berechnung),
|
||
Injection-Abgrenzung (Kontext ist Daten, keine Anweisungen) und
|
||
Verdict-Format (Abschnitt „Plausibilitätsprüfung:“ mit OK/WARN ⚠/OFFEN-
|
||
Zeilen; OK/WARN brauchen KB-Beleg, sonst Regenerierung → bleibt der
|
||
Abschnitt aus, fällt das Verdict ehrlich auf `not_checkable` statt die
|
||
zitiergültige Fachantwort zu verwerfen). Feature-Flag `PV_REVIEW_MODE`
|
||
(default aus — Test-Agent bleibt knowledge-only). `grounding.data_scope`
|
||
im Review: `knowledge_base_plus_review_context`. Audit speichert den
|
||
Kontext in `context_json` (Metadatenmodus: ohne Freitext). Tests
|
||
`tests/test_review.py` (8 Fälle, offline).
|
||
6. **Audit:** Odoo protokolliert gesendete facts/Ergebnis + request_id;
|
||
agentseitig deckt sich `data/audit.db` über dieselbe Request-ID.
|
||
7. **Nicht-Ziele Phase B:** keine Lohnart-Erstellung durch den Agenten, keine
|
||
automatischen Buchungen, kein direkter Mitarbeiterzugriff im Chat
|
||
(Mandanten-/Rollengrenze bleibt in Odoo).
|