What we want
Surface dropped_citations in the /ask API response and in the CLI, instead of computing it and throwing it away.
Why it matters
Citation verification is the point of app/generate.py. Its docstring, rule 2, says models "cite chunk 3 while paraphrasing chunk 1", so the code checks what the model claimed against what it was actually given and drops the invented ones (lines 136 to 146). The dropped indices are recorded on the answer:
dropped_citations: list[int] = field(default_factory=list) # model cited these; we couldn't verify
(app/generate.py, Answer, line 67.)
Nothing ever reads it in a code path a user can see:
app/main.py, AskResponse (lines 46 to 52) has answer, answered, citations, conflict, refusal_reason, and query_id. No dropped citations field, and the construction at lines 159 to 168 does not pass one.
app/cli.py, _ask (lines 93 to 98) prints the answer, the citations, and the refusal reason. Not the dropped ones.
So the single clearest signal that the model just hallucinated a source is detected, stored on the dataclass, and then silently discarded. A caller cannot tell the difference between "the model cited two sources and both checked out" and "the model cited five and three were invented". That is exactly the signal an operator wants to alert on.
Note the one case where it does escape: no_verifiable_citation at lines 148 to 155 passes dropped_citations through, but only when every citation was invented. The partial case, which test_partially_hallucinated_citations_keep_the_real_ones (tests/test_generate.py line 73) proves is real, is invisible.
How to fix it
- Add
dropped_citations: list[int] = [] to AskResponse in app/main.py (near line 49), and pass answer.dropped_citations where the response is built (line 159 onward).
- In
app/cli.py _ask, print a short warning when answer.dropped_citations is non-empty, in the same [yellow] style already used for conflicts and staleness at lines 88 to 91. Something like: unverified citations dropped: [4, 7].
- Add a test in
tests/test_api.py using the existing client fixture and a FakeLLM response that cites an out-of-range index, asserting the field comes back populated. tests/test_generate.py line 60, test_hallucinated_citation_is_dropped, shows how to script that response.
- Update the API table in the README (the
/ask row, around line 222) if the response shape is documented there.
Running it
docker compose up -d db
pytest -q
Comment here if you would like to take this one and I will assign it. I usually reply within a day.
What we want
Surface
dropped_citationsin the/askAPI response and in the CLI, instead of computing it and throwing it away.Why it matters
Citation verification is the point of
app/generate.py. Its docstring, rule 2, says models "cite chunk 3 while paraphrasing chunk 1", so the code checks what the model claimed against what it was actually given and drops the invented ones (lines 136 to 146). The dropped indices are recorded on the answer:(
app/generate.py,Answer, line 67.)Nothing ever reads it in a code path a user can see:
app/main.py,AskResponse(lines 46 to 52) hasanswer,answered,citations,conflict,refusal_reason, andquery_id. No dropped citations field, and the construction at lines 159 to 168 does not pass one.app/cli.py,_ask(lines 93 to 98) prints the answer, the citations, and the refusal reason. Not the dropped ones.So the single clearest signal that the model just hallucinated a source is detected, stored on the dataclass, and then silently discarded. A caller cannot tell the difference between "the model cited two sources and both checked out" and "the model cited five and three were invented". That is exactly the signal an operator wants to alert on.
Note the one case where it does escape:
no_verifiable_citationat lines 148 to 155 passesdropped_citationsthrough, but only when every citation was invented. The partial case, whichtest_partially_hallucinated_citations_keep_the_real_ones(tests/test_generate.py line 73) proves is real, is invisible.How to fix it
dropped_citations: list[int] = []toAskResponseinapp/main.py(near line 49), and passanswer.dropped_citationswhere the response is built (line 159 onward).app/cli.py_ask, print a short warning whenanswer.dropped_citationsis non-empty, in the same[yellow]style already used for conflicts and staleness at lines 88 to 91. Something like:unverified citations dropped: [4, 7].tests/test_api.pyusing the existing client fixture and aFakeLLMresponse that cites an out-of-range index, asserting the field comes back populated.tests/test_generate.pyline 60,test_hallucinated_citation_is_dropped, shows how to script that response./askrow, around line 222) if the response shape is documented there.Running it
Comment here if you would like to take this one and I will assign it. I usually reply within a day.