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

162 lines
5.9 KiB
Python

"""Adaptive RAG mixin module."""
from __future__ import annotations
from .shared import *
class AdaptiveRAGAnswerEntryMixin:
@staticmethod
def _should_enforce_scene_prompt_only(
*,
asked_via: str,
message_intent: Optional[str],
) -> bool:
intent = str(message_intent or "").strip().lower()
if intent == "mixed":
return True
channel = str(asked_via or "").strip().lower()
return channel in {
"feishu",
"lark",
"feishu_group",
"feishu_dm",
"pmflow_manual",
"/ai-pmo/process-text",
"ai-pmo-process-text",
"ai_pmo",
"ai_pmo_ui",
}
@staticmethod
def _should_trigger_write_ack_gate(
*,
sync_requirement: bool,
asked_via: str,
effective_action_mode: str,
) -> bool:
return (
bool(sync_requirement)
and str(asked_via or "").strip().lower() != "feishu"
and str(effective_action_mode or "").strip().lower() in {ACTION_MODE_WRITE, ACTION_MODE_READ_WRITE}
)
async def answer_question(
self,
*,
question: str,
asked_by: Optional[int],
asked_via: str,
project_id: Optional[int] = None,
source_chat_id: Optional[str] = None,
session_id: Optional[int] = None,
conversation_key: Optional[str] = None,
force_new_session: bool = False,
thinking_mode: str = THINKING_MODE_NORMAL,
action_mode: str = ACTION_MODE_AUTO,
sync_requirement: bool = True,
message_intent: Optional[str] = None,
mixed_signal_part: Optional[str] = None,
) -> dict[str, Any]:
started = time.perf_counter()
clean_q = str(question or "").strip()
active_thinking_mode = self._normalize_thinking_mode(thinking_mode)
deep_thinking = active_thinking_mode == THINKING_MODE_DEEP
asked_via_lower = str(asked_via or "").strip().lower()
enforce_scene_prompt_only = self._should_enforce_scene_prompt_only(
asked_via=asked_via,
message_intent=message_intent,
)
deep_retrieval_steps = DEEP_RETRIEVAL_STEPS
deep_vector_top_k = DEEP_VECTOR_TOP_K
if asked_via_lower == "feishu":
# 飞书链路优先低时延,保留 deep 能力但收紧预算。
deep_retrieval_steps = min(DEEP_RETRIEVAL_STEPS, 3)
deep_vector_top_k = min(DEEP_VECTOR_TOP_K, 8)
if not clean_q:
return self._build_response_payload(
answer="问题为空,无法检索。",
sources=[],
domains_used=[],
response_time_ms=0,
db_query_ms=0,
vector_search_ms=0,
db_timeout=False,
vector_timeout=False,
qa_id=None,
session_id=None,
turn_index=1,
relevance_max=0.0,
query_type=QUERY_TYPE_UNKNOWN,
confidence=0.0,
missing_fields=["question"],
thinking_mode=active_thinking_mode,
)
runtime = await self._prepare_answer_runtime(
clean_q=clean_q,
asked_by=asked_by,
asked_via=asked_via,
project_id=project_id,
source_chat_id=source_chat_id,
session_id=session_id,
conversation_key=conversation_key,
force_new_session=force_new_session,
action_mode=action_mode,
)
session_id = runtime["session_id"]
turn_index = int(runtime["turn_index"] or 1)
context_session_id = runtime["context_session_id"]
conversation = runtime["conversation"]
state_row = runtime["state_row"]
effective_q = str(runtime["effective_q"] or "").strip()
rewrite_meta = runtime["rewrite_meta"] if isinstance(runtime["rewrite_meta"], dict) else {}
query_type = str(runtime["query_type"] or QUERY_TYPE_UNKNOWN)
effective_action_mode = str(runtime["effective_action_mode"] or ACTION_MODE_AUTO).strip().lower()
if self._should_trigger_write_ack_gate(
sync_requirement=sync_requirement,
asked_via=asked_via,
effective_action_mode=effective_action_mode,
):
ack_result = await self._handle_ack_first_requirement(
question=clean_q,
effective_question=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,
query_type=query_type,
action_mode=effective_action_mode,
started_at=started,
)
if ack_result is not None:
return ack_result
return await self._execute_base_answer_mainline(
clean_q=clean_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,
effective_q=effective_q,
rewrite_meta=rewrite_meta,
query_type=query_type,
thinking_mode=thinking_mode,
active_thinking_mode=active_thinking_mode,
deep_thinking=deep_thinking,
enforce_scene_prompt_only=enforce_scene_prompt_only,
deep_retrieval_steps=deep_retrieval_steps,
deep_vector_top_k=deep_vector_top_k,
started=started,
message_intent=message_intent,
mixed_signal_part=mixed_signal_part,
)