Files
pv-agent/agent/README.md
T
fegger 6cd37429f3 Bake-off ergaenzt: muse-glimmer:latest (Rang 5 von 6)
Nach User-Wunsch im selben Protokoll gemessen (35 Goldset-Fragen):
100% Zitier-Praezision bei nur 1 Regenerierung (diszipliniertestes
Modell), aber 8/35 falsche Verweigerungen (Ueberverweigerung teils trotz
vorhandener Zitate) und 37,5s mean - schlaegt qwen3.8:27b in keiner
Kennzahl. D7 unveraendert: qwen3.8:27b bleibt fixiert.
2026-09-14 23:55:45 +02:00

136 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PV RAG Agent
Lokaler RAG-Agent für österreichische Personalverrechnung: beantwortet
Fragen **ausschließlich** aus der kuratierten Wissensbasis (Layer 2,
`wissensbasis/`, 601 Einträge) — mit ID- und Stand-Beleg, ohne
Trainingswissen, ohne Web-Zugriff. Verbindliche Regeln:
`.agents/skills/pv-rag-agent/SKILL.md`, Plan: `planung.md`.
## Architektur (Kurzfassung)
```
wissensbasis/dokumente/*.md ──ingest──▶ data/index.db
├─ chunks (FTS5, BM25, Umlaut-Folding)
├─ vectors (bge-m3, Content-Hash-Cache)
└─ Metadaten (stand, topic, tags, …)
Frage ──retrieve──▶ Hybrid BM25+Dense (RRF) + cross_ref-Erweiterung
──generate──▶ Ollama (Systemprompt, Zitierpflicht)
──validate──▶ zitierte IDs ⊆ Retrieved-Set? sonst 1× regenerieren, dann verweigern
```
- **Nur Layer 2** als Korpus (kuratiert, lizenzkonform). Layer-1-Volltexte
(`.lexis360/`, `.wiku/`) bleiben außen vor (offener Lizenzpunkt).
- **Kein Ausweg nach außen:** keine Tools, kein Browsing — der einzige
HTTP-Client spricht mit Ollama.
- **Verweigerungspflicht:** leeres/schwaches Retrieval → deterministische
Antwort „Dazu enthält die Wissensbasis keine Aussage." (kein LLM-Call).
## Schnellstart
```bash
pip install -r requirements.txt
# 1) Index bauen (mit Embeddings, wenn Ollama erreichbar)
python -m agent.cli ingest # --no-embed erzwingt BM25-only
# 2) Frage im Terminal
python -m agent.cli ask "Wie hoch ist die AMS-Ersatzquote bei geblockter Altersteilzeit?"
# 3) Goldset-Evaluation (offline: Retrieval-Metriken)
python -m agent.cli eval
# inkl. Antworten + Verweigerungsfälle (benötigt Ollama):
python -m agent.cli eval --answers --json-out data/eval-report.json
# 4) HTTP-API + Test-Chat
python -m agent.cli serve # http://127.0.0.1:8080 (/ask, /health, /reindex)
```
## Konfiguration (Umgebungsvariablen)
| Variable | Default | Bedeutung |
|---|---|---|
| `OLLAMA_URL` | `http://100.103.83.12:11435` | Ollama-Ziel-Instanz — Remote-GPU-Maschine im Tailscale-Netz (nicht localhost:11434 — das ist ein anderer, lokaler Ollama) |
| `PV_ANSWER_MODEL` | `qwen3.8:27b` | Antwortmodell (provisorisch bis Bake-off M3) |
| `PV_EMBED_MODEL` | `bge-m3` | Embedding-Modell |
| `PV_DB_PATH` | `data/index.db` | SQLite-Index |
| `PV_KB_DIR` | `wissensbasis` | Wissensbasis-Verzeichnis |
| `PV_THINK` | `false` | Thinking per Request (qwen3.8: default an) |
| `PV_EMBED_OFF` | `false` | `true` = BM25-only |
| `PV_CONTEXT_BLOCKS` | `8` | Kontextblöcke im Prompt |
| `PV_PORT` | `8080` | API-Port |
## Deployment auf dem Host (Ollama-Maschine)
```bash
# Ollama-Ziel-Instanz prüfen (Custom-Port! Achtung: auf dem Host läuft
# zusätzlich eine fast leere Instanz auf 11434 — nicht verwexseln)
curl http://100.103.83.12:11435/api/tags # qwen3.8:27b, bge-m3, Bake-off-Feld installiert
# Vollständiger Index (BM25 + Dense)
python -m agent.cli ingest
python -m agent.cli eval
python -m agent.cli eval --answers --json-out data/eval-report.json # Zitier-Präzision, Verweigerungen, Latenz
```
## Baseline (2026-09-14, Hybrid BM25 + bge-m3, Ollama :11435)
**Retrieval** (Goldset, 31 Fragen): Hit-Rate 0,968 · **Recall@8 0,952** ·
MRR 0,690 — M1-Ziel >0,9 erreicht (BM25-only war 0,855; die vier
BM25-Fehltreffer behebt die Dense-Suche alle).
**Antworten** (Bake-off-Sieger qwen3.8:27b, Thinking aus, Temperatur 0,1):
**Zitier-Präzision 100 %** (4 Zitierverletzungen wurden von der
Post-Validierung abgefangen und regeneriert) · Verweigerung korrekt
94,3 % · erwartete Quelle zitiert 80,6 % · Latenz mean 32 s / p95 53 s.
**Modell-Bake-off (M3, 2026-09-14)** — Entscheidung: **qwen3.8:27b**
(Protokoll: Zitier-Präzision → Verweigerungskorrektheit → Latenz):
| Kandidat | Zitier-Präz. | Verweig. korrekt | Erw. Quelle | mean/p95 |
|---|---|---|---|---|
| **qwen3.8:27b** | **100 %** | **94,3 %** | **80,6 %** | 32 s / 53 s |
| gemma4:26b | 100 % | 91,4 % | 67,7 % | **7,9 s** / 12 s |
| gemma4:12B | 94,3 % | 91,4 % | 77,4 % | 21 s / 40 s |
| qwen3.6:27B | 94,3 % | 85,7 % | 80,6 % | 43 s / 89 s |
| muse-glimmer:latest | 100 % | 77,1 % | 74,2 % | 37,5 s / 51 s |
| mistral-small3.1:24b | 100 % | 71,4 % | 58,1 % | 20 s / 43 s |
`gemma4:26b` bleibt als dokumentierter Latenz-Kandidat für späteres
interaktives Tuning.
Bekannte Fehlverweigerungen: q-008 (Abfertigung Verfügungsmöglichkeiten),
q-031 (Mindestlohngesetz) — breite Fragen, Retrieval erfolgreich, Modell
verweigert trotzdem (sicheres Versagensmuster; M3-Prompt-Tuning-Kandidat).
Latenz-Hebel für M3: weniger Kontextblöcke, schnellere Kandidaten
(gemma4:12B, MoE).
## Dateien
```
agent/
config.py Env-Konfiguration
kb.py Layer-2-Parsing + kb.json-Gate
normalize.py Umlaut-Folding, FTS-Query-Bau
ingest.py Index-Bau (chunks + FTS5 + Vektoren-Cache)
retrieve.py Hybrid-Retrieval (BM25 + Dense, RRF, cross_refs)
ollama_client.py Ollama-HTTP (embed + chat, think-Fallback)
generate.py Systemprompt, Post-Validierung, Verweigerung
api.py FastAPI (/ask, /health, /reindex)
cli.py ingest | ask | eval | serve
eval/ goldset.yaml + evaluate.py
web/index.html Minimaler Test-Chat
tests/ 41 Tests (offline, Fake-Ollama)
data/ index.db (gitignored)
```
## Tests
```bash
python -m pytest -q # 41 Tests, alle offline
```
## Lizenz-Disziplin
`.lexis360/`, `.wiku/`, `.firecrawl/`, `.ris/` sind lokal und unversioniert
(`.gitignore`). Der Index enthält ausschließlich Layer-2-Kuratierung;
Layer-1-Prompts wären ein Lizenzverstoß und sind im Code nicht vorgesehen.