## 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 steht aus. Vor einem Commit der quellentreuen Volltexte ist die Publikations-/Lizenzfreigabe bewusst zu bestätigen; sie folgt nicht automatisch aus der Regel zu amtlichen Gesetzestexten. - Betrieb: `agent/README.md`.