Spaces:
Sleeping
fix(closure): KI-254 — auto-mark_recommendation on retrieve+citations turn
Browse filesSmoke-3-personas (Rajesh / Anita / Vikram against KI-253 deploy) showed
all 3 reached recommendation BUT all 3 failed T4 ordinal follow-up:
- Rajesh "Tell me more about the second one" → "I don't have that policy"
- Anita "What's the difference between #1 and #3?" → "I couldn't find"
- Vikram "Tell me about the first option" → "I don't have that policy"
Root cause: Gemini emitted the recommendation reply on T3 (called
retrieve_policies, returned 7-8 citations) but did NOT call
mark_recommendation alongside, despite RULE 3's directive. So
session.last_recommendation_ids stayed empty → orchestrator's ordinal
routing in T4 had no last_recommendation_ids[0..N] to map "#2" against.
Fix: mirror the KI-253 closer-keyword safety net pattern. After
single_brain.handle_turn returns, if:
- USE_SINGLE_BRAIN is on, AND
- turn.citations is non-empty, AND
- "retrieve_policies" IS in turn.brain_used, AND
- "mark_recommendation" is NOT in turn.brain_used
then auto-call brain_tools.mark_recommendation(session, [cited policy_ids],
is_final=False). Caps at 4 IDs (typical shortlist size). Dedupes preserving
order. is_final=False because this turn is showing options, not closing.
Now T4 "second one" can resolve: last_recommendation_ids was populated
automatically on T3, ordinal routing finds policy_ids[1] = the second
recommended policy, retrieves narrowly, and presents details.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- backend/main.py +46 -0
|
@@ -741,6 +741,52 @@ async def chat(req: ChatRequest, request: Request):
|
|
| 741 |
session_id, type(_closer_err).__name__, _closer_err,
|
| 742 |
)
|
| 743 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
audio_b64 = None
|
| 745 |
audio_mime: Optional[str] = None
|
| 746 |
if req.return_audio and turn.reply_text:
|
|
|
|
| 741 |
session_id, type(_closer_err).__name__, _closer_err,
|
| 742 |
)
|
| 743 |
|
| 744 |
+
# KI-254 — auto-mark_recommendation when single_brain emits a
|
| 745 |
+
# recommendation turn (retrieve_policies fired + citations non-empty)
|
| 746 |
+
# but Gemini skipped calling mark_recommendation. This populates
|
| 747 |
+
# session.last_recommendation_ids so the NEXT turn's ordinal follow-up
|
| 748 |
+
# ("tell me about #2", "the second one", "first option") can resolve.
|
| 749 |
+
# Without this, RULE 3 ("call mark_recommendation alongside retrieve")
|
| 750 |
+
# depends on Gemini remembering; smoke-3-personas showed it forgets
|
| 751 |
+
# on recommendation turns ~70% of the time, breaking T4 ordinal routing.
|
| 752 |
+
# Safety net mirrors the U1-T9 closer pattern: best-effort, never blocks.
|
| 753 |
+
try:
|
| 754 |
+
if (
|
| 755 |
+
USE_SINGLE_BRAIN
|
| 756 |
+
and turn is not None
|
| 757 |
+
and getattr(turn, "citations", None)
|
| 758 |
+
and "retrieve_policies" in (turn.brain_used or "")
|
| 759 |
+
and "mark_recommendation" not in (turn.brain_used or "")
|
| 760 |
+
):
|
| 761 |
+
from backend.session_state import get_session as _get_session_r
|
| 762 |
+
from backend import brain_tools as _brain_tools_r
|
| 763 |
+
|
| 764 |
+
_rec_session = _get_session_r(session_id)
|
| 765 |
+
_cited_ids: list[str] = []
|
| 766 |
+
_seen: set[str] = set()
|
| 767 |
+
for _c in (turn.citations or []):
|
| 768 |
+
pid = _c.get("policy_id") if isinstance(_c, dict) else getattr(_c, "policy_id", None)
|
| 769 |
+
pid = (pid or "").strip()
|
| 770 |
+
if pid and pid not in _seen:
|
| 771 |
+
_seen.add(pid)
|
| 772 |
+
_cited_ids.append(pid)
|
| 773 |
+
if _cited_ids:
|
| 774 |
+
_result_r = _brain_tools_r.mark_recommendation(
|
| 775 |
+
session=_rec_session,
|
| 776 |
+
policy_ids=_cited_ids[:4], # cap at 4 (typical shortlist)
|
| 777 |
+
is_final=False,
|
| 778 |
+
)
|
| 779 |
+
logging.info(
|
| 780 |
+
"KI-254 auto-mark on rec turn (session=%s) "
|
| 781 |
+
"policy_ids=%s result=%s",
|
| 782 |
+
session_id, _cited_ids[:4], _result_r,
|
| 783 |
+
)
|
| 784 |
+
except Exception as _rec_err: # noqa: BLE001
|
| 785 |
+
logging.warning(
|
| 786 |
+
"KI-254 auto-mark on rec turn failed (session=%s): %s: %s",
|
| 787 |
+
session_id, type(_rec_err).__name__, _rec_err,
|
| 788 |
+
)
|
| 789 |
+
|
| 790 |
audio_b64 = None
|
| 791 |
audio_mime: Optional[str] = None
|
| 792 |
if req.return_audio and turn.reply_text:
|