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:
+29
-8
@@ -227,20 +227,41 @@ def answer_question(
|
||||
{"role": "user", "content": build_user_content(question, results)},
|
||||
]
|
||||
|
||||
def chat(msgs):
|
||||
return strip_think(
|
||||
client.chat(
|
||||
def chat(msgs, num_predict: int | None = None):
|
||||
"""Ein Chat-Zug; liefert (gestrippter Content, done_reason).
|
||||
Bei done_reason='length' (Antwort bei num_predict abgeschnitten)
|
||||
sind Zitationen ggf. unvollstaendig — der Aufrufer ruft einmal mit
|
||||
doppeltem Budget neu (technischer Retry, kein Regel-Regeneration)."""
|
||||
budget = num_predict or cfg.num_predict
|
||||
if hasattr(client, "chat_full"):
|
||||
raw, done_reason = client.chat_full(
|
||||
cfg.answer_model,
|
||||
msgs,
|
||||
temperature=cfg.temperature,
|
||||
num_ctx=cfg.num_ctx,
|
||||
num_predict=cfg.num_predict,
|
||||
num_predict=budget,
|
||||
think=cfg.think,
|
||||
)
|
||||
)
|
||||
else:
|
||||
raw = client.chat(
|
||||
cfg.answer_model,
|
||||
msgs,
|
||||
temperature=cfg.temperature,
|
||||
num_ctx=cfg.num_ctx,
|
||||
num_predict=budget,
|
||||
think=cfg.think,
|
||||
)
|
||||
done_reason = "stop"
|
||||
return strip_think(raw), done_reason
|
||||
|
||||
def chat_with_length_retry(msgs):
|
||||
final, done_reason = chat(msgs)
|
||||
if done_reason == "length":
|
||||
final, done_reason = chat(msgs, num_predict=cfg.num_predict * 2)
|
||||
return final
|
||||
|
||||
if map_messages is not None:
|
||||
summary = chat(map_messages)
|
||||
summary = chat_with_length_retry(map_messages)
|
||||
if summary:
|
||||
messages = [
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
@@ -255,7 +276,7 @@ def answer_question(
|
||||
]
|
||||
# leerer Map-Output -> Fallback: messages bleibt die Einzelantwort
|
||||
|
||||
final = chat(messages)
|
||||
final = chat_with_length_retry(messages)
|
||||
violations = validate_answer(final, allowed)
|
||||
regenerations = 0
|
||||
if violations:
|
||||
@@ -267,7 +288,7 @@ def answer_question(
|
||||
"Beantworte die Frage erneut und zitiere nur diese IDs — oder verweigere "
|
||||
f"mit dem vorgesehenen Satz („{REFUSAL_MESSAGE}“)."
|
||||
)
|
||||
retry = chat(
|
||||
retry = chat_with_length_retry(
|
||||
messages
|
||||
+ [{"role": "assistant", "content": final},
|
||||
{"role": "user", "content": warn}]
|
||||
|
||||
Reference in New Issue
Block a user