fix(generation): dignified refusal — point to closest documented topics#21
Open
yuzushi-dev wants to merge 2 commits into
Open
fix(generation): dignified refusal — point to closest documented topics#21yuzushi-dev wants to merge 2 commits into
yuzushi-dev wants to merge 2 commits into
Conversation
When the RAG context has no direct answer, keep the verbatim refusal opening but append a short 'Closest documented topics:' section listing 2-3 related sources already present in the context, each cited [[Source: X]]. Prompt-only change: no new retrieval, no flags, no router. Only sources actually in the context may be listed, so no answer is fabricated. Motivated by prod log triage: of 78 no-doc refusals over 5 months, 50 were hard refusals; most are KB fact-gaps where inventing content would hallucinate, so the win is a dignified pointer to nearby docs, not a reasoning dial.
…ied refusals
The dignified-refusal prompt makes a refusal look like a real answer (it
appends a cited "Closest documented topics:" section), which breaks two
assumptions:
1. _looks_like_refusal did a bare substring match on the pinned opener and a
no-sources check. Refusals now carry sources, and the model paraphrases the
opener ("I don't have direct documentation on ..."), so refusals slipped
through — getting persisted as reusable conversation memory that biases the
retrieval-time rewriter into repeating the miss.
2. VerifiedQAService.get_similar_examples injected any admin-"verified" Q&A
verbatim into future prompts as a positive few-shot example, with no refusal
check. A refusal dressed as a helpful answer is easy to thumbs-up / verify,
permanently seeding the golden set with a non-answer the model is told to
imitate.
Add a shared, adverb-tolerant detector (src/shared/refusal.py) that also
matches the "Closest documented topics" section marker. _looks_like_refusal
now delegates to it; get_similar_examples filters out refusal-shaped answers so
they are never injected even if verified. Behavioural tests cover paraphrased
openers, the dignified-refusal shape, and real answers.
Owner
Author
|
Follow-up commit The dignified refusal makes a refusal look like a real answer, which broke two assumptions:
Fix: shared adverb-tolerant detector Deploy: pure code, no new deps → runbook restart-only ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
When the RAG pipeline has no direct answer in the retrieved context, Amber
replies with a bare
"I don't have documentation on that topic.". This PR keepsthat verbatim refusal opening but, only when related sources are already
present in the context, appends a short
Closest documented topics:sectionwith 2-3 cited
[[Source: X]]pointers to the nearest documented material.Prompt-only change. No new retrieval, no new flag, no router change, no
SearchMode/thinking-level machinery. The sources listed must actually be inthe context (they carry
[Document: <title>]headers viaContextBuilderalready), so nothing is fabricated and the strict grounding guardrail is intact.
Motivation (prod log triage)
Triaged 5 months of prod queries (915 turns / 379 unique). Findings:
are hard refusals.
a "more reasoning" dial would hallucinate these, not fix them.
queries, and the flagship example ("rip & replace strategy") already answers
well.
Conclusion: the real, low-risk win is a dignified refusal that points to
nearby docs — not a thinking-level feature. This is the minimal version of that.
Changes
src/core/generation/application/prompts/templates.py— one line: instruction1 of
SYSTEM_PROMPT_v1.tests/unit/test_refusal_prompt.py— pins the template contract (refusalphrase kept verbatim, marker present + cited, flows through the real
PROMPTS["latest"]→resolve_rag_promptspath, tenant-override limitation).Test
python3 -m pytest tests/unit/test_refusal_prompt.py→ 5 passed. Reverting theprompt change fails 3/5 (tests are not vacuous).
make verifygate to runbefore merge.
Reviewer caveats
rag_system_promptin configbypasses the default template (
effective_config.py), so the fix won't takeeffect there until the override is updated. Verify read-only on prod admin
config after review.
check = replay the 50 hard refusals offline and count valid cited pointers.
Proposed as a follow-up, out of scope here.