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

3.2 KiB

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.