4b86b2a660
- server-dev start/stop/deploy 및 Gitea push 자동 배포 - local-dev 로컬 개발 환경 Co-authored-by: Cursor <cursoragent@cursor.com>
194 lines
6.2 KiB
Python
194 lines
6.2 KiB
Python
"""
|
|
제안 핸들러 모듈
|
|
──────────────
|
|
낮은 신뢰도 시 대안 질문 제안
|
|
"""
|
|
|
|
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)로 문의해 주세요."
|
|
)
|