""" 제안 핸들러 모듈 ────────────── 낮은 신뢰도 시 대안 질문 제안 """ from typing import List, Dict, Any, Optional SUGGESTION_BLOCK_MARKER = "💡 혹시 이런 것을 찾으셨나요?" SUGGESTION_BLOCK_MARKER_ALT = "혹시 이런 것을 찾으셨나요?" def strip_suggestion_block(text: Optional[str]) -> str: """LLM 이력용: 추천 블록(및 LLM 모방 블록) 제거, FAQ 본문만 반환""" if not text: return text or "" indices = [] for marker in (SUGGESTION_BLOCK_MARKER, SUGGESTION_BLOCK_MARKER_ALT): idx = text.find(marker) if idx != -1: indices.append(idx) if not indices: return text return text[: min(indices)].rstrip() def has_suggestion_block(text: Optional[str]) -> bool: if not text: return False return any(marker in text for marker in (SUGGESTION_BLOCK_MARKER, SUGGESTION_BLOCK_MARKER_ALT)) class SuggestionHandler: """낮은 신뢰도 답변에 대한 대안 질문 제안""" # 신뢰도 임계값 (기본값 — Config.from_env / LOW_CONFIDENCE_THRESHOLD 로 override) DEFAULT_LOW_CONFIDENCE_THRESHOLD = 0.65 DEFAULT_HIGH_CONFIDENCE_THRESHOLD = 0.75 def __init__( self, low_confidence_threshold: float = DEFAULT_LOW_CONFIDENCE_THRESHOLD, high_confidence_threshold: float = DEFAULT_HIGH_CONFIDENCE_THRESHOLD, ): """ Args: low_confidence_threshold: 낮은 신뢰도 기준 (기본 0.65, env LOW_CONFIDENCE_THRESHOLD) high_confidence_threshold: 높은 신뢰도 기준 (기본 0.75, env HIGH_CONFIDENCE_THRESHOLD) """ self.low_confidence_threshold = low_confidence_threshold self.high_confidence_threshold = high_confidence_threshold def is_low_confidence(self, top_score: Optional[float]) -> bool: """신뢰도가 낮은지 판단 Args: top_score: 최상위 검색 결과의 점수 Returns: 낮은 신뢰도 여부 """ if top_score is None: return True return top_score < self.low_confidence_threshold def generate_suggestions( self, top_results: List[Dict[str, Any]], top_scores: List[float], max_suggestions: int = 3 ) -> Optional[str]: """대안 질문 제안 생성 Args: top_results: 검색 결과 리스트 top_scores: 검색 점수 리스트 max_suggestions: 최대 제안 개수 (기본 3개) Returns: 제안 문자열 또는 None """ if not top_results or not top_scores: return None # 최상위 점수가 충분히 높으면 제안 불필요 if not self.is_low_confidence(top_scores[0]): return None # 1위 포함 상위 max_suggestions개 질문 제안 (번호 1부터) suggestion_count = min(max_suggestions, len(top_results)) if suggestion_count <= 0: return None suggestions = [] for i in range(suggestion_count): question = top_results[i]["q"] score = top_scores[i] if i < len(top_scores) else None suggestions.append({ "index": i + 1, "question": question, "score": score, }) if not suggestions: return None # 제안 텍스트 생성 suggestion_text = f"\n\n{SUGGESTION_BLOCK_MARKER}\n" for suggestion in suggestions: # 점수가 있으면 신뢰도 표시 (선택적) # suggestion_text += f"{suggestion['index']}. {suggestion['question']} (유사도: {suggestion['score']:.2f})\n" suggestion_text += f"{suggestion['index']}. {suggestion['question']}\n" suggestion_text += "\n위 질문 중 하나를 선택하시면 정확한 답변을 드리겠습니다." return suggestion_text def enhance_answer_with_suggestions( self, answer: str, top_results: List[Dict[str, Any]], top_scores: List[float], max_suggestions: int = 3 ) -> str: """답변에 제안 추가 Args: answer: 원본 답변 top_results: 검색 결과 top_scores: 검색 점수 max_suggestions: 최대 제안 개수 Returns: 제안이 추가된 답변 (또는 원본) """ if has_suggestion_block(answer): return answer suggestions = self.generate_suggestions(top_results, top_scores, max_suggestions) if suggestions: return answer + suggestions return answer def get_confidence_level(self, score: Optional[float]) -> str: """점수를 신뢰도 레벨로 변환 Args: score: 검색 점수 Returns: "high", "medium", "low" 중 하나 """ if score is None: return "low" if score >= self.high_confidence_threshold: return "high" elif score >= self.low_confidence_threshold: return "medium" else: return "low" def should_show_confidence_warning(self, top_score: Optional[float]) -> bool: """신뢰도 경고를 표시해야 하는지 Args: top_score: 최상위 점수 Returns: 경고 표시 여부 """ if top_score is None: return True # 매우 낮은 신뢰도 (0.50 미만) return top_score < 0.50 def generate_confidence_warning(self) -> str: """신뢰도 경고 메시지 생성""" return ( "\n\n⚠️ **정확도 안내**\n" "질문과 정확히 일치하는 정보를 찾기 어려웠습니다.\n" "더 구체적으로 질문하시거나, 아래 옵션을 참고해 주세요.\n" "정확한 답변이 필요하시면 한국도로공사 콜센터(1588-2504)로 문의해 주세요." )