Files
pv-agent/agent/config.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

87 lines
3.2 KiB
Python

"""Konfiguration des PV RAG Agent (alle Werte per Umgebungsvariable übersteuerbar)."""
from __future__ import annotations
import os
from dataclasses import dataclass
def _env_str(name: str, default: str) -> str:
v = os.environ.get(name)
return v if v not in (None, "") else default
def _env_int(name: str, default: int) -> int:
try:
return int(os.environ.get(name, default))
except (TypeError, ValueError):
return default
def _env_float(name: str, default: float) -> float:
try:
return float(os.environ.get(name, default))
except (TypeError, ValueError):
return default
def _env_bool(name: str, default: bool) -> bool:
v = os.environ.get(name)
if v is None:
return default
return v.strip().lower() in ("1", "true", "yes", "on")
@dataclass
class Config:
# Pfade (relativ zum Repo-Root, sofern nicht absolut)
kb_dir: str = "wissensbasis"
db_path: str = "data/index.db"
# Ollama (Custom-Port 11435 — nicht "korrigieren", s. Skill)
ollama_url: str = "http://100.183.83.12:11435"
embed_model: str = "bge-m3"
answer_model: str = "qwen3.8:27b" # provisorisch bis Bake-off (M3)
# Generierung
temperature: float = 0.1
num_ctx: int = 16384
num_predict: int = 1024
think: bool = False # Thinking per Request abschalten (Latenz)
chat_timeout_s: float = 300.0
embed_timeout_s: float = 240.0
# Retrieval
embed_off: bool = False # True = BM25-only (ohne Dense-Index/-Suche)
candidate_pool: int = 50 # Kandidaten je Liste vor der Fusion
context_blocks: int = 8 # Kontext-Blöcke im Prompt
cross_ref_expand: int = 3 # Top-Einträge, deren cross_refs ergänzt werden
cross_ref_max_extra: int = 6 # Obergrenze der Ergänzungen
rrf_k: int = 60
recency_boost: float = 0.005 # additiv auf RRF-Score, gewichtet nach Stand
# Service
port: int = 8080
@classmethod
def from_env(cls) -> "Config":
d = cls()
return cls(
kb_dir=_env_str("PV_KB_DIR", d.kb_dir),
db_path=_env_str("PV_DB_PATH", d.db_path),
ollama_url=_env_str("OLLAMA_URL", d.ollama_url),
embed_model=_env_str("PV_EMBED_MODEL", d.embed_model),
answer_model=_env_str("PV_ANSWER_MODEL", d.answer_model),
temperature=_env_float("PV_TEMPERATURE", d.temperature),
num_ctx=_env_int("PV_NUM_CTX", d.num_ctx),
num_predict=_env_int("PV_NUM_PREDICT", d.num_predict),
think=_env_bool("PV_THINK", d.think),
chat_timeout_s=_env_float("PV_CHAT_TIMEOUT_S", d.chat_timeout_s),
embed_timeout_s=_env_float("PV_EMBED_TIMEOUT_S", d.embed_timeout_s),
embed_off=_env_bool("PV_EMBED_OFF", d.embed_off),
candidate_pool=_env_int("PV_CANDIDATE_POOL", d.candidate_pool),
context_blocks=_env_int("PV_CONTEXT_BLOCKS", d.context_blocks),
cross_ref_expand=_env_int("PV_CROSS_REF_EXPAND", d.cross_ref_expand),
cross_ref_max_extra=_env_int("PV_CROSS_REF_MAX_EXTRA", d.cross_ref_max_extra),
recency_boost=_env_float("PV_RECENCY_BOOST", d.recency_boost),
port=_env_int("PV_PORT", d.port),
)