rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
fc0cd9e
·
1 Parent(s): 8a7350c

fix(closure): KI-254 — auto-mark_recommendation on retrieve+citations turn

Browse files

Smoke-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>

Files changed (1) hide show
  1. backend/main.py +46 -0
backend/main.py CHANGED
@@ -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: