PMFlow/backend/app/services/adaptive_rag_service/mixin_answer_mainline.py

311 lines
12 KiB
Python

"""Adaptive RAG answer mainline orchestration helpers."""
from __future__ import annotations
from .shared import *
class AdaptiveRAGAnswerMainlineMixin:
async def _prepare_answer_runtime(
self,
*,
clean_q: str,
asked_by: Optional[int],
asked_via: str,
project_id: Optional[int],
source_chat_id: Optional[str],
session_id: Optional[int],
conversation_key: Optional[str],
force_new_session: bool,
action_mode: str,
source_message_id: Optional[str] = None,
source_sender_open_id: Optional[str] = None,
thread_context: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
external_conversation_key = self._build_external_conversation_key(
asked_via=asked_via,
source_chat_id=source_chat_id,
conversation_key=conversation_key,
source_sender_open_id=source_sender_open_id,
sender=None,
thread_context=thread_context,
)
self._set_active_external_qa_context(
source_chat_id=source_chat_id,
source_message_id=source_message_id,
source_sender_open_id=source_sender_open_id,
conversation_scope_key=external_conversation_key,
thread_context=thread_context,
)
session_id, turn_index = await self._touch_session_for_new_record(
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
question=clean_q,
preferred_session_id=session_id,
force_new_session=bool(force_new_session),
external_conversation_key=external_conversation_key,
)
context_session_id: Optional[int]
if session_id is not None:
context_session_id = int(session_id)
elif asked_by is None:
context_session_id = self._external_session_id_from_key(external_conversation_key)
else:
context_session_id = None
conversation = await self._load_recent_conversation(
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
session_id=session_id,
external_session_id=context_session_id,
limit=10,
)
state_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
)
state_row = await self._ensure_session_state(session_id=state_session_id)
pending_selection = self._consume_pending_clarification_selection(
question=clean_q,
session_state=state_row,
)
resolved_question = str(pending_selection.get("effective_question") or clean_q).strip() if isinstance(
pending_selection, dict
) else clean_q
anchor_resolution = await self._resolve_followup_anchor(
question=resolved_question,
session_id=state_session_id,
context_session_id=context_session_id,
session_state=state_row,
conversation_scope_key=external_conversation_key,
source_chat_id=source_chat_id,
source_sender_open_id=source_sender_open_id,
source_message_id=source_message_id,
thread_context=thread_context,
)
effective_q, rewrite_meta = self._rewrite_question_with_state(
question=resolved_question,
conversation=conversation,
session_state=state_row,
anchor_resolution=anchor_resolution,
)
query_type = self._classify_query_type(effective_q)
requested_action_mode = self._normalize_action_mode(action_mode)
effective_action_mode = self._resolve_action_mode(
requested_mode=requested_action_mode,
question=resolved_question,
effective_question=effective_q,
)
return {
"session_id": session_id,
"turn_index": turn_index,
"context_session_id": context_session_id,
"conversation": conversation,
"state_row": state_row,
"resolved_question": resolved_question,
"effective_q": effective_q,
"rewrite_meta": rewrite_meta,
"query_type": query_type,
"effective_action_mode": effective_action_mode,
"anchor_resolution": anchor_resolution,
}
async def _execute_base_answer_mainline(
self,
*,
clean_q: str,
asked_by: Optional[int],
asked_via: str,
project_id: Optional[int],
source_chat_id: Optional[str],
session_id: Optional[int],
turn_index: int,
context_session_id: Optional[int],
conversation: list[dict[str, str]],
state_row: Optional[QASessionState],
effective_q: str,
rewrite_meta: dict[str, Any],
query_type: str,
thinking_mode: str,
active_thinking_mode: str,
deep_thinking: bool,
enforce_scene_prompt_only: bool,
deep_retrieval_steps: int,
deep_vector_top_k: int,
started: float,
message_intent: Optional[str],
mixed_signal_part: Optional[str],
anchor_resolution: Optional[FollowupAnchorResolution] = None,
) -> dict[str, Any]:
forced_deep_reason = ""
if not deep_thinking:
forced_deep_reason = self._forced_deep_reason(
question=clean_q,
effective_question=effective_q,
conversation=conversation,
)
if forced_deep_reason and self._should_force_deep_for_channel(
asked_via=asked_via,
question=clean_q,
effective_question=effective_q,
reason=forced_deep_reason,
):
deep_thinking = True
active_thinking_mode = THINKING_MODE_DEEP
else:
forced_deep_reason = ""
clarification_payload = self._build_followup_clarification_payload(
question=clean_q,
anchor_resolution=anchor_resolution,
session_state=state_row,
)
meta_followup_payload = await self._build_meta_followup_payload(
question=clean_q,
anchor_resolution=anchor_resolution,
conversation_scope_key=self._get_active_external_qa_context().get("conversation_scope_key"),
)
meta_followup_result = await self._maybe_return_meta_followup_response(
meta_followup_payload=meta_followup_payload,
started=started,
clean_q=clean_q,
effective_q=effective_q,
query_type=query_type,
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
session_id=session_id,
turn_index=turn_index,
state_row=state_row,
rewrite_meta=rewrite_meta,
context_session_id=context_session_id,
active_thinking_mode=active_thinking_mode,
)
if meta_followup_result is not None:
return meta_followup_result
clarification_result = await self._maybe_return_clarification_response(
clarification_payload=clarification_payload,
started=started,
clean_q=clean_q,
effective_q=effective_q,
query_type=query_type,
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
session_id=session_id,
turn_index=turn_index,
state_row=state_row,
rewrite_meta=rewrite_meta,
context_session_id=context_session_id,
active_thinking_mode=active_thinking_mode,
)
if clarification_result is not None:
return clarification_result
system_snapshot = await self.context_cache.get_system_snapshot(
db=self.db,
tenant_id=self.tenant_id,
)
user_snapshot = await self.context_cache.get_user_snapshot(
db=self.db,
tenant_id=self.tenant_id,
user_id=asked_by,
)
if not deep_thinking and not enforce_scene_prompt_only:
deterministic_result = await self._run_deterministic_query(
question=effective_q,
project_id=project_id,
query_type=query_type,
)
if deterministic_result is not None:
return await self._persist_fast_answer(
question=clean_q,
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
fast_result=deterministic_result,
session_id=session_id,
external_session_id=context_session_id,
turn_index=turn_index,
query_type=str(deterministic_result.get("query_type") or query_type),
missing_fields=list(deterministic_result.get("missing_fields") or []),
state_row=state_row,
effective_question=effective_q,
rewrite_meta=rewrite_meta,
thinking_mode=active_thinking_mode,
)
direct_answer = None
if not deep_thinking and not enforce_scene_prompt_only:
direct_answer = self._build_snapshot_direct_answer(
effective_q,
system_snapshot=system_snapshot,
user_snapshot=user_snapshot,
)
direct_result = await self._maybe_return_snapshot_direct_response(
direct_answer=direct_answer,
started=started,
clean_q=clean_q,
effective_q=effective_q,
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
session_id=session_id,
turn_index=turn_index,
state_row=state_row,
rewrite_meta=rewrite_meta,
context_session_id=context_session_id,
active_thinking_mode=active_thinking_mode,
user_snapshot=user_snapshot,
)
if direct_result is not None:
return direct_result
is_snapshot_direct_question = self._is_snapshot_direct_question(effective_q)
if not deep_thinking and not enforce_scene_prompt_only and not is_snapshot_direct_question:
glm_semantic_result = await self._try_glm_project_semantic_answer(clean_q=effective_q, project_id=project_id)
if glm_semantic_result is not None:
return await self._persist_fast_answer(
question=clean_q,
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
fast_result=glm_semantic_result,
session_id=session_id,
external_session_id=context_session_id,
turn_index=turn_index,
query_type=query_type if query_type != QUERY_TYPE_UNKNOWN else QUERY_TYPE_EXPLAIN,
missing_fields=[],
state_row=state_row,
effective_question=effective_q,
rewrite_meta=rewrite_meta,
thinking_mode=active_thinking_mode,
)
return await self._build_retrieval_response(
clean_q=clean_q,
effective_q=effective_q,
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
source_chat_id=source_chat_id,
session_id=session_id,
turn_index=turn_index,
context_session_id=context_session_id,
conversation=conversation,
state_row=state_row,
rewrite_meta=rewrite_meta,
query_type=query_type,
thinking_mode=thinking_mode,
active_thinking_mode=active_thinking_mode,
deep_thinking=deep_thinking,
forced_deep_reason=forced_deep_reason,
deep_retrieval_steps=deep_retrieval_steps,
deep_vector_top_k=deep_vector_top_k,
system_snapshot=system_snapshot,
user_snapshot=user_snapshot,
started=started,
message_intent=message_intent,
mixed_signal_part=mixed_signal_part,
)