Skip to content

Fallback discards retrieved evidence when raw_answer is empty and generation fails #172

Description

@lucaosti

Found by

test_stream_dispatch_final_fallback_uses_raw_answer_not_the_evidence_pack in #171 (services/ai/tests/unit/test_streaming_dispatch.py), while auditing test coverage for #154.

What happens

Both study_service.dispatch() and study_service.stream_dispatch() share the same final-fallback branch, used when generation fails (or, for streaming, when the stream yields nothing and the buffered retry also fails):

answer = raw_answer or "No relevant content found."

raw_answer is the bare answer string QVAC's own /query endpoint returns alongside the retrieved chunks — not pack.context_block(), the assembled, deduplicated evidence the rest of the pipeline treats as the actual retrieved content.

The gap

If raw_answer happens to be empty while pack.chunks holds real retrieved passages — which QVAC can return, e.g. when its own answer field is blank but sources are populated — the student sees "No relevant content found." even though relevant content was retrieved and is sitting right there in the pack.

This contradicts the graceful-degradation principle stated in docs/overview.md:

With generation disabled, every study action still returns source passages, so the system remains usable on memory-constrained machines.

Here generation isn't even disabled — it just failed — and the evidence still gets dropped.

Suggested fix

Prefer the pack when it has content:

answer = pack.context_block() or raw_answer or "No relevant content found."

This is already the pattern used one branch up, in the not meta.generation_required or rag_only path of dispatch() — so the fix is bringing the failure-fallback branch in line with the retrieval-only branch's existing behaviour, not inventing a new pattern.

Scope

Two call sites, both in services/ai/app/services/study_service.py:

  • _route()'s else branch (non-streaming dispatch())
  • stream_dispatch()'s final else branch

Tasks

  • Change both branches to prefer pack.context_block()
  • Update/replace test_stream_dispatch_final_fallback_uses_raw_answer_not_the_evidence_pack — it currently documents the old behaviour on purpose; once fixed it should assert the passage text is present
  • Check for an equivalent test on the non-streaming dispatch() path and add one if missing
  • Confirm this doesn't change behaviour when pack.chunks is genuinely empty (should still fall through to raw_answer, then the generic message)

Acceptance criteria

  • A retrieval that returns real chunks but an empty raw_answer, combined with a generation failure, surfaces the retrieved passages rather than "No relevant content found."
  • Behaviour is identical between dispatch() and stream_dispatch() for this branch, as it already is everywhere else in the file

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingragRAG pipeline improvements

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions