length-Retry: abgeschnittene Antworten sind die q-024-Ursache, kein Thinking (D14)

- Diagnose: q-024-Flakiness war KEIN Think-Ghost (thinking-Feld leer,
  keine Tags), sondern num_predict=1024 — lange belegte Antworten
  brachen bei done_reason=length ab (3/3 Sondenlaeufe), Zitationen
  wurden unvollstaendig, CITE_RE matchte partielle IDs -> Verletzung ->
  Regenerierungs-Eskalation -> UNCERTAIN.
- Fix: num_predict 1024 -> 2048; OllamaClient.chat_full() liefert
  (content, done_reason); chat_with_length_retry() wiederholt bei
  length einmal mit 2x Budget (technischer Retry, kein Regel-
  Regenerierungszaehler) - im Antwort-, Map- und Regenerierungspfad.
- Voll-Eval (46 Fragen): Zitier-Praezision 100 %, Verweigerung korrekt
  100 % (46/46) - erstmals alle M3-Gates erfuellt; erwartete Quelle
  92,7 %; Latenz mean 39,6 s / p95 78 s (vollstaendige statt
  abgeschnittener Antworten). q-024: 4/4 stabil.
- Tests 59 -> 60. Report lokal data/eval-qwen38-lengthfix.json.
This commit is contained in:
2026-09-15 14:14:27 +02:00
parent 4a9e06f66e
commit 1594aa9cbd
7 changed files with 115 additions and 13 deletions
+30
View File
@@ -167,3 +167,33 @@ def test_trim_results_drops_tail_under_budget():
assert sum(len(b.text) for b in trimmed) + len(trimmed) * 64 <= 90_000 + 8_000
# Unbegrenzt: Original unveraendert
assert trim_results(blocks, None) is blocks
def test_length_retry_doubles_budget(mini_index):
"""done_reason='length' (abgeschnittene Antwort) -> ein technischer
Retry mit doppeltem num_predict; zaehlt nicht als Regel-Regenerierung."""
class LengthyOllama:
def __init__(self):
self.calls = []
self.budgets = []
def chat_full(self, model, messages, temperature=0.1, num_ctx=16384,
num_predict=1024, think=False):
self.calls.append(messages[-1])
self.budgets.append(num_predict)
if len(self.budgets) == 1:
return "Halbe Zitation [lb-min-0", "length"
return "Antwort mit Beleg [lb-min-01].", "stop"
def chat(self, *a, **k):
raise AssertionError("chat() sollte via chat_full laufen")
def close(self):
pass
client = LengthyOllama()
result = answer_question("Altersteilzeit?", mini_index, client=client)
assert result["verified"] is True
assert result["citations"] == ["lb-min-01"]
assert result["regenerations"] == 0 # technischer Retry, keine Regel-Regen
assert client.budgets == [mini_index.num_predict, mini_index.num_predict * 2]