Agent 2.0 exdev 서버 배포 스택
- server-dev start/stop/deploy 및 Gitea push 자동 배포 - local-dev 로컬 개발 환경 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
감정 분석 모듈
|
||||
──────────────
|
||||
사용자 질문의 감정 상태를 세밀하게 분석
|
||||
"""
|
||||
|
||||
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})"
|
||||
Reference in New Issue
Block a user