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

131 lines
5.4 KiB
Python

"""Adaptive RAG mixin module."""
from __future__ import annotations
from .shared import *
class AdaptiveRAGAnswerPayloadMixin:
def _build_response_payload(
self,
*,
answer: str,
sources: list[dict[str, Any]],
domains_used: list[str],
response_time_ms: int,
db_query_ms: int,
vector_search_ms: int,
graph_query_ms: int = 0,
db_timeout: bool = False,
vector_timeout: bool = False,
graph_timeout: bool = False,
qa_id: Optional[int] = None,
session_id: Optional[int] = None,
turn_index: int = 1,
relevance_max: float = 0.0,
query_type: str = QUERY_TYPE_UNKNOWN,
confidence: float = 0.0,
missing_fields: Optional[list[str]] = None,
tool_trace: Optional[list[dict[str, Any]]] = None,
thinking_mode: str = THINKING_MODE_NORMAL,
action_taken: Optional[str] = None,
signal_id: Optional[int] = None,
reply_required: bool = False,
reply_text: Optional[str] = None,
ack_latency_ms: Optional[int] = None,
trace_id: Optional[str] = None,
processing_stage: Optional[str] = None,
) -> dict[str, Any]:
evidence: list[dict[str, Any]] = []
for item in sources[:8]:
if not isinstance(item, dict):
continue
evidence.append(
{
"source": str(item.get("source") or "unknown"),
"label": str(item.get("label") or "unknown"),
"score": round(self._to_float(item.get("score"), 0.0), 4),
"metadata": item.get("metadata") if isinstance(item.get("metadata"), dict) else {},
}
)
return {
"answer": str(answer or "").strip(),
"evidence": evidence,
"confidence": round(max(0.0, min(1.0, self._to_float(confidence, 0.0))), 4),
"thinking_mode": self._normalize_thinking_mode(thinking_mode),
"missing_fields": [str(x) for x in (missing_fields or []) if str(x).strip()],
"query_type": str(query_type or QUERY_TYPE_UNKNOWN),
"sources": sources,
"domains_used": domains_used,
"response_time_ms": int(response_time_ms or 0),
"retrieval_metrics": {
"db_query_ms": int(db_query_ms or 0),
"vector_search_ms": int(vector_search_ms or 0),
"graph_query_ms": int(graph_query_ms or 0),
"db_timeout": bool(db_timeout),
"vector_timeout": bool(vector_timeout),
"graph_timeout": bool(graph_timeout),
},
"tool_trace": tool_trace if isinstance(tool_trace, list) else [],
"qa_id": int(qa_id) if qa_id is not None else None,
"session_id": int(session_id) if session_id is not None else None,
"turn_index": int(turn_index or 1),
"relevance_max": round(max(0.0, self._to_float(relevance_max, 0.0)), 4),
"action_taken": str(action_taken).strip() if action_taken else None,
"signal_id": int(signal_id) if signal_id is not None else None,
"reply_required": bool(reply_required),
"reply_text": str(reply_text or "").strip() if reply_text else None,
"ack_latency_ms": int(ack_latency_ms) if ack_latency_ms is not None else None,
"trace_id": str(trace_id or "").strip() or None,
"processing_stage": str(processing_stage or "").strip() or None,
}
@staticmethod
def _extract_ack_metadata_from_sources(
sources: Any,
*,
answer: str = "",
) -> dict[str, Any]:
rows = sources if isinstance(sources, list) else []
action_taken = ""
signal_id: Optional[int] = None
reply_required = False
ack_latency_ms: Optional[int] = None
trace_id = ""
processing_stage = ""
for source in rows:
if not isinstance(source, dict):
continue
meta = source.get("metadata") if isinstance(source.get("metadata"), dict) else {}
candidate_action = str(meta.get("action_taken") or "").strip()
if not candidate_action:
continue
action_taken = candidate_action
try:
signal_id = int(meta.get("signal_id")) if meta.get("signal_id") is not None else None
except Exception:
signal_id = None
trace_id = str(meta.get("trace_id") or "").strip()
processing_stage = str(meta.get("processing_stage") or meta.get("action_mode") or "").strip()
try:
ack_latency_ms = (
int(meta.get("ack_latency_ms")) if meta.get("ack_latency_ms") is not None else None
)
except Exception:
ack_latency_ms = None
reply_required = True
break
reply_text = str(answer or "").strip() if reply_required else ""
normalized_stage = processing_stage
if normalized_stage == "write":
normalized_stage = "ack_only"
return {
"action_taken": action_taken or None,
"signal_id": signal_id,
"reply_required": bool(reply_required),
"reply_text": reply_text or None,
"ack_latency_ms": ack_latency_ms,
"trace_id": trace_id or None,
"processing_stage": normalized_stage or None,
}