b6a6f5b788
Der Host betreibt zwei Ollama-Instanzen: 11434 (fast leer, 0.16.2) und 11435 (Ziel-Instanz, 0.32.13, Modell-Zoo) — Port nicht mehr auf 11434 'korrigieren' (Dokumentation + Skill + Config-Default angepasst). Validierung gegen das echte System: bge-m3 per API gepullt, Hybrid-Index (3005 Chunks, 100 % eingebettet, 144 s), M1-Akzeptanz erreicht (Recall@8 0,952 > 0,9; Hit-Rate 0,968). Antwortmodus-Eval mit qwen3.8:27b (Thinking verifiziert aus): Zitier-Präzision 100 % (4 Regenerierungen), Verweigerung korrekt 94,3 %, Latenz mean 32 s. ATZ-Konfliktfall korrekt beide Werte mit Warnung. generate.py: sources enthaelt jetzt nur zitierte Quellen; Systemprompt ohne redundante Quellenzeile. Bake-off-Feld (M3) bereits installiert: qwen3.6:27B, gemma4:26b, mistral-small3.1:24b, gemma4:12B.
121 lines
4.9 KiB
Markdown
121 lines
4.9 KiB
Markdown
# 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-Server (Ziel-Instanz; der Host betreibt zusätzlich eine fast leere Instanz auf 11434 — nicht "korrigieren") |
|
||
| `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** (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.
|
||
|
||
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. |