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
+18 -4
View File
@@ -74,10 +74,14 @@ class OllamaClient:
# -- Chat ---------------------------------------------------------------
def chat(self, model: str, messages: list[dict],
temperature: float = 0.1, num_ctx: int = 16384,
num_predict: int = 1024, think: bool = False) -> str:
"""POST /api/chat, stream=False; `think`-Flag mit 404/400-Fallback."""
def chat_full(self, model: str, messages: list[dict],
temperature: float = 0.1, num_ctx: int = 16384,
num_predict: int = 1024,
think: bool = False) -> tuple[str, str]:
"""POST /api/chat; liefert (content, done_reason). done_reason ==
'length' bedeutet: Antwort wurde bei num_predict abgeschnitten —
Zitationen koennen dann unvollstaendig sein (Think-Ghost-Ursache
q-024, D13-Follow-up)."""
body: dict[str, Any] = {
"model": model,
"messages": messages,
@@ -105,4 +109,14 @@ class OllamaClient:
content = msg.get("content") or ""
if not content.strip():
raise OllamaError(f"empty response from {model} (keys: {list(data.keys())})")
return content, str(data.get("done_reason") or "stop")
def chat(self, model: str, messages: list[dict],
temperature: float = 0.1, num_ctx: int = 16384,
num_predict: int = 1024, think: bool = False) -> str:
"""POST /api/chat, stream=False; `think`-Flag mit 404/400-Fallback."""
content, _ = self.chat_full(
model, messages, temperature=temperature, num_ctx=num_ctx,
num_predict=num_predict, think=think,
)
return content