Stop presenting a truncated /ask answer as a complete one (#426) #486
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/426-ask-context-hygiene"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
/askreceived none of the context hygiene the summarisation path did. Three defects, one root cause: four near-duplicate transports, and a fix that only ever reached some of them.What was wrong
max_tokens_qa_anthropic_qa_openai_qa_llamacpp_qa_ollamaoptionsat all_qa_ollamais the serious one. With nooptionsdict, Ollama applied its own 2–4k default to the prompt, so the model answered a question it had only been shown part of. That is exactly the defect #337 was filed to fix, still live on this path a release later, on the provider a self-hoster is most likely to reach for.The others degrade rather than destroy: an answer cut at the output cap is prose, so it simply stops, and there was no
finish_reasoncheck to notice. 1024 tokens is small for an answer drawn from ten session summaries plus a 40-entry wiki slice.Where the flag goes — the design question this issue owned
Truncation flags rather than raises, per your call on #338. That leniency is only defensible while the truncation is visible, and unlike the summary path there is no
SummarisationRunto hang it on. So:answer_questionreturns aQaAnswer→AskResponse.truncated→ the bot renders it in the embed description, not the footer. A GM may act on a partial answer, and footer text is small grey print. The answer budget shrinks to 3900 characters to make room, so the notice cannot push the embed past Discord's 4096-character description cap — otherwise a degraded answer becomes no answer, which is strictly worse.No
BOT_CONTRACT_VERSIONbump: the field is additive with a default. An older bot ignores it; a newer bot against an older backend reads it as absent. There's a test for that direction. Both sides change in one commit per the cross-component rule.Why the transports stay separate
The four
_qa_*functions are near-duplicates and folding them into the shared transport is #484 — I'm not smuggling that refactor in here. But the checks now go through_check_usage, the same helper the structured paths use. Duplicated transports are precisely how one of them got a fix the other three did not; sharing the checks means they can't drift apart again even while the transports remain separate./askalso repeated the URL sniff independently ofllm_service, which is part of how it drifted — it now shares the registry, and gets the preflight it was one of fifteen call sites to miss (#336).Two test doubles fixed rather than worked around
Both were passing while diverging from reality, which is #441's whole concern:
test_recap_emailused atype("Cfg", ...)shim that lackedcontext_tokens, so it failed on anAttributeErrorraised before the code under test. Replaced with a realLLMConfig, which cannot drift.test_context_boundsmocked_qa_llamacppwith a bare"ok". Against the new tuple return that would have unpacked into two characters and passed.Verification