Files
relata/docs/decisions/0007-unusable-model-output-degrades.md
2026-08-11 10:40:10 +01:00

63 lines
3.2 KiB
Markdown

# 7. Unusable model output degrades the answer; it does not fail the request
**Status:** accepted
## Context
Model output is validated strictly: strict JSON decoding, enum checks, citations resolved
against the references actually supplied, quotations grounded in the source. Anything that
fails is rejected rather than repaired.
Adding degeneracy validation — rejecting free text that is a schema field name, a placeholder,
or too short to carry meaning — made a pre-existing weakness visible. `Answer` returned
`nil, err` for *any* generation failure. A model that satisfied the schema without answering
therefore produced no sources, no citations and no conflicts: the retrieval was discarded
along with the response.
`llama3.2:3b` returns `{"answer": "sufficient", "sufficient": true, "citations": []}` often
enough that this was the normal experience on a small model, not an edge case.
## Decision
Output rejected as invalid produces an answer with no prose, `sufficient = false`, the
retrieved sources intact, the recorded contradictions between them surfaced, and the rejection
reason stated in the uncertainty section.
Transport failures — a timeout, an unreachable provider — still return an error.
## Rationale
Retrieval and contradiction detection are deterministic and do not involve the model at all.
Discarding them because the model misbehaved throws away the part of the system that worked,
in precisely the situation where the user most needs to read the sources themselves.
It also matches what the surrounding code already does. "Nothing was retrieved" and "the
retrieved items had no readable text" both return an answer explaining the situation rather
than an error. A useless model response is the third case of the same kind, and it was the
only one that behaved differently.
The distinction between an invalid response and a transport failure is the distinction between
a bad answer and a broken dependency. The first is a normal outcome of asking a small model a
question, and the user can work around it by reading the sources. The second is an operational
condition: it needs to reach the operator as a failure, with the 504 that names the remedy,
because no amount of reading sources will fix Ollama being down.
Stating the rejection reason matters. Silently omitting the prose would look like the model
had nothing to say about material that plainly addresses the question.
## Consequences
**Good:** a weak model degrades the experience instead of removing it. The guarantees that do
not depend on the model — sources shown, contradictions surfaced independently of what the
model said — hold in exactly the case where the model is least trustworthy.
**Bad:** a caller can no longer treat "no error" as "an answer was generated". `Sufficient` and
the uncertainty list have to be read, which the API documents and the interface renders, but
it is more to get right than a bare error would be.
**Rejected alternative:** retrying on rejection. A permanently invalid response does not become
valid by repetition, and retrying spends the user's time — 100 seconds per attempt on CPU — to
reach the same place. Rejections are counted on
`relata_model_invalid_output_total{task,reason}` so a model that does this constantly is
visible, which is the actionable signal.