"""Adaptive RAG mixin module.""" from __future__ import annotations from .shared import * class AdaptiveRAGAnswerPrecheckMixin: async def _maybe_return_meta_followup_response( self, *, meta_followup_payload: Optional[dict[str, Any]], started: float, clean_q: str, effective_q: str, query_type: str, asked_by: Optional[int], asked_via: str, project_id: Optional[int], session_id: Optional[int], turn_index: int, state_row: Optional[QASessionState], rewrite_meta: dict[str, Any], context_session_id: Optional[int], active_thinking_mode: str, ) -> Optional[dict[str, Any]]: if not isinstance(meta_followup_payload, dict): return None answer = str(meta_followup_payload.get("answer") or "").strip() if not answer: return None reason_code = str(meta_followup_payload.get("reason_code") or "").strip() or "meta_followup_answered" anchor_kind = str(meta_followup_payload.get("anchor_kind") or "").strip() or None anchor_mode = str(meta_followup_payload.get("anchor_mode") or "").strip() or None meta_followup_kind = str(meta_followup_payload.get("meta_followup_kind") or "").strip() or None routing_snapshot = ( meta_followup_payload.get("routing_snapshot") if isinstance(meta_followup_payload.get("routing_snapshot"), dict) else {} ) elapsed = int((time.perf_counter() - started) * 1000) direct_sources = [ { "source": "system", "label": "Followup Anchor", "score": 0.97, "metadata": { "mode": "meta_followup", "reason_code": reason_code, "anchor_kind": anchor_kind, "anchor_mode": anchor_mode, "meta_followup_kind": meta_followup_kind, }, } ] active_session_id = int(session_id) if session_id is not None else ( int(context_session_id) if context_session_id is not None else None ) qa = QARecord( tenant_id=self.tenant_id, question=clean_q, answer=answer, sources=direct_sources, intent="question", domains_used=["system_context"], response_time_ms=elapsed, asked_by=asked_by, asked_via=(asked_via or "pmflow")[:20], project_id=project_id, session_id=active_session_id, turn_index=turn_index, **self._build_qa_record_external_fields( anchor_kind=anchor_kind, anchor_ref=str(meta_followup_payload.get("anchor_ref") or "").strip() or None, routing_snapshot=routing_snapshot, ), ) self.db.add(qa) await self._update_session_state_after_turn( session_state=state_row, question=clean_q, effective_question=effective_q, answer=answer, query_type=query_type, ambiguity="clear", rewrite_meta=rewrite_meta, sources=direct_sources, ) await self.db.commit() await self.db.refresh(qa) await self.context_cache.append_session_messages( tenant_id=self.tenant_id, session_id=context_session_id, question=clean_q, answer=answer, ) return self._build_response_payload( answer=answer, sources=direct_sources, domains_used=["system_context"], response_time_ms=elapsed, db_query_ms=0, vector_search_ms=0, db_timeout=False, vector_timeout=False, qa_id=int(qa.id), session_id=active_session_id, turn_index=int(turn_index), relevance_max=0.97, query_type=query_type, confidence=0.94, missing_fields=[], thinking_mode=active_thinking_mode, anchor_mode=anchor_mode, anchor_kind=anchor_kind, meta_followup_kind=meta_followup_kind, reason_code=reason_code, ) async def _maybe_return_clarification_response( self, *, clarification_payload: Optional[dict[str, Any]], started: float, clean_q: str, effective_q: str, query_type: str, asked_by: Optional[int], asked_via: str, project_id: Optional[int], session_id: Optional[int], turn_index: int, state_row: Optional[QASessionState], rewrite_meta: dict[str, Any], context_session_id: Optional[int], active_thinking_mode: str, ) -> Optional[dict[str, Any]]: if not isinstance(clarification_payload, dict): return None clarification_answer = str(clarification_payload.get("answer") or "").strip() if not clarification_answer: return None clarification_code = str(clarification_payload.get("code") or "no_anchor_found").strip() or "no_anchor_found" clarification_candidates = ( clarification_payload.get("candidates") if isinstance(clarification_payload.get("candidates"), list) else [] ) missing_fields = [ str(item) for item in ( clarification_payload.get("missing_fields") if isinstance(clarification_payload.get("missing_fields"), list) else ["reply_anchor"] ) if str(item).strip() ] elapsed = int((time.perf_counter() - started) * 1000) clarify_sources = [ { "source": "system", "label": "Session State", "score": 0.95, "metadata": {"mode": "clarification", "reason": clarification_code}, } ] active_session_id = int(session_id) if session_id is not None else ( int(context_session_id) if context_session_id is not None else None ) qa = QARecord( tenant_id=self.tenant_id, question=clean_q, answer=clarification_answer, sources=clarify_sources, intent="question", domains_used=["system_context"], response_time_ms=elapsed, asked_by=asked_by, asked_via=(asked_via or "pmflow")[:20], project_id=project_id, session_id=active_session_id, turn_index=turn_index, **self._build_qa_record_external_fields(), ) self.db.add(qa) if clarification_code == "ambiguous_recent_reply" and clarification_candidates: previous = self._get_pending_clarification(session_state=state_row) previous_attempts = int(previous.get("attempts") or 0) if isinstance(previous, dict) else 0 self._store_pending_clarification( session_state=state_row, original_question=clean_q, clarification_code=clarification_code, candidates=clarification_candidates, previous_attempts=previous_attempts, ) else: self._clear_pending_clarification(session_state=state_row) await self._update_session_state_after_turn( session_state=state_row, question=clean_q, effective_question=effective_q, answer=clarification_answer, query_type=query_type, ambiguity="needs_clarification", rewrite_meta=rewrite_meta, sources=clarify_sources, ) await self.db.commit() await self.db.refresh(qa) await self.context_cache.append_session_messages( tenant_id=self.tenant_id, session_id=context_session_id, question=clean_q, answer=clarification_answer, ) return self._build_response_payload( answer=clarification_answer, sources=clarify_sources, domains_used=["system_context"], response_time_ms=elapsed, db_query_ms=0, vector_search_ms=0, db_timeout=False, vector_timeout=False, qa_id=int(qa.id), session_id=active_session_id, turn_index=int(turn_index), relevance_max=0.95, query_type=query_type, confidence=0.92, missing_fields=missing_fields, thinking_mode=active_thinking_mode, clarification_code=clarification_code, clarification_candidates=clarification_candidates, reason_code=clarification_code, ) async def _maybe_return_snapshot_direct_response( self, *, direct_answer: Optional[str], started: float, clean_q: str, effective_q: str, asked_by: Optional[int], asked_via: str, project_id: Optional[int], session_id: Optional[int], turn_index: int, state_row: Optional[QASessionState], rewrite_meta: dict[str, Any], context_session_id: Optional[int], active_thinking_mode: str, user_snapshot: Optional[dict[str, Any]], ) -> Optional[dict[str, Any]]: if not direct_answer: return None elapsed = int((time.perf_counter() - started) * 1000) direct_sources = [ { "source": "system", "label": "System Snapshot", "score": 0.99, "metadata": {"mode": "snapshot_direct", "topic": "identity_or_architecture"}, } ] if user_snapshot: direct_sources.append( { "source": "system", "label": "User Snapshot", "score": 0.98, "metadata": {"mode": "snapshot_direct", "topic": "current_user"}, } ) qa = QARecord( tenant_id=self.tenant_id, question=clean_q, answer=direct_answer, sources=direct_sources, intent="question", domains_used=["system_context"], response_time_ms=elapsed, asked_by=asked_by, asked_via=(asked_via or "pmflow")[:20], project_id=project_id, session_id=int(session_id) if session_id is not None else (int(context_session_id) if context_session_id is not None else None), turn_index=turn_index, **self._build_qa_record_external_fields(), ) self.db.add(qa) await self._update_session_state_after_turn( session_state=state_row, question=clean_q, effective_question=effective_q, answer=direct_answer, query_type=QUERY_TYPE_EXPLAIN, ambiguity="clear", rewrite_meta=rewrite_meta, sources=direct_sources, ) await self.db.commit() await self.db.refresh(qa) await self.context_cache.append_session_messages( tenant_id=self.tenant_id, session_id=context_session_id, question=clean_q, answer=direct_answer, ) return self._build_response_payload( answer=direct_answer, sources=direct_sources, domains_used=["system_context"], response_time_ms=elapsed, db_query_ms=0, vector_search_ms=0, db_timeout=False, vector_timeout=False, qa_id=int(qa.id), session_id=int(session_id) if session_id is not None else None, turn_index=int(turn_index), relevance_max=1.0, query_type=QUERY_TYPE_EXPLAIN, confidence=0.99, missing_fields=[], thinking_mode=active_thinking_mode, )