Tuning: Prompt-Budget + cross_ref-Erweiterung heilt Fehlverweigerungen (D11)

- Ursache der 4 Fehlverweigerungen nach KV/RIS-Erweiterung: lange
  KV-Chunks ueberlieferten num_ctx=16384 (bis 62,7 KB Prompt) — Ollama
  trunciert den Systemprompt vorn, das Modell verliert die Zitierregeln
  ('Block-N'-Zitate statt IDs -> Regenerierung -> Verweigerung).
- Fix: num_ctx 32768; trim_results (max_context_chars=90.000, niedrig
  gerankte Bloecke ganz weglassen statt truncieren, Mindestbestand 6);
  cross_ref_expand 3 -> 6 (Top-3 sind oft Branchen-KV-Bloecke mit leeren
  cross_refs - kuratierte Nachbarn kamen sonst nie nach).
- Eval: alle 4 Faelle geheilt, 10/10 Bestaetigungslaeufe OK.
  Retrieval Recall@8 0,851 -> 0,946 (cross_ref-Extras bringen
  Expected-IDs nach). Antworten: Zitier-Praezision 97,6 %, Verweigerung
  97,6 % (>94,3 %-Gate), erwartete Quelle 83,8 -> 91,9 %, Latenz mean 33 s.
- Tests 49 -> 50 (trim_results); Reports lokal (data/eval-*).
This commit is contained in:
2026-09-15 08:31:02 +02:00
parent a8234771cb
commit c594c553b5
6 changed files with 105 additions and 17 deletions
+19
View File
@@ -69,6 +69,24 @@ def looks_like_refusal(answer: str) -> bool:
)
def trim_results(results: list[ChunkResult], max_chars: int | None) -> list[ChunkResult]:
"""Prompt-Budget: niedrig gerankte Blöcke (hinten, meist cross_ref-Extras)
ganz weglassen, statt das Modell-Fenster truncieren zu lassen — bei
Overflow schneidet Ollama den Systemprompt weg und das Modell verliert
die Zitierregeln (Fehlverweigerungen/Fließtext-Zitationen, D10-Follow-up).
Mindestens 6 Blöcke bleiben erhalten."""
if not max_chars:
return results
out = list(results)
def total(rows: list[ChunkResult]) -> int:
return sum(len(r.title) + len(r.section) + len(r.text) + 64 for r in rows)
while out and total(out) > max_chars and len(out) > 6:
out.pop()
return out
def build_user_content(question: str, results: list[ChunkResult]) -> str:
blocks = []
for i, r in enumerate(results, 1):
@@ -130,6 +148,7 @@ def answer_question(
finally:
if own_retriever:
retriever.close()
results = trim_results(results, cfg.max_context_chars)
def finish(answer, refused, verified, citations, regenerations=0,
draft=None, sources=None):