Files
exAichatbot_agent/exAiChatBot-chatbot2.0-agent/scripts/handlers/emotion_detector.py
T
Macbook 4b86b2a660 Agent 2.0 exdev 서버 배포 스택
- server-dev start/stop/deploy 및 Gitea push 자동 배포
- local-dev 로컬 개발 환경

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 22:57:30 +09:00

170 lines
6.0 KiB
Python

"""
감정 분석 모듈
──────────────
사용자 질문의 감정 상태를 세밀하게 분석
"""
from typing import Dict, List, Optional
from dataclasses import dataclass
@dataclass
class Emotion:
"""감정 분석 결과"""
primary: str # 주요 감정: angry, frustrated, satisfied, confused, worried, neutral
intensity: float # 강도: 0.0 (약함) ~ 1.0 (강함)
confidence: float # 신뢰도: 0.0 ~ 1.0
matched_keywords: List[str] # 매칭된 키워드
def needs_empathy(self) -> bool:
"""공감이 필요한 감정인지 (부정적 감정)"""
return self.primary in ["angry", "frustrated", "worried"]
def is_positive(self) -> bool:
"""긍정적 감정인지"""
return self.primary == "satisfied"
def needs_clarification(self) -> bool:
"""명확한 설명이 필요한지 (혼란)"""
return self.primary == "confused"
class EmotionDetector:
"""사용자 감정 분석기"""
# 감정별 키워드 및 강도
EMOTION_PATTERNS = {
"angry": {
"keywords": {
# (키워드, 강도)
"화나": 0.9, "화남": 0.9, "분노": 1.0, "열받": 0.9,
"짜증": 0.7, "짜증나": 0.8, "짜증남": 0.8,
"불만": 0.6, "불쾌": 0.7, "기분 나쁘": 0.6,
"최악": 0.9, "엉망": 0.7, "터무니": 0.8,
"너무해": 0.8, "심각": 0.7, "문제": 0.5,
"불친절": 0.7, "무례": 0.8, "실망": 0.6
},
"description": "화남/분노"
},
"frustrated": {
"keywords": {
"답답": 0.8, "막막": 0.7, "곤란": 0.6,
"어렵": 0.5, "힘들": 0.6, "난감": 0.7,
"복잡": 0.5, "이해가 안": 0.6, "잘 모르": 0.5,
"왜 안": 0.6, "계속": 0.4, "여전": 0.5,
"해결이 안": 0.7, "안 되": 0.6
},
"description": "답답함/막막함"
},
"satisfied": {
"keywords": {
"감사": 0.8, "고마워": 0.8, "고맙": 0.8,
"좋": 0.7, "훌륭": 0.9, "최고": 0.9,
"도움": 0.7, "해결": 0.8, "완벽": 0.9,
"잘": 0.6, "쉽": 0.6, "편리": 0.7,
"만족": 0.9, "훌륭": 0.9
},
"description": "만족/긍정"
},
"confused": {
"keywords": {
"모르겠": 0.8, "헷갈": 0.9, "혼란": 0.9,
"무슨": 0.6, "뭐가": 0.6,
"이해가": 0.6, "뜻이": 0.5, "의미": 0.5,
"차이": 0.5, "구별": 0.6, "잘 안": 0.6
},
"description": "혼란/이해 부족"
},
"worried": {
"keywords": {
"걱정": 0.8, "불안": 0.9, "염려": 0.7,
"겁나": 0.8, "무섭": 0.7, "두렵": 0.7,
"조심": 0.5, "주의": 0.5, "위험": 0.6,
"문제가": 0.6, "괜찮": 0.5
},
"description": "걱정/불안"
}
}
def detect(self, query: str, ts: Optional[str] = None) -> Emotion:
"""감정 분석
Args:
query: 사용자 질문
ts: 타임스탬프 (로깅용)
Returns:
Emotion 객체
"""
query_lower = query.lower().strip()
# 빈 문자열 체크
if not query_lower:
return Emotion(
primary="neutral",
intensity=0.0,
confidence=1.0,
matched_keywords=[]
)
# 각 감정별 점수 계산
emotion_scores = {}
for emotion_name, config in self.EMOTION_PATTERNS.items():
matched = []
total_intensity = 0.0
for keyword, intensity in config["keywords"].items():
if keyword in query_lower:
matched.append(keyword)
total_intensity += intensity
if matched:
# 평균 강도 계산
avg_intensity = total_intensity / len(matched)
# 매칭 개수와 강도를 결합한 점수
score = len(matched) * avg_intensity
emotion_scores[emotion_name] = {
"score": score,
"intensity": min(1.0, avg_intensity),
"matched": matched
}
# 매칭된 감정이 없으면 중립
if not emotion_scores:
return Emotion(
primary="neutral",
intensity=0.0,
confidence=1.0,
matched_keywords=[]
)
# 가장 높은 점수의 감정 선택
best_emotion = max(emotion_scores.items(), key=lambda x: x[1]["score"])
emotion_name = best_emotion[0]
emotion_data = best_emotion[1]
# 신뢰도 계산 (매칭 개수와 강도 기반)
confidence = min(0.95, 0.6 + (emotion_data["score"] * 0.1))
result = Emotion(
primary=emotion_name,
intensity=emotion_data["intensity"],
confidence=confidence,
matched_keywords=emotion_data["matched"]
)
if ts:
print(f"[EmotionDetector] {ts} 감정 분석: {result.primary} "
f"(강도: {result.intensity:.2f}, 신뢰도: {result.confidence:.2f}, "
f"키워드: {result.matched_keywords})")
return result
def get_emotion_summary(self, emotion: Emotion) -> str:
"""감정 요약 문자열"""
description = self.EMOTION_PATTERNS.get(emotion.primary, {}).get("description", emotion.primary)
intensity_label = "강함" if emotion.intensity > 0.7 else "중간" if emotion.intensity > 0.4 else "약함"
return f"{description} ({intensity_label})"