Files
pv-agent/tests/conftest.py
T
fegger 2cba72aeb0 M1+M2: RAG-Pipeline mit verbindlichem Grounding
agent/-Paket: Ingest (601 Layer-2-Eintraege -> 3005 Chunks, FTS5-BM25 +
Vektoren-Cache), Hybrid-Retrieval (RRF, Stand-Boost, cross_ref-Erweiterung),
Ollama-Client (embed/chat, think-Flag-Fallback, kurzes Connect-Budget),
Systemprompt mit Zitierpflicht, Post-Validierung (zitierte IDs gemaess
Retrieved-Set, 1x Regenerierung, dann Verweigerung), FastAPI (/ask, /health,
/reindex), CLI, Goldset (31 Fragen, IDs gegen kb.json verifiziert, inkl.
ATZ-Konfliktfall + 4 Verweigerungsfaelle), Eval-Suite, Test-Chat.

41 Offline-Tests gruen. Baseline BM25-only: Hit-Rate 0,871 / Recall@8 0,855 /
MRR 0,476. Hybrid-Messung, Antwortmodus-Eval und Modell-Bake-off (M3) auf
dem Host ausstaendig (Ollama aus der Zed-Sandbox nicht erreichbar).

MEMORY.md und planung.md Umsetzungsstand aktualisiert.
2026-09-14 16:53:04 +02:00

200 lines
4.6 KiB
Python
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.
"""Fixtures: Mini-Wissensbasis (3 Einträge), Index, Fake-Ollama-Client.
Alle Tests laufen offline — kein Ollama, kein Netz. Die Mini-KB folgt dem
verbindlichen Layer-2-Schema (Frontmatter + H2-Sektionen + kb.json-Gate).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from agent.config import Config
DOC_ATZ = """---
id: lb-min-01
batch: 1
title: "Altersteilzeit - Überblick"
work: "Lexis Briefings Personalrecht"
chapter: "Beschäftigungsverhältnisse"
topic: altersteilzeit
author: "Marek"
stand: 2026-01
source:
pdf: ".lexis360/Lexis360_test_atz.pdf"
text: ".lexis360/md/test_atz.md"
legal_bases: ["AlVG", "AZG § 19e"]
tags: [altersteilzeit, lohnausgleich]
cross_refs: ["lb-min-02"]
---
# Altersteilzeit Überblick
*Lexis Briefings Personalrecht, Marek, Stand Jänner 2026 (lb-min-01).*
## Zusammenfassung
Altersteilzeit ist eine Teilzeit, bei der der Arbeitnehmer zusätzlich zum
Teilzeitentgelt einen Lohnausgleich erhält; das AMS ersetzt dem Arbeitgeber
einen Teil der Zusatzkosten (Altersteilzeitgeld).
## Kernwerte & Fristen (Stand 2026-01)
| Wert / Regel | Detail |
|---|---|
| Ersatzquote | 28,5 % der ersetzbaren Kosten (Stand 2026-01) |
## Rechtsgrundlagen
- AZG § 19e (Kündigungszuschlag)
## Verweise
- lb-min-02
"""
DOC_URL = """---
id: lb-min-02
batch: 1
title: "Urlaubsanspruch und Verbrauch"
work: "Lexis Briefings Personalrecht"
chapter: "Urlaub & Karenzierung"
topic: urlaub
author: "Marek"
stand: 2026-07
source:
pdf: ".lexis360/Lexis360_test_urlaub.pdf"
text: ".lexis360/md/test_urlaub.md"
legal_bases: ["UrlG"]
tags: [urlaub, urlaubsentgelt]
cross_refs: ["lb-min-01"]
---
# Urlaubsanspruch und Verbrauch
*Lexis Briefings Personalrecht, Marek, Stand Juli 2026 (lb-min-02).*
## Zusammenfassung
Der gesetzliche Urlaubsanspruch beträgt fünf Werktage Wochenurlaub je
Dienstjahr; das Urlaubsentgelt ist wie Entgeltfortzahlung zu berechnen.
## Kernwerte & Fristen (Stand 2026-07)
| Wert / Regel | Detail |
|---|---|
| Mindesturlaub | 5 Wochen je Dienstjahr (Stand 2026-07) |
## Verweise
- lb-min-01
"""
DOC_WIKU = """---
id: wk-min-01
batch: 1
title: "WIKU Praxisfall: Lohnverrechnung"
work: "WIKU Fachbroschüre"
chapter: "WIKU Fachbroschüre"
topic: lohnverrechnung
author: "Wilhelm Kurzböck"
stand: 2026-03
source:
pdf: ".wiku/test_lohnverrechnung.pdf"
text: ".wiku/md/test_lohnverrechnung.md"
legal_bases: ["EStG § 25"]
tags: [lohnverrechnung, praxisfall]
cross_refs: ["lb-min-01"]
---
# WIKU Praxisfall: Lohnverrechnung
*WIKU Fachbroschüre, Wilhelm Kurzböck, Stand März 2026 (wk-min-01).*
## Zusammenfassung
Praxisfall zur laufenden Lohnverrechnung: Abrechnungsperiode und
Beitragsgrundlagen sind monatlich festzulegen.
## Verweise
- lb-min-01
"""
MIN_DOCS = {
"altersteilzeit_uberblick.md": DOC_ATZ,
"urlaubsanspruch.md": DOC_URL,
"wiku_lohnverrechnung.md": DOC_WIKU,
}
MIN_IDS = ["lb-min-01", "lb-min-02", "wk-min-01"]
def write_mini_kb(root: Path) -> Path:
doc = root / "dokumente"
doc.mkdir(parents=True, exist_ok=True)
for name, content in MIN_DOCS.items():
(doc / name).write_text(content, encoding="utf-8")
kb = {
"n_entries": len(MIN_IDS),
"entries": [{"id": i, "title": i} for i in MIN_IDS],
}
(root / "kb.json").write_text(
json.dumps(kb, ensure_ascii=False, indent=1), encoding="utf-8"
)
return root
class FakeOllama:
"""Skriptbarer Chat-Client; embed() schlägt fehl (Dense bleibt ungetestet)."""
def __init__(self, answers: list[str] | None = None):
self.answers = list(answers or [])
self.calls = 0
self.last_messages: list | None = None
def chat(self, model, messages, **kwargs):
self.calls += 1
self.last_messages = messages
if not self.answers:
raise AssertionError("FakeOllama: keine skriptierte Antwort mehr")
return self.answers.pop(0)
def embed(self, model, texts):
raise RuntimeError("embed nicht verfügbar (offline Test)")
def is_up(self):
return False
def close(self):
pass
@pytest.fixture
def mini_kb(tmp_path):
return write_mini_kb(tmp_path / "kb")
@pytest.fixture
def mini_cfg(tmp_path, mini_kb) -> Config:
return Config(
kb_dir=str(mini_kb),
db_path=str(tmp_path / "index.db"),
embed_off=True,
)
@pytest.fixture
def mini_index(mini_cfg) -> Config:
from agent.ingest import build_index
stats = build_index(mini_cfg)
assert stats.n_entries == 3
assert stats.n_chunks >= 7
assert stats.embed_error is None
return mini_cfg
@pytest.fixture
def fake_ollama():
return FakeOllama